Commit pendiente de Etapas 3+4. Son cambios validados (migraciones corriendo, build verde, 521 tests). Hay que commitearlos antes de empezar la siguiente etapa.

This commit is contained in:
2026-06-16 21:52:44 -03:00
parent 33b1d47229
commit 025393cd43
14 changed files with 82 additions and 1208 deletions

View File

@@ -1,220 +0,0 @@
/**
* 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> = {}): Process {
return {
id,
name: `Proceso ${id}`,
clientName: 'Cliente Test',
bpmnXml: '<definitions/>',
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<string, unknown> = {
id: 'p1',
name: 'Proceso Legado',
clientName: 'Cliente A',
bpmnXml: '<definitions/>',
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<string, unknown> = {
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<string, unknown> = {
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<string, unknown> = {
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)
})
})