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

9.3 KiB
Raw Blame History

Sprint 2 — Etapa 1: Migración del modelo de recursos

Tipo: dominio puro + persistencia. Sin UI. Sin cambios visuales. Objetivo: migrar ActivityResourceAssignment.utilizationPercentutilizationMinutes + 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)

// 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
// src/domain/schemas.ts — HOY
export const ActivityResourceAssignmentSchema = z.object({
  resourceId: z.string().uuid(),
  utilizationPercent: z.number().min(0).max(1),
})
// 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:

// 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:

// 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:

// 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):

// 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:

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:

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]