Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
loadImageBase64 ahora retorna { base64, pixelWidth, pixelHeight } (320×210px tras el resize) en vez de solo el string.
PdfHeaderOptions agrega logoPixelWidth/logoPixelHeight.
drawSharedHeader: logo a 15mm de ancho con alto calculado proporcionalmente desde las dimensiones reales (≈9.8mm, ratio real ~1.53:1, no el 3.56:1 hardcodeado anterior). El logo se posiciona arriba (MARGIN - 1), y processName + fecha bajan a la línea siguiente sin superponerse. clientName baja una línea adicional cuando hay logo (para no chocar con la fecha), ajustando y antes del border-bottom.
Fallback a texto "InQ ROI" intacto cuando no hay logo.
157 lines
5.8 KiB
Markdown
157 lines
5.8 KiB
Markdown
# 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<string> {
|
|
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<void> {
|
|
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 `<span>` 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 `<span>`:
|
|
|
|
```tsx
|
|
// Buscar el span que tiene el valor formateado de la Eficiencia y agregar whitespace-nowrap
|
|
<span className="font-mono text-slate-700 text-xs w-14 text-right whitespace-nowrap">
|
|
{format(value)}
|
|
</span>
|
|
```
|
|
|
|
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.
|