Sprint 3 completo — 7 etapas: - Etapa 1-2: biblioteca de procesos con agrupación por cliente (Dexie v4, LibraryPage, library-store, groupId/tags en Process) - Etapa 3: modal de configuración global por proceso (horizonte, frecuencia, inversión, moneda) con validación y persistencia - Etapa 4: panel de recursos rediseñado con soporte de unidades y minutos de utilización (utilizationMinutes + units) - Etapa 5: recursos en escenario automatizado — motor de simulación, UI de edición en ActivityPanel, comparación AS-IS/TO-BE en reporte - Etapa 6: PDF recursos automatizados, automatedAssignedResources requerido, 561 tests pasando - Etapa 7: identidad de marca — AppHeader gradiente naranja/magenta con logo InQuality en 5 páginas, favicon con logo real, logo/marca navegan a home Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
152 lines
5.5 KiB
TypeScript
152 lines
5.5 KiB
TypeScript
/**
|
|
* Tests de migración Dexie v1 → v2 (Sprint 1).
|
|
*
|
|
* Verifican que:
|
|
* 1. Registros sin los campos nuevos reciben defaults correctos tras el upgrade.
|
|
* 2. Registros con los campos ya presentes no son modificados.
|
|
* 3. Los nuevos campos se pueden leer y persistir correctamente.
|
|
*
|
|
* Usan fake-indexeddb (parcheado en tests/setup.ts).
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
import { db } from '@/persistence/db'
|
|
import { activityRepo, processRepo } from '@/persistence/repositories'
|
|
import type { Activity, Process } from '@/domain/types'
|
|
|
|
// ─── Fixtures de registros "viejos" (sin campos Sprint 1) ─────────────────────
|
|
|
|
function makeLegacyActivity(id: string, processId: string): Omit<Activity, 'automatable' | 'automatedCostFixed' | 'automatedTimeMinutes' | 'automatedAssignedResources'> {
|
|
return {
|
|
id, processId,
|
|
bpmnElementId: `task_${id}`,
|
|
name: `Tarea ${id}`,
|
|
type: 'task',
|
|
directCostFixed: 500,
|
|
executionTimeMinutes: 45,
|
|
assignedResources: [],
|
|
}
|
|
}
|
|
|
|
function makeLegacyProcess(id: string): Omit<Process, 'annualFrequency' | 'analysisHorizonYears' | 'automationInvestment' | 'groupId' | 'tags'> {
|
|
return {
|
|
id, name: 'Proceso Legacy', clientName: 'Cliente Viejo',
|
|
bpmnXml: '<definitions/>', currency: 'USD', overheadPercentage: 0.15,
|
|
createdAt: 1000, updatedAt: 2000,
|
|
}
|
|
}
|
|
|
|
// ─── Setup / Teardown ─────────────────────────────────────────────────────────
|
|
|
|
beforeEach(async () => {
|
|
await db.open()
|
|
await db.transaction('rw', [db.processes, db.activities], async () => {
|
|
await db.processes.clear()
|
|
await db.activities.clear()
|
|
})
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await db.close()
|
|
})
|
|
|
|
// ─── Tests de defaults tras migración ─────────────────────────────────────────
|
|
|
|
describe('Migración Dexie v2 — Activity: defaults de campos Sprint 1', () => {
|
|
it('actividades nuevas tienen automatable=false por defecto', async () => {
|
|
const act: Activity = {
|
|
...makeLegacyActivity('a1', 'p1'),
|
|
automatable: false,
|
|
automatedCostFixed: 0,
|
|
automatedTimeMinutes: 0,
|
|
automatedAssignedResources: [],
|
|
}
|
|
await activityRepo.save(act)
|
|
const retrieved = await activityRepo.getByProcess('p1')
|
|
expect(retrieved[0].automatable).toBe(false)
|
|
expect(retrieved[0].automatedCostFixed).toBe(0)
|
|
expect(retrieved[0].automatedTimeMinutes).toBe(0)
|
|
})
|
|
|
|
it('los campos de automatización persisten con valores no-default', async () => {
|
|
const act: Activity = {
|
|
...makeLegacyActivity('a2', 'p1'),
|
|
automatable: true,
|
|
automatedCostFixed: 12.5,
|
|
automatedTimeMinutes: 3,
|
|
automatedAssignedResources: [],
|
|
}
|
|
await activityRepo.save(act)
|
|
const [retrieved] = await activityRepo.getByProcess('p1')
|
|
expect(retrieved.automatable).toBe(true)
|
|
expect(retrieved.automatedCostFixed).toBe(12.5)
|
|
expect(retrieved.automatedTimeMinutes).toBe(3)
|
|
})
|
|
|
|
it('update de actividad preserva los campos de automatización existentes', async () => {
|
|
const act: Activity = {
|
|
...makeLegacyActivity('a3', 'p1'),
|
|
automatable: true,
|
|
automatedCostFixed: 5,
|
|
automatedTimeMinutes: 10,
|
|
automatedAssignedResources: [],
|
|
}
|
|
await activityRepo.save(act)
|
|
// Actualizar solo directCostFixed — los campos de automatización no deben cambiar
|
|
await activityRepo.save({ ...act, directCostFixed: 999 })
|
|
const [updated] = await activityRepo.getByProcess('p1')
|
|
expect(updated.directCostFixed).toBe(999)
|
|
expect(updated.automatable).toBe(true)
|
|
expect(updated.automatedCostFixed).toBe(5)
|
|
})
|
|
})
|
|
|
|
describe('Migración Dexie v2 — Process: defaults de campos Sprint 1', () => {
|
|
it('procesos nuevos tienen annualFrequency=1000 por defecto', async () => {
|
|
const proc: Process = {
|
|
...makeLegacyProcess('proc-1'),
|
|
annualFrequency: 1000,
|
|
analysisHorizonYears: 3,
|
|
automationInvestment: 0,
|
|
groupId: null, tags: [],
|
|
}
|
|
await processRepo.save(proc)
|
|
const retrieved = await processRepo.getById('proc-1')
|
|
expect(retrieved!.annualFrequency).toBe(1000)
|
|
expect(retrieved!.analysisHorizonYears).toBe(3)
|
|
expect(retrieved!.automationInvestment).toBe(0)
|
|
})
|
|
|
|
it('los campos de volumetría persisten con valores configurados', async () => {
|
|
const proc: Process = {
|
|
...makeLegacyProcess('proc-2'),
|
|
annualFrequency: 5000,
|
|
analysisHorizonYears: 5,
|
|
automationInvestment: 75_000,
|
|
groupId: null, tags: [],
|
|
}
|
|
await processRepo.save(proc)
|
|
const retrieved = await processRepo.getById('proc-2')
|
|
expect(retrieved!.annualFrequency).toBe(5000)
|
|
expect(retrieved!.analysisHorizonYears).toBe(5)
|
|
expect(retrieved!.automationInvestment).toBe(75_000)
|
|
})
|
|
|
|
it('update de proceso preserva campos de volumetría', async () => {
|
|
const proc: Process = {
|
|
...makeLegacyProcess('proc-3'),
|
|
annualFrequency: 2000,
|
|
analysisHorizonYears: 4,
|
|
automationInvestment: 30_000,
|
|
groupId: null, tags: [],
|
|
}
|
|
await processRepo.save(proc)
|
|
// Actualizar solo el nombre — los campos de volumetría no deben cambiar
|
|
await processRepo.save({ ...proc, name: 'Nombre Actualizado' })
|
|
const updated = await processRepo.getById('proc-3')
|
|
expect(updated!.name).toBe('Nombre Actualizado')
|
|
expect(updated!.annualFrequency).toBe(2000)
|
|
expect(updated!.automationInvestment).toBe(30_000)
|
|
})
|
|
})
|