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:
79
src/domain/roi.ts
Normal file
79
src/domain/roi.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user