/** * Tests de invalidación del simulation store. * * Regla dura del Sprint 1: cualquier mutación en Activity o Process invalida * AMBOS resultados (resultActual y resultAutomated) a null simultáneamente. * Nunca puede haber un resultado "nuevo" y el otro "viejo". * * Nota: los tests poblan el store con setState() directamente (sin Dexie) * porque prueban el comportamiento en memoria, no la persistencia. */ import { describe, it, expect, beforeEach } from 'vitest' import { useSimulationStore } from '@/store/simulation-store' import { useProcessStore } from '@/store/process-store' import type { SimulationResult, Activity, Process } from '@/domain/types' // Resultado mock mínimo para poblar el store sin Dexie function makeMockResult(totalCost = 1000): SimulationResult { return { totalCost, totalDirectCost: totalCost * 0.8, totalIndirectCost: totalCost * 0.2, totalTimeMinutes: 60, perActivity: [], perResource: [], warnings: [], } } function setMockResults(actual = makeMockResult(), automated = makeMockResult(500)) { useSimulationStore.setState({ resultActual: actual, resultAutomated: automated, lastSimulatedAt: new Date(), }) } beforeEach(() => { // Resetear ambos stores antes de cada test para aislamiento useSimulationStore.getState().reset() useProcessStore.setState({ currentProcess: null, activities: [], gateways: [], resources: [], isLoading: false, error: null, }) }) // ─── invalidateResults() directa ───────────────────────────────────────────── describe('simulation store — invalidateResults()', () => { it('pone resultActual y resultAutomated a null', () => { setMockResults() expect(useSimulationStore.getState().resultActual).not.toBeNull() expect(useSimulationStore.getState().resultAutomated).not.toBeNull() useSimulationStore.getState().invalidateResults() expect(useSimulationStore.getState().resultActual).toBeNull() expect(useSimulationStore.getState().resultAutomated).toBeNull() }) it('también limpia lastSimulatedAt', () => { setMockResults() expect(useSimulationStore.getState().lastSimulatedAt).not.toBeNull() useSimulationStore.getState().invalidateResults() expect(useSimulationStore.getState().lastSimulatedAt).toBeNull() }) it('setState directo establece resultados independientes para actual y automated', () => { const mockA = makeMockResult(1000) const mockB = makeMockResult(500) setMockResults(mockA, mockB) const state = useSimulationStore.getState() expect(state.resultActual!.totalCost).toBe(1000) expect(state.resultAutomated!.totalCost).toBe(500) expect(state.lastSimulatedAt).toBeInstanceOf(Date) }) }) // ─── Invalidación reactiva vía suscripción al process store ────────────────── describe('simulation store — invalidación por cambio en process store', () => { it('cambio en activities invalida ambos resultados', () => { setMockResults() expect(useSimulationStore.getState().resultActual).not.toBeNull() useProcessStore.setState({ activities: [] }) expect(useSimulationStore.getState().resultActual).toBeNull() expect(useSimulationStore.getState().resultAutomated).toBeNull() }) it('cambio en currentProcess invalida ambos resultados', () => { setMockResults() const proc: Process = { id: 'new-proc', name: 'Nuevo', clientName: '', bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 500, analysisHorizonYears: 2, automationInvestment: 10_000, createdAt: Date.now(), updatedAt: Date.now(), groupId: null, tags: [], ownerId: 'test-user', updatedBy: 'test-user', } useProcessStore.setState({ currentProcess: proc }) expect(useSimulationStore.getState().resultActual).toBeNull() expect(useSimulationStore.getState().resultAutomated).toBeNull() }) it('editar campo de actividad invalida ambos resultados', () => { // Test explícito pedido en el brief: simula lo que hace updateActivity setMockResults() const newActivities: Activity[] = [{ id: 'a1', processId: 'p1', bpmnElementId: 'task_1', name: 'Tarea', type: 'task', directCostFixed: 999, // campo modificado executionTimeMinutes: 30, assignedResources: [], automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0, automatedAssignedResources: [], }] useProcessStore.setState({ activities: newActivities }) expect(useSimulationStore.getState().resultActual).toBeNull() expect(useSimulationStore.getState().resultAutomated).toBeNull() }) it('setState con las MISMAS activities (mismo reference) NO invalida', () => { const activities: Activity[] = [{ id: 'a1', processId: 'p1', bpmnElementId: 't1', name: 'T', type: 'task', directCostFixed: 100, executionTimeMinutes: 30, assignedResources: [], automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0, automatedAssignedResources: [], }] useProcessStore.setState({ activities }) setMockResults() // Misma referencia: no debe invalidar useProcessStore.setState({ activities }) expect(useSimulationStore.getState().resultActual).not.toBeNull() }) })