import { describe, it, expect } from 'vitest' import { runSimulation } from '@/domain/simulation' import type { Activity, Resource, SimulationInput } from '@/domain/types' // BPMN de un solo nodo — execProb = 1.0, sin gateways const SINGLE_TASK_BPMN = ` f1 f1f2 f2 ` const makeResource = (id: string, costPerHour: number): Resource => ({ id, name: `Recurso ${id}`, type: 'role', costPerHour, }) const baseActivity = (overrides: Partial = {}): Activity => ({ id: 'act-taskA', processId: 'proc1', bpmnElementId: 'taskA', name: 'Tarea A', type: 'task', directCostFixed: 0, executionTimeMinutes: 0, assignedResources: [], automatable: true, automatedCostFixed: 0, automatedTimeMinutes: 0, automatedAssignedResources: [], ...overrides, }) const baseInput = (activity: Activity, resources: Resource[] = []): SimulationInput => ({ processXml: SINGLE_TASK_BPMN, activities: new Map([['taskA', activity]]), gateways: new Map(), resources: new Map(resources.map((r) => [r.id, r])), globalSettings: { currency: 'USD', overheadPercentage: 0 }, scenario: 'automated', }) describe('Sprint 3 — automatedAssignedResources en motor de simulación', () => { it('Caso 1: solo costo fijo, sin recursos TO-BE — resourceCostBreakdown vacío', () => { const act = baseActivity({ automatedCostFixed: 100, automatedTimeMinutes: 60, automatedAssignedResources: [] }) const result = runSimulation(baseInput(act)) const actResult = result.perActivity[0] expect(actResult.expectedDirectCost).toBeCloseTo(100) expect(actResult.resourceCostBreakdown).toHaveLength(0) }) it('Caso 2: solo recursos TO-BE — costo = costPerHour × (min/60) × units × execProb', () => { const r1 = makeResource('r1', 30) const act = baseActivity({ automatedCostFixed: 0, automatedTimeMinutes: 30, automatedAssignedResources: [{ resourceId: 'r1', utilizationMinutes: 60, units: 2 }], }) const result = runSimulation(baseInput(act, [r1])) // 30 $/h × (60min/60) × 2 units × 1.0 execProb = 60 const actResult = result.perActivity[0] expect(actResult.expectedDirectCost).toBeCloseTo(60) expect(actResult.resourceCostBreakdown).toHaveLength(1) expect(actResult.resourceCostBreakdown[0].resourceId).toBe('r1') expect(actResult.resourceCostBreakdown[0].cost).toBeCloseTo(60) }) it('Caso 3: mixto — costo fijo + recurso TO-BE se suman', () => { const r1 = makeResource('r1', 60) const act = baseActivity({ automatedCostFixed: 50, automatedTimeMinutes: 30, automatedAssignedResources: [{ resourceId: 'r1', utilizationMinutes: 30, units: 1 }], }) const result = runSimulation(baseInput(act, [r1])) // fijo=50 + 60*(30/60)*1=30 → total=80 const actResult = result.perActivity[0] expect(actResult.expectedDirectCost).toBeCloseTo(80) expect(actResult.resourceCostBreakdown[0].cost).toBeCloseTo(30) }) it('Caso 4: actividad NO automatable — usa assignedResources, ignora automatedAssignedResources', () => { const r1 = makeResource('r1', 60) const r2 = makeResource('r2', 100) const act = baseActivity({ automatable: false, directCostFixed: 0, assignedResources: [{ resourceId: 'r1', utilizationMinutes: 60, units: 1 }], automatedAssignedResources: [{ resourceId: 'r2', utilizationMinutes: 60, units: 1 }], }) const result = runSimulation(baseInput(act, [r1, r2])) // escenario automated pero automatable=false → usa r1 (assignedResources), no r2 const actResult = result.perActivity[0] expect(actResult.resourceCostBreakdown).toHaveLength(1) expect(actResult.resourceCostBreakdown[0].resourceId).toBe('r1') expect(actResult.resourceCostBreakdown[0].cost).toBeCloseTo(60) }) })