Deuda técnica: el caso de negocio para refactorizar
Un marco práctico para medir la deuda técnica en términos que convencen: métricas de código, velocidad de desarrollo y correlación con incidentes.

Por qué decir "tenemos deuda técnica" no es suficiente
Todos los equipos de ingeniería saben que tienen deuda técnica. El código tiene soluciones improvisadas de una fecha límite de hace dos años. El esquema de la base de datos refleja el primer producto de la startup, no el actual. La cobertura de pruebas del módulo de pagos es del 12%.
El problema no es la falta de conciencia. El problema es la conversión. Traducir "este código es malo" en "este código nos cuesta $47,000 al mes en funcionalidades retrasadas y respuesta a incidentes" es lo que separa quejarse de conseguir tiempo de refactorización en el roadmap.
A los interesados del negocio no les importa la calidad del código como principio abstracto. Les importa la velocidad de entrega, la fiabilidad y el costo. La cuantificación de la deuda técnica cierra esta brecha al expresar los problemas del código en términos que los interesados ya valoran.
Cómo medir el impacto en la velocidad de desarrollo
La métrica de deuda más convincente es su efecto sobre la velocidad de entrega. Si agregar una funcionalidad al Módulo A toma tres veces más tiempo que una funcionalidad de la misma complejidad en el Módulo B, esa diferencia es cuantificable.
interface VelocityMetrics {
module: string;
avgCycleTimeDays: number;
avgReviewRounds: number;
reworkPercentage: number;
incidentFrequency: number;
onboardingTimeDays: number;
}
function calculateDebtCost(
metrics: VelocityMetrics,
baselineMetrics: VelocityMetrics,
engineerDailyCost: number
): {
module: string;
cycleCostOverhead: number;
monthlyOverhead: number;
annualOverhead: number;
} {
const cycleOverhead = metrics.avgCycleTimeDays - baselineMetrics.avgCycleTimeDays;
const featuresPerMonth = 30 / metrics.avgCycleTimeDays;
const monthlyOverheadDays = cycleOverhead * featuresPerMonth;
const monthlyOverhead = monthlyOverheadDays * engineerDailyCost;
return {
module: metrics.module,
cycleCostOverhead: cycleOverhead * engineerDailyCost,
monthlyOverhead,
annualOverhead: monthlyOverhead * 12,
};
}
// Example: Payment module vs. a healthy module
const paymentModuleMetrics: VelocityMetrics = {
module: "payments",
avgCycleTimeDays: 12,
avgReviewRounds: 4.2,
reworkPercentage: 35,
incidentFrequency: 3.5, // per month
onboardingTimeDays: 21,
};
const healthyModuleMetrics: VelocityMetrics = {
module: "notifications",
avgCycleTimeDays: 4,
avgReviewRounds: 1.8,
reworkPercentage: 12,
incidentFrequency: 0.5,
onboardingTimeDays: 5,
};
const cost = calculateDebtCost(paymentModuleMetrics, healthyModuleMetrics, 600);
// cycleCostOverhead: $4,800 per feature
// monthlyOverhead: $12,000
// annualOverhead: $144,000El número no necesita ser exacto. Necesita ser defendible. Extrae los datos de tiempo de ciclo de tu herramienta de gestión de proyectos, cuenta las rondas de revisión desde tu historial de Git y calcula la diferencia respecto a tu módulo más saludable. Incluso las estimaciones aproximadas hacen visible lo invisible.
Métricas de complejidad de código que se correlacionan con errores
El análisis estático proporciona mediciones objetivas que se correlacionan con la densidad de defectos. La complejidad ciclomática, las métricas de acoplamiento y las tasas de cambio (churn) identifican los archivos que causan más problemas.
import { execSync } from "child_process";
interface FileDebtScore {
path: string;
complexity: number;
churnCount: number;
couplingScore: number;
bugCorrelation: number;
combinedDebtScore: number;
}
function getFileChurn(filepath: string, months: number = 6): number {
const since = new Date();
since.setMonth(since.getMonth() - months);
const dateStr = since.toISOString().split("T")[0];
const result = execSync(
`git log --since="${dateStr}" --oneline -- "${filepath}"`,
{ encoding: "utf-8" }
);
return result.trim().split("\n").filter(Boolean).length;
}
function calculateDebtScore(files: FileDebtScore[]): FileDebtScore[] {
// Normalize each metric to 0-1 scale
const maxComplexity = Math.max(...files.map((f) => f.complexity));
const maxChurn = Math.max(...files.map((f) => f.churnCount));
const maxCoupling = Math.max(...files.map((f) => f.couplingScore));
return files
.map((f) => ({
...f,
combinedDebtScore:
(f.complexity / maxComplexity) * 0.3 +
(f.churnCount / maxChurn) * 0.4 +
(f.couplingScore / maxCoupling) * 0.3,
}))
.sort((a, b) => b.combinedDebtScore - a.combinedDebtScore);
}La combinación de alta complejidad y alto churn es la señal más fuerte. Un archivo complejo que nadie toca es deuda estable: molesta, pero no activamente dañina. Un archivo complejo que cambia en cada sprint es una bomba de tiempo. Prioriza la refactorización donde se cruzan la complejidad y la frecuencia de cambio.
Análisis de correlación de incidentes
Los incidentes en producción son la manifestación más costosa de la deuda técnica. Vincular los incidentes con las áreas del código convierte las vagas "preocupaciones de estabilidad" en evaluaciones de riesgo concretas.
interface Incident {
id: string;
date: string;
severity: "low" | "medium" | "high" | "critical";
rootCauseModule: string;
timeToResolveMins: number;
engineersInvolved: number;
customerImpact: boolean;
}
interface ModuleIncidentProfile {
module: string;
totalIncidents: number;
criticalIncidents: number;
avgResolutionMins: number;
totalEngineerHours: number;
estimatedCost: number;
}
function analyzeIncidentsByModule(
incidents: Incident[],
hourlyEngineerCost: number = 75,
customerIncidentCost: number = 5000
): ModuleIncidentProfile[] {
const grouped: Record<string, Incident[]> = {};
for (const incident of incidents) {
const mod = incident.rootCauseModule;
if (!grouped[mod]) grouped[mod] = [];
grouped[mod].push(incident);
}
return Object.entries(grouped)
.map(([module, moduleIncidents]) => {
const totalEngineerMins = moduleIncidents.reduce(
(sum, i) => sum + i.timeToResolveMins * i.engineersInvolved,
0
);
const totalEngineerHours = totalEngineerMins / 60;
const customerImpactCount = moduleIncidents.filter(
(i) => i.customerImpact
).length;
return {
module,
totalIncidents: moduleIncidents.length,
criticalIncidents: moduleIncidents.filter(
(i) => i.severity === "critical"
).length,
avgResolutionMins:
moduleIncidents.reduce((s, i) => s + i.timeToResolveMins, 0) /
moduleIncidents.length,
totalEngineerHours,
estimatedCost:
totalEngineerHours * hourlyEngineerCost +
customerImpactCount * customerIncidentCost,
};
})
.sort((a, b) => b.estimatedCost - a.estimatedCost);
}Un módulo con 12 incidentes por trimestre, con un promedio de 3 horas de resolución y 2 ingenieros involucrados, cuesta aproximadamente 72 horas-ingeniero por trimestre solo en tiempo de respuesta, antes de contar el costo del cambio de contexto, las reuniones de post-mortem y el trabajo de funcionalidades que quedó desplazado.
Cómo construir el registro de deuda
Un registro de deuda es un documento vivo que hace seguimiento a los elementos conocidos de deuda técnica junto con sus mediciones de impacto. Transforma la deuda de una sensación vaga en un backlog priorizado.
interface DebtItem {
id: string;
title: string;
description: string;
affectedModules: string[];
estimatedRefactorDays: number;
velocityImpact: "low" | "medium" | "high";
incidentCorrelation: number; // incidents per quarter
monthlyCarryingCost: number;
paybackPeriodMonths: number;
priority: number;
}
function calculatePriority(item: DebtItem): number {
const costWeight = item.monthlyCarryingCost / 10000;
const incidentWeight = item.incidentCorrelation * 2;
const effortPenalty = item.estimatedRefactorDays / 30;
return (costWeight + incidentWeight) / effortPenalty;
}
function buildDebtReport(items: DebtItem[]): string {
const sorted = items.sort((a, b) => b.priority - a.priority);
const totalMonthly = items.reduce(
(sum, i) => sum + i.monthlyCarryingCost,
0
);
let report = `# Technical Debt Register\n\n`;
report += `**Total Monthly Carrying Cost:** $${totalMonthly.toLocaleString()}\n`;
report += `**Total Annual Carrying Cost:** $${(totalMonthly * 12).toLocaleString()}\n\n`;
report += `| Priority | Item | Monthly Cost | Refactor Days | Payback |\n`;
report += `|----------|------|-------------|---------------|----------|\n`;
for (const item of sorted) {
report += `| ${item.priority.toFixed(1)} | ${item.title} | $${item.monthlyCarryingCost.toLocaleString()} | ${item.estimatedRefactorDays} | ${item.paybackPeriodMonths}mo |\n`;
}
return report;
}El período de retorno es la métrica más convincente para las conversaciones de negocio. "Esta refactorización toma 15 días-ingeniero, pero ahorra $8,000 al mes, y se paga sola en 7 semanas" es un lenguaje que los product managers entienden.
Cómo presentar la propuesta a los interesados
El formato de la presentación importa tanto como los datos. Presenta la deuda técnica como una decisión de negocio, no como una queja técnica.
// ❌ Bad: Engineer-centric framing
const badPitch = {
title: "We need to refactor the payment module",
argument: "The code is messy, has high cyclomatic complexity, " +
"and uses deprecated patterns. It needs to be rewritten properly.",
ask: "Give us 3 sprints to clean it up",
};
// ✅ Good: Business-outcome framing
const goodPitch = {
title: "Reducing payment feature delivery time by 60%",
context:
"Payment features take 3x longer to ship than equivalent " +
"features in other modules. This gap costs ~$144K annually in " +
"engineering overhead and contributed to 14 production incidents " +
"in the last 6 months.",
proposal:
"A targeted 15-day refactoring investment reduces cycle time " +
"from 12 days to 5 days per feature and cuts incident frequency " +
"by an estimated 70%.",
roi:
"Investment: ~$9,000 (15 engineer-days). " +
"Annual savings: ~$120,000 (velocity) + ~$35,000 (incidents). " +
"Payback period: 3 weeks.",
risk:
"We phase this alongside feature work — no feature freeze required. " +
"Each phase delivers measurable improvement independently.",
};Nunca pidas un "sprint de refactorización". Pide un resultado de negocio específico respaldado por datos. La conversación cambia de "¿deberíamos limpiar el código?" a "¿vale la pena esta inversión de 3 semanas por un ahorro anual de $155K?". Eso es un sí mucho más fácil de conseguir.
Seguimiento de la reducción de deuda a lo largo del tiempo
Después de conseguir la aprobación del tiempo de refactorización, necesitas demostrar que funcionó. Haz seguimiento de las mismas métricas antes y después para demostrar el retorno de inversión (ROI).
interface DebtReductionReport {
period: string;
module: string;
metricsBefore: VelocityMetrics;
metricsAfter: VelocityMetrics;
incidentsBefore: number;
incidentsAfter: number;
investmentDays: number;
measuredSavingsMonthly: number;
}
function generateImpactReport(report: DebtReductionReport): string {
const cycleImprovement =
((report.metricsBefore.avgCycleTimeDays -
report.metricsAfter.avgCycleTimeDays) /
report.metricsBefore.avgCycleTimeDays) *
100;
const incidentReduction =
((report.incidentsBefore - report.incidentsAfter) /
report.incidentsBefore) *
100;
return `## Refactoring Impact: ${report.module}\n` +
`**Period:** ${report.period}\n` +
`**Investment:** ${report.investmentDays} engineer-days\n\n` +
`| Metric | Before | After | Improvement |\n` +
`|--------|--------|-------|-------------|\n` +
`| Cycle Time | ${report.metricsBefore.avgCycleTimeDays}d | ${report.metricsAfter.avgCycleTimeDays}d | ${cycleImprovement.toFixed(0)}% |\n` +
`| Review Rounds | ${report.metricsBefore.avgReviewRounds} | ${report.metricsAfter.avgReviewRounds} | — |\n` +
`| Incidents/mo | ${report.incidentsBefore} | ${report.incidentsAfter} | ${incidentReduction.toFixed(0)}% |\n` +
`| Monthly Savings | — | — | $${report.measuredSavingsMonthly.toLocaleString()} |\n`;
}Este informe cumple dos propósitos: valida la inversión actual y genera credibilidad para futuras solicitudes de refactorización. Cuando puedes demostrar que "la última refactorización generó un ROI de 2.5x en tres meses", conseguir la aprobación para la siguiente se vuelve mucho más fácil.
Conclusiones clave
La deuda técnica no es una falla moral: es un pasivo financiero. El camino de "deberíamos refactorizar esto" a "esto está financiado" pasa por la cuantificación. Mide el impacto en la velocidad, correlaciona los incidentes con las áreas del código, calcula los costos de mantenimiento y plantea la conversación en términos de resultados de negocio.
El registro de deuda es tu herramienta principal: un documento vivo que hace seguimiento a cada elemento de deuda con su costo mensual de mantenimiento y su período de retorno estimado. Actualízalo trimestralmente, compártelo con los interesados y úsalo para priorizar la refactorización junto con el trabajo de nuevas funcionalidades.
Los ingenieros que consiguen tiempo de refactorización no son los que más se quejan de la calidad del código. Son los que traducen los problemas del código en números de negocio y presentan la refactorización como una inversión con retornos medibles.


