# Sprint 6 — BRIEF

> InQ ROI — Client-readiness polish + análisis de sensibilidad
> Inicio: 2026-06-23
> Foco: primer uso real con cliente externo como norte operativo

---

## Contexto

Sprint 5 cerró con el producto estable, en uso interno y con el reporte de ROI funcional. La pregunta que orientó la apertura de Sprint 6 fue: **¿qué le falta a InQ ROI para correrlo en vivo frente a un cliente y producir un informe de ROI creíble hoy?**

Diagnóstico al cierre de Sprint 5:

- El motor de simulación, el heatmap, la ComparisonTable y los 3 PDFs están listos.
- El consultor puede preparar el proceso con anticipación y correr la simulación en reunión.
- Dos gaps concretos antes de la primera sesión con cliente externo:
  1. **Sin logo en el header de los PDFs** — el entregable parece borrador sin marca visual.
  2. **Sin análisis de sensibilidad** — el ROI es un número puntual; cualquier cliente sofisticado pregunta "¿y si el volumen cae 20%?" y no hay respuesta dentro de la herramienta.

Sprint 6 cierra esos dos gaps.

---

## Etapas

### Etapa 1 — Logo InQuality en header de PDFs + whitespace-nowrap

**Prioridad:** bloqueante para entregable a cliente externo.

**Logo en PDF header:**

Los assets ya existen en `assets/`. Cuatro PNGs disponibles — usar el horizontal color para fondos claros:
- `MARCA FINAL 2023 AREA DE SEGURIDAD 1 COLOR.png` — candidato principal (header blanco)
- `MARCA FINAL 2023 AREA DE SEGURIDAD 1 BCO.png` — alternativa para headers con fondo oscuro (si aplica)

El logo va en el header compartido de los 3 PDFs (Actual, Automatizado, ROI). La función `drawSharedHeader` en `src/lib/export/pdf-sections-shared.ts` es el punto de inserción.

**Implementación:**

Convertir el PNG a base64 en tiempo de ejecución usando `fetch` + `arrayBuffer` → `btoa`, o pre-convertirlo como asset importado con `?url` de Vite. Insertar con `doc.addImage(base64, 'PNG', x, y, width, height)`. El logo reemplaza o complementa el texto "InQuality" que pueda existir actualmente en el header.

Restricciones:
- Ancho máximo: ~35mm (no puede dominar el header, el título del proceso sigue siendo protagonista)
- Alineación: derecha del header (o izquierda — validar visualmente con el director antes del OK formal)
- Mantener "Powered by InQuality" en el footer — no reemplaza, complementa

**Fold-in — whitespace-nowrap en celda de Eficiencia:**

En `src/features/report/ComparisonTable.tsx`, en el `<span>` del número dentro de `MetricCell` para la columna Eficiencia, agregar `whitespace-nowrap` para que "1.02 USD/min" no se corte en dos líneas.

Una línea de cambio. Sin tests que actualizar.

**Criterios:**
- [ ] Logo visible en el header de los 3 PDFs exportados
- [ ] Tamaño y posición validados visualmente por el director
- [ ] "Powered by InQuality" sigue en el footer de todas las páginas
- [ ] "1.02 USD/min" aparece en una sola línea en ComparisonTable
- [ ] `npm run lint` sin errores
- [ ] `npm run build` limpio
- [ ] Tests Vitest verdes (sin regresiones)
- [ ] Validación visual del director sobre el PDF antes del OK formal

---

### Etapa 2 — Panel de análisis de sensibilidad

**Contexto del problema:**

El ROI que calcula InQ ROI hoy es nominal y puntual: dadas estas condiciones exactas, el retorno es X. Pero las condiciones de un proceso real tienen incertidumbre — el volumen puede variar, la inversión puede dispararse, las estimaciones de tiempo pueden estar erradas. Un consultor que presente un ROI de USD 1.2M sin poder responder "¿y si el volumen cae 30%?" pierde credibilidad frente a cualquier CFO.

**Qué NO hacer:** re-simulación completa (implica cargar el BPMN, recorrer gateways, recalcular recursos — es lento y no necesario). Los costos por instancia del proceso ya están calculados y son estables. Solo cambian los agregados anuales.

**Qué SÍ hacer:** un panel que recalcula únicamente las fórmulas de ROI (`domain/roi.ts`) con inputs modificados por sliders — sin tocar el motor de simulación. El cálculo es instantáneo.

**Variables de sensibilidad (dos sliders):**

1. **Volumen (frecuencia anual):** rango -50% a +100%, paso 10%, default 0% (= valor configurado en el proceso). Afecta: ahorro anual, ROI, payback, trayectoria acumulada.

2. **Inversión en automatización:** rango -50% a +100%, paso 10%, default 0% (= valor configurado). Afecta: ROI, payback, trayectoria acumulada. No afecta el ahorro por ejecución.

**UX — versión mínima:**

- Panel colapsable "Análisis de sensibilidad" debajo de los KPIs de ROI en la pestaña "Comparación + ROI"
- Dos sliders con label, valor actual y delta en porcentaje (ej: "Volumen: 12.000 ejecuciones/año (+20%)")
- Los 4 KPIs de ROI (Ahorro por ejecución, Ahorro anual, Payback, ROI anual) se actualizan en tiempo real al mover los sliders
- Badge o indicador visual que muestra que los valores están en modo "análisis" (no son los valores base)
- Botón "Restablecer" que vuelve a los valores originales del proceso

**UX — versión wow (+criterio de wow):**

Además de la versión mínima, agregar una visualización de rango: para la variable más sensible (volumen), mostrar una banda de incertidumbre en el `CumulativeSavingsChart` existente — la línea actual se convierte en el caso base, y se agrega un área sombreada que muestra el rango [-30%, +30%] en el mismo gráfico. Misma paleta naranja, opacidad reducida para el rango. Esto convierte el gráfico de trayectoria de "certeza" a "escenario con rango", que es la forma en que un consultor financiero presenta proyecciones.

Implementar versión wow si el costo extra es < 40% del tiempo de la versión mínima.

**Implementación técnica:**

- El estado de los sliders vive en un `useState` local en `ReportPage.tsx` o en un componente `SensitivityPanel` nuevo — no persiste (se resetea al recargar, es análisis exploratorio)
- El recálculo de KPIs se hace importando las funciones de `domain/roi.ts` directamente, pasando los inputs modificados
- `CumulativeSavingsChart` recibe los datos recalculados vía props — no requiere cambios internos si ya acepta `roi: RoiResult` como prop
- El panel NO se exporta al PDF en esta etapa (es una feature interactiva del reporte web)

**Restricciones:**
- NO re-simular (no llamar al motor BPMN ni a `simulation.ts`)
- NO persistir en Supabase (es exploración, no configuración)
- NO tocar `domain/roi.ts` (solo consumirlo)
- El valor base del proceso (frecuencia, inversión) NO cambia en base de datos

**Criterios:**
- [ ] Panel visible en la pestaña "Comparación + ROI", colapsado por defecto
- [ ] Slider de volumen: -50% a +100%, los KPIs se actualizan en tiempo real
- [ ] Slider de inversión: -50% a +100%, los KPIs se actualizan en tiempo real
- [ ] Los valores base (sin ajuste de sliders) son idénticos a los KPIs actuales
- [ ] Botón "Restablecer" vuelve los sliders a 0%
- [ ] Indicador visual de "modo análisis" activo
- [ ] (Wow) Banda de rango visible en CumulativeSavingsChart si se implementa
- [ ] Tests unitarios del recálculo de sensibilidad en Vitest
- [ ] `npm run lint` sin errores
- [ ] `npm run build` limpio
- [ ] Validación visual del director antes del OK formal

---

### Etapa 3 — Quick wins y limpieza técnica

**3a — Investigación auth E2E localStorage (TECH_DEBT `[INVESTIGATING]`):**

Contradicción documentada en TECH_DEBT.md: en producción, `ProfileSheet` lee `supabase.auth.token`; en E2E, `auth-setup.ts` escribe `sb-${projectRef}-auth-token` y los smoke tests pasan. Determinar definitivamente qué clave usa el cliente Supabase de la app en producción comparando el localStorage real vs el `auth-state.json`.

Acción concreta: agregar un spec E2E que haga una query autenticada a Supabase y afirme que devuelve el `email` real del usuario de test (no vacío). Si el test pasa, el auth E2E funciona realmente. Si falla, el bug está confirmado.

**3b — Cleanup `setTimeout(350)` en ProfileSheet:**

`src/components/ProfileSheet.tsx` tiene un `setTimeout(350)` que difiere `updateUser` como mitigación de la race condition con Radix. El fix definitivo vive en `sheet.tsx` (useEffect sobre prop `open`). El setTimeout es redundante pero inofensivo.

Eliminar el setTimeout, verificar que el fix de `sheet.tsx` sea suficiente. Un test manual de guardar perfil confirma que funciona.

**3c — Animación de expansión sección "Automatización" en ActivityPanel:**

La sección usa el truco de `max-height` para la animación de expand/collapse. La aceleración no es lineal en los últimos píxeles (se nota el "freno" al final).

Fix: usar `useRef + scrollHeight` para animar a la altura real del contenido con una transición CSS. Impacto: micro-pulido visual, moderado esfuerzo.

Solo implementar si 3a y 3b no consumen el slot completo de la etapa.

**Criterios:**
- [ ] Spec E2E de auth: verde si el auth funciona, o diagnóstico definitivo + fix si no funciona
- [ ] `setTimeout(350)` eliminado de ProfileSheet, funcionalidad verificada manualmente
- [ ] TECH_DEBT.md actualizado: marcar RESOLVED los ítems cerrados
- [ ] (Opcional) Animación de expansión más fluida en ActivityPanel
- [ ] `npm run test` → mantener o superar 542 tests verdes
- [ ] Validación del director sobre el estado de la documentación

---

## Distribución del sprint

| Área | Etapas | % estimado |
|---|---|---|
| PDF (logo) | 1 parcial | ~15% |
| Web app features (sensibilidad) | 2 | ~65% |
| Infraestructura / deuda técnica | 3 | ~20% |

Dentro del rango de rebalanceo comprometido (60-70% web, 15-25% PDF, 10-15% infra).

---

## Qué NO entra en Sprint 6

- Historial de simulaciones / múltiples simulaciones por proceso
- Modelo B (dos BPMNs AS-IS / TO-BE)
- Export Excel
- Modo offline / PWA
- i18n
- Compartir procesos con usuarios externos (`setShared()`)
- Keycloak / migración de auth
- Análisis de sensibilidad en PDF (es feature interactiva, PDF es estático)

---

## Definition of Done del sprint

- [ ] 3 etapas con OK formal del director (validación visual de Etapas 1 y 2; validación documental de Etapa 3)
- [ ] `npm run build` limpio
- [ ] `npm run lint` sin warnings nuevos
- [ ] Tests Vitest: mantener o superar 542
- [ ] OBSERVATIONS.md con hallazgos del sprint
- [ ] STRATEGIC_DIRECTION.md actualizado al cerrar el sprint
