# Sprint 3 — Etapa 5: Recursos en escenario automatizado
## 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 1–4 completadas. Dexie v4 activo. Biblioteca operativa con grupos, tags, búsqueda. Indicador stale funcionando (persiste en recarga). Botón fit-viewport en BpmnCanvas.
- `Activity.automatedAssignedResources?: ActivityResourceAssignment[]` existe en `types.ts` como campo opcional desde Etapa 1. La migración v4 ya popula las actividades existentes con `[]`.
- `simulation.ts` actualmente usa `activity.assignedResources` en ambos escenarios. La rama `useAutomated` solo cambia `directCostFixed` y `executionTimeMinutes`, pero **no cambia los recursos** — bug intencional pendiente de esta etapa.
- `ActivityPanel.tsx` tiene la sección "Automatización" con toggle, `automatedCostFixed` e `automatedTimeMinutes`. Los recursos automatizados no tienen UI todavía.
- `ActivitiesTable.tsx` recibe `perActivity: ActivitySimResult[]` y muestra `resourceCostBreakdown` en filas expandibles.
**Esta etapa conecta las tres capas: motor de simulación, UI de edición, y visualización en reporte.**
---
## Objetivo
**E.1 — Motor de simulación**
Cuando `useAutomated = true`, usar `activity.automatedAssignedResources ?? []` en lugar de `activity.assignedResources` para el cálculo de costos de recursos.
**E.2 — UI en ActivityPanel**
Agregar subsección "Recursos TO-BE" dentro de la sección Automatización del ActivityPanel (visible cuando `automatable = true`). Mismo patrón de asignación que los recursos AS-IS.
**E.3 — Reporte web (versión mínima)**
Sin cambios necesarios: cuando el motor usa `automatedAssignedResources`, el `resourceCostBreakdown` de `SimulationResult` ya refleja correctamente los recursos del escenario automatizado. Las filas expandibles existentes en `ActivitiesTable` muestran el breakdown del escenario que corresponde a cada tab.
**E.3 wow — Comparación lado a lado**
En el tab "Automatizado", las filas expandibles de actividades automatizables muestran AMBOS breakdowns: "Recursos AS-IS" y "Recursos TO-BE", para facilitar la comparación.
---
## Arquitectura — leer antes de implementar
### `automatedAssignedResources` sigue siendo opcional en types.ts
**No cambiar `types.ts` en esta etapa.** El campo permanece como `automatedAssignedResources?: ActivityResourceAssignment[]`. La promoción a requerido + actualización de tests va en Etapa 6 para hacer el cambio en un solo paso limpio.
Consecuencia: en todo el código nuevo usar `activity.automatedAssignedResources ?? []` para manejar el caso de actividades antiguas que podrían no tener el campo.
### Dependencias autorizadas
`ActivityPanel` ya importa de `useProcessStore` para leer `resources`. No necesita importar nuevas stores para esta feature.
---
## Cambios autorizados y arquitectura
### `src/domain/simulation.ts`
**Cambio mínimo: usar los recursos correctos según el escenario.**
Localizar el bloque de la línea ~207:
```typescript
// ACTUAL (usa siempre assignedResources):
for (const assignment of activity.assignedResources) {
```
Reemplazar por:
```typescript
// CORRECTO: seleccionar el array según el escenario
const resourceAssignments = useAutomated
? (activity.automatedAssignedResources ?? [])
: activity.assignedResources
for (const assignment of resourceAssignments) {
```
Este es el único cambio en `simulation.ts`. No tocar nada más en este archivo.
### `src/features/import/ImportPage.tsx`
Agregar `automatedAssignedResources: []` a la construcción de actividades (~línea 89):
```typescript
const activities: Activity[] = activityElements.map((el) => ({
id: uuidv4(),
processId,
bpmnElementId: el.bpmnElementId,
name: el.name,
type: el.type,
directCostFixed: 0,
executionTimeMinutes: 0,
assignedResources: [],
automatable: false,
automatedCostFixed: 0,
automatedTimeMinutes: 0,
automatedAssignedResources: [], // 🆕
}))
```
**Clasificación de iniciativa:** Nivel 1 — compatibilidad preventiva, no hay cambio visible.
### `src/features/workspace/ActivityPanel.tsx`
#### 1 — Inicialización del estado local
Actualizar el `setLocalActivity` inicial para incluir `automatedAssignedResources`:
```typescript
setLocalActivity(activity ? {
...activity,
assignedResources: [...activity.assignedResources],
automatedAssignedResources: [...(activity.automatedAssignedResources ?? [])],
} : null)
```
#### 2 — Handlers de recursos automatizados
Agregar tres handlers paralelos a los de AS-IS (cerca de `addResource`, `removeResource`, `updateResourceAssignment`):
```typescript
const addAutomatedResource = (resourceId: string) => {
if (!localActivity) return
const already = (localActivity.automatedAssignedResources ?? []).some(
(r) => r.resourceId === resourceId
)
if (already) return
handleChange('automatedAssignedResources', [
...(localActivity.automatedAssignedResources ?? []),
{ resourceId, utilizationMinutes: 60, units: 1 },
])
}
const removeAutomatedResource = (resourceId: string) => {
if (!localActivity) return
handleChange(
'automatedAssignedResources',
(localActivity.automatedAssignedResources ?? []).filter((r) => r.resourceId !== resourceId)
)
}
const updateAutomatedAssignment = (
resourceId: string,
field: 'utilizationMinutes' | 'units',
value: number
) => {
if (!localActivity) return
handleChange(
'automatedAssignedResources',
(localActivity.automatedAssignedResources ?? []).map((r) =>
r.resourceId === resourceId ? { ...r, [field]: value } : r
)
)
}
```
#### 3 — Dropdown de recursos disponibles para TO-BE
```typescript
const availableAutomatedResources = resources.filter(
(r) => !(localActivity.automatedAssignedResources ?? []).some((ar) => ar.resourceId === r.id)
)
```
#### 4 — Costo total de recursos TO-BE (para mostrar en UI)
```typescript
const localAutomatedResourceCost = (localActivity.automatedAssignedResources ?? []).reduce(
(total, assignment) => {
const r = resources.find((res) => res.id === assignment.resourceId)
if (!r) return total
return total + r.costPerHour * (assignment.utilizationMinutes / 60) * assignment.units
},
0
)
```
#### 5 — UI dentro del contenedor de Automatización
**Cambio requerido previo:** el contenedor animado actual tiene `max-h-80` (320px), insuficiente para el nuevo contenido. Cambiar a `max-h-[640px]`.
Dentro del contenedor animado (después de los inputs `automatedCostFixed` y `automatedTimeMinutes`), agregar:
```tsx
{/* Separator + Recursos TO-BE */}
Recursos TO-BE
{/* Dropdown para agregar recurso */} {availableAutomatedResources.length > 0 && ( )}Sin recursos TO-BE — solo costo fijo automatizado.
) : (Min. por ejecución
updateAutomatedAssignment( assignment.resourceId, 'utilizationMinutes', Math.max(0, parseFloat(e.target.value) || 0) ) } className="h-6 text-xs font-mono" />Unidades
updateAutomatedAssignment( assignment.resourceId, 'units', Math.max(1, parseInt(e.target.value) || 1) ) } className="h-6 text-xs font-mono" />AS-IS
{/* Lista del pairedBreakdown (recursos AS-IS) */} {pairedBreakdown[act.activityId].map((r) => (TO-BE
{act.resourceCostBreakdown.length === 0 ?Sin recursos TO-BE
: act.resourceCostBreakdown.map((r) => (