Files
inq-roi-simulador-web/sprints/sprint-1-5/ETAPA_5_PROMPT.md
Marcos Benítez 2f4633a33c feat(sprint-1.5): rediseño visual PDF ROI + identidad InQ (Etapas 4-7)
- Header/footer compartidos con paleta InQ naranja en los 3 PDFs (Etapa 4)
- Layout híbrido portrait+landscape en PDFs Actual/Automatizado:
  página 2 landscape para diagrama BPMN heatmap (Etapa 5)
- Página 1 PDF ROI rediseñada: header denso, banner narrativo, KPI Hero
  con gradiente naranja→magenta, grid 2×2 de KPIs secundarios, Top 3
  actividades por ahorro (Etapa 6)
- Páginas 2-5 PDF ROI con identidad InQ: tabla con paleta naranja,
  análisis del ahorro, nota metodológica, trayectoria del ahorro
  acumulado con gráfico SVG nativo (Etapa 7)
- 500 tests verdes (+ 34 tests nuevos del Sprint 1.5)
- BACKLOG.md, TECH_DEBT.md, OBSERVATIONS.md: sistema de planificación
  reemplaza TODO.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:45:01 -03:00

12 KiB

Sprint 1.5 — Etapa 5: PDF — Layout híbrido portrait + landscape

Objetivo: cambiar la orientación de la página que contiene el diagrama BPMN (heatmap) a landscape, mientras el resto del PDF se mantiene en portrait. Aplica a los 3 PDFs (Actual, Automatizado, ROI).

Pre-requisitos: Etapa 4 cerrada — header y footer compartidos, paleta InQ aplicada en headers/footers.

Etapa siguiente: Etapa 6 — Rediseño completo de página 1 del PDF ROI (KPI Hero gradiente, top 3, header denso).


1. Por qué este cambio

El diagrama BPMN con heatmap es el elemento visual más importante del PDF. En layout vertical A4 (210mm de ancho útil), procesos con más de 7-8 actividades no caben legiblemente — el diagrama se renderiza chiquito y los nombres de las actividades se truncan.

En landscape A4 (297mm de ancho útil), el diagrama tiene 41% más espacio horizontal. Los procesos típicos de InQuality (10-15 actividades con gateways) se ven legibles. Es la diferencia entre un PDF que se entiende y uno que se descarga y nadie lee.


2. Alcance estricto de Etapa 5

Sí entra

  • La página del diagrama BPMN (heatmap) cambia de portrait a landscape en los 3 PDFs (Actual, Automatizado, ROI)
  • Las demás páginas (resumen ejecutivo, tabla comparativa, metodología) se mantienen en portrait
  • El header y footer compartidos (creados en Etapa 4) se adaptan al ancho de la página landscape
  • El diagrama BPMN renderizado ocupa MÁS espacio horizontal (no queda con padding excesivo)
  • Tests E2E verifican que cada PDF tiene la mezcla correcta de orientaciones

NO entra

  • Rediseño del contenido del diagrama (Etapa 6 o posterior)
  • Cambio de la leyenda del heatmap (bajo costo / alto costo) — solo se adapta al ancho landscape
  • Cambio de los KPI cards de la página 1 (Etapa 6)
  • Cambio de la tabla comparativa (Etapa 7)
  • Embebido de fonts Unicode (Sprint 2, anotado en TODO)
  • Tocar el resto de páginas del PDF

3. Decisiones técnicas obligatorias

3.1 Método correcto para cambiar orientación en jsPDF

SOLO usar este método — los otros tienen bugs conocidos:

// ✅ CORRECTO — usar el parámetro de orientación en addPage
doc.addPage('a4', 'landscape');

// Para volver a portrait después:
doc.addPage('a4', 'portrait');

Métodos prohibidos:

// ❌ NO USAR — método deprecated y comportamiento inconsistente
doc.addPage([842, 595]);  // dimensiones manuales sin orientación

// ❌ NO USAR — rota el ordering de páginas y autotable
doc.internal.pageSize.width = 842;
doc.internal.pageSize.height = 595;

// ❌ NO USAR — transform/rotate de contenido sobre página portrait
// "Rotar el contenido 90° en una página portrait" se ve mal y rompe
// el cálculo de posiciones de texto

3.2 Detección del ancho disponible en cada página

El módulo pdf-sections-shared.ts ya recibe el doc y debería usar:

const pageWidth = doc.internal.pageSize.getWidth();
const pageHeight = doc.internal.pageSize.getHeight();

Esto retorna dinámicamente el ancho de la página ACTUAL (portrait o landscape). NO hardcodear 595 (A4 portrait) ni 842 (A4 landscape). Si encontrás esos números mágicos en el código, reemplazalos.

3.3 Generación del diagrama BPMN en landscape

El diagrama BPMN se renderiza con html2canvas → imagen JPEG embebida en el PDF. La función actual probablemente está calculando dimensiones basadas en página portrait. Hay que ajustarla para landscape:

// Antes (asumiendo portrait):
const maxImageWidth = pageWidth - 2 * marginX;  // ~170mm
const imageHeight = maxImageWidth * (canvas.height / canvas.width);

// Después (funciona en ambas orientaciones):
const pageWidth = doc.internal.pageSize.getWidth();  // dinámico
const pageHeight = doc.internal.pageSize.getHeight();  // dinámico
const usableWidth = pageWidth - 2 * marginX;
const usableHeight = pageHeight - headerHeight - footerHeight - 2 * marginY;

// Calcular escala que cabe en ambas dimensiones
const scaleX = usableWidth / canvas.width;
const scaleY = usableHeight / canvas.height;
const scale = Math.min(scaleX, scaleY);

const imageWidth = canvas.width * scale;
const imageHeight = canvas.height * scale;

Esto garantiza que el diagrama:

  • Aprovecha el espacio horizontal en landscape
  • No se sale de los márgenes
  • Mantiene aspect ratio del canvas original

3.4 Páginas con qué orientación — orden y dimensiones

PDF Actual y Automatizado (3 páginas):

Página Contenido Orientación
1 Resumen ejecutivo (KPI cards) Portrait
2 Diagrama BPMN con heatmap Landscape
3 Tabla de costos + nota metodológica Portrait

PDF ROI (4 páginas):

Página Contenido Orientación
1 Resumen ejecutivo ROI (KPI hero + cards) Portrait
2 Comparación por actividad (tabla) Portrait
3 Composición + trayectoria + transparencia Portrait
4 Nota metodológica Portrait

Decisión arquitectónica: el PDF ROI NO incluye página con diagrama BPMN en este sprint. El BPMN está en los PDFs Actual y Automatizado. Si en el futuro se decide incluir BPMN en ROI, se replica el patrón landscape.

El header y footer compartidos ya están en pdf-sections-shared.ts (Etapa 4). Deben:

  • Adaptarse al nuevo ancho automáticamente (porque usan getWidth(), no constantes)
  • Mantener la misma estructura visual (logo izquierda, metadata derecha, border-bottom naranja)
  • NO requieren código nuevo si la implementación de Etapa 4 fue correcta

Si necesitás modificar drawSharedHeader o drawSharedFooter para soportar landscape, PARÁ y consultá. Probablemente significa que la implementación de Etapa 4 tiene un hardcode que se nos pasó.


4. Validaciones automatizadas mandatorias

Antes de present_files, ejecutá y confirmá:

Para cada PDF generado:

1. Cantidad de páginas correcta:
   - actual: 3 páginas
   - automatizado: 3 páginas
   - roi: 4 páginas

2. Orientación de cada página (usando pdf-parse o pdfjs-dist para detectar):
   - actual:        portrait, LANDSCAPE, portrait
   - automatizado:  portrait, LANDSCAPE, portrait
   - roi:           portrait, portrait, portrait, portrait

3. Header y footer presentes en TODAS las páginas (incluyendo landscape):
   - "InQ ROI" en header de cada página
   - "Powered by InQuality" en footer de cada página
   - Paginación correcta "Página X de Y"

4. Análisis de píxeles del diagrama BPMN en página 2 (actual/automatizado):
   - El ancho del diagrama supera 200mm (40% más que en portrait)
   - Heatmap presente con verde + ámbar + rojo
   - Texto de las actividades legible (no truncado a "...")

5. Tests previos verdes: >= 469

6. NO hay regresiones en marcas:
   - "InQ ROI" presente en todas las páginas
   - "Process Cost Platform" ausente
   - "v0.1.0" ausente

Si CUALQUIER validación falla, NO uses present_files. Reportá qué falló y esperá instrucciones.


5. Entregables (Definition of Done)

[ ] Página BPMN del PDF Actual ahora en landscape
[ ] Página BPMN del PDF Automatizado ahora en landscape  
[ ] PDF ROI mantiene las 4 páginas en portrait (no tiene página BPMN)
[ ] Header y footer compartidos funcionan correctamente en ambas 
    orientaciones SIN modificar pdf-sections-shared.ts
    (si requirió modificación, justificá la causa)
[ ] Diagrama BPMN aprovecha el ancho landscape (mínimo 200mm de ancho)
[ ] 6 validaciones automatizadas confirmadas
[ ] PDFs presentados con present_files
[ ] Tests previos siguen verdes (469+)
[ ] Tests nuevos para verificar orientación de páginas (3-5 casos mínimo)
[ ] npm run build limpio
[ ] Decisiones tomadas por iniciativa documentadas en reporte (Patrón C.6)

6. Tests obligatorios

6.1 Tests unitarios

Crear tests/lib/export/pdf-orientation.test.ts:

describe('PDF orientation', () => {
  it('el PDF actual tiene mezcla portrait-landscape-portrait', async () => {
    const pdf = await generateActualPdf(mockData);
    const orientations = getPageOrientations(pdf);
    expect(orientations).toEqual(['portrait', 'landscape', 'portrait']);
  });

  it('el PDF automatizado tiene mezcla portrait-landscape-portrait', async () => {
    const pdf = await generateAutomatedPdf(mockData);
    const orientations = getPageOrientations(pdf);
    expect(orientations).toEqual(['portrait', 'landscape', 'portrait']);
  });

  it('el PDF ROI mantiene las 4 páginas en portrait', async () => {
    const pdf = await generateRoiPdf(mockData);
    const orientations = getPageOrientations(pdf);
    expect(orientations).toEqual(['portrait', 'portrait', 'portrait', 'portrait']);
  });

  it('el diagrama BPMN en landscape ocupa más de 200mm de ancho', async () => {
    const pdf = await generateActualPdf(mockData);
    const bpmnImageWidth = getImageWidthOnPage(pdf, 2);
    expect(bpmnImageWidth).toBeGreaterThan(200);
  });

  it('header funciona en página landscape sin modificación', async () => {
    // verificar que drawSharedHeader se llama desde página landscape
    // y produce output coherente (logo izquierda, fecha derecha)
  });
});

7. Reporte final a entregar

Cuando termines, devolveme:

  1. Tabla de archivos modificados/creados con líneas
  2. Resultado de las 6 validaciones automatizadas
  3. Cantidad final de tests verdes (esperado 474+)
  4. 3 PDFs presentados con present_files para validación visual
  5. Comparación de ancho del diagrama: ¿antes (portrait) vs ahora (landscape)?
  6. Hallazgos no esperados
  7. Decisiones tomadas por iniciativa según política CLAUDE.md

NO arranques Etapa 6 sin mi OK explícito.


8. Reglas operativas (recordatorios)

  1. Usar SOLO doc.addPage('a4', 'landscape'). Los otros métodos están prohibidos por gotchas conocidos.
  2. No hardcodear dimensiones de página. Usar doc.internal.pageSize.getWidth() y getHeight() siempre.
  3. No modificar pdf-sections-shared.ts salvo causa documentada. Si la implementación de Etapa 4 fue correcta, el header/footer deben funcionar en landscape sin tocar.
  4. No tocar contenido de páginas que NO son la BPMN. El resto del PDF se mantiene visualmente igual.
  5. No racionalizar problemas de renderizado. Si el diagrama se ve mal en landscape, investigar causa raíz (probablemente cálculo de escala).
  6. Política de iniciativa proactiva (CLAUDE.md):
    • Cambios Nivel 1 (refactor interno, mejor naming): ejecutar
    • Cambios Nivel 2 (visible pero reversible): ejecutar y reportar
    • Cambios Nivel 3 (marca, arquitectura, datos): CONSULTAR antes
  7. Si encontrás contradicción entre este prompt y la realidad del código: PARÁ y consultá.

9. Riesgo conocido — orientation switching en jsPDF

Tres bugs históricos en jsPDF al cambiar orientación entre páginas:

  1. Bug del autotable que se reinicia mal: si la página landscape contiene una tabla autotable y la siguiente página vuelve a portrait, autotable puede continuar en el ancho de la landscape. Solución: la página landscape de esta etapa contiene solo el diagrama BPMN como imagen, no tablas. Si en el futuro hay una tabla en landscape, requiere atención adicional.

  2. Bug de imagen recortada al cambiar orientación: si después de addPage('a4', 'landscape') no se recalcula el ancho disponible, la imagen se inserta con el ancho anterior (portrait) y se ve chiquita o se corta. Solución: siempre llamar getWidth() después de addPage.

  3. Footer absoluto en posición incorrecta: si el footer se posiciona con doc.internal.pageSize.height - 20, en landscape eso es ~575px, pero la altura landscape A4 es ~595px. Solución: usar getHeight() dinámico, no asumir altura.

Si durante la implementación encontrás algún comportamiento extraño en jsPDF que no esté en esta lista, investigá causa raíz antes de aplicar workaround. Documentá el hallazgo en el reporte final.