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

280 lines
12 KiB
Markdown

# 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:
```typescript
// ✅ 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:**
```typescript
// ❌ 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:
```typescript
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:**
```typescript
// 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.
### 3.5 Header y footer en página 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`:
```typescript
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.