/** * Tests del recálculo de ROI ajustado por sliders de sensibilidad (Sprint 6, Etapa 2). * * La lógica vive inline en el useMemo `adjustedRoi` de ReportPage.tsx — no hay una función * pura exportada (no se modifica domain/roi.ts por scope de la etapa). Estos tests replican * exactamente esa transformación de inputs y verifican el comportamiento de calculateRoi() * con los valores ajustados, igual que lo hace el componente. */ import { describe, it, expect } from 'vitest' import { calculateRoi, type RoiInput } from '@/domain/roi' const BASE: Omit = { costPerExecutionActual: 600, costPerExecutionAutomated: 60, analysisHorizonYears: 3, } const BASE_FREQUENCY = 1200 const BASE_INVESTMENT = 50_000 function adjustedRoi(volumeAdjust: number, investmentAdjust: number) { return calculateRoi({ ...BASE, annualFrequency: BASE_FREQUENCY * (1 + volumeAdjust / 100), automationInvestment: BASE_INVESTMENT * (1 + investmentAdjust / 100), }) } describe('Sensibilidad — ajuste de volumen anual', () => { it('volumen +20%: annualFrequency y annualSavings escalan 1.2x respecto al base', () => { const base = adjustedRoi(0, 0) const adjusted = adjustedRoi(20, 0) expect(adjusted.annualSavings).toBeCloseTo(base.annualSavings * 1.2) }) it('volumen -30%: annualSavings escala 0.7x, payback se alarga', () => { const base = adjustedRoi(0, 0) const adjusted = adjustedRoi(-30, 0) expect(adjusted.annualSavings).toBeCloseTo(base.annualSavings * 0.7) expect(adjusted.paybackMonths).toBeGreaterThan(base.paybackMonths) }) }) describe('Sensibilidad — ajuste de inversión en automatización', () => { it('inversión +50%: payback se alarga 1.5x, ROI% baja proporcionalmente', () => { const base = adjustedRoi(0, 0) const adjusted = adjustedRoi(0, 50) expect(adjusted.paybackMonths).toBeCloseTo(base.paybackMonths * 1.5) expect(adjusted.roiAnnualPercent).toBeCloseTo(base.roiAnnualPercent / 1.5) }) it('inversión -50%: payback se acorta a la mitad, ROI% se duplica', () => { const base = adjustedRoi(0, 0) const adjusted = adjustedRoi(0, -50) expect(adjusted.paybackMonths).toBeCloseTo(base.paybackMonths * 0.5) expect(adjusted.roiAnnualPercent).toBeCloseTo(base.roiAnnualPercent * 2) }) }) describe('Sensibilidad — ajuste combinado', () => { it('volumen +20% e inversión +50% simultáneos: ambos efectos se componen', () => { const base = adjustedRoi(0, 0) const adjusted = adjustedRoi(20, 50) // annualSavings depende solo de volumen expect(adjusted.annualSavings).toBeCloseTo(base.annualSavings * 1.2) // payback = inversión / annualSavings → (1.5 * inv) / (1.2 * savings) = base * 1.25 expect(adjusted.paybackMonths).toBeCloseTo(base.paybackMonths * (1.5 / 1.2)) }) it('volumen -30% e inversión -50% simultáneos', () => { const base = adjustedRoi(0, 0) const adjusted = adjustedRoi(-30, -50) expect(adjusted.annualSavings).toBeCloseTo(base.annualSavings * 0.7) expect(adjusted.paybackMonths).toBeCloseTo(base.paybackMonths * (0.5 / 0.7)) }) }) describe('Sensibilidad — reset a 0', () => { it('volumeAdjust=0 e investmentAdjust=0 reproduce exactamente el ROI base', () => { const base = calculateRoi({ ...BASE, annualFrequency: BASE_FREQUENCY, automationInvestment: BASE_INVESTMENT, }) const reset = adjustedRoi(0, 0) expect(reset).toEqual(base) }) it('después de mover sliders, volver a 0/0 coincide con el valor base original', () => { adjustedRoi(40, -20) // simula haber movido los sliders const resetAgain = adjustedRoi(0, 0) const base = adjustedRoi(0, 0) expect(resetAgain).toEqual(base) }) }) describe('Sensibilidad — límites del rango (-50 a +100)', () => { it('volumen en el límite inferior (-50%) no produce frecuencia negativa', () => { const adjusted = adjustedRoi(-50, 0) expect(adjusted.annualSavings).toBeGreaterThanOrEqual(0) }) it('volumen en el límite superior (+100%) duplica el ahorro anual', () => { const base = adjustedRoi(0, 0) const adjusted = adjustedRoi(100, 0) expect(adjusted.annualSavings).toBeCloseTo(base.annualSavings * 2) }) })