/** * Tests para la página 1 rediseñada del PDF ROI — Etapa 6 Sprint 1.5. * * Verifica: * - drawRoiPage1 invoca los bloques clave (hero, top3, grid, banner) * - exportToPdf ROI llama addPage 4 veces (5 páginas) * - Ninguna página ROI usa landscape * - El hero llama setFontSize(28) para la cifra principal */ import { describe, it, expect, vi, beforeEach } from 'vitest' const mockDoc = { setProperties: vi.fn(), setFont: vi.fn(), setFontSize: vi.fn(), setTextColor: vi.fn(), setFillColor: vi.fn(), setDrawColor: vi.fn(), setLineWidth: vi.fn(), text: vi.fn(), rect: vi.fn(), roundedRect: vi.fn(), line: vi.fn(), addImage: vi.fn(), addPage: vi.fn(), setPage: vi.fn(), save: vi.fn(), splitTextToSize: vi.fn().mockImplementation((t: string) => [t]), lastAutoTable: { finalY: 150 }, internal: { getNumberOfPages: vi.fn().mockReturnValue(5), pageSize: { getWidth: vi.fn().mockReturnValue(210), getHeight: vi.fn().mockReturnValue(297), }, }, } vi.mock('jspdf', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any function MockJsPDF(this: any) { return mockDoc } return { jsPDF: MockJsPDF } }) vi.mock('jspdf-autotable', () => ({ default: vi.fn(), })) import type { Process, Simulation } from '@/domain/types' import type { RoiResult } from '@/domain/roi' const mockProcess: Process = { id: 'p1', name: 'Test Process', clientName: 'Test Client', bpmnXml: '', currency: 'USD', overheadPercentage: 0.2, annualFrequency: 5000, analysisHorizonYears: 3, automationInvestment: 80000, createdAt: 0, updatedAt: 0, groupId: null, tags: [], } const mockSimulation: Simulation = { id: 'sim1', processId: 'p1', executedAt: new Date('2026-05-17T10:00:00Z').getTime(), result: { totalCost: 600, totalDirectCost: 500, totalIndirectCost: 100, totalTimeMinutes: 90, perActivity: [ { activityId: 'a1', bpmnElementId: 'task1', activityName: 'Tarea Alpha', expectedDirectCost: 400, expectedIndirectCost: 80, expectedTotalCost: 400, percentOfTotal: 66, executionProbability: 1.0, expectedExecutions: 1.0, executionTimeMinutes: 30, resourceCostBreakdown: [], }, { activityId: 'a2', bpmnElementId: 'task2', activityName: 'Tarea Beta', expectedDirectCost: 200, expectedIndirectCost: 40, expectedTotalCost: 200, percentOfTotal: 34, executionProbability: 1.0, expectedExecutions: 1.0, executionTimeMinutes: 20, resourceCostBreakdown: [], }, ], perResource: [], warnings: [], }, resultAutomated: { totalCost: 60, totalDirectCost: 50, totalIndirectCost: 10, totalTimeMinutes: 10, perActivity: [ { activityId: 'a1', bpmnElementId: 'task1', activityName: 'Tarea Alpha', expectedDirectCost: 40, expectedIndirectCost: 10, expectedTotalCost: 40, percentOfTotal: 66, executionProbability: 1.0, expectedExecutions: 1.0, executionTimeMinutes: 5, resourceCostBreakdown: [], }, { activityId: 'a2', bpmnElementId: 'task2', activityName: 'Tarea Beta', expectedDirectCost: 20, expectedIndirectCost: 5, expectedTotalCost: 20, percentOfTotal: 34, executionProbability: 1.0, expectedExecutions: 1.0, executionTimeMinutes: 5, resourceCostBreakdown: [], }, ], perResource: [], warnings: [], }, } const mockRoi: RoiResult = { savingsPerExecution: 540, savingsPerExecutionPercent: 90, annualSavings: 2_700_000, paybackMonths: 0.36, paybackYears: 0.03, cumulativeSavingsHorizon: 8_020_000, breaksEvenInHorizon: true, roiAnnualPercent: 3375, } describe('PDF ROI — Página 1 rediseñada (Etapa 6)', () => { beforeEach(() => { vi.clearAllMocks() mockDoc.internal.pageSize.getWidth.mockReturnValue(210) mockDoc.internal.pageSize.getHeight.mockReturnValue(297) mockDoc.internal.getNumberOfPages.mockReturnValue(5) }) it('drawRoiPage1 completa sin errores y llama text() múltiples veces', async () => { const { drawRoiPage1 } = await import('@/lib/export/pdf-sections-roi') expect(() => drawRoiPage1( mockDoc as any, mockProcess, mockRoi, mockSimulation.executedAt, mockSimulation.result.perActivity, mockSimulation.resultAutomated!.perActivity, 'USD' ) ).not.toThrow() expect(mockDoc.text.mock.calls.length).toBeGreaterThan(5) }) it('drawRoiPage1 incluye texto "InQ ROI" en el header denso', async () => { const { drawRoiPage1 } = await import('@/lib/export/pdf-sections-roi') drawRoiPage1( mockDoc as any, mockProcess, mockRoi, mockSimulation.executedAt, mockSimulation.result.perActivity, mockSimulation.resultAutomated!.perActivity, 'USD' ) const textCalls = mockDoc.text.mock.calls.map((c: unknown[]) => c[0]) expect(textCalls).toContain('InQ ROI') }) it('drawRoiPage1 incluye texto "TOP 3 ACTIVIDADES POR AHORRO"', async () => { const { drawRoiPage1 } = await import('@/lib/export/pdf-sections-roi') drawRoiPage1( mockDoc as any, mockProcess, mockRoi, mockSimulation.executedAt, mockSimulation.result.perActivity, mockSimulation.resultAutomated!.perActivity, 'USD' ) const textCalls = mockDoc.text.mock.calls.map((c: unknown[]) => c[0]) expect(textCalls).toContain('TOP 3 ACTIVIDADES POR AHORRO') }) it('drawRoiPage1 usa setFontSize(26) para la cifra del KPI Hero (Etapa 11: bajada de 28→26pt)', async () => { const { drawRoiPage1 } = await import('@/lib/export/pdf-sections-roi') drawRoiPage1( mockDoc as any, mockProcess, mockRoi, mockSimulation.executedAt, mockSimulation.result.perActivity, mockSimulation.resultAutomated!.perActivity, 'USD' ) const fontSizes = mockDoc.setFontSize.mock.calls.map((c: number[]) => c[0]) expect(fontSizes).toContain(26) }) it('drawRoiPage1 dibuja rectángulos para la grilla 2x2 y barras top3', async () => { const { drawRoiPage1 } = await import('@/lib/export/pdf-sections-roi') drawRoiPage1( mockDoc as any, mockProcess, mockRoi, mockSimulation.executedAt, mockSimulation.result.perActivity, mockSimulation.resultAutomated!.perActivity, 'USD' ) // roundedRect para cards de KPI secundarios (4 cards) + metadata strip expect(mockDoc.roundedRect).toHaveBeenCalled() // rect para elementos del banner y barras top3 expect(mockDoc.rect).toHaveBeenCalled() }) it('exportToPdf ROI: 4 addPage calls (5 páginas) sin landscape', async () => { const { exportToPdf } = await import('@/lib/export/pdf-export') await exportToPdf({ process: mockProcess, simulation: mockSimulation, resources: [], heatmapImageData: null, tab: 'roi', activities: [], }) expect(mockDoc.addPage).toHaveBeenCalledTimes(4) const hasLandscape = mockDoc.addPage.mock.calls.some( (c: unknown[]) => c[1] === 'landscape' ) expect(hasLandscape).toBe(false) }) it('drawRoiPage1 dibuja nombre de actividad del top3 para actividades con ahorro', async () => { const { drawRoiPage1 } = await import('@/lib/export/pdf-sections-roi') drawRoiPage1( mockDoc as any, mockProcess, mockRoi, mockSimulation.executedAt, mockSimulation.result.perActivity, mockSimulation.resultAutomated!.perActivity, 'USD' ) const textCalls = mockDoc.text.mock.calls.map((c: unknown[]) => c[0]) expect(textCalls).toContain('Tarea Alpha') }) })