feat(sprint-3): biblioteca, recursos TO-BE, ROI mejorado e identidad de marca
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>
This commit is contained in:
@@ -14,7 +14,7 @@ function makeProcess(extras: Partial<Process> = {}): Process {
|
||||
id: 'p1', name: 'Proceso de Crédito', clientName: 'Banco XYZ',
|
||||
bpmnXml: '', currency: 'USD', overheadPercentage: 0.2,
|
||||
annualFrequency: 1200, analysisHorizonYears: 3, automationInvestment: 50_000,
|
||||
createdAt: 0, updatedAt: 0, ...extras,
|
||||
createdAt: 0, updatedAt: 0, groupId: null, tags: [], ...extras,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,11 +66,13 @@ const mockActivities: Activity[] = [
|
||||
id: 'a1', processId: 'p1', bpmnElementId: 'task_recv', name: 'Recibir solicitud', type: 'task',
|
||||
directCostFixed: 200, executionTimeMinutes: 60, assignedResources: [],
|
||||
automatable: true, automatedCostFixed: 20, automatedTimeMinutes: 5,
|
||||
automatedAssignedResources: [],
|
||||
},
|
||||
{
|
||||
id: 'a2', processId: 'p1', bpmnElementId: 'task_valid', name: 'Validar datos', type: 'task',
|
||||
directCostFixed: 100, executionTimeMinutes: 30, assignedResources: [],
|
||||
automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0,
|
||||
automatedAssignedResources: [],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ function makeProcess(currency: string, extras: Partial<Process> = {}): Process {
|
||||
id: 'p1', name: 'Proceso de Ventas', clientName: 'Empresa ABC',
|
||||
bpmnXml: '', currency, overheadPercentage: 0.2,
|
||||
annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0,
|
||||
createdAt: 0, updatedAt: 0, ...extras,
|
||||
createdAt: 0, updatedAt: 0, groupId: null, tags: [], ...extras,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function buildInput(xml: string, directCost: number): SimulationInput {
|
||||
actEls.map((el) => [el.bpmnElementId, {
|
||||
id: uuidv4(), processId, bpmnElementId: el.bpmnElementId, name: el.name,
|
||||
type: el.type, directCostFixed: directCost, executionTimeMinutes: 30, assignedResources: [],
|
||||
automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0,
|
||||
automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0, automatedAssignedResources: [],
|
||||
}])
|
||||
)
|
||||
|
||||
@@ -66,6 +66,7 @@ describe('Medición de tamaños de CSV — 3 sample BPMNs', () => {
|
||||
bpmnXml: xml, currency: 'USD', overheadPercentage: 0.2,
|
||||
annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0,
|
||||
createdAt: 0, updatedAt: 0,
|
||||
groupId: null, tags: [],
|
||||
}
|
||||
|
||||
const simulation: Simulation = {
|
||||
|
||||
@@ -51,6 +51,7 @@ const mockProcess: Process = {
|
||||
bpmnXml: '', currency: 'USD', overheadPercentage: 0.2,
|
||||
annualFrequency: 1200, analysisHorizonYears: 3, automationInvestment: 50_000,
|
||||
createdAt: 0, updatedAt: 0,
|
||||
groupId: null, tags: [],
|
||||
}
|
||||
|
||||
const perActivityActual = [
|
||||
@@ -101,11 +102,13 @@ const mockActivities: Activity[] = [
|
||||
id: 'a1', processId: 'p1', bpmnElementId: 'task_recv', name: 'Recibir solicitud', type: 'task',
|
||||
directCostFixed: 200, executionTimeMinutes: 60, assignedResources: [],
|
||||
automatable: true, automatedCostFixed: 20, automatedTimeMinutes: 5,
|
||||
automatedAssignedResources: [],
|
||||
},
|
||||
{
|
||||
id: 'a2', processId: 'p1', bpmnElementId: 'task_valid', name: 'Validar datos', type: 'task',
|
||||
directCostFixed: 100, executionTimeMinutes: 30, assignedResources: [],
|
||||
automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0,
|
||||
automatedAssignedResources: [],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ const mockProcess: Process = {
|
||||
bpmnXml: '', currency: 'USD', overheadPercentage: 0.2,
|
||||
annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0,
|
||||
createdAt: 0, updatedAt: 0,
|
||||
groupId: null, tags: [],
|
||||
}
|
||||
|
||||
// Simulación SIN recursos en ninguna actividad
|
||||
@@ -72,6 +73,7 @@ const mockActivity: Activity = {
|
||||
directCostFixed: 200, executionTimeMinutes: 60,
|
||||
assignedResources: [{ resourceId: 'r1', utilizationMinutes: 60, units: 1 }],
|
||||
automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0,
|
||||
automatedAssignedResources: [],
|
||||
}
|
||||
|
||||
const mockSimWithResources: Simulation = {
|
||||
|
||||
@@ -54,6 +54,7 @@ const mockProcess: Process = {
|
||||
overheadPercentage: 0.2,
|
||||
annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 0,
|
||||
createdAt: 0, updatedAt: 0,
|
||||
groupId: null, tags: [],
|
||||
}
|
||||
|
||||
const mockSimulation: Simulation = {
|
||||
|
||||
@@ -59,6 +59,7 @@ const mockProcess: Process = {
|
||||
overheadPercentage: 0.2,
|
||||
annualFrequency: 1000, analysisHorizonYears: 3, automationInvestment: 50000,
|
||||
createdAt: 0, updatedAt: 0,
|
||||
groupId: null, tags: [],
|
||||
}
|
||||
|
||||
const mockSimulation: Simulation = {
|
||||
|
||||
@@ -30,6 +30,7 @@ const mockActivity: Activity = {
|
||||
directCostFixed: 600, executionTimeMinutes: 60,
|
||||
assignedResources: [{ resourceId: 'r1', utilizationMinutes: 60, units: 1 }],
|
||||
automatable: false, automatedCostFixed: 0, automatedTimeMinutes: 0,
|
||||
automatedAssignedResources: [{ resourceId: 'r1', utilizationMinutes: 5, units: 3 }],
|
||||
}
|
||||
|
||||
const activityWithResources: ActivitySimResult = {
|
||||
@@ -122,3 +123,43 @@ describe('drawResourcesSection', () => {
|
||||
expect(autoTableMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// ─── Escenario automatizado usa automatedAssignedResources ────────────────────
|
||||
|
||||
describe('drawResourcesSection — escenario automatizado', () => {
|
||||
let autoTableMock: ReturnType<typeof vi.fn>
|
||||
|
||||
beforeEach(() => {
|
||||
autoTableMock = vi.fn()
|
||||
})
|
||||
|
||||
it('escenario automatizado: pasa utilizationMinutes y units de automatedAssignedResources', async () => {
|
||||
const { drawResourcesSection } = await import('@/lib/export/pdf-sections')
|
||||
drawResourcesSection(
|
||||
mockDoc, autoTableMock,
|
||||
{ perActivity: [activityWithResources] },
|
||||
[mockResource], [mockActivity], 'USD', 40,
|
||||
'automatizado'
|
||||
)
|
||||
const body = (autoTableMock.mock.calls[0][1] as any).body as any[][]
|
||||
const row = body[0]
|
||||
// Min. (índice 4) y Uds. (índice 5) deben venir de automatedAssignedResources: { utilMin: 5, units: 3 }
|
||||
expect(row[4].content).toBe('5')
|
||||
expect(row[5].content).toBe('3')
|
||||
})
|
||||
|
||||
it('escenario actual: pasa utilizationMinutes y units de assignedResources (sin regresión)', async () => {
|
||||
const { drawResourcesSection } = await import('@/lib/export/pdf-sections')
|
||||
drawResourcesSection(
|
||||
mockDoc, autoTableMock,
|
||||
{ perActivity: [activityWithResources] },
|
||||
[mockResource], [mockActivity], 'USD', 40,
|
||||
'actual'
|
||||
)
|
||||
const body = (autoTableMock.mock.calls[0][1] as any).body as any[][]
|
||||
const row = body[0]
|
||||
// assignedResources: { utilMin: 60, units: 1 }
|
||||
expect(row[4].content).toBe('60')
|
||||
expect(row[5].content).toBe('1')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -56,6 +56,7 @@ const mockProcess: Process = {
|
||||
overheadPercentage: 0.2,
|
||||
annualFrequency: 5000, analysisHorizonYears: 3, automationInvestment: 80000,
|
||||
createdAt: 0, updatedAt: 0,
|
||||
groupId: null, tags: [],
|
||||
}
|
||||
|
||||
const mockSimulation: Simulation = {
|
||||
|
||||
@@ -52,6 +52,7 @@ const mockProcess: Process = {
|
||||
bpmnXml: '', currency: 'USD', overheadPercentage: 0.2,
|
||||
annualFrequency: 5000, analysisHorizonYears: 3, automationInvestment: 80000,
|
||||
createdAt: 0, updatedAt: 0,
|
||||
groupId: null, tags: [],
|
||||
}
|
||||
|
||||
const mockRoi: RoiResult = {
|
||||
@@ -103,10 +104,10 @@ const mockSimulation: Simulation = {
|
||||
const mockActivities: Activity[] = [
|
||||
{ id: 'a1', processId: 'p1', bpmnElementId: 't1', name: 'Tarea A', type: 'task',
|
||||
automatable: true, directCostFixed: 400, executionTimeMinutes: 30,
|
||||
automatedCostFixed: 40, automatedTimeMinutes: 5, assignedResources: [] },
|
||||
automatedCostFixed: 40, automatedTimeMinutes: 5, assignedResources: [], automatedAssignedResources: [] },
|
||||
{ id: 'a2', processId: 'p1', bpmnElementId: 't2', name: 'Tarea B', type: 'task',
|
||||
automatable: false, directCostFixed: 200, executionTimeMinutes: 20,
|
||||
automatedCostFixed: 0, automatedTimeMinutes: 0, assignedResources: [] },
|
||||
automatedCostFixed: 0, automatedTimeMinutes: 0, assignedResources: [], automatedAssignedResources: [] },
|
||||
]
|
||||
|
||||
describe('PDF ROI — Etapa 7 páginas 2-5', () => {
|
||||
|
||||
@@ -52,6 +52,7 @@ const mockProcess: Process = {
|
||||
bpmnXml: '', currency: 'USD', overheadPercentage: 0.2,
|
||||
annualFrequency: 5000, analysisHorizonYears: 3, automationInvestment: 80000,
|
||||
createdAt: 0, updatedAt: 0,
|
||||
groupId: null, tags: [],
|
||||
}
|
||||
|
||||
const mockSimulation: Simulation = {
|
||||
|
||||
Reference in New Issue
Block a user