feat(sprint-3): biblioteca, recursos TO-BE, ROI mejorado e identidad de marca

Sprint 3 completo — 7 etapas:

- Etapa 1-2: biblioteca de procesos con agrupación por cliente (Dexie v4,
  LibraryPage, library-store, groupId/tags en Process)
- Etapa 3: modal de configuración global por proceso (horizonte, frecuencia,
  inversión, moneda) con validación y persistencia
- Etapa 4: panel de recursos rediseñado con soporte de unidades y minutos
  de utilización (utilizationMinutes + units)
- Etapa 5: recursos en escenario automatizado — motor de simulación,
  UI de edición en ActivityPanel, comparación AS-IS/TO-BE en reporte
- Etapa 6: PDF recursos automatizados, automatedAssignedResources requerido,
  561 tests pasando
- Etapa 7: identidad de marca — AppHeader gradiente naranja/magenta con logo
  InQuality en 5 páginas, favicon con logo real, logo/marca navegan a home

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 11:49:43 -03:00
parent d2746f07d1
commit 67e259e6ed
57 changed files with 4372 additions and 82 deletions

View File

@@ -0,0 +1,270 @@
# Sprint 3 — Etapa 6: PDF recursos automatizados + tests del sprint
## Contexto
Sos Claude Code trabajando en **InQ ROI**. Las reglas de implementación están en `simulador-web/CLAUDE.md`. El brief del sprint está en `sprints/sprint-3/BRIEF.md`.
**Estado al iniciar esta etapa:**
- Etapas 15 completadas. Motor de simulación usa `automatedAssignedResources` correctamente. ActivityPanel tiene sección "Recursos TO-BE". Reporte web muestra comparación AS-IS/TO-BE en filas expandibles.
- `Activity.automatedAssignedResources` sigue siendo `?` (opcional) en `types.ts` — pendiente de esta etapa.
- `drawResourcesSection` en `pdf-sections.ts` lee `activityRow?.assignedResources` para los campos `utilizationMinutes` y `units`, incluso cuando se exporta el tab "automatizado". Esto es un bug: para el escenario automatizado debería leer `automatedAssignedResources`.
- Los factories de Activity en tests (`makeAutoActivity`, `makeActivity`) no incluyen `automatedAssignedResources`. Cuando hagamos el campo requerido, TypeScript los marcará con error.
**Esta etapa cierra los tres pendientes del sprint: PDF correcto, campo promovido a requerido, tests del sprint.**
---
## Objetivo
**F.1 — PDF recursos automatizados**
Corregir `drawResourcesSection` para que cuando se exporta el escenario automatizado lea `automatedAssignedResources` (no `assignedResources`) al mostrar `utilizationMinutes` y `units` por fila.
**F.2 — Promover campo a requerido**
Cambiar `automatedAssignedResources?: ActivityResourceAssignment[]``automatedAssignedResources: ActivityResourceAssignment[]` en `types.ts`. Corregir todos los factories en tests que crean `Activity` sin ese campo.
**F.3 — Tests nuevos del sprint**
Tests unitarios del motor de simulación cubriendo los tres casos con `automatedAssignedResources`: solo fijo, solo recursos, mixto. Tests de `drawResourcesSection` para escenario automatizado.
---
## Cambios autorizados
### `src/domain/types.ts`
Remover el `?` del campo:
```typescript
// ANTES:
automatedAssignedResources?: ActivityResourceAssignment[] // 🆕 Sprint 3
// DESPUÉS:
automatedAssignedResources: ActivityResourceAssignment[] // requerido desde Etapa 6
```
Después de este cambio, TypeScript reportará errores en los factories de tests. Corregir todos los que aparezcan (ver sección de tests).
### `src/lib/export/pdf-sections.ts`
**Agregar parámetro `scenario` a `drawResourcesSection`:**
```typescript
export function drawResourcesSection(
doc: any,
autoTable: Function,
result: { perActivity: ActivitySimResult[] },
resources: Resource[],
activities: Activity[],
currency: string,
startY: number,
scenario: 'actual' | 'automatizado' = 'actual' // 🆕 default 'actual' = retrocompatible
): void
```
**Corregir el lookup de asignación** (línea ~547 del archivo actual):
```typescript
// ANTES (siempre usa AS-IS):
const assignment = activityRow?.assignedResources.find((a) => a.resourceId === br.resourceId)
// DESPUÉS (usa el array del escenario correcto):
const assignment = scenario === 'automatizado'
? activityRow?.automatedAssignedResources?.find((a) => a.resourceId === br.resourceId)
: activityRow?.assignedResources.find((a) => a.resourceId === br.resourceId)
```
**Nota:** `automatedAssignedResources` puede ser `undefined` en actividades antiguas migradas sin pasar por ImportPage (aunque la migración Dexie v4 debería poblarlas con `[]`). El optional chaining `?.find` ya cubre ese caso.
El resto del cuerpo de `drawResourcesSection` no cambia.
### `src/lib/export/pdf-export.ts`
En `exportScenarioPdf`, actualizar la llamada a `drawResourcesSection` para pasar el `tab`:
```typescript
// Antes:
drawResourcesSection(docAny, autoTable, result, resources, activities, currency, y)
// Después:
drawResourcesSection(docAny, autoTable, result, resources, activities, currency, y, tab)
```
Esta es la única línea que cambia en este archivo.
---
## Tests — correcciones de factories
Cuando se promueva `automatedAssignedResources` a requerido, TypeScript reportará error en los siguientes archivos. Agregar `automatedAssignedResources: []` a cada factory que construye un `Activity`:
### `tests/domain/simulation.test.ts`
**Factory `makeAutoActivity`** (~línea 218): agregar campo:
```typescript
automatedCostFixed: opts.automatedCost ?? 0,
automatedTimeMinutes: opts.automatedTime ?? 0,
automatedAssignedResources: opts.automatedAssignedResources ?? [], // 🆕
```
Si la interfaz de `opts` no incluye el campo, agregar:
```typescript
automatedAssignedResources?: ActivityResourceAssignment[]
```
**Factory genérico** (~línea 77): agregar `automatedAssignedResources: []`.
**Factory `makeActivity` para recursos** (~línea 410): agregar `automatedAssignedResources: []`.
### `tests/integration/indexeddb-persistence.test.ts`
**`makeActivity`** (~línea 40): agregar `automatedAssignedResources: []` al objeto retornado.
### `tests/integration/sprint1-report-roi.test.ts`
**Factories inline** (~líneas 51, 311): agregar `automatedAssignedResources: []` a cada construcción de Activity.
### `tests/lib/export/pdf-resources-section.test.ts`
**`mockActivity`** (~línea 34): agregar `automatedAssignedResources: []`.
### `tests/integration/dexie-migration.test.ts`
**`makeLegacyActivity`** (~línea 19): usa `Omit<Activity, 'automatable' | 'automatedCostFixed' | 'automatedTimeMinutes'>`. Verificar si el tipo resultante cubre `automatedAssignedResources`. Si TypeScript reporta error, agregar `'automatedAssignedResources'` al Omit O cambiar la función para retornar directamente el campo con `[]`.
---
## Tests — nuevos
### `tests/domain/simulation-sprint3.test.ts` (NUEVO)
Cubrir los tres casos de recursos en escenario automatizado. Usar el mismo BPMN lineal simple de un nodo que ya existe en otros tests del dominio.
#### Caso 1: solo costo fijo, sin recursos TO-BE
```
Actividad automatable, automatedCostFixed=100, automatedTimeMinutes=60, automatedAssignedResources=[]
→ scenario=automated: costo esperado = 100 * execProb (sin costo de recursos)
→ resourceCostBreakdown vacío
```
#### Caso 2: solo recursos TO-BE, sin costo fijo
```
Actividad automatable, automatedCostFixed=0, automatedTimeMinutes=30
automatedAssignedResources=[{ resourceId: 'r1', utilizationMinutes: 60, units: 2 }]
Resource r1: costPerHour=30
→ scenario=automated: resourceCost = 30 * (60/60) * 2 * execProb = 60 * execProb
→ resourceCostBreakdown: [{ resourceId: 'r1', resourceName: 'Bot', cost: 60 * execProb }]
```
#### Caso 3: mixto — costo fijo + recurso TO-BE
```
Actividad automatable, automatedCostFixed=50, automatedTimeMinutes=30
automatedAssignedResources=[{ resourceId: 'r1', utilizationMinutes: 30, units: 1 }]
Resource r1: costPerHour=60
→ scenario=automated: totalCost = (50 + 60*(30/60)*1) * execProb = (50+30) * execProb = 80 * execProb
```
#### Caso 4: actividad NO automatable → usa assignedResources (regresión)
```
Actividad automatable=false, con assignedResources=[r1] y automatedAssignedResources=[r2]
→ scenario=automated: usa r1 (assignedResources), NO r2
```
**Nota de arquitectura:** para los tests de dominio puro, usar un grafo BPMN de un solo nodo secuencial (sin gateways), igual que en el `describe('scenario=automated')` ya existente en `simulation.test.ts`. Los inputs del motor (`activities`, `resources`, `gateways`, `graph`) se construyen inline.
### `tests/lib/export/pdf-resources-section.test.ts` — tests nuevos (agregar al archivo existente)
Agregar un `describe` nuevo: `drawResourcesSection — escenario automatizado`:
#### Test 1: escenario automatizado usa automatedAssignedResources para min/unidades
```
mockActivity con assignedResources=[{ utilMin: 30, units: 1 }] y automatedAssignedResources=[{ utilMin: 5, units: 3 }]
Llamar drawResourcesSection(..., scenario='automatizado')
→ autoTable debe recibir min='5' y units='3' (no '30' y '1')
```
#### Test 2: escenario actual usa assignedResources (regresión)
```
Mismo mockActivity
Llamar drawResourcesSection(..., scenario='actual') [o sin el param]
→ autoTable recibe min='30' y units='1'
```
---
## ❌ Lo que NO entra en esta etapa
- Tests E2E de la biblioteca (Playwright) — diferido a Etapa 7 o post-sprint si hay tiempo
- PDF de ROI — no modificar `pdf-sections-roi.ts`
- Cambios en `simulation.ts`, `ActivityPanel.tsx`, `ActivitiesTable.tsx` — ya cerrados en Etapa 5
- Cambios en `WorkspacePage.tsx`, `BpmnCanvas.tsx`, stores — ya cerrados en Etapas anteriores
- Nuevas features de UI
---
## Verificación antes de entregar
```bash
# 1. Build limpio (verifica que el campo requerido no deja ningún any suelto)
npm run build
# 2. Tests — el count debe subir respecto a 555
npm run test
# 3. Control de scope
git diff --name-only HEAD
```
El diff debe mostrar únicamente:
- `src/domain/types.ts`
- `src/lib/export/pdf-sections.ts`
- `src/lib/export/pdf-export.ts`
- `tests/domain/simulation.test.ts`
- `tests/domain/simulation-sprint3.test.ts` (nuevo)
- `tests/lib/export/pdf-resources-section.test.ts`
- `tests/integration/indexeddb-persistence.test.ts`
- `tests/integration/sprint1-report-roi.test.ts`
- `tests/integration/dexie-migration.test.ts`
Si aparece cualquier archivo de `src/features/`, `src/store/`, o `src/persistence/`: **revertir y reportar**.
### Verificación funcional mandatoria
Antes de reportar, verificar manualmente:
1. **PDF automatizado muestra min/unidades correctas:** asignar un recurso TO-BE con `utilizationMinutes=5, units=3` → exportar PDF desde el tab Automatizado → verificar que la tabla de recursos muestra `5` y `3` (no los valores del AS-IS).
2. **PDF actual no regresiona:** exportar PDF desde tab Actual → tabla de recursos muestra los valores de `assignedResources` sin cambio.
3. **TypeScript limpio:** `npm run build` sin errores — el campo promovido a requerido no deja `any` ni errores de tipo.
---
## Criterios de aceptación
- [ ] `automatedAssignedResources` requerido en `types.ts` sin errores TypeScript en build.
- [ ] PDF tab "Automatizado": tabla de recursos muestra `utilizationMinutes` y `units` de `automatedAssignedResources`.
- [ ] PDF tab "Actual": sin regresión, tabla sin cambio.
- [ ] Todos los tests previos siguen verdes (555+).
- [ ] 4 nuevos tests en `simulation-sprint3.test.ts` (casos 14) verdes.
- [ ] 2 nuevos tests en `pdf-resources-section.test.ts` verdes.
- [ ] Validación visual del director aprobada (PDF tab Automatizado con datos correctos).
---
## Reporte esperado al terminar
1. Resultado de `git diff --name-only HEAD`.
2. Resultado de `npm run build` y `npm run test` (incluir count final de tests).
3. Confirmación de los 3 casos de verificación funcional.
4. Lista de factories de tests corregidos (archivo + línea aproximada).
5. Cualquier decisión de iniciativa (Nivel 1/2/3).
6. Si algún archivo de test tuvo más cambios de los esperados: declararlo.
---
## Referencias
- `sprints/sprint-3/BRIEF.md` — sección 4, Feature F
- `src/lib/export/pdf-sections.ts``drawResourcesSection` (~línea 479), lookup de `assignedResources` (~línea 547)
- `src/lib/export/pdf-export.ts``exportScenarioPdf` (~línea 54), llamada a `drawResourcesSection` (~línea 147)
- `tests/domain/simulation.test.ts` — factories `makeAutoActivity` (~línea 218) y genérico (~línea 77)
- `tests/lib/export/pdf-resources-section.test.ts``mockActivity` fixture (~línea 34)
- `methodology/ANTI_PATTERNS.md` — AP.8 (adelanto silencioso), AP.9 (restricciones contradictorias)
- `methodology/PATTERNS.md` — C.7 (git diff como control preventivo)