80 lines
3.2 KiB
TypeScript
80 lines
3.2 KiB
TypeScript
// 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,
|
||
}
|
||
}
|