109 lines
4.2 KiB
TypeScript
109 lines
4.2 KiB
TypeScript
/**
|
|
* 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<RoiInput, 'annualFrequency' | 'automationInvestment'> = {
|
|
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)
|
|
})
|
|
})
|