/** * Tests de migración Dexie v3 → v4 (Sprint 3). * * Verifican que: * 1. Proceso existente conserva todos sus campos y recibe groupId=null, tags=[]. * 2. Activity existente conserva assignedResources y recibe automatedAssignedResources=[]. * 3. Settings singleton creado con groupLabel='Cliente' durante el upgrade. * 4. El upgrade es idempotente: datos ya migrados no se corrompen. * 5. groupId puede ser un UUID válido y es queryable como índice. * * Usan fake-indexeddb (parcheado en tests/setup.ts). */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { db } from '@/persistence/db' import type { Process, ProcessGroup } from '@/domain/types' // ─── Helper ─────────────────────────────────────────────────────────────────── function makeProcess(id: string, overrides: Partial = {}): Process { return { id, name: `Proceso ${id}`, clientName: 'Cliente Test', bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0, createdAt: 1000, updatedAt: 2000, groupId: null, tags: [], ...overrides, } } // ─── Test 1 — Process: lógica del upgrade (puro, sin base de datos) ─────────── describe('Migración Dexie v4 — Process: lógica de upgrade', () => { it('proceso v3 (sin groupId ni tags) recibe defaults correctos preservando campos originales', () => { const proc: Record = { id: 'p1', name: 'Proceso Legado', clientName: 'Cliente A', bpmnXml: '', currency: 'PYG', overheadPercentage: 0.15, annualFrequency: 500, analysisHorizonYears: 5, automationInvestment: 50_000, createdAt: 1000, updatedAt: 2000, // Sin groupId ni tags — formato v3 } // Simular callback del upgrade if (proc.groupId === undefined) proc.groupId = null if (!Array.isArray(proc.tags)) proc.tags = [] // Campos originales intactos expect(proc.id).toBe('p1') expect(proc.name).toBe('Proceso Legado') expect(proc.clientName).toBe('Cliente A') expect(proc.currency).toBe('PYG') expect(proc.overheadPercentage).toBe(0.15) expect(proc.annualFrequency).toBe(500) expect(proc.analysisHorizonYears).toBe(5) expect(proc.automationInvestment).toBe(50_000) expect(proc.createdAt).toBe(1000) expect(proc.updatedAt).toBe(2000) // Nuevos campos con defaults correctos expect(proc.groupId).toBeNull() expect(proc.tags).toEqual([]) }) }) // ─── Test 2 — Activity: lógica del upgrade (puro, sin base de datos) ───────── describe('Migración Dexie v4 — Activity: lógica de upgrade', () => { it('activity v3 con assignedResources poblados recibe automatedAssignedResources=[]', () => { const act: Record = { id: 'a1', processId: 'p1', bpmnElementId: 'task_a1', name: 'Tarea A', type: 'task', directCostFixed: 500, executionTimeMinutes: 60, assignedResources: [{ resourceId: 'r1', utilizationMinutes: 30, units: 1 }], automatable: true, automatedCostFixed: 200, automatedTimeMinutes: 15, // Sin automatedAssignedResources — formato v3 } // Simular callback del upgrade if (!Array.isArray(act.automatedAssignedResources)) { act.automatedAssignedResources = [] } // assignedResources no fue alterado expect(act.assignedResources).toEqual([{ resourceId: 'r1', utilizationMinutes: 30, units: 1 }]) // campos de automatización intactos expect(act.automatable).toBe(true) expect(act.automatedCostFixed).toBe(200) expect(act.automatedTimeMinutes).toBe(15) // nuevo campo vacío expect(act.automatedAssignedResources).toEqual([]) }) }) // ─── Test 3 — Settings singleton (integración) ─────────────────────────────── describe('Migración Dexie v4 — Settings singleton', () => { beforeEach(async () => { await db.open() }) afterEach(async () => { await db.close() }) it('singleton creado con groupLabel=Cliente durante el upgrade', async () => { const settings = await db.settings.get('singleton') expect(settings).toBeDefined() expect(settings!.id).toBe('singleton') expect(settings!.groupLabel).toBe('Cliente') }) }) // ─── Test 4 — Idempotencia (puro, sin base de datos) ───────────────────────── describe('Migración Dexie v4 — Idempotencia', () => { it('upgrade no corrompe proceso ya migrado (groupId y tags con valores)', () => { const proc: Record = { id: 'p2', groupId: null, tags: ['facturación', 'BPMN'], } // Simular upgrade (idempotente) if (proc.groupId === undefined) proc.groupId = null if (!Array.isArray(proc.tags)) proc.tags = [] expect(proc.groupId).toBeNull() expect(proc.tags).toEqual(['facturación', 'BPMN']) }) it('upgrade no corrompe activity ya migrada (automatedAssignedResources poblado)', () => { const act: Record = { id: 'a2', automatedAssignedResources: [{ resourceId: 'r2', utilizationMinutes: 10, units: 2 }], } if (!Array.isArray(act.automatedAssignedResources)) { act.automatedAssignedResources = [] } expect(act.automatedAssignedResources).toEqual([ { resourceId: 'r2', utilizationMinutes: 10, units: 2 }, ]) }) }) // ─── Test 5 — groupId como UUID válido (integración) ───────────────────────── describe('Migración Dexie v4 — groupId como UUID válido', () => { beforeEach(async () => { await db.open() await db.transaction('rw', [db.processes, db.groups], async () => { await db.processes.clear() await db.groups.clear() }) }) afterEach(async () => { await db.close() }) it('proceso con groupId persiste y es queryable por el índice groupId', async () => { const group: ProcessGroup = { id: 'group-abc-123', name: 'Grupo Test', createdAt: 1000, updatedAt: 2000, } await db.groups.put(group) const proc: Process = makeProcess('p3', { groupId: 'group-abc-123', tags: ['test'], }) await db.processes.put(proc) // El campo persiste const retrieved = await db.processes.get('p3') expect(retrieved!.groupId).toBe('group-abc-123') expect(retrieved!.tags).toEqual(['test']) // El índice funciona para query por grupo const byGroup = await db.processes.where('groupId').equals('group-abc-123').toArray() expect(byGroup).toHaveLength(1) expect(byGroup[0].id).toBe('p3') }) it('proceso con groupId=null persiste y es recuperable', async () => { const proc: Process = makeProcess('p4', { groupId: null, tags: [] }) await db.processes.put(proc) const retrieved = await db.processes.get('p4') expect(retrieved).toBeDefined() expect(retrieved!.groupId).toBeNull() // Verificar con filter (Dexie no acepta null en .equals()) const sinGrupo = await db.processes.filter((p) => p.groupId == null).toArray() expect(sinGrupo.some((p) => p.id === 'p4')).toBe(true) }) })