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:
2026-05-15 22:14:53 -03:00
parent b6f31f4371
commit 8b71c6ee13
54 changed files with 6497 additions and 261 deletions

79
src/domain/roi.ts Normal file
View File

@@ -0,0 +1,79 @@
// Cálculos de ROI de automatización — funciones puras, sin dependencias de UI ni persistencia.
// Fórmulas nominales simples (ADR-014): sin VPN, TIR ni tasa de descuento.
// Los edge cases (inversión cero, ahorro negativo, división por cero) están explicitados.
export interface RoiInput {
costPerExecutionActual: number // costo total de una ejecución en escenario actual
costPerExecutionAutomated: number // costo total de una ejecución en escenario automatizado
annualFrequency: number // ejecuciones por año
analysisHorizonYears: number // horizonte de análisis en años
automationInvestment: number // inversión total única del proyecto de automatización
}
export interface RoiResult {
savingsPerExecution: number // ahorro por ejecución individual (actual - automatizado)
savingsPerExecutionPercent: number // ahorro % sobre el costo actual (0 si actual = 0)
annualSavings: number // ahorro anual = ahorro/ejecución × frecuencia
paybackMonths: number // meses para recuperar inversión (Infinity si ahorro <= 0)
paybackYears: number // años para recuperar inversión
cumulativeSavingsHorizon: number // ahorro acumulado neto al final del horizonte
breaksEvenInHorizon: boolean // ¿el payback ocurre dentro del horizonte?
roiAnnualPercent: number // ROI anualizado % = (ahorroAnual / inversión) × 100
// Infinity si inversión = 0 y ahorro > 0
}
export function calculateRoi(input: RoiInput): RoiResult {
const {
costPerExecutionActual,
costPerExecutionAutomated,
annualFrequency,
analysisHorizonYears,
automationInvestment,
} = input
const savingsPerExecution = costPerExecutionActual - costPerExecutionAutomated
// Evitar división por cero cuando el costo actual es 0
const savingsPerExecutionPercent =
costPerExecutionActual > 0
? (savingsPerExecution / costPerExecutionActual) * 100
: 0
const annualSavings = savingsPerExecution * annualFrequency
// Payback: si no hay ahorro (o es negativo), nunca se recupera la inversión
let paybackMonths: number
if (annualSavings <= 0) {
paybackMonths = Infinity
} else if (automationInvestment === 0) {
// Inversión cero con ahorro positivo: recuperación instantánea
paybackMonths = 0
} else {
paybackMonths = (automationInvestment / annualSavings) * 12
}
const paybackYears = paybackMonths / 12 // Infinity / 12 = Infinity, 0 / 12 = 0
const cumulativeSavingsHorizon = annualSavings * analysisHorizonYears - automationInvestment
const breaksEvenInHorizon = isFinite(paybackYears) && paybackYears <= analysisHorizonYears
// ROI anualizado: Infinity si inversión = 0 con ahorro > 0; 0 si inversión = 0 y ahorro = 0
let roiAnnualPercent: number
if (automationInvestment === 0) {
roiAnnualPercent = annualSavings > 0 ? Infinity : 0
} else {
roiAnnualPercent = (annualSavings / automationInvestment) * 100
}
return {
savingsPerExecution,
savingsPerExecutionPercent,
annualSavings,
paybackMonths,
paybackYears,
cumulativeSavingsHorizon,
breaksEvenInHorizon,
roiAnnualPercent,
}
}