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>
This commit is contained in:
280
sprints/sprint-1-5/ETAPA_5_PROMPT.md
Normal file
280
sprints/sprint-1-5/ETAPA_5_PROMPT.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user