Files
inq-roi-simulador-web/sprints/sprint-2/ETAPA_1_PROMPT.md

260 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sprint 2 — Etapa 1: Migración del modelo de recursos
> **Tipo:** dominio puro + persistencia. Sin UI. Sin cambios visuales.
> **Objetivo:** migrar `ActivityResourceAssignment.utilizationPercent` → `utilizationMinutes + units`, actualizar la fórmula de simulación, incrementar Dexie a v3, y garantizar que los datos existentes se preservan correctamente.
> **Resultado esperado:** tests verdes + reporte de archivos modificados. La UI en Etapas 23.
---
## Contexto imprescindible
### Estado actual del modelo (lo que vas a encontrar en el código)
```typescript
// src/domain/types.ts — HOY
interface ActivityResourceAssignment {
resourceId: UUID
utilizationPercent: number // 01, fracción del tiempo de la actividad
}
// Fórmula actual en src/domain/simulation.ts
const hoursUsed = (executionTimeMinutes / 60) * assignment.utilizationPercent
const cost = resource.costPerHour * hoursUsed * execProb
```
```typescript
// src/domain/schemas.ts — HOY
export const ActivityResourceAssignmentSchema = z.object({
resourceId: z.string().uuid(),
utilizationPercent: z.number().min(0).max(1),
})
```
```typescript
// src/persistence/db.ts — HOY
// Dexie versión 2 (v1 original + v2 de Sprint 1)
// La tabla 'resources' ya existe desde v1.
// ActivityResourceAssignment está embebido en Activity (no es tabla propia).
```
### Lo que NO existe todavía (no confundir con lo anterior)
- No existe tabla `resourceAssignments` separada. Los assignments están embebidos en el array `Activity.assignedResources`.
- No existe campo `utilizationMinutes`. Solo existe `utilizationPercent`.
- No existe campo `units`. Hay que agregarlo.
---
## Cambios requeridos
### 1. `src/domain/types.ts`
Reemplazar `ActivityResourceAssignment`:
```typescript
// ANTES
export interface ActivityResourceAssignment {
resourceId: UUID
utilizationPercent: number
}
// DESPUÉS
export interface ActivityResourceAssignment {
resourceId: UUID
utilizationMinutes: number // minutos que este recurso dedica por ejecución (independiente del tiempo de la actividad)
units: number // cantidad de unidades del recurso (ej: 2 impresoras). Default: 1.
}
```
El tipo `Resource` NO cambia. Solo cambia `ActivityResourceAssignment`.
### 2. `src/domain/schemas.ts`
Reemplazar `ActivityResourceAssignmentSchema`:
```typescript
// ANTES
export const ActivityResourceAssignmentSchema = z.object({
resourceId: z.string().uuid(),
utilizationPercent: z.number().min(0).max(1),
})
// DESPUÉS
export const ActivityResourceAssignmentSchema = z.object({
resourceId: z.string().uuid(),
utilizationMinutes: z.number().min(0, 'Los minutos no pueden ser negativos'),
units: z.number().int().min(1, 'Las unidades deben ser al menos 1').default(1),
})
```
### 3. `src/domain/simulation.ts`
Actualizar la fórmula de costo de recursos. Buscar el bloque que calcula `hoursUsed` y reemplazarlo:
```typescript
// ANTES
const hoursUsed = (executionTimeMinutes / 60) * assignment.utilizationPercent
const cost = resource.costPerHour * hoursUsed * execProb
// DESPUÉS
const hoursUsed = (assignment.utilizationMinutes / 60) * assignment.units
const cost = resource.costPerHour * hoursUsed * execProb
```
El `execProb` se mantiene igual — sigue siendo la probabilidad de ejecución del nodo en el grafo BPMN. El costo esperado del recurso por ejecución del proceso es `costPerHour × utilizationMinutes/60 × units × execProb`.
También actualizar la línea de acumulación de minutos por recurso (buscar `resourceTotals`):
```typescript
// ANTES
minutes: prev.minutes + activity.executionTimeMinutes * assignment.utilizationPercent * execProb,
// DESPUÉS
minutes: prev.minutes + assignment.utilizationMinutes * assignment.units * execProb,
```
### 4. `src/persistence/db.ts`
Agregar versión 3 con función de upgrade. La migración convierte los assignments embebidos en Activity:
```typescript
this.version(3)
.stores({
// Los stores no cambian — ActivityResourceAssignment está embebido en Activity, no es tabla propia
processes: 'id, name, updatedAt',
activities: 'id, processId, bpmnElementId',
gateways: 'id, processId, bpmnElementId',
resources: 'id, processId, name',
simulations: 'id, processId, executedAt',
})
.upgrade(async (tx) => {
// Migrar assignedResources de cada actividad: utilizationPercent → utilizationMinutes + units
await tx.table('activities').toCollection().modify((act) => {
if (!Array.isArray(act.assignedResources)) {
act.assignedResources = []
return
}
act.assignedResources = act.assignedResources.map((assignment: Record<string, unknown>) => {
if ('utilizationPercent' in assignment) {
// Heurística de conversión: minutos = round(executionTimeMinutes × utilizationPercent)
// Si executionTimeMinutes no está disponible o es 0, usar 0 (conservador)
const execMinutes = typeof act.executionTimeMinutes === 'number' ? act.executionTimeMinutes : 0
const utilizationMinutes = Math.round(execMinutes * (assignment.utilizationPercent as number))
return {
resourceId: assignment.resourceId,
utilizationMinutes,
units: 1,
}
}
// Ya está en el nuevo formato (por si se corre la migración dos veces)
if ('utilizationMinutes' in assignment) {
return {
resourceId: assignment.resourceId,
utilizationMinutes: assignment.utilizationMinutes ?? 0,
units: assignment.units ?? 1,
}
}
// Fallback defensivo
return { resourceId: assignment.resourceId, utilizationMinutes: 0, units: 1 }
})
})
})
```
**Importante:** no eliminar las versiones 1 y 2 anteriores. Dexie requiere el historial completo de versiones.
### 5. Puntos de inicialización en `src/features/import/ImportPage.tsx`
Buscar donde se inicializa una nueva actividad (al importar BPMN). Hay una línea similar a:
```typescript
assignedResources: [],
```
Esto está bien — un array vacío es válido con el nuevo modelo. No requiere cambio, pero verificar que no haya ningún lugar que inicialice con `utilizationPercent`.
**Grep de verificación:** buscar en todo `src/` cualquier referencia a `utilizationPercent`. Al terminar la etapa, esta búsqueda debe devolver cero resultados.
---
## Tests requeridos
### Tests unitarios — `tests/domain/simulation.test.ts`
Agregar los siguientes casos al archivo existente (no borrar los tests actuales):
```
describe('recursos con modelo utilizationMinutes + units', () => {
test('actividad sin recursos: costo = directCostFixed')
test('actividad con 1 recurso: costo = fijo + costPerHour × utilizationMinutes/60 × units × execProb')
test('actividad con 2 recursos: costo = fijo + suma de costos de recursos')
test('units = 2: costo de recurso se duplica')
test('utilizationMinutes = 0: el recurso no agrega costo')
test('resource no encontrado en el mapa: se ignora sin lanzar error')
})
```
### Test de migración — `tests/integration/` (crear archivo nuevo `db-migration-v3.test.ts`)
```
test('migración v2→v3: actividad con utilizationPercent se convierte a utilizationMinutes correctamente')
test('migración v2→v3: actividad sin assignedResources mantiene array vacío')
test('migración v2→v3: actividad con assignedResources vacío no falla')
test('migración v2→v3: el costo calculado después de la migración es aproximadamente igual al anterior')
// El costo debe ser igual ± redondeo de Math.round()
```
---
## Verificaciones finales obligatorias
Antes de reportar la etapa como completa:
1. **Grep de limpieza:** `grep -r "utilizationPercent" src/` — debe devolver 0 resultados.
2. **Build limpio:** `npm run build` sin errores de TypeScript.
3. **Tests:** `npm run test` — todos los tests (existentes + nuevos) deben pasar.
4. **Smoke test manual:** cargar la app en dev (`npm run dev`), abrir un proceso existente con recursos, verificar que no crashea y que el workspace carga.
---
## Alcance estricto — lo que NO entra en esta etapa
- ❌ Cambios en `ActivityPanel.tsx` (UI del panel de actividad) — Etapa 2
- ❌ Cambios en `ResourcesPanel.tsx` — Etapa 2 si necesita ajustes
- ❌ Cambios en `ActivitiesTable.tsx` ni en el reporte — Etapa 3
- ❌ Cambios en PDFs — Etapa 5
- ❌ Edición inline de proceso/cliente — Etapa 4
- ❌ CumulativeSavingsChart, payback label, badge — Etapa 3
Si al hacer el grep de limpieza encontrás referencias a `utilizationPercent` en componentes UI, **no las corrijas en esta etapa**. Documentalas en el reporte para que Etapa 2 las tome.
---
## Reporte esperado al terminar
```
## Etapa 1 completada
### Archivos modificados
- src/domain/types.ts — ActivityResourceAssignment: utilizationPercent → utilizationMinutes + units
- src/domain/schemas.ts — ActivityResourceAssignmentSchema actualizado
- src/domain/simulation.ts — fórmula de costo de recursos actualizada
- src/persistence/db.ts — versión 3 con migración
### Archivos creados
- tests/integration/db-migration-v3.test.ts
### Tests
- X tests unitarios nuevos (todos verdes)
- X tests de migración (todos verdes)
- X tests previos (todos siguen verdes)
### Grep de limpieza
- "utilizationPercent" en src/: 0 resultados ✅
(o lista de archivos UI pendientes para Etapa 2)
### Build
- npm run build: ✅ sin errores TypeScript
### Notas
[cualquier hallazgo relevante para Etapas siguientes]
```