# 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.