Antes de la última revisión de etapa 4. Sprint 1. Previo al ajuste de look n feel de app, reportes, nombre y colores.
This commit is contained in:
161
tests/domain/roi.test.ts
Normal file
161
tests/domain/roi.test.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Tests del módulo domain/roi.ts.
|
||||
* Cubren: happy path, edge cases de inversión cero, ahorro cero/negativo,
|
||||
* división por cero, escenarios de largo plazo y corto plazo.
|
||||
* Todas las fórmulas son nominales simples (ADR-014).
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { calculateRoi } from '@/domain/roi'
|
||||
import type { RoiInput } from '@/domain/roi'
|
||||
|
||||
// ─── Fixture base ─────────────────────────────────────────────────────────────
|
||||
|
||||
function baseInput(overrides: Partial<RoiInput> = {}): RoiInput {
|
||||
return {
|
||||
costPerExecutionActual: 100,
|
||||
costPerExecutionAutomated: 20,
|
||||
annualFrequency: 1000,
|
||||
analysisHorizonYears: 3,
|
||||
automationInvestment: 50_000,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Happy path ───────────────────────────────────────────────────────────────
|
||||
|
||||
describe('calculateRoi — happy path', () => {
|
||||
it('calcula ahorro por ejecución correctamente', () => {
|
||||
const r = calculateRoi(baseInput())
|
||||
expect(r.savingsPerExecution).toBeCloseTo(80)
|
||||
})
|
||||
|
||||
it('calcula ahorro por ejecución en %', () => {
|
||||
const r = calculateRoi(baseInput())
|
||||
expect(r.savingsPerExecutionPercent).toBeCloseTo(80) // 80/100 = 80%
|
||||
})
|
||||
|
||||
it('calcula ahorro anual = ahorro/ejecución × frecuencia', () => {
|
||||
const r = calculateRoi(baseInput())
|
||||
expect(r.annualSavings).toBeCloseTo(80_000) // 80 × 1000
|
||||
})
|
||||
|
||||
it('calcula payback en meses correctamente', () => {
|
||||
const r = calculateRoi(baseInput())
|
||||
// $50.000 / $80.000/año × 12 = 7.5 meses
|
||||
expect(r.paybackMonths).toBeCloseTo(7.5)
|
||||
})
|
||||
|
||||
it('calcula payback en años', () => {
|
||||
const r = calculateRoi(baseInput())
|
||||
expect(r.paybackYears).toBeCloseTo(0.625)
|
||||
})
|
||||
|
||||
it('calcula ahorro acumulado neto al horizonte', () => {
|
||||
const r = calculateRoi(baseInput())
|
||||
// ($80.000 × 3 años) - $50.000 = $190.000
|
||||
expect(r.cumulativeSavingsHorizon).toBeCloseTo(190_000)
|
||||
})
|
||||
|
||||
it('el payback dentro del horizonte es true cuando paybackYears < horizonYears', () => {
|
||||
const r = calculateRoi(baseInput()) // payback ~0.625 < 3 años
|
||||
expect(r.breaksEvenInHorizon).toBe(true)
|
||||
})
|
||||
|
||||
it('calcula ROI anualizado %', () => {
|
||||
const r = calculateRoi(baseInput())
|
||||
// ($80.000 / $50.000) × 100 = 160%
|
||||
expect(r.roiAnnualPercent).toBeCloseTo(160)
|
||||
})
|
||||
})
|
||||
|
||||
// ─── Edge case: inversión cero con ahorro positivo ────────────────────────────
|
||||
|
||||
describe('calculateRoi — inversión cero', () => {
|
||||
it('payback es 0 meses (recuperación instantánea)', () => {
|
||||
const r = calculateRoi(baseInput({ automationInvestment: 0 }))
|
||||
expect(r.paybackMonths).toBe(0)
|
||||
expect(r.paybackYears).toBe(0)
|
||||
})
|
||||
|
||||
it('breaksEvenInHorizon es true', () => {
|
||||
const r = calculateRoi(baseInput({ automationInvestment: 0 }))
|
||||
expect(r.breaksEvenInHorizon).toBe(true)
|
||||
})
|
||||
|
||||
it('roiAnnualPercent es Infinity', () => {
|
||||
const r = calculateRoi(baseInput({ automationInvestment: 0 }))
|
||||
expect(r.roiAnnualPercent).toBe(Infinity)
|
||||
})
|
||||
|
||||
it('inversión cero con ahorro cero: roiAnnualPercent es 0', () => {
|
||||
const r = calculateRoi(baseInput({
|
||||
automationInvestment: 0,
|
||||
costPerExecutionActual: 100,
|
||||
costPerExecutionAutomated: 100,
|
||||
}))
|
||||
expect(r.roiAnnualPercent).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
// ─── Edge case: ahorro cero o negativo ────────────────────────────────────────
|
||||
|
||||
describe('calculateRoi — ahorro negativo o cero', () => {
|
||||
it('si el costo automatizado = costo actual, ahorro = 0', () => {
|
||||
const r = calculateRoi(baseInput({ costPerExecutionAutomated: 100 }))
|
||||
expect(r.savingsPerExecution).toBeCloseTo(0)
|
||||
expect(r.annualSavings).toBeCloseTo(0)
|
||||
})
|
||||
|
||||
it('ahorro cero → payback Infinity', () => {
|
||||
const r = calculateRoi(baseInput({ costPerExecutionAutomated: 100 }))
|
||||
expect(r.paybackMonths).toBe(Infinity)
|
||||
expect(r.paybackYears).toBe(Infinity)
|
||||
})
|
||||
|
||||
it('ahorro cero → breaksEvenInHorizon false', () => {
|
||||
const r = calculateRoi(baseInput({ costPerExecutionAutomated: 100 }))
|
||||
expect(r.breaksEvenInHorizon).toBe(false)
|
||||
})
|
||||
|
||||
it('ahorro negativo (automatizado más caro): payback Infinity', () => {
|
||||
const r = calculateRoi(baseInput({ costPerExecutionAutomated: 150 }))
|
||||
expect(r.annualSavings).toBeLessThan(0)
|
||||
expect(r.paybackMonths).toBe(Infinity)
|
||||
expect(r.breaksEvenInHorizon).toBe(false)
|
||||
})
|
||||
|
||||
it('ahorro negativo: cumulativeSavings también es negativo', () => {
|
||||
const r = calculateRoi(baseInput({ costPerExecutionAutomated: 150 }))
|
||||
expect(r.cumulativeSavingsHorizon).toBeLessThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
// ─── Edge case: costo actual cero (evitar div/0) ──────────────────────────────
|
||||
|
||||
describe('calculateRoi — costo actual cero', () => {
|
||||
it('savingsPerExecutionPercent es 0, no NaN ni Infinity', () => {
|
||||
const r = calculateRoi(baseInput({
|
||||
costPerExecutionActual: 0,
|
||||
costPerExecutionAutomated: 0,
|
||||
}))
|
||||
expect(r.savingsPerExecutionPercent).toBe(0)
|
||||
expect(Number.isFinite(r.savingsPerExecutionPercent)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
// ─── Escenario de largo plazo (horizonte 10 años) ─────────────────────────────
|
||||
|
||||
describe('calculateRoi — horizonte extendido', () => {
|
||||
it('payback fuera del horizonte corto → breaksEvenInHorizon false', () => {
|
||||
// savingsPerExec=10, freq=1000 → annualSavings=$10.000; payback=$500.000/$10.000=50 años
|
||||
const r = calculateRoi(baseInput({
|
||||
automationInvestment: 500_000,
|
||||
costPerExecutionActual: 100,
|
||||
costPerExecutionAutomated: 90,
|
||||
annualFrequency: 1000,
|
||||
}))
|
||||
expect(r.paybackYears).toBeGreaterThan(3)
|
||||
expect(r.breaksEvenInHorizon).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user