Sprint 2 Etapas 1-2: modelo recursos utilizationMinutes+units, Dexie v3, ActivityPanel wow
This commit is contained in:
259
sprints/sprint-2/ETAPA_1_PROMPT.md
Normal file
259
sprints/sprint-2/ETAPA_1_PROMPT.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# 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 2–3.
|
||||
|
||||
---
|
||||
|
||||
## 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 // 0–1, 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]
|
||||
```
|
||||
Reference in New Issue
Block a user