# Etapa 1 — Logo InQuality en header de PDFs + whitespace-nowrap **Sprint:** 6 **Fecha:** 2026-06-23 **Alcance:** 3 PDFs (Actual, Automatizado, ROI) + un fix de una línea en ComparisonTable. --- ## Contexto El header compartido de los 3 PDFs (`src/lib/export/pdf-sections-shared.ts`, función `drawSharedHeader`) hoy dibuja el texto "InQ ROI" en naranja bold como identidad de marca. El logo PNG de InQuality ya existe en `assets/`. El objetivo es reemplazar el texto por el logo, manteniendo el layout del header sin cambios estructurales. --- ## Parte A — Logo en header de PDFs ### Assets disponibles En `assets/` (ruta desde la raíz del proyecto): - `MARCA FINAL 2023 AREA DE SEGURIDAD 1 COLOR.png` — logo horizontal color, sobre fondos claros — **usar este** - `MARCA FINAL 2023 AREA DE SEGURIDAD 1 BCO.png` — logo horizontal blanco (no aplica, el header es blanco) ### Cómo cargar el PNG en jsPDF Importar la URL del asset con Vite: ```ts // En pdf-export.ts (o en un helper de carga) import logoColorUrl from '../../../assets/MARCA FINAL 2023 AREA DE SEGURIDAD 1 COLOR.png?url' ``` Convertir a base64 antes de generar cualquier PDF (operación async, una vez por export): ```ts async function loadImageBase64(url: string): Promise { const response = await fetch(url) const blob = await response.blob() return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(reader.result as string) reader.onerror = reject reader.readAsDataURL(blob) }) } ``` Llamar una vez al inicio de cada función de export (`exportPdf`, `exportRoiPdf`) y pasar el resultado como parámetro. ### Cambios en `PdfHeaderOptions` Agregar un campo opcional: ```ts export interface PdfHeaderOptions { pageNumber: number processName: string sectionTitle?: string clientName?: string simulatedAt: number logoBase64?: string // ← nuevo, opcional para retrocompatibilidad } ``` ### Cambios en `drawSharedHeader` Reemplazar el bloque de texto "InQ ROI" por lógica condicional: ```ts // ANTES: doc.setFont('helvetica', 'bold') doc.setFontSize(13) doc.setTextColor(...PDF_COLORS.inqOrange) doc.text('InQ ROI', MARGIN, y) // DESPUÉS: if (options.logoBase64) { // Logo PNG: ancho 32mm, alto proporcional (~9mm para este logo horizontal) // Alinear el baseline del logo con la primera línea de texto: y - 7 (arriba del baseline) doc.addImage(options.logoBase64, 'PNG', MARGIN, y - 7, 32, 9) } else { // Fallback al texto (retrocompatibilidad y tests) doc.setFont('helvetica', 'bold') doc.setFontSize(13) doc.setTextColor(...PDF_COLORS.inqOrange) doc.text('InQ ROI', MARGIN, y) } ``` **Nota sobre dimensiones:** el logo horizontal de InQuality tiene una proporción aproximada de 3.5:1 (ancho:alto). A 32mm de ancho → ~9mm de alto. Ajustar si el resultado visual no queda bien — la validación visual del director confirma antes del OK formal. ### Cambios en `pdf-export.ts` En las dos funciones de export (`exportPdf` y `exportRoiPdf`), agregar la carga del logo al inicio: ```ts export async function exportPdf(...): Promise { const logoBase64 = await loadImageBase64(logoColorUrl).catch(() => undefined) const headerOpts: PdfHeaderOptions = { // ...opciones existentes... logoBase64, } // resto sin cambios } ``` El `.catch(() => undefined)` garantiza que si el fetch del asset falla (ej: entorno sin red), el PDF se genera igual con el fallback de texto. No bloquear el export por el logo. ### Importante — NO cambiar - La estructura del header (posiciones de proceso, cliente, fecha, border-bottom) no cambia. - El footer ("Powered by InQuality") no cambia. - `drawSharedFooter` y `applyFootersToAllPages` no se tocan. - No agregar el logo en páginas 1 de cada PDF que tengan su propio diseño de portada (verificar si `drawScenarioPage1` y `drawRoiPage1` usan o no `drawSharedHeader` — si no lo usan, el logo solo aplica a las páginas que sí lo llaman). --- ## Parte B — whitespace-nowrap en celda de Eficiencia En `src/features/report/ComparisonTable.tsx`, dentro de `MetricCell`, el `` que muestra el valor numérico de la columna "Eficiencia" se corta en dos líneas ("1.02" / "USD/min") porque la celda es angosta. Fix: agregar `whitespace-nowrap` al className del ``: ```tsx // Buscar el span que tiene el valor formateado de la Eficiencia y agregar whitespace-nowrap {format(value)} ``` Una línea de cambio. Sin tests que actualizar. --- ## Criterios de validación - [ ] Logo visible en el header de todas las páginas de los 3 PDFs (Actual, Automatizado, ROI) - [ ] Logo en tamaño coherente con el header — no dominante, no microscópico - [ ] Alineación correcta: logo a la izquierda, fecha y cliente a la derecha - [ ] El footer "Powered by InQuality" sigue presente en todas las páginas - [ ] Si el asset no carga (fetch falla), el PDF se genera igual con texto "InQ ROI" como fallback - [ ] "1.02 USD/min" aparece en una sola línea en ComparisonTable - [ ] `npm run lint` sin errores - [ ] `npm run build` limpio - [ ] `npm run test` → 542 tests verdes (sin regresiones — los tests de PDF existentes usan mocks y no verifican el logo) - [ ] **Validación visual del director sobre el PDF exportado antes del OK formal** — esta es la validación principal de la etapa --- ## Archivos a modificar 1. `src/lib/export/pdf-sections-shared.ts` — `PdfHeaderOptions` + `drawSharedHeader` 2. `src/lib/export/pdf-export.ts` — carga del logo + pasar `logoBase64` a `headerOpts` 3. `src/features/report/ComparisonTable.tsx` — `whitespace-nowrap` en `MetricCell` No modificar ningún otro archivo. En particular, no tocar los tests de PDF existentes — el fallback de texto garantiza que sigan pasando sin cambios.