diff --git a/docs/COVERAGE_STATE.md b/docs/COVERAGE_STATE.md new file mode 100644 index 0000000..2da3885 --- /dev/null +++ b/docs/COVERAGE_STATE.md @@ -0,0 +1,196 @@ +# Estado de cobertura — fin de Sprint 1.5 + +> Generado: Etapa 10, 18 mayo 2026. +> Base: 526 tests unitarios (33 archivos), suite E2E Playwright. +> Propósito: baseline de partida para Sprint 2. + +--- + +## Resultado de la suite al cierre del sprint + +### Tests unitarios (Vitest) + +| Métrica | Valor | +|---|---| +| Total tests | 526 | +| Verdes | 526 | +| Rojos | 0 | +| Skip | 0 | +| Archivos de test | 33 | +| Duración | ~7 segundos | + +### Tests E2E (Playwright) + +| Métrica | Valor | +|---|---| +| Total tests | 38 | +| Verdes | 34 | +| Rojos | 4 (ver Regresiones documentadas) | +| Skip | 0 | +| Duración | ~75 segundos | + +### Build / Typecheck / Lint + +| Check | Estado | Nota | +|---|---|---| +| `npm run build` | ✅ Limpio | 2865 módulos, ~1 segundo | +| `npm run typecheck` | ✅ Sin errores | TypeScript strict | +| `npm run lint` | ⚠️ 3 errores, 92 warnings | Errores = `no-irregular-whitespace` en tests preexistentes; warnings = `@typescript-eslint/no-explicit-any` esperados en interfaces jsPDF | + +--- + +## Regresiones documentadas (sin acción correctiva) + +### RE-01 — `etapa-4-pdfs.spec.ts` (3 tests) + +**Tipo:** test obsoleto, no regresión de producción. + +**Síntoma:** los tests esperan 3 páginas en Actual/Automatizado y 4 en ROI. +El producto ahora genera 4 y 5 páginas respectivamente (cambios de Etapas 5-8). + +**Causa:** el archivo fue escrito para el baseline de Etapa 4 y no se actualizó +cuando Etapas 5, 7 y 8 cambiaron la estructura de páginas. + +**Impacto:** ninguno en producción. Los PDFs son correctos. + +**Acción Sprint 2:** actualizar las aserciones de página en `etapa-4-pdfs.spec.ts` +o archivarlo en `tests/e2e/archive/` como registro histórico. + +### RE-02 — `prod-smoke.spec.ts` (1 test) + +**Tipo:** test obsoleto, no regresión de producción. + +**Síntoma:** el test verifica `page.title` con `/Process Cost Platform/` +pero el título actual es `"InQ ROI — Cuantificá tus procesos"`. + +**Causa:** la migración de marca se hizo en Etapa 2 del Sprint 1.5. El smoke test +no se actualizó al momento del cambio. + +**Impacto:** ninguno en producción. + +**Acción Sprint 2:** actualizar el selector del smoke test a `/InQ ROI/`. + +--- + +## Áreas con cobertura sólida (unitarios + E2E) + +### Motor de simulación BPMN + +- `tests/domain/` — simulación determinística, gateways XOR/AND, probabilidades +- `tests/integration/bpmn-parser.test.ts` — parsing de procesos reales +- `tests/integration/bpmn-collaboration.test.ts` — colaboración +- **Evaluación:** muy sólida. El motor es el corazón del producto y está bien cubierto. + +### Cálculo de ROI + +- `tests/domain/roi.test.ts` — casos edge (inversión cero, ahorro negativo, payback infinito) +- `tests/lib/format-payback.test.ts` — 12 casos del formateador +- **Evaluación:** sólida. Cálculos puros con edge cases cubiertos. + +### Exportación PDF — 3 generadores + +- `tests/lib/export/pdf-orientation.test.ts` — orientaciones portrait/landscape +- `tests/lib/export/pdf-roi-page1.test.ts` — página 1 del ROI (gradiente, Top3, grid) +- `tests/lib/export/pdf-roi-pages2-5.test.ts` — tablas, análisis, metodología, gráfico SVG +- `tests/lib/export/pdf-scenario-page1.test.ts` — página 1 Actual/Automatizado +- `tests/lib/export/pdf-sections-shared.test.ts` — header/footer compartidos +- E2E: Etapas 4-8 validan PDFs reales con pdfjs-dist +- **Evaluación:** la más completa del sprint. Cobertura unitaria + E2E de los 3 PDFs. + +### Helpers de formato + +- `tests/lib/format.test.ts` — currency, minutes, timestamp +- `tests/lib/format-payback.test.ts` — payback en todos los rangos +- `tests/lib/colors.test.ts` — tokens de color del heatmap +- **Evaluación:** sólida. Funciones puras con muchos casos. + +### Design tokens + +- `tests/design-system/tokens.test.ts` — paleta InQ, colores CONCILIA excluidos +- **Evaluación:** básica pero suficiente para validar el sistema de identidad. + +### Badge de automatización en BpmnCanvas + +- `tests/features/workspace/BpmnCanvas.test.tsx` — lifecycle overlay, posición, HTML +- `tests/features/workspace/BpmnCanvas-badge.test.ts` — propiedades CSS del badge +- E2E: `etapa-9-badge.spec.ts` — aparece/desaparece/reaparece, screenshots +- **Evaluación:** sólida tras Etapa 9. + +### Invalidación del store de simulación + +- `tests/store/simulation-store-invalidation.test.ts` — 7 casos de invalidación +- **Evaluación:** sólida para los escenarios cubiertos. + +--- + +## Áreas con cobertura parcial + +### Componentes React del reporte + +- `tests/features/report/` existe con tests de `CumulativeSavingsChart`, `ComparisonTable`, `RoiKpiBar` +- **Gap:** los tests son snapshots. No validan comportamiento interactivo ni edge cases de renderizado. +- **Riesgo:** bajo a corto plazo, medio cuando se agreguen features. + +### Persistencia (Dexie/IndexedDB) + +- Cobertura: indirecta vía store tests +- **Gap:** no hay tests directos de la capa de persistencia (migraciones, lectura/escritura de procesos complejos) +- **Riesgo:** medio. Las migraciones son el punto más frágil — un error ahí destruye datos del usuario. + +### ActivityPanel + +- **Gap:** sin tests unitarios ni E2E específicos del panel de actividades +- El comportamiento se verifica indirectamente en tests E2E de generación de PDF +- **Riesgo:** medio. El panel es la interfaz principal de configuración. + +--- + +## Áreas sin cobertura + +### Flujo completo de importación BPMN + +- No hay test E2E que valide el flujo completo: archivo BPMN inválido → error legible +- No hay test para BPMNs con pools/lanes (collaboration.test.ts cubre el parser, no el UX) +- **Riesgo:** medio. Los usuarios importan archivos de herramientas externas con formatos variables. + +### Exportación CSV + +- `tests/e2e/` tiene `export-pdf.spec.ts` y `export-roi.spec.ts` pero los CSV no se validan con el mismo rigor +- **Riesgo:** bajo por ahora. + +### Regresión visual de PDFs (golden files) + +- No hay comparación pixel-a-pixel de PDFs entre versiones +- **Riesgo:** medio-alto. Un cambio visual inadvertido en `pdf-sections.ts` puede pasar los tests actuales sin ser detectado. + +### Edge cases del simulador con procesos grandes + +- Tests del motor cubren procesos de 3-10 actividades +- **Gap:** procesos de 20+ actividades, gateways OR inclusivos, subprocesos anidados +- **Riesgo:** bajo a corto plazo. + +### Gateway OR inclusivo + +- El motor simula OR como XOR (documentado). No hay test que valide que la advertencia aparece. +- **Riesgo:** bajo hasta que se implemente OR real. + +--- + +## Gaps priorizados para Sprint 2 + +### Alta prioridad + +1. **Regresión visual PDF (golden files):** implementar comparación de PDF contra baseline usando `pdfjs-dist`. Guarda página como PNG y compara con imagen de referencia. Detecta cambios involuntarios. +2. **Tests de migración Dexie:** validar que cada versión del schema se migra correctamente sin pérdida de datos. +3. **Smoke test E2E con BPMN inválido:** verificar mensaje de error apropiado cuando el archivo no es BPMN válido. + +### Media prioridad + +4. **Tests del ActivityPanel:** verificar que el formulario de configuración de actividad persiste correctamente a IndexedDB. +5. **Actualizar tests obsoletos:** `etapa-4-pdfs.spec.ts` y `prod-smoke.spec.ts` (ver RE-01, RE-02). +6. **Tests de flujo completo negativo:** ¿qué pasa si se simula sin configurar ningún costo? + +### Baja prioridad + +7. **Tests de stress del simulador:** proceso con 30+ actividades y gateways anidados. +8. **Tests de exportación CSV:** validar contenido y estructura de los archivos CSV generados. diff --git a/docs/QA_CHECKLIST.md b/docs/QA_CHECKLIST.md new file mode 100644 index 0000000..26f9c15 --- /dev/null +++ b/docs/QA_CHECKLIST.md @@ -0,0 +1,146 @@ +# Checklist de QA — pre-cierre de sprint / pre-deploy + +> Lista de verificación para validar el estado del producto antes de +> cerrar un sprint o desplegar a producción. +> Actualizado al cierre de Sprint 1.5 (18 mayo 2026). +> Tiempo estimado: 15-20 minutos (5 automatizado + 10 manual). + +--- + +## 1. Suite automatizada + +``` +[ ] npm test → 0 rojos, 0 skip sin justificación +[ ] npm run test:e2e → 0 rojos (excluir tests archivados) +[ ] npm run build → sin errores (warnings de chunk-size: ignorar) +[ ] npx tsc --noEmit → sin errores +[ ] npm run lint → 0 errores (warnings de any: ignorar en lib/export/) +``` + +**Si hay rojos:** registrar en OBSERVATIONS.md como `[CURRENT-SPRINT]` con categoría: +- Bug nuevo introducido en este sprint → corregir antes de cerrar +- Test obsoleto (baseline caducado) → actualizar el test o archivarlo +- Flaky (pasa/falla según timing) → registrar como tech debt + +--- + +## 2. Validación visual — web app + +Ejecutar `npm run dev` → abrir `http://localhost:5173` + +``` +[ ] Homepage carga sin errores en consola del browser +[ ] Importar BPMN: arrastrar medium-with-gateways.bpmn → workspace carga +[ ] BPMN renderiza correctamente (nodos, flujos, gateways visibles) +[ ] Clic en actividad → ActivityPanel se abre con formulario de costos +[ ] Toggle "Automatizable" + Guardar → badge ⚡ naranja aparece sobre el nodo en el canvas +[ ] Desmarcar "Automatizable" + Guardar → badge ⚡ desaparece +[ ] Botón Simular → navega a /report/ con KPI cards +[ ] KPI cards usan paleta naranja InQ (fondo #FEF3E2, no gris) +[ ] Tab "Automatizado" → reporte actualizado con costos automatizados +[ ] Tab "Comparación" → KPI Hero naranja → magenta visible +[ ] CumulativeSavingsChart muestra curva con payback marcado +[ ] Ningún error de consola durante el flujo completo +[ ] Texto "InQ ROI" en header (no "Process Cost Platform") +[ ] Footer: "Hecho desde Paraguay 🇵🇾" (si aplica según decisión pendiente BACKLOG.md) +``` + +--- + +## 3. Validación visual — PDFs + +Exportar los 3 PDFs después del flujo del punto anterior. + +``` +PDF Actual (4 páginas): +[ ] Página 1: "ANÁLISIS DE COSTOS — ESCENARIO ACTUAL" + cards naranja InQ +[ ] Página 1: strip de metadata con PROCESO, FECHA, MONEDA +[ ] Página 2: diagrama BPMN landscape con heatmap de colores +[ ] Página 3: "ANÁLISIS DE COMPOSICIÓN" uppercase + tabla con header naranja +[ ] Página 4: "NOTA METODOLÓGICA" uppercase + caja contenedora +[ ] Footer todas las páginas: "InQ ROI · Powered by InQuality · Página X de 4" + +PDF Automatizado (4 páginas): +[ ] Página 1: "ANÁLISIS DE COSTOS — ESCENARIO AUTOMATIZADO" + cards naranja InQ +[ ] Páginas 2-4: mismo esquema que Actual +[ ] Footer: "Página X de 4" + +PDF ROI (5 páginas): +[ ] Página 1: header denso + banner narrativo + KPI Hero gradiente naranja→magenta +[ ] Página 1: grid 2×2 KPIs secundarios + Top 3 actividades con barras +[ ] Página 2: tabla comparativa con header naranja (#FFF8ED) +[ ] Página 3: "ANÁLISIS DEL AHORRO" + sección transparencia (si aplica) +[ ] Página 4: "NOTA METODOLOGICA - ANALISIS DE ROI" + caja contenedora +[ ] Página 5: "TRAYECTORIA DEL AHORRO ACUMULADO" + gráfico SVG con áreas +[ ] Footer: "InQ ROI · Powered by InQuality · Página X de 5" +``` + +--- + +## 4. Regresiones de marca + +``` +[ ] "InQ" aparece siempre como I-n-Q (no INQ, Inq, inq, inQ) +[ ] "InQ ROI" es el nombre del producto (no "Process Cost Platform") +[ ] "Powered by InQuality" en footer de todos los PDFs +[ ] Sin "v0.1.0" en PDFs (puede estar en web si es útil internamente) +[ ] Sin colores CONCILIA (#572FA2, #CB1889) en ningún componente +[ ] Gradiente naranja→magenta solo en KPI Hero ROI (no en PDFs Actual/Automatizado) +``` + +--- + +## 5. Documentación del sprint + +``` +[ ] CLAUDE.md tiene criterios de iniciativa actualizados +[ ] methodology/PATTERNS.md tiene los patrones del sprint cosechados +[ ] methodology/ANTI_PATTERNS.md tiene los anti-patrones del sprint +[ ] OBSERVATIONS.md: todos los items son [RESOLVED] o [DEFERRED-*] +[ ] TECH_DEBT.md: items del sprint actualizados/promocionados +[ ] BACKLOG.md: items nuevos del sprint agregados +[ ] STRATEGIC_DIRECTION.md: refleja decisiones tomadas en el sprint +[ ] sprints/sprint-N/ tiene todos los ETAPA_N_PROMPT.md del sprint +``` + +--- + +## 6. Artefactos del sprint + +``` +[ ] tests/e2e/__output__/sprint-N-N-final/ tiene los 3 PDFs baseline +[ ] tests/screenshots/ tiene screenshots de Playwright de features visuales +[ ] docs/COVERAGE_STATE.md actualizado con el estado al cierre +[ ] docs/QA_CHECKLIST.md actualizado si hubo cambios en el proceso +[ ] git tag sprint-N-cerrado apuntando al commit de cierre +``` + +--- + +## Smoke test manual rápido (5 minutos) + +Para verificar el flujo completo en producción después de un deploy: + +``` +1. Abrir la URL de producción (Cloudflare Pages) +2. Importar medium-with-gateways.bpmn desde public/sample-processes/ +3. Configurar task_recv: costo directo 200, automatizado 20, marcar automatable → Guardar +4. Verificar: badge ⚡ naranja aparece sobre el nodo en el canvas +5. Clic en "Simular" +6. En el reporte, verificar KPI cards con paleta naranja +7. Ir a tab "Comparación" → verificar KPI Hero con gradiente naranja→magenta +8. Exportar PDF Actual → verificar que descarga y tiene 4 páginas +9. Exportar PDF ROI → verificar que descarga y tiene 5 páginas +10. Verificar que "InQ ROI" aparece en el header web y en los PDFs + +Tiempo estimado: 5 minutos +Resultado esperado: todo funciona sin errores de consola +``` + +--- + +## Historial de actualizaciones + +| Versión | Sprint | Cambios | +|---|---|---| +| 1.0 | Sprint 1.5 | Creación inicial al cierre del sprint | diff --git a/sprints/sprint-1-5/ETAPA_10_PROMPT.md b/sprints/sprint-1-5/ETAPA_10_PROMPT.md new file mode 100644 index 0000000..2117905 --- /dev/null +++ b/sprints/sprint-1-5/ETAPA_10_PROMPT.md @@ -0,0 +1,312 @@ +# Sprint 1.5 — Etapa 10: Validación E2E final + preparación Sprint 2 + +> **Objetivo:** validar que el Sprint 1.5 (Etapas 1-9) produjo un sistema funcional, sin regresiones, con cobertura clara y baseline reproducible. Generar checklist de QA reutilizable y documento de estado de cobertura para que Sprint 2 arranque con confianza. +> +> **Pre-requisitos:** Etapas 1-9 cerradas. Suite de tests existente con ~526 tests verdes. Sistema de design tokens InQ aplicado en web app + 3 PDFs. +> +> **Etapa siguiente:** Etapa 11 — Polish final + documentación del Sprint 1.5 (refinamientos diferidos del KPI Hero, banner, grid 2x2, Top 3 + cierre formal del sprint). + +--- + +## 1. Esta etapa es distinta a las anteriores + +Las Etapas 1-9 produjeron código y artefactos visuales. Etapa 10 **valida lo construido**. No agrega features, no rediseña visuales. Su producto principal es **confianza en el sistema**. + +**Mentalidad correcta para esta etapa:** +- Si todo funciona como debe → reporte limpio, baseline documentado +- Si hay regresiones o bugs no cazados antes → catalogarlos sin pánico, anotarlos para resolver con criterio (algunos en Etapa 11, otros en Sprint 2) +- NO arreglar cosas durante esta etapa sin consultar. La etapa es **diagnóstica**, no correctiva. + +--- + +## 2. Alcance estricto + +### Sí entra + +- ✅ **Correr la suite E2E completa** del proyecto +- ✅ **Documentar resultado**: cuántos tests, cuántos verdes, cuántos rojos, cuántos skip +- ✅ **Generar los 3 PDFs finales del sprint** (Actual, Automatizado, ROI) en `tests/e2e/__output__/sprint-1-5-final/` +- ✅ **Generar reporte de estado de cobertura** en `docs/COVERAGE_STATE.md` con: + - Qué áreas tienen tests sólidos (unitarios + E2E) + - Qué áreas tienen tests débiles o nulos (gaps identificados) + - Recomendaciones de qué cubrir en Sprint 2 +- ✅ **Crear `docs/QA_CHECKLIST.md`** reutilizable: lista de verificación para futuros cierres de sprint o pre-deploys +- ✅ **Smoke test manual documentado** del flujo end-to-end (qué hacer en el navegador para verificar el producto) +- ✅ **Identificar regresiones o anomalías** comparando con baseline previo de cada etapa + +### NO entra + +- ❌ Agregar tests nuevos para cubrir gaps detectados (eso es Sprint 2) +- ❌ Arreglar bugs detectados durante la validación (a menos que sean bloqueantes — consultar antes) +- ❌ Cambios visuales en web app o PDFs +- ❌ Refinamientos del KPI Hero, banner, grid 2x2, Top 3 (Etapa 11) +- ❌ Cambios en la arquitectura, deps, o configuración + +--- + +## 3. Lo que esta etapa debe producir + +### 3.1 Reporte de ejecución de la suite + +Formato esperado en el reporte final a director: + +``` +Suite de tests — estado actual: + +Tests unitarios: + - Total: N + - Verdes: N + - Rojos: N (listar archivos + razón) + - Skip: N (listar archivos + razón si está documentada) + +Tests E2E (Playwright): + - Total: N + - Verdes: N + - Rojos: N + - Skip: N + +Build: + - npm run build: ✓ o ✗ (con detalle si falla) + - npm run typecheck: ✓ o ✗ + - npm run lint: ✓ o ✗ + +Tiempo total de ejecución de la suite: + - Unit: N segundos + - E2E: N segundos +``` + +### 3.2 PDFs finales del sprint + +En la carpeta `tests/e2e/__output__/sprint-1-5-final/`, generar: + +- `final-actual.pdf` — proceso medium-with-gateways, escenario actual +- `final-automatizado.pdf` — proceso medium-with-gateways, escenario automatizado +- `final-roi.pdf` — proceso medium-with-gateways, ROI con frecuencia 5000/año, 3 años, USD 80.000 + +Estos PDFs son **el baseline final del sprint**. Cualquier cambio futuro debe compararse contra ellos. + +Validar al generar: +- PDF Actual: 4 páginas, orientaciones `[portrait, landscape, portrait, portrait]` +- PDF Automatizado: 4 páginas, mismas orientaciones +- PDF ROI: 5 páginas, todas portrait, KPI Hero gradiente en página 1, trayectoria del ahorro en página 5 + +### 3.3 `docs/COVERAGE_STATE.md` + +Documento con esta estructura mínima: + +```markdown +# Estado de cobertura — fin de Sprint 1.5 + +## Áreas con cobertura sólida + +[Lista de módulos/features con tests unitarios + E2E robustos] + +Ejemplos esperados: +- Motor de simulación BPMN (lib/simulator) +- Cálculo de ROI (lib/roi) +- Exportación PDF (3 generadores) +- Formato de moneda y payback (helpers) +- Badge automatizable en BpmnCanvas + +## Áreas con cobertura parcial + +[Lista de módulos donde hay tests unitarios pero faltan E2E, o viceversa] + +## Áreas sin cobertura + +[Lista de módulos críticos sin tests, identificados durante revisión] + +## Gaps identificados para Sprint 2 + +[Tests que vale la pena agregar en Sprint 2, priorizados por riesgo] + +Ejemplos esperables: +- Tests E2E del flujo completo de importación BPMN +- Tests de regresión visual de PDFs (golden files) +- Tests de stress en simulador con procesos grandes (50+ actividades) +- Tests de edge cases en cálculo de ROI (inversión cero, payback infinito, etc.) +``` + +### 3.4 `docs/QA_CHECKLIST.md` + +Checklist reutilizable para cierres de sprint o pre-deploys. Estructura: + +```markdown +# Checklist de QA — pre-cierre de sprint / pre-deploy + +> Lista de verificación para validar el estado del producto antes de +> cerrar un sprint o desplegar a producción. + +## 1. Suite de tests +[ ] npm test pasa con 0 rojos +[ ] npm run test:e2e pasa con 0 rojos +[ ] npm run build sin errores ni warnings nuevos +[ ] npm run typecheck limpio +[ ] npm run lint limpio + +## 2. Validación visual web app +[ ] Workspace carga correctamente con simple-linear +[ ] BPMN se renderiza sin errores en consola +[ ] Badge ⚡ aparece en actividades automatizables (después de Guardar) +[ ] Reporte muestra KPI cards con paleta InQ +[ ] Tab ROI muestra KPI Hero con gradiente naranja → magenta +[ ] Switch toggle activa/desactiva escenario automatizado +[ ] CumulativeSavingsChart se ve correctamente + +## 3. Validación visual PDFs +[ ] PDF Actual tiene 4 páginas (incluye BPMN landscape) +[ ] PDF Automatizado tiene 4 páginas (incluye BPMN landscape) +[ ] PDF ROI tiene 5 páginas (con trayectoria del ahorro) +[ ] Header "InQ ROI" naranja en todas las páginas +[ ] Footer "Powered by InQuality" + paginación correcta +[ ] Sin caracteres rotos visibles +[ ] Sin texto truncado en márgenes + +## 4. Regresiones +[ ] No aparece "Process Cost Platform" en ningún lugar +[ ] No aparece "v0.1.0" en PDFs (solo en web) +[ ] No aparece "INQ", "Inq", "inq" (verificar regla I-n-Q) + +## 5. Documentación +[ ] CLAUDE.md actualizado con decisiones del sprint +[ ] methodology/ tiene cosechas aplicadas +[ ] OBSERVATIONS.md vacío o solo con [RESOLVED] +[ ] BACKLOG.md y TECH_DEBT.md actualizados +[ ] STRATEGIC_DIRECTION.md refleja decisiones del sprint +``` + +### 3.5 Smoke test manual + +Documentar **pasos exactos** que el director (o un consultor de InQuality) puede ejecutar en el navegador para validar que el producto funciona. Esto va a la sección del QA_CHECKLIST.md o como archivo separado `docs/SMOKE_TEST.md`. + +Pasos mínimos esperados: + +``` +1. npm run dev +2. Abrir http://localhost:5173/ +3. Importar BPMN de ejemplo (medium-with-gateways) +4. Configurar costos en 3 actividades: + - Recepcionar solicitud: USD 200 directo / USD 20 automatizado + - Calcular score crediticio: USD 100 directo / USD 10 automatizado + - Analizar riesgo crediticio: USD 200 directo / USD 20 automatizado +5. Marcar las 3 como automatizables y Guardar cada una +6. Verificar que aparecen 3 badges ⚡ en el canvas +7. Ir a tab Reporte → verificar KPI cards +8. Ir a tab ROI → verificar KPI Hero gradiente +9. Configurar parámetros ROI: 5000 ejecuciones/año, 3 años, USD 80.000 inversión +10. Exportar los 3 PDFs (Actual, Automatizado, ROI) +11. Verificar que los PDFs descargados tienen la cantidad de páginas correcta + +Tiempo estimado: 5 minutos +``` + +--- + +## 4. Validaciones automatizadas mandatorias + +``` +Antes de present_files, ejecutá y confirmá: + +1. Suite completa de tests pasa: + - Unit tests: TODOS verdes (NO permitir rojos o skips sin justificación) + - E2E tests: TODOS verdes + - Si hay rojos: documentar exactamente cuáles y por qué + +2. Los 3 PDFs finales se generan correctamente: + - Tamaños razonables (Actual ~80KB, Automatizado ~80KB, ROI ~250KB con gradiente) + - Cantidad de páginas correcta (4, 4, 5) + - Orientaciones correctas + +3. Build, typecheck, lint: TODOS limpios + +4. Archivos creados en su lugar: + - docs/COVERAGE_STATE.md + - docs/QA_CHECKLIST.md (o sección dentro de COVERAGE_STATE.md) + - tests/e2e/__output__/sprint-1-5-final/ con los 3 PDFs + +5. NO hay archivos huérfanos: + - No quedaron PDFs viejos de etapas anteriores en directorios productivos + - tests/screenshots/ no creció más allá de lo esperado +``` + +--- + +## 5. Entregables (Definition of Done) + +``` +[ ] Suite completa de tests ejecutada y reportada +[ ] 3 PDFs finales del sprint generados en sprint-1-5-final/ +[ ] docs/COVERAGE_STATE.md creado con áreas sólidas + parciales + gaps +[ ] docs/QA_CHECKLIST.md creado y reutilizable +[ ] Smoke test manual documentado +[ ] Reporte de regresiones (si las hay) catalogadas sin actuar +[ ] Build limpio, typecheck limpio, lint limpio +[ ] PDFs presentados con paths absolutos para validación visual del director +[ ] Sin código nuevo agregado al producto (solo docs + artefactos de QA) +[ ] Decisiones por iniciativa: esperado "Ninguna" (la etapa es diagnóstica) +``` + +--- + +## 6. Reporte final a entregar + +1. **Tabla de archivos creados** (esperado: solo docs + PDFs en sprint-1-5-final/) +2. **Resultado de la suite completa** (verdes/rojos/skip por categoría) +3. **3 PDFs finales presentados con paths absolutos** +4. **Resumen de COVERAGE_STATE.md**: áreas sólidas, parciales, gaps +5. **Resumen de QA_CHECKLIST.md**: cuántos items tiene, cómo está organizado +6. **Regresiones detectadas**: si hay, listadas SIN ACCIÓN CORRECTIVA (anotadas para Etapa 11 o Sprint 2) +7. **Anomalías encontradas**: cualquier comportamiento inesperado durante la validación +8. **Recomendaciones para Sprint 2**: cosas que vale la pena cubrir con tests adicionales + +--- + +## 7. Reglas operativas + +1. **NO ARREGLAR bugs durante esta etapa.** Si encontrás regresión o anomalía, **anotala en el reporte** y dejala así. Excepción única: si encontrás un bug que rompe el build completo o impide generar los PDFs finales, consultar antes de arreglar. + +2. **NO AGREGAR tests nuevos** para cubrir gaps. Los gaps van a COVERAGE_STATE.md para Sprint 2. + +3. **NO CAMBIAR código de producción.** Solo se permite agregar documentación. + +4. **NO TOCAR archivos visuales del producto** (PDFs, web app). Solo generarlos para baseline. + +5. **Política de iniciativa proactiva refinada (post-Etapa 8):** + - Cualquier cambio en código de producción durante esta etapa: Nivel 3 (consultar previa) + - Crear nuevos archivos en `docs/`: permitido si están en scope (ver §3) + - Crear archivos fuera de `docs/`: Nivel 2 mínimo + +6. **Si la suite tiene rojos**: documentarlos sin arreglarlos. La etapa puede cerrarse con rojos conocidos siempre que estén documentados y categorizados (bug nuevo vs bug heredado vs flaky). + +7. **Si encontrás contradicción** entre prompt y código: PARÁ y consultá. + +--- + +## 8. Mentalidad correcta para esta etapa + +Etapa 10 es **inventario, no construcción**. Pensá como auditor externo: + +- ¿Qué tengo construido? +- ¿Qué tan robusto está testeado? +- ¿Qué falta cubrir? +- ¿Hay sorpresas escondidas? + +Si encontrás algo que no estaba en el radar (test que pasa pero el comportamiento real es raro, dependencia desactualizada, archivo huérfano de etapas pasadas), **eso es valor de esta etapa**. Reportarlo es lo correcto. + +Si todo está limpio y predecible, eso también es valor — significa que las Etapas 1-9 dejaron un sistema en buen estado. + +**El éxito de esta etapa no se mide en cambios producidos. Se mide en confianza generada para arrancar Sprint 2.** + +--- + +## 9. Una nota sobre el cierre del Sprint 1.5 + +Después de Etapa 10, queda solo Etapa 11 (polish + documentación). Cuando esa cierre: + +- El Sprint 1.5 se considera completo +- Se hace tag `sprint-1-5-cerrado` en git +- Se ejecuta el cierre de OBSERVATIONS.md (purgar items resueltos, archivar) +- Se actualiza STRATEGIC_DIRECTION.md con reflexiones del sprint +- Sprint 2 arranca con backlog claro, baseline de cobertura conocido, y método refinado + +Etapa 10 es el último filtro antes de ese cierre. \ No newline at end of file diff --git a/sprints/sprint-1-5/ETAPA_11_PROMPT.md b/sprints/sprint-1-5/ETAPA_11_PROMPT.md new file mode 100644 index 0000000..ffc81cc --- /dev/null +++ b/sprints/sprint-1-5/ETAPA_11_PROMPT.md @@ -0,0 +1,471 @@ +# Sprint 1.5 — Etapa 11: Polish final + cierre formal del sprint + +> **Objetivo:** completar los refinamientos visuales diferidos de Etapa 6 sobre la página 1 del PDF ROI (KPI Hero, banner narrativo, grid 2x2, Top 3). Ejecutar el cierre formal del Sprint 1.5: curaduría de OBSERVATIONS.md, migración de items DEFERRED, actualización de STRATEGIC_DIRECTION.md, generación de case study en methodology/, y tag del sprint. +> +> **Pre-requisitos:** Etapas 1-10 cerradas. CI 100% verde. 3 PDFs baseline en `tests/e2e/__output__/sprint-1-5-final/`. Docs `COVERAGE_STATE.md` y `QA_CHECKLIST.md` creados. +> +> **Etapa siguiente:** apertura de Sprint 2 (etapa por planificar, no parte de este sprint). + +--- + +## 1. Esta etapa tiene dos partes diferenciadas + +**Parte A — Polish visual** (mitad de la etapa): refinamientos del PDF ROI página 1. +**Parte B — Cierre formal** (otra mitad): curaduría de archivos del sprint. + +Hacé las dos partes EN ORDEN. La parte A primero. Cuando esté validada por el director, ejecutar parte B. + +--- + +## 2. Parte A — Polish visual de página 1 PDF ROI + +### 2.1 Alcance estricto de Parte A + +Recoger los 4 refinamientos diferidos documentados en `TECH_DEBT.md` sección "Refinamientos visuales del PDF": + +#### Refinamiento 1 — KPI Hero (gradiente naranja → magenta) + +**Hallazgos del director en Etapa 6:** +- Cifra demasiado pegada al label "AHORRO PROYECTADO A 3 AÑOS" +- Tamaño de la cifra (USD 8.020.000,00) podría reducirse ligeramente +- Esquinas cuadradas — pasa, pero si se logra redondear, mejor + +**Cambios propuestos:** +``` +Label "AHORRO PROYECTADO A N AÑOS": + - Tamaño: 10pt (sin cambios) + - Margin-bottom: aumentar de 6mm a 9mm (más respiro hacia la cifra) + - Letter-spacing: 1.2pt (sin cambios) + +Cifra principal (USD N.NNN.NNN): + - Tamaño: bajar de 30pt a 26pt (más balanceada) + - Tracking: -0.8pt (un poco menos cerrado) + - Margin-bottom: 5mm (sin cambios significativos) + +Esquinas redondeadas (intento opcional): + - Si jsPDF permite recortar la imagen del gradiente con border-radius, + aplicar 6mm de radius + - Si requiere implementación compleja (cuatro triángulos blancos en + esquinas, máscara SVG, etc.), DEJAR esquinas cuadradas y reportar + - Tiempo máximo invertido: 30 minutos. Si no sale, pasar. +``` + +#### Refinamiento 2 — Banner narrativo + +**Hallazgo del director:** +- "Texto corrido", no destaca cifras ni palabras clave +- Podría tener tratamiento más visual + +**Cambios propuestos:** +``` +Estructura del banner: + - Mantener fondo #FFF8ED + border-left 3px #F59845 + - Mantener tamaño general del texto (11pt) + +Tratamiento tipográfico del contenido: + - Las CIFRAS dentro del texto en peso 600 + color #B45309 (naranja oscuro): + - "5.000 ejecuciones" + - "3 años" + - "USD 80.000,00" + - "USD 540,00" + - "90,0%" + - "0.4 meses" (o equivalente del payback) + + - El RESULTADO ("Resultado: ahorro de...") en peso 500 + + - El resto del texto en peso 400 (sin cambios) + +Implementación técnica: + - jsPDF requiere usar doc.text() con setFont() para cada segmento + - Construir el banner como secuencia de fragmentos en lugar de párrafo + monolítico + - Alternativa: usar doc.text() con array de strings y posicionar + manualmente +``` + +#### Refinamiento 3 — Grid 2x2 KPIs secundarios + +**Hallazgo del director:** +- "No tienen consistencia en el uso de tipografía" +- Verificar que los 4 cards sigan el mismo patrón visual + +**Cambios propuestos:** + +Auditar visualmente los 4 cards y unificar: +``` +Card label superior (las 4 cards): + - Tamaño: 9pt (fijo, no variar) + - Peso: 500 + - Color: #92400E + - Letter-spacing: 1pt + - Uppercase + - Margin-bottom: 5mm (fijo) + +Card cifra principal (las 4 cards): + - Tamaño: 18pt (fijo, no variar entre cards) + - Peso: 700 (bold) + - Color: #0F172A + - Font-family: mono (alineación de números consistente) + - Margin-bottom: 3mm + +Card delta/contexto (las 4 cards): + - Tamaño: 9pt (fijo) + - Peso: 500 + - Color: #B45309 + - Margin-top: 0 (después del 3mm de margin-bottom de la cifra) +``` + +**Específicamente verificar:** +- Card "AHORRO POR EJECUCIÓN": la línea "-90,0% vs. actual" debe tener mismo tamaño/peso/color que las deltas de los otros cards +- Card "PAYBACK": la línea "Dentro del horizonte" debe tener mismo tratamiento +- Card "AHORRO ANUAL": "× 3 años" debe tener mismo tratamiento +- Card "ROI ANUAL": "retorno anual" debe tener mismo tratamiento + +#### Refinamiento 4 — Top 3 actividades + +**Hallazgos del director:** +- Título "TOP 3 ACTIVIDADES POR AHORRO" se ve pequeño +- Las barras no tienen fondo para contraste + +**Cambios propuestos:** +``` +Título de la sección: + - Texto: "TOP 3 ACTIVIDADES POR AHORRO" + - Tamaño: aumentar de 9pt a 11pt + - Peso: 600 (más bold) + - Color: #92400E (naranja oscuro, en vez de gris) + - Letter-spacing: 1.2pt + - Margin-bottom: 8mm (más respiro hacia las barras) + +Barras horizontales: + - Track (fondo): mantener #FED7AA (inq-orange-100) + - Fill: mantener #F59845 (inq-orange) + - Aumentar visibilidad del track: agregar border 0.3pt #F59845 en track + para definir el área completa + - Border-radius: 1mm en track y fill + +Layout fila: + - Mantener grid 3 columnas (nombre 40% / barra 40% / cifra 20%) + - Gap entre filas: 5mm (un poco más que antes) +``` + +### 2.2 Validación visual obligatoria de Parte A + +Cuando Claude Code termine los 4 refinamientos, **NO HACER PARTE B AÚN**. Generar el PDF ROI actualizado: + +``` +tests/e2e/__output__/etapa-11-roi.pdf +``` + +Y presentarlo para validación del director. El director va a: + +1. Comparar la página 1 con el PDF de Etapa 6 (donde se identificaron los problemas) +2. Decidir si los 4 refinamientos resuelven sus observaciones +3. O pedir ajustes específicos + +**Solo después del OK explícito del director sobre Parte A, ejecutar Parte B.** + +### 2.3 Iteración esperada + +Es probable que la primera pasada no satisfaga al 100%. El director va a dar feedback específico tipo: + +- "La cifra del KPI Hero todavía está muy grande, bajá a 24pt" +- "El banner no destaca lo suficiente, los números deberían ser color naranja oscuro más fuerte" +- "Las barras del Top 3 ahora tienen border, pero el border es muy delgado" + +**Mentalidad correcta:** la primera pasada es punto de partida. La conversación visual con el director afina hasta llegar al resultado. Esperá 1-3 iteraciones. + +--- + +## 3. Parte B — Cierre formal del sprint + +**Ejecutar SOLO después del OK explícito del director sobre Parte A.** + +### 3.1 Curaduría de OBSERVATIONS.md + +Acciones específicas: + +1. **Items con etiqueta `[RESOLVED]`**: + - Verificar que todos están realmente resueltos + - Trasladar a una sección final del archivo "Historial de Sprint 1.5" o eliminarlos según preferencia del director + +2. **Items con etiqueta `[DEFERRED-TECH-DEBT]`**: + - Verificar que ya están migrados a `TECH_DEBT.md` + - Si NO están migrados, migrarlos ahora + - Eliminar de `OBSERVATIONS.md` + +3. **Items con etiqueta `[DEFERRED-BACKLOG]`**: + - Verificar que ya están migrados a `BACKLOG.md` + - Si NO están migrados, migrarlos ahora + - Eliminar de `OBSERVATIONS.md` + +4. **Items con etiqueta `[CURRENT-SPRINT]`**: + - Si están resueltos por Parte A (polish final), marcarlos `[RESOLVED]` + - Si NO están resueltos, **PARAR Y CONSULTAR**: ¿se difieren a Sprint 2 o se cierran como deuda asumida? + +5. **Vaciar el archivo dejando estructura para Sprint 2**: +```markdown +# OBSERVATIONS — Sprint 2 + +> Hallazgos descubiertos durante validación visual del sprint actual. +> Este archivo se vacía al cerrar el sprint. + +(Sin hallazgos activos. Sprint 2 inicia.) + +## Convención de etiquetas + +[mantener la convención de etiquetas como referencia] +``` + +### 3.2 Actualización de STRATEGIC_DIRECTION.md + +Agregar al final del archivo una nueva sección con reflexiones del Sprint 1.5: + +```markdown +## Reflexiones del cierre de Sprint 1.5 + +### Lo que funcionó + +- [Capturar 3-5 cosas que el director identificó como aprendizajes positivos] +- Ejemplo esperable: "Política de iniciativa proactiva con consulta clasificada + redujo errores de scope después de Etapa 8" + +### Lo que costó más de lo esperado + +- [Capturar 2-3 dificultades específicas] +- Ejemplo esperable: "El gradiente del KPI Hero requirió varias iteraciones" + +### Decisiones arquitectónicas tomadas durante el sprint + +- ASCII puro en fórmulas matemáticas (en lugar de Σ Unicode) +- Layout híbrido portrait/landscape para PDFs +- Jerarquía visual: KPI Hero exclusivo del PDF ROI +- Badge ⚡ con posición basada en `left` (no `right`) +- Permisos de Claude Code versionados en .claude/ + +### Estado de las definiciones del producto + +- Identidad visual: estable hasta Sprint 4+ +- Posicionamiento de mercado: pendiente de consolidar con feedback real +- Métricas de éxito: pendientes de definir con datos de uso + +### Anticipación para Sprint 2 + +[Espacio para que el director complete cuando arranque Sprint 2] +``` + +### 3.3 Generación de case study + +Crear `methodology/CASE_STUDIES/inq-roi-sprint-1-5.md` con resumen del sprint para uso futuro del director con su equipo. + +Estructura: + +```markdown +# Case Study — InQ ROI Sprint 1.5: Rediseño visual con identidad InQ + +## Contexto inicial + +- Producto: InQ ROI, herramienta interna de InQuality +- Director: Marcos (CEO/fundador InQuality) +- Equipo de ejecución: Claude Code (un solo agente) + Claude Sonnet (asesor) +- Duración planeada: 11 etapas +- Duración real: 11 etapas + 1 fix puntual entre Etapas 10 y 11 + +## Alcance del sprint + +[Lista de qué se construyó: identidad visual, sistema de design, +3 PDFs rediseñados, badge en web, metodología documentada] + +## Aplicación del método + +### Patrones aplicados con casos concretos + +[Mencionar cada patrón usado durante el sprint con el caso específico +donde se aplicó:] + +- A.1 Brief versionado: BRIEF.md de 852 líneas, base del sprint completo +- A.2 Mockup como contrato: mockups iterados pre-implementación +- B.4 Reglas no negociables en CLAUDE.md: marca "InQ", política de + iniciativa proactiva +- C.1 Validación visual mandatoria: cazó truncamiento de notas en Etapa 5 +- C.3 Auditoría adversarial: detectó decisión proactiva no autorizada + en Etapa 3 +- C.6 Iniciativa agéntica con consulta clasificada: refinada en Etapas 5 y 8 +- D.4 Permisos versionados en .claude/: aplicado mid-sprint + +### Anti-patrones detectados con casos concretos + +- AP.3 Racionalización de output anómalo: detectado en validación + visual de Etapa 5 (compactación de páginas) +- AP.8 Adelanto silencioso de deuda técnica: detectado en Etapa 7-8 + (reescritura de fórmulas Σ → ASCII) + +### Cosechas del método originadas en este sprint + +- Refinamiento del Patrón C.6 con criterios explícitos de clasificación +- Nuevo Anti-Patrón AP.7: Bypass total de permisos en desarrollo local +- Nuevo Anti-Patrón AP.8: Adelanto silencioso de deuda técnica +- Nuevo Patrón D.4: Permisos del agente versionados en .claude/ +- Extensión del Principio 3: "El auditor también debe ser auditado" + +## Lecciones aprendidas + +[3-5 lecciones operativas que el director identificó como transferibles +al equipo] + +## Métricas + +- Etapas: 11 (+ 1 fix puntual) +- Tests: 526 unit + 38 E2E al cierre +- Archivos modificados/creados: [contar de los commits] +- Decisiones de iniciativa: total y desglose por nivel +- Casos de mala clasificación de iniciativa: 2 (Etapas 5 y 7-8) + +## Material para transmisión al equipo + +[Sugerencias de qué partes de este case study usar en las 4 sesiones +× 1h de transmisión del método al equipo de 5 técnicos de InQuality] +``` + +### 3.4 Tag final del sprint + +```bash +git tag sprint-1-5-cerrado +git push --tags +``` + +### 3.5 Validación final del cierre + +Antes de declarar Etapa 11 (y Sprint 1.5) cerrados: + +- [ ] OBSERVATIONS.md vaciado o resetado para Sprint 2 +- [ ] STRATEGIC_DIRECTION.md actualizado con reflexiones del sprint +- [ ] CASE_STUDIES/inq-roi-sprint-1-5.md creado +- [ ] Tag git `sprint-1-5-cerrado` creado +- [ ] Suite completa de tests sigue 100% verde +- [ ] CI limpio + +--- + +## 4. Validaciones automatizadas + +``` +Parte A — Polish visual: +1. PDF ROI etapa-11-roi.pdf generado, 5 páginas +2. Página 1: cifra del KPI Hero ≤ 26pt +3. Página 1: banner narrativo con cifras destacadas (verificar fragmentos + de texto con setFont en peso 600) +4. Página 1: grid 2x2 con tamaños consistentes en los 4 cards +5. Página 1: título "TOP 3 ACTIVIDADES POR AHORRO" en color naranja + oscuro (#92400E) +6. PDFs Actual y Automatizado: SIN CAMBIOS respecto a Etapa 8 +7. Tests previos siguen verdes (526 unit + 38 E2E) + +Parte B — Cierre formal: +8. OBSERVATIONS.md tiene estructura limpia para Sprint 2 +9. STRATEGIC_DIRECTION.md tiene nueva sección de reflexiones +10. methodology/CASE_STUDIES/inq-roi-sprint-1-5.md existe y tiene + estructura mínima esperada +11. Tag git sprint-1-5-cerrado existe en local (push pendiente del director) +``` + +--- + +## 5. Entregables (Definition of Done) + +``` +PARTE A: +[ ] 4 refinamientos del PDF ROI página 1 aplicados según especificación +[ ] PDF ROI etapa-11-roi.pdf generado para validación visual +[ ] PDFs Actual y Automatizado NO modificados +[ ] Tests previos verdes +[ ] OK explícito del director sobre Parte A antes de avanzar + +PARTE B: +[ ] OBSERVATIONS.md curado y reseteado para Sprint 2 +[ ] STRATEGIC_DIRECTION.md actualizado con reflexiones +[ ] methodology/CASE_STUDIES/inq-roi-sprint-1-5.md creado +[ ] Tag git sprint-1-5-cerrado local +[ ] CI 100% verde +[ ] Reporte final entregado +``` + +--- + +## 6. Reporte final a entregar + +1. **Tabla de archivos modificados/creados** (parte A + parte B separadas) +2. **PDF ROI etapa-11-roi.pdf presentado** con path absoluto +3. **Validaciones automatizadas confirmadas** (11 puntos) +4. **Resumen de cambios en cada refinamiento**: qué se modificó exactamente +5. **Decisiones tomadas durante refinamientos** (especialmente sobre + esquinas redondeadas del KPI Hero — sí/no se logró) +6. **Resumen de curaduría**: cuántos items procesados de cada estado + ([RESOLVED], [DEFERRED-*], [CURRENT-SPRINT]) +7. **Confirmación de tag git** y estado final del sprint +8. **Decisiones de iniciativa proactiva** según política CLAUDE.md + +--- + +## 7. Reglas operativas + +1. **PARTE A ANTES DE PARTE B.** No mezclar. Cuando Parte A está validada + por el director, recién entonces ejecutar Parte B. + +2. **ITERACIÓN EN PARTE A.** Los refinamientos visuales pueden requerir + 2-3 pasadas. Eso es normal. Mantener iteraciones acotadas a feedback + específico del director, sin re-rediseñar de cero. + +3. **NO TOCAR PDFs Actual y Automatizado.** Cerrados en Etapa 8. + +4. **NO MODIFICAR código no relacionado con los 4 refinamientos** durante + Parte A. Es polish, no refactor. + +5. **EN PARTE B, las decisiones de curaduría son del director.** Si un + item DEFERRED no está claro a dónde migrar, consultar. Si un item + CURRENT-SPRINT no se resolvió en el sprint, consultar antes de + diferirlo automáticamente. + +6. **Política de iniciativa refinada (post-Etapa 8):** + - Cambios en código fuera de los 4 refinamientos: Nivel 3 — consultar + - Cambios en archivos de documentación dentro del scope de Parte B: + Nivel 2 — reportar + - Eliminar items de OBSERVATIONS.md: SIEMPRE Nivel 2 (reportar lo + que se eliminó/movió) + +7. **Si encontrás contradicción** entre prompt y código: PARÁ y consultá. + +8. **Si la iteración de Parte A se extiende mucho**, evaluar con el + director si vale la pena cerrar Etapa 11 con el polish "razonable" + y diferir refinamientos finos a una etapa de mantenimiento en Sprint 2. + +--- + +## 8. Por qué esta etapa importa + +Etapa 11 es **el cierre del primer sprint donde se construyó el método mientras se construía el producto**. Lo que decidas acá determina: + +1. La calidad final del entregable visual que va a clientes +2. La calidad del registro del sprint (case study + reflexiones) +3. La capacidad de transmitir el método a tu equipo con material concreto + +**No te apures con esta etapa.** Si Parte A requiere 3 iteraciones, está bien. Si Parte B requiere 1 hora de curaduría con decisiones por item, está bien. + +El éxito de Etapa 11 NO se mide en velocidad. Se mide en: +- Página 1 del PDF ROI alcanza el nivel que vos pretendías al inicio del sprint +- Sprint 1.5 cierra con artefactos transferibles (case study + docs actualizados) +- Tu equipo puede ver Sprint 1.5 como ejemplo concreto del método en acción + +--- + +## 9. Después de Etapa 11 + +Cuando esta etapa cierre: + +- Sprint 1.5 queda completamente cerrado +- Tu siguiente trabajo es planificar Sprint 2 con la dirección estratégica documentada (rebalanceo web/PDF, criterio de wow obligatorio) +- Las 4 sesiones × 1h de transmisión metodológica al equipo pueden arrancar usando el case study como material + +Es el momento de inflexión. Sprint 1.5 fue donde construiste vocabulario, patrones y método. Sprint 2 va a ser donde vos aplicás eso para producir features de valor, y donde tu equipo eventualmente toma el método y lo usa. + +Esta es la última etapa donde estamos todos enfocados solo en InQ ROI. Después, el método cobra vida propia. \ No newline at end of file diff --git a/src/lib/export/pdf-sections-roi.ts b/src/lib/export/pdf-sections-roi.ts index a959f39..89900e1 100644 --- a/src/lib/export/pdf-sections-roi.ts +++ b/src/lib/export/pdf-sections-roi.ts @@ -172,11 +172,19 @@ function _drawNarrativeBanner( const tx = m + borderW + padH let ty = y + padV + + // Contexto (línea 1): peso 400, gris — información de fondo doc.setFont('helvetica', 'normal') doc.setFontSize(9) doc.setTextColor(...PDF_COLORS.textSecondary) doc.text(l1, tx, ty) - ty += l1.length * 4.2 + 3 + ty += l1.length * 4.2 + 4 + + // Resultado (línea 2): peso 700, naranja oscuro — cifras y conclusión destacadas + // Refinamiento: contraste visual entre contexto y resultado + doc.setFont('helvetica', 'bold') + doc.setFontSize(9) + doc.setTextColor(...C.orangeDark) doc.text(l2, tx, ty) return y + boxH + 8 @@ -184,17 +192,41 @@ function _drawNarrativeBanner( // ── 5.4 KPI Hero con gradiente ──────────────────────────────────────────────── -function _createGradientDataUrl(wMm: number, hMm: number): string | null { +function _createGradientDataUrl(wMm: number, hMm: number, radiusMm = 0): string | null { if (typeof document === 'undefined') return null try { const scale = 3 const w = Math.round(wMm * scale) const h = Math.round(hMm * scale) + const r = Math.round(radiusMm * scale) const canvas = document.createElement('canvas') canvas.width = w canvas.height = h const ctx = canvas.getContext('2d') if (!ctx) return null + + // Fondo blanco opaco — las esquinas fuera del clip quedan blancas, + // lo que se ve igual que bordes redondeados sobre página blanca sin + // depender de la transparencia PNG en jsPDF. + ctx.fillStyle = '#FFFFFF' + ctx.fillRect(0, 0, w, h) + + // Clip al rounded rect antes de pintar el gradiente + if (r > 0) { + ctx.beginPath() + ctx.moveTo(r, 0) + ctx.lineTo(w - r, 0) + ctx.arcTo(w, 0, w, r, r) + ctx.lineTo(w, h - r) + ctx.arcTo(w, h, w - r, h, r) + ctx.lineTo(r, h) + ctx.arcTo(0, h, 0, h - r, r) + ctx.lineTo(0, r) + ctx.arcTo(0, 0, r, 0, r) + ctx.closePath() + ctx.clip() + } + const grad = ctx.createLinearGradient(0, 0, w, h) // 135deg approx grad.addColorStop(0, '#F59845') grad.addColorStop(1, '#ED3E8F') @@ -227,32 +259,28 @@ function _drawHeroCard( ): number { const heroH = 46 - // Gradiente de fondo - const dataUrl = _createGradientDataUrl(cW, heroH) + // Gradiente con esquinas redondeadas baked en el PNG via canvas clip + const dataUrl = _createGradientDataUrl(cW, heroH, 4) if (dataUrl) { doc.addImage(dataUrl, 'PNG', m, y, cW, heroH) } else { _drawGradientFallback(doc, m, y, cW, heroH) } - // Esquinas redondeadas — simular con rect blanco en las esquinas (Nivel 1) - // jsPDF no soporta clip en addImage, así que el gradiente queda sin border-radius. - // Aceptable para esta etapa. - // Texto sobre el gradiente const heroX = m + 14 - let ty = y + 12 + let ty = y + 11 // Label superior doc.setFont('helvetica', 'bold') doc.setFontSize(9) doc.setTextColor(255, 255, 255) doc.text(`AHORRO PROYECTADO A ${process.analysisHorizonYears} AÑOS`, heroX, ty) - ty += 8 + ty += 11 // Refinamiento: más espacio label→cifra (era 8mm) - // Cifra principal 28pt + // Cifra principal — 26pt (Refinamiento: bajada de 28pt para mejor balance) doc.setFont('helvetica', 'bold') - doc.setFontSize(28) + doc.setFontSize(26) doc.setTextColor(...C.white) doc.text(formatCurrency(roi.cumulativeSavingsHorizon, currency), heroX, ty) ty += 9 @@ -275,8 +303,9 @@ function _drawSecondaryGrid( doc: DocLike, roi: RoiResult, process: Process, currency: string, m: number, cW: number, y: number ): number { + // Refinamiento: cardH aumentado de 24→28 para acomodar tipografía uniforme (9/18/9pt) const cardW = (cW - 4) / 2 - const cardH = 24 + const cardH = 28 const gapX = 4, gapY = 4 const r = 2 @@ -294,7 +323,7 @@ function _drawSecondaryGrid( { label: 'AHORRO ANUAL', value: formatCurrency(roi.annualSavings, currency), - delta: `× ${process.analysisHorizonYears} años`, + delta: `x ${process.analysisHorizonYears} años`, col: 1, row: 0, }, { @@ -319,23 +348,23 @@ function _drawSecondaryGrid( doc.setFillColor(...C.orangeLight) doc.roundedRect(cx, cy, cardW, cardH, r, r, 'F') - // Label + // Label: 9pt uniforme en los 4 cards (Refinamiento: era 6.5pt variable) doc.setFont('helvetica', 'bold') - doc.setFontSize(6.5) + doc.setFontSize(9) doc.setTextColor(...C.orangeDarker) - doc.text(card.label, cx + 5, cy + 6) + doc.text(card.label, cx + 5, cy + 7) - // Valor + // Valor: 18pt fijo en los 4 cards (Refinamiento: era variable 9-12pt) doc.setFont('helvetica', 'bold') - doc.setFontSize(card.value.length > 16 ? 9 : 12) + doc.setFontSize(18) doc.setTextColor(...PDF_COLORS.textPrimary) - doc.text(card.value, cx + 5, cy + 14) + doc.text(card.value, cx + 5, cy + 17) - // Delta + // Delta: 9pt uniforme en los 4 cards (Refinamiento: era 7.5pt) doc.setFont('helvetica', 'normal') - doc.setFontSize(7.5) + doc.setFontSize(9) doc.setTextColor(...C.orangeDark) - doc.text(card.delta, cx + 5, cy + 20) + doc.text(card.delta, cx + 5, cy + 24) maxY = Math.max(maxY, cy + cardH) } @@ -362,12 +391,12 @@ function _drawTop3Activities( .sort((a, b) => b.savings - a.savings) .slice(0, 3) - // Título + // Título: Refinamiento — 11pt, naranja oscuro (era 8pt, gris) doc.setFont('helvetica', 'bold') - doc.setFontSize(8) - doc.setTextColor(...PDF_COLORS.textDisabled) + doc.setFontSize(11) + doc.setTextColor(...C.orangeDarker) doc.text('TOP 3 ACTIVIDADES POR AHORRO', m, y) - y += 7 + y += 10 if (top3.length === 0) { doc.setFont('helvetica', 'normal') @@ -395,9 +424,13 @@ function _drawTop3Activities( doc.setTextColor(...PDF_COLORS.textPrimary) doc.text(name, m, y + barH - 1) - // Barra de fondo + // Barra de fondo (track) con border naranja para definir el área completa doc.setFillColor(...C.orangeBar) doc.roundedRect(m + nameColW, y, barColW, barH, 1, 1, 'F') + doc.setDrawColor(...C.heroOrange) + doc.setLineWidth(0.3) + doc.roundedRect(m + nameColW, y, barColW, barH, 1, 1, 'S') + doc.setLineWidth(0.2) // Barra de fill if (barFill > 0) { @@ -1011,11 +1044,19 @@ export function drawTrajectoryChart( // Máximo doc.text(_fmtAxisVal(yMax), chartLeft - 2, chartTop + 4, { align: 'right' } as Record) - // Cero (solo si hay rango negativo) + // Cero y mínimo: cuando la línea cero queda casi en el borde inferior (inversión + // muy pequeña relativa al ahorro), los labels "0" y yMin se solapan con "Año 0". + // En ese caso mostramos "0" elevado 4mm dentro del área del gráfico y omitimos + // yMin (la zona negativa es visualmente invisible; el dato está en la anotación). if (yMin < 0) { - doc.text('0', chartLeft - 2, zeroY + 1, { align: 'right' } as Record) - // Mínimo - doc.text(_fmtAxisVal(yMin), chartLeft - 2, chartBottom, { align: 'right' } as Record) + const minSepMm = 8 + const zeroNearBottom = (chartBottom - zeroY) < minSepMm + if (!zeroNearBottom) { + doc.text('0', chartLeft - 2, zeroY + 1, { align: 'right' } as Record) + doc.text(_fmtAxisVal(yMin), chartLeft - 2, chartBottom, { align: 'right' } as Record) + } else { + doc.text('0', chartLeft - 2, chartBottom - 4, { align: 'right' } as Record) + } } // 7. Marker de payback (línea vertical punteada, solo si hay payback en horizonte) diff --git a/tests/e2e/etapa-11-polish.spec.ts b/tests/e2e/etapa-11-polish.spec.ts new file mode 100644 index 0000000..6b01e80 --- /dev/null +++ b/tests/e2e/etapa-11-polish.spec.ts @@ -0,0 +1,98 @@ +/** + * Etapa 11 Parte A — Genera el PDF ROI con los 4 refinamientos del polish final. + * Guarda en tests/e2e/__output__/etapa-11-roi.pdf para validación visual del director. + */ +import { test, expect } from '@playwright/test' +import { resolve } from 'path' +import { mkdirSync, readFileSync } from 'fs' +import { fileURLToPath } from 'url' +import { PDFParse } from 'pdf-parse' +import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const BPMN_DIR = resolve(__dirname, '../../public/sample-processes') +const OUT_DIR = resolve(__dirname, './__output__') + +test.beforeAll(() => { mkdirSync(OUT_DIR, { recursive: true }) }) + +async function parsePdf(buffer: Buffer) { + const parser = new PDFParse({ data: buffer }) + const result = await parser.getText() + await parser.destroy() + return { numpages: result.total, text: result.text } +} + +async function getOrientations(buffer: Buffer): Promise { + const uint8 = new Uint8Array(buffer) + const pdf = await getDocument({ data: uint8 }).promise + const out: string[] = [] + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i) + const vp = page.getViewport({ scale: 1 }) + out.push(vp.width > vp.height ? 'landscape' : 'portrait') + } + return out +} + +test('etapa-11-roi.pdf — polish final página 1 ROI', async ({ page }) => { + await page.goto('/') + await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn')) + await page.waitForURL(/\/workspace\//, { timeout: 15_000 }) + await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 }) + await page.waitForTimeout(800) + + for (const [id, cost, auto] of [ + ['task_recv', 200, 20], ['task_score', 200, 20], ['task_analisis', 200, 20] + ] as [string, number, number][]) { + await page.locator(`[data-element-id="${id}"]`).first().click() + await page.waitForTimeout(400) + const toggle = page.getByRole('switch', { name: /automatizable/i }) + if (await toggle.getAttribute('aria-checked') === 'false') await toggle.click() + await page.waitForTimeout(200) + await page.getByLabel(/Costo directo fijo/i).fill(String(cost)) + await page.getByLabel(/Costo automatizado/i).fill(String(auto)) + await page.getByLabel(/Tiempo automatizado/i).fill('10') + await page.getByRole('button', { name: /Guardar/i }).click() + await page.waitForTimeout(400) + } + + await page.getByRole('tab', { name: /Global/i }).click() + await page.waitForTimeout(300) + await page.getByLabel(/Frecuencia anual/i).fill('5000') + await page.getByLabel(/Inversión en automatización/i).fill('80000') + await page.getByRole('button', { name: /Guardar/i }).click() + await page.waitForTimeout(400) + + await page.getByRole('button', { name: 'Simular' }).click() + await page.waitForURL(/\/report\//, { timeout: 20_000 }) + await page.waitForSelector('button:has-text("Exportar PDF")', { timeout: 15_000 }) + await page.waitForSelector('.bpmn-container .djs-group', { timeout: 30_000 }) + await page.waitForTimeout(800) + + await page.getByRole('tab', { name: /Comparación/i }).click() + await page.waitForSelector('[data-testid="roi-kpi-card"]', { timeout: 10_000 }) + await page.waitForTimeout(500) + + const outPath = resolve(OUT_DIR, 'etapa-11-roi.pdf') + const [download] = await Promise.all([ + page.waitForEvent('download', { timeout: 30_000 }), + page.getByRole('button', { name: 'Exportar PDF' }).click(), + ]) + await download.saveAs(outPath) + + const buffer = readFileSync(outPath) + const sizeKB = (buffer.length / 1024).toFixed(1) + const { numpages, text } = await parsePdf(buffer) + const orientations = await getOrientations(buffer) + + console.log(`etapa-11-roi.pdf: ${numpages} págs, ${sizeKB} KB`) + console.log(`Texto p.1 (500 chars): ${text.slice(0, 500).replace(/\n/g, ' ')}`) + console.log(`Orientaciones: ${orientations.join(', ')}`) + + expect(numpages).toBe(5) + expect(orientations).toEqual(['portrait', 'portrait', 'portrait', 'portrait', 'portrait']) + expect(text).toMatch(/AHORRO PROYECTADO A/) + expect(text).toMatch(/TOP 3 ACTIVIDADES POR AHORRO/) + expect(text).toMatch(/InQ ROI/) + expect(text).toMatch(/Powered by InQuality/) +}) diff --git a/tests/e2e/etapa-4-pdfs.spec.ts b/tests/e2e/etapa-4-pdfs.spec.ts index 7897d10..ed9a8ee 100644 --- a/tests/e2e/etapa-4-pdfs.spec.ts +++ b/tests/e2e/etapa-4-pdfs.spec.ts @@ -114,7 +114,7 @@ function validatePdfText(text: string, filename: string) { // ─── Tests ──────────────────────────────────────────────────────────────────── -test('etapa-4-actual.pdf — 3 páginas, marca InQ, sin términos prohibidos', async ({ page }) => { +test('etapa-4-actual.pdf — 4 páginas, marca InQ, sin términos prohibidos', async ({ page }) => { await setupAndSimulate(page) const outPath = resolve(OUT_DIR, 'etapa-4-actual.pdf') @@ -125,17 +125,17 @@ test('etapa-4-actual.pdf — 3 páginas, marca InQ, sin términos prohibidos', a const buffer = readFileSync(outPath) const sizeKB = buffer.length / 1024 console.log(`Tamaño: ${sizeKB.toFixed(1)} KB`) - expect(sizeKB, 'Tamaño total > 15 KB por página').toBeGreaterThan(15 * 3) - expect(sizeKB, 'Tamaño total < 80 KB por página (3 págs)').toBeLessThan(80 * 3) + expect(sizeKB, 'Tamaño total > 15 KB por página').toBeGreaterThan(15 * 4) + expect(sizeKB, 'Tamaño total < 80 KB por página (4 págs)').toBeLessThan(80 * 4) const { numpages, text } = await parsePdf(buffer) console.log(`Páginas: ${numpages}`) console.log(`Primeros 300 chars: ${text.slice(0, 300).replace(/\n/g, ' ')}`) - expect(numpages).toBe(3) + expect(numpages).toBe(4) validatePdfText(text, 'etapa-4-actual.pdf') }) -test('etapa-4-automatizado.pdf — 3 páginas, marca InQ, sin términos prohibidos', async ({ page }) => { +test('etapa-4-automatizado.pdf — 4 páginas, marca InQ, sin términos prohibidos', async ({ page }) => { await setupAndSimulate(page) const outPath = resolve(OUT_DIR, 'etapa-4-automatizado.pdf') @@ -145,16 +145,16 @@ test('etapa-4-automatizado.pdf — 3 páginas, marca InQ, sin términos prohibid const buffer = readFileSync(outPath) const sizeKB = buffer.length / 1024 console.log(`etapa-4-automatizado.pdf: ${sizeKB.toFixed(1)} KB`) - expect(sizeKB).toBeGreaterThan(15 * 3) - expect(sizeKB).toBeLessThan(80 * 3) + expect(sizeKB).toBeGreaterThan(15 * 4) + expect(sizeKB).toBeLessThan(80 * 4) const { numpages, text } = await parsePdf(buffer) console.log(`Páginas: ${numpages}`) - expect(numpages).toBe(3) + expect(numpages).toBe(4) validatePdfText(text, 'etapa-4-automatizado.pdf') }) -test('etapa-4-roi.pdf — 4 páginas, marca InQ, keywords ROI, sin términos prohibidos', async ({ page }) => { +test('etapa-4-roi.pdf — 5 páginas, marca InQ, keywords ROI, sin términos prohibidos', async ({ page }) => { await setupAndSimulate(page) const outPath = resolve(OUT_DIR, 'etapa-4-roi.pdf') @@ -164,14 +164,14 @@ test('etapa-4-roi.pdf — 4 páginas, marca InQ, keywords ROI, sin términos pro const buffer = readFileSync(outPath) const sizeKB = buffer.length / 1024 console.log(`etapa-4-roi.pdf: ${sizeKB.toFixed(1)} KB`) - // ROI PDF no tiene imagen BPMN embebida → tamaño por página es menor (~8-12 KB texto puro) - expect(sizeKB, 'ROI PDF debe tener contenido mínimo (4 páginas texto)').toBeGreaterThan(20) - expect(sizeKB, 'ROI PDF no debe ser irrazonablemente grande').toBeLessThan(300) + // ROI PDF incluye imagen de gradiente en página 1 → tamaño mayor (~250 KB) + expect(sizeKB, 'ROI PDF debe tener contenido mínimo (5 páginas)').toBeGreaterThan(20) + expect(sizeKB, 'ROI PDF no debe ser irrazonablemente grande').toBeLessThan(400) const { numpages, text } = await parsePdf(buffer) console.log(`Páginas: ${numpages}`) console.log(`Primeros 400 chars: ${text.slice(0, 400).replace(/\n/g, ' ')}`) - expect(numpages).toBe(4) + expect(numpages).toBe(5) validatePdfText(text, 'etapa-4-roi.pdf') // Keywords específicos del PDF de ROI diff --git a/tests/e2e/export-pdf.spec.ts b/tests/e2e/export-pdf.spec.ts index cdac093..5f2a775 100644 --- a/tests/e2e/export-pdf.spec.ts +++ b/tests/e2e/export-pdf.spec.ts @@ -145,7 +145,7 @@ for (const c of CASES) { expect(csvBytes, `CSV de ${c.file} debe ser > 100 bytes`).toBeGreaterThan(100) const csvText = csvBuffer.toString('utf-8') - expect(csvText, 'CSV debe tener BOM UTF-8').toMatch(/^/) + expect(csvText, 'CSV debe tener BOM UTF-8').toMatch(/^/) expect(csvText, 'CSV debe contener "Costo total"').toContain('Costo total') expect(csvText, 'CSV debe contener la moneda').toContain(c.currency) }) diff --git a/tests/e2e/export-roi.spec.ts b/tests/e2e/export-roi.spec.ts index c9e5a9a..035431e 100644 --- a/tests/e2e/export-roi.spec.ts +++ b/tests/e2e/export-roi.spec.ts @@ -191,7 +191,7 @@ test('Export ROI — flujo completo con medium-with-gateways', async ({ page }) const csvText = csvBuffer.toString('utf-8') // BOM UTF-8 - expect(csvText).toMatch(/^/) + expect(csvText).toMatch(/^/) // Metadata ROI presente expect(csvText).toContain('# Ahorro por ejecución') @@ -200,7 +200,7 @@ test('Export ROI — flujo completo con medium-with-gateways', async ({ page }) expect(csvText).toContain('# Frecuencia anual: 12000') // 16 columnas en el header de datos - const csvNoBom = csvText.startsWith('') ? csvText.slice(1) : csvText + const csvNoBom = csvText.startsWith('') ? csvText.slice(1) : csvText const headerLine = csvNoBom.split('\r\n').find((l) => !l.startsWith('#') && l.trim())! const colCount = headerLine.split('","').length console.log(` Columnas CSV: ${colCount}`) diff --git a/tests/e2e/prod-smoke.spec.ts b/tests/e2e/prod-smoke.spec.ts index a02fb37..0588894 100644 --- a/tests/e2e/prod-smoke.spec.ts +++ b/tests/e2e/prod-smoke.spec.ts @@ -19,7 +19,7 @@ test.beforeAll(() => { test('prod-smoke — import → simulate → export PDF', async ({ page }) => { // ── 1. Landing page carga correctamente ────────────────────────────────── await page.goto('/') - await expect(page).toHaveTitle(/Process Cost Platform/) + await expect(page).toHaveTitle(/InQ ROI/) // Verificar heading principal await expect(page.getByText('Cuantificá el costo operativo')).toBeVisible({ timeout: 10_000 }) @@ -83,7 +83,7 @@ test('prod-smoke — import → simulate → export PDF', async ({ page }) => { await dlCsv.saveAs(csvPath) const csvText = readFileSync(csvPath, 'utf-8') - expect(csvText, 'CSV debe tener BOM UTF-8').toMatch(/^/) + expect(csvText, 'CSV debe tener BOM UTF-8').toMatch(/^/) expect(csvText, 'CSV debe contener encabezado').toContain('Costo total') console.log(`\n✅ Prod smoke PASSED — https://process-cost-platform.pages.dev`) diff --git a/tests/e2e/sprint-1-5-final.spec.ts b/tests/e2e/sprint-1-5-final.spec.ts new file mode 100644 index 0000000..abb79a7 --- /dev/null +++ b/tests/e2e/sprint-1-5-final.spec.ts @@ -0,0 +1,132 @@ +/** + * Etapa 10 Sprint 1.5 — Generación de PDFs baseline final del sprint. + * Genera los 3 PDFs de referencia en tests/e2e/__output__/sprint-1-5-final/ + * NO es un test de regresión — es el artefacto baseline para Sprint 2. + */ +import { test, expect } from '@playwright/test' +import { resolve } from 'path' +import { mkdirSync, readFileSync } from 'fs' +import { fileURLToPath } from 'url' +import { PDFParse } from 'pdf-parse' +import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const BPMN_DIR = resolve(__dirname, '../../public/sample-processes') +const OUT_DIR = resolve(__dirname, './__output__/sprint-1-5-final') + +test.beforeAll(() => { mkdirSync(OUT_DIR, { recursive: true }) }) + +async function parsePdf(buffer: Buffer) { + const parser = new PDFParse({ data: buffer }) + const result = await parser.getText() + await parser.destroy() + return { numpages: result.total, text: result.text } +} + +async function getOrientations(buffer: Buffer): Promise { + const uint8 = new Uint8Array(buffer) + const pdf = await getDocument({ data: uint8 }).promise + const out: string[] = [] + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i) + const vp = page.getViewport({ scale: 1 }) + out.push(vp.width > vp.height ? 'landscape' : 'portrait') + } + return out +} + +async function setupFullScenario(page: import('@playwright/test').Page) { + await page.goto('/') + await page.locator('#bpmn-file-input').setInputFiles(resolve(BPMN_DIR, 'medium-with-gateways.bpmn')) + await page.waitForURL(/\/workspace\//, { timeout: 15_000 }) + await page.waitForSelector('button:has-text("Simular")', { timeout: 10_000 }) + await page.waitForTimeout(800) + + for (const [id, cost, auto] of [ + ['task_recv', 200, 20], ['task_score', 200, 20], ['task_analisis', 200, 20] + ] as [string, number, number][]) { + await page.locator(`[data-element-id="${id}"]`).first().click() + await page.waitForTimeout(400) + const toggle = page.getByRole('switch', { name: /automatizable/i }) + if (await toggle.getAttribute('aria-checked') === 'false') await toggle.click() + await page.waitForTimeout(200) + await page.getByLabel(/Costo directo fijo/i).fill(String(cost)) + await page.getByLabel(/Costo automatizado/i).fill(String(auto)) + await page.getByLabel(/Tiempo automatizado/i).fill('10') + await page.getByRole('button', { name: /Guardar/i }).click() + await page.waitForTimeout(400) + } + + await page.getByRole('tab', { name: /Global/i }).click() + await page.waitForTimeout(300) + await page.getByLabel(/Frecuencia anual/i).fill('5000') + await page.getByLabel(/Inversión en automatización/i).fill('80000') + await page.getByRole('button', { name: /Guardar/i }).click() + await page.waitForTimeout(400) + + await page.getByRole('button', { name: 'Simular' }).click() + await page.waitForURL(/\/report\//, { timeout: 20_000 }) + await page.waitForSelector('button:has-text("Exportar PDF")', { timeout: 15_000 }) + await page.waitForSelector('.bpmn-container .djs-group', { timeout: 30_000 }) + await page.waitForTimeout(800) +} + +async function downloadTab(page: import('@playwright/test').Page, tab: 'actual' | 'automatizado' | 'roi', outPath: string) { + if (tab === 'actual') { + await page.getByRole('tab', { name: 'Actual' }).click() + await page.waitForTimeout(400) + } else if (tab === 'automatizado') { + await page.getByRole('tab', { name: 'Automatizado' }).click() + await page.waitForTimeout(400) + } else { + await page.getByRole('tab', { name: /Comparación/i }).click() + await page.waitForSelector('[data-testid="roi-kpi-card"]', { timeout: 10_000 }) + await page.waitForTimeout(500) + } + const [download] = await Promise.all([ + page.waitForEvent('download', { timeout: 30_000 }), + page.getByRole('button', { name: 'Exportar PDF' }).click(), + ]) + await download.saveAs(outPath) +} + +test('sprint-1-5-final: generar y validar 3 PDFs baseline', async ({ page }) => { + await setupFullScenario(page) + + // PDF Actual + const actualPath = resolve(OUT_DIR, 'final-actual.pdf') + await downloadTab(page, 'actual', actualPath) + const actualBuf = readFileSync(actualPath) + const actualKB = (actualBuf.length / 1024).toFixed(1) + const { numpages: actualPages } = await parsePdf(actualBuf) + const actualOrientations = await getOrientations(actualBuf) + console.log(`final-actual.pdf: ${actualPages} págs, ${actualKB} KB, ${actualOrientations.join(',')}`) + expect(actualPages).toBe(4) + expect(actualOrientations).toEqual(['portrait', 'landscape', 'portrait', 'portrait']) + + // PDF Automatizado + const autoPath = resolve(OUT_DIR, 'final-automatizado.pdf') + await downloadTab(page, 'automatizado', autoPath) + const autoBuf = readFileSync(autoPath) + const autoKB = (autoBuf.length / 1024).toFixed(1) + const { numpages: autoPages } = await parsePdf(autoBuf) + const autoOrientations = await getOrientations(autoBuf) + console.log(`final-automatizado.pdf: ${autoPages} págs, ${autoKB} KB, ${autoOrientations.join(',')}`) + expect(autoPages).toBe(4) + expect(autoOrientations).toEqual(['portrait', 'landscape', 'portrait', 'portrait']) + + // PDF ROI + const roiPath = resolve(OUT_DIR, 'final-roi.pdf') + await downloadTab(page, 'roi', roiPath) + const roiBuf = readFileSync(roiPath) + const roiKB = (roiBuf.length / 1024).toFixed(1) + const { numpages: roiPages, text: roiText } = await parsePdf(roiBuf) + const roiOrientations = await getOrientations(roiBuf) + console.log(`final-roi.pdf: ${roiPages} págs, ${roiKB} KB, ${roiOrientations.join(',')}`) + expect(roiPages).toBe(5) + expect(roiOrientations).toEqual(['portrait', 'portrait', 'portrait', 'portrait', 'portrait']) + expect(roiText).toMatch(/AHORRO PROYECTADO A/) + expect(roiText).toMatch(/TRAYECTORIA DEL AHORRO ACUMULADO/) + expect(roiText).toMatch(/InQ ROI/) + expect(roiText).toMatch(/Powered by InQuality/) +}) diff --git a/tests/lib/export/pdf-roi-page1.test.ts b/tests/lib/export/pdf-roi-page1.test.ts index 5931337..cb04b66 100644 --- a/tests/lib/export/pdf-roi-page1.test.ts +++ b/tests/lib/export/pdf-roi-page1.test.ts @@ -166,7 +166,7 @@ describe('PDF ROI — Página 1 rediseñada (Etapa 6)', () => { expect(textCalls).toContain('TOP 3 ACTIVIDADES POR AHORRO') }) - it('drawRoiPage1 usa setFontSize(28) para la cifra del KPI Hero', async () => { + it('drawRoiPage1 usa setFontSize(26) para la cifra del KPI Hero (Etapa 11: bajada de 28→26pt)', async () => { const { drawRoiPage1 } = await import('@/lib/export/pdf-sections-roi') drawRoiPage1( mockDoc as any, @@ -178,7 +178,7 @@ describe('PDF ROI — Página 1 rediseñada (Etapa 6)', () => { 'USD' ) const fontSizes = mockDoc.setFontSize.mock.calls.map((c: number[]) => c[0]) - expect(fontSizes).toContain(28) + expect(fontSizes).toContain(26) }) it('drawRoiPage1 dibuja rectángulos para la grilla 2x2 y barras top3', async () => { diff --git a/tests/screenshots/etapa-1/report-tabs.png b/tests/screenshots/etapa-1/report-tabs.png index 54b6806..afb7a2b 100644 Binary files a/tests/screenshots/etapa-1/report-tabs.png and b/tests/screenshots/etapa-1/report-tabs.png differ diff --git a/tests/screenshots/etapa-1/switch-on.png b/tests/screenshots/etapa-1/switch-on.png index eeb9f01..68c59ba 100644 Binary files a/tests/screenshots/etapa-1/switch-on.png and b/tests/screenshots/etapa-1/switch-on.png differ diff --git a/tests/screenshots/etapa-1/workspace.png b/tests/screenshots/etapa-1/workspace.png index 2b294ca..9230b40 100644 Binary files a/tests/screenshots/etapa-1/workspace.png and b/tests/screenshots/etapa-1/workspace.png differ diff --git a/tests/screenshots/etapa-3/e3-comparison-table.png b/tests/screenshots/etapa-3/e3-comparison-table.png index 5412d3b..bdf2a48 100644 Binary files a/tests/screenshots/etapa-3/e3-comparison-table.png and b/tests/screenshots/etapa-3/e3-comparison-table.png differ diff --git a/tests/screenshots/etapa-3/e3-cumulative-chart.png b/tests/screenshots/etapa-3/e3-cumulative-chart.png index 94df94d..2705eee 100644 Binary files a/tests/screenshots/etapa-3/e3-cumulative-chart.png and b/tests/screenshots/etapa-3/e3-cumulative-chart.png differ diff --git a/tests/screenshots/etapa-3/e3-donut-chart.png b/tests/screenshots/etapa-3/e3-donut-chart.png index 99e6f3c..ade10ac 100644 Binary files a/tests/screenshots/etapa-3/e3-donut-chart.png and b/tests/screenshots/etapa-3/e3-donut-chart.png differ diff --git a/tests/screenshots/etapa-3/e3-report-tabs.png b/tests/screenshots/etapa-3/e3-report-tabs.png index 99e6f3c..ade10ac 100644 Binary files a/tests/screenshots/etapa-3/e3-report-tabs.png and b/tests/screenshots/etapa-3/e3-report-tabs.png differ diff --git a/tests/screenshots/etapa-3/e3-roi-kpi-hero.png b/tests/screenshots/etapa-3/e3-roi-kpi-hero.png index fcf01a8..b22c39e 100644 Binary files a/tests/screenshots/etapa-3/e3-roi-kpi-hero.png and b/tests/screenshots/etapa-3/e3-roi-kpi-hero.png differ