Toda base de código tiene deuda técnica. La tuya, la nuestra, la de Google. La pregunta no es si tienes deuda técnica — la pregunta es si sabes cuánta tienes y si es la correcta.
En EnviaIT gestionamos la deuda técnica de múltiples proyectos simultáneamente. Algunos son MVPs donde la velocidad manda. Otros son plataformas maduras donde la estabilidad es crítica. El approach no puede ser el mismo. Este es el framework que usamos para decidir cuándo aceptar deuda, cuándo pagarla y cuándo vivir con ella.
Qué es (y qué no es) deuda técnica
Ward Cunningham acuñó el término en 1992 como analogía financiera: tomar atajos técnicos es como pedir un préstamo. Puedes avanzar más rápido hoy, pero pagarás intereses mañana en forma de código más difícil de cambiar.
Lo que es deuda técnica:
- Decisiones conscientes de simplificar para ir más rápido
- Código funcional que necesitará refactoring cuando los requisitos crezcan
- Abstracciones que sabes que son limitadas pero suficientes por ahora
Lo que NO es deuda técnica:
- Código mal escrito por falta de conocimiento (eso es mala calidad)
- Bugs conocidos que no se arreglan (eso es negligencia)
- Falta de tests (eso es riesgo operacional)
La distinción importa porque la solución es diferente. La deuda técnica se paga con refactoring planificado. La mala calidad se corrige con formación, code reviews y estándares.
El cuadrante de la deuda técnica
Martin Fowler propuso un modelo de cuatro tipos de deuda que usamos como base para clasificar:
| | Prudente | Imprudente | |---|---|---| | Deliberada | "Sabemos que esto no escala, pero necesitamos lanzar esta semana" | "No tenemos tiempo para diseñar esto bien" | | Accidental | "Ahora que entendemos el dominio, vemos que la estructura debería ser otra" | "¿Qué es la inyección de dependencias?" |
Deuda prudente y deliberada: la buena
Esta es la deuda que queremos tener. Es una decisión informada con trade-offs claros.
Ejemplo real: En un proyecto de marketplace para un cliente, necesitábamos lanzar el sistema de pagos en 3 semanas. Teníamos dos opciones:
- Implementar un sistema de pagos completo con split payments, reembolsos parciales y multi-moneda
- Integrar Stripe Checkout directamente, sin abstracción, solo EUR, reembolsos manuales
Elegimos la opción 2. Documentamos la deuda:
## DEUDA: Sistema de pagos simplificado
- **Fecha:** 2025-03-15
- **Decisión:** Integración directa con Stripe Checkout sin capa de abstracción
- **Limitaciones:** Solo EUR, sin split payments, reembolsos manuales
- **Trigger para pagar:** Cuando necesitemos multi-moneda o volumen > 500 tx/día
- **Estimación de pago:** 3-4 sprints para abstraer la capa de pagos
- **Responsable:** Equipo Backend
El marketplace lanzó a tiempo, validó el modelo de negocio, y 6 meses después refactorizamos el sistema de pagos con datos reales sobre qué necesitaban los usuarios.
Deuda prudente y accidental: la inevitable
Esta deuda aparece cuando aprendes algo nuevo sobre el dominio. No podías haberla evitado porque no tenías la información cuando escribiste el código.
Ejemplo: Diseñamos un modelo de datos para eventos asumiendo que cada evento tiene una única sede. Tres meses después, un cliente grande necesitaba eventos multi-sede. El modelo no lo soportaba. No fue una mala decisión — fue una evolución natural del producto.
Deuda imprudente: la peligrosa
Esta es la deuda que hay que evitar o pagar inmediatamente:
- Copiar y pegar código sin entender qué hace
- Ignorar patrones de seguridad por "ir más rápido"
- Hardcodear credenciales, URLs o configuraciones
- No manejar errores porque "ya lo haremos después"
La deuda imprudente no genera velocidad — genera la ilusión de velocidad. Lo que ahorras hoy lo pagas con intereses compuestos cuando explota en producción un viernes a las 22:00.
Cómo medimos la deuda técnica
No puedes gestionar lo que no mides. Usamos una combinación de métricas cuantitativas y cualitativas:
Métricas automatizadas
# Métricas que recogemos en CI/CD
metricas_deuda:
cobertura_tests:
umbral_minimo: 70%
accion: bloquear PR si baja del umbral
complejidad_ciclomatica:
herramienta: ESLint complexity rule
umbral: 15 por función
accion: warning en PR review
duplicacion_codigo:
herramienta: jscpd
umbral: 5% máximo
accion: report semanal
dependencias_desactualizadas:
herramienta: npm audit + npm outdated
frecuencia: semanal
accion: ticket automático si hay vulnerabilidades
tiempo_build:
umbral: 5 minutos máximo
accion: investigar si sube más de 20%
Métricas cualitativas
Cada dos semanas, en la retrospectiva del sprint, preguntamos:
- "¿Qué parte del código te da miedo tocar?" — Señala zonas de alto riesgo
- "¿Dónde tardaste más de lo esperado?" — Indica código difícil de cambiar
- "¿Qué harías diferente si empezaras de cero?" — Revela deuda accidental descubierta
Mantenemos un registro de deuda en el repo — un archivo TECH_DEBT.md que cualquier dev puede actualizar:
# Registro de Deuda Técnica
## Alta prioridad
- [ ] **Migrar auth a JWT con refresh tokens** — El sistema actual usa sesiones
en memoria, no escala horizontalmente. Impacta: escalado, deploys.
Estimación: 2 sprints. Trigger: antes de pasar a 2+ instancias.
## Media prioridad
- [ ] **Extraer servicio de notificaciones** — Actualmente acoplado al
monolito. Añadir un nuevo canal (SMS, push) requiere tocar código
core. Estimación: 1 sprint.
## Baja prioridad
- [ ] **Migrar de Moment.js a date-fns** — Moment está deprecated y pesa
300KB. No bloquea nada, pero infla el bundle. Estimación: 3 días.
Framework de priorización
No toda la deuda merece ser pagada. Usamos una matriz de impacto vs esfuerzo adaptada a deuda técnica:
Paso 1: Clasificar por impacto
- Impacto alto: Bloquea o ralentiza features de negocio, causa incidentes en producción, afecta a la seguridad
- Impacto medio: Ralentiza el desarrollo pero no lo bloquea, degrada la experiencia de developer
- Impacto bajo: Molesto pero funcional, cosmético, no afecta al usuario final
Paso 2: Estimar esfuerzo de pago
- Bajo: Menos de 1 día — un dev, sin riesgo de regresión
- Medio: 1-5 días — requiere planning, posible impacto en otras áreas
- Alto: Más de 1 sprint — proyecto dedicado, requiere migración gradual
Paso 3: Decidir la acción
| | Esfuerzo bajo | Esfuerzo medio | Esfuerzo alto | |---|---|---|---| | Impacto alto | Pagar ya | Planificar este sprint | Proyecto dedicado | | Impacto medio | Incluir en PR relacionado | Backlog priorizado | Evaluar trimestralmente | | Impacto bajo | Boy Scout Rule | Solo si hay tiempo | Ignorar conscientemente |
La Boy Scout Rule ("deja el código mejor de como lo encontraste") es nuestra herramienta favorita para deuda de bajo esfuerzo: si tocas un archivo para una feature, mejora lo que veas.
Estrategias de refactoring
Cuando decidimos pagar deuda, no siempre significa "reescribir". Tenemos un toolkit de estrategias según la situación:
1. Strangler Fig Pattern
Para reemplazar un sistema complejo gradualmente. Construyes el nuevo sistema al lado del viejo, redirigiendo tráfico poco a poco.
// Fase 1: Wrapper que delega al sistema viejo
class NotificationService {
async send(notification: Notification) {
// TODO: Migrar al nuevo sistema event-driven
return this.legacyService.sendNotification(
notification.userId,
notification.message,
notification.channel
);
}
}
// Fase 2: Feature flag para redirigir al nuevo sistema
class NotificationService {
async send(notification: Notification) {
if (await this.featureFlag.isEnabled('new-notifications', notification.userId)) {
return this.eventBus.publish('notification.send', notification);
}
return this.legacyService.sendNotification(
notification.userId,
notification.message,
notification.channel
);
}
}
// Fase 3: Eliminar el código viejo cuando el 100% está migrado
class NotificationService {
async send(notification: Notification) {
return this.eventBus.publish('notification.send', notification);
}
}
2. Branch by Abstraction
Cuando necesitas reemplazar una dependencia o componente sin parar el desarrollo:
- Crea una abstracción sobre el componente actual
- Haz que todo el código use la abstracción
- Implementa la nueva versión detrás de la abstracción
- Cambia la implementación
- Elimina la vieja
3. Refactoring incremental
Para deuda dispersa (naming inconsistente, patrones mixtos, etc.), dedicamos un porcentaje fijo del sprint:
- 20% del sprint dedicado a pagar deuda en sprints normales
- 1 sprint de "tech health" cada trimestre para deuda mayor
- Boy Scout Rule siempre activa para mejoras pequeñas
Cuándo reescribir vs refactorizar
La tentación de "reescribir desde cero" es fuerte. Casi siempre es un error.
Reescribir es justificable cuando:
- La tecnología base está end-of-life y sin soporte
- El modelo de datos fundamental no soporta los requisitos actuales
- El coste de mantener el sistema viejo supera el de construir uno nuevo (con datos que lo demuestren)
- El equipo que escribió el código ya no está y nadie entiende el sistema
Refactorizar es mejor cuando:
- El sistema funciona y genera valor
- Los problemas son de estructura, no de concepto
- Puedes aislar las partes a mejorar
- El negocio no puede permitirse meses sin features nuevas
Joel Spolsky lo dijo hace 25 años y sigue siendo cierto: reescribir desde cero es "el peor error estratégico que una compañía de software puede cometer". No porque nunca sea necesario — sino porque se subestima brutalmente el esfuerzo.
El business case para pagar deuda
El mayor reto no es técnico — es convencer a stakeholders de que invertir en algo invisible tiene ROI. Estos son los argumentos que funcionan:
1. Velocidad de desarrollo medible
Sprint 1 (con deuda): 5 features, 3 bugs encontrados en QA
Sprint 2 (con deuda): 4 features, 5 bugs encontrados en QA
Sprint 3 (pagando deuda): 2 features + refactoring
Sprint 4 (post-pago): 7 features, 1 bug encontrado en QA
Sprint 5 (post-pago): 8 features, 0 bugs en QA
Los números hablan solos. Tracking del velocity antes y después del refactoring demuestra el ROI.
2. Reducción de incidentes
Cada incidente en producción tiene un coste: tiempo de ingeniería para diagnosticar, impacto en usuarios, daño reputacional. Si puedes correlacionar incidentes con zonas de deuda conocida, el argumento se construye solo.
3. Retención de talento
Los buenos desarrolladores no quieren trabajar en código que les hace infelices. Si el equipo menciona constantemente que el código es "frustrante" o "frágil", hay un coste de rotación que nadie está calculando.
4. Time-to-market futuro
La deuda técnica es un impuesto invisible sobre cada feature futura. Si añadir un campo a un formulario toma 2 días en vez de 2 horas, el negocio está pagando intereses sin saberlo.
Nuestro proceso en la práctica
Resumiendo cómo gestionamos la deuda técnica en proyectos de EnviaIT:
- Documentar al crear: Cada atajo deliberado se documenta con fecha, razón, limitaciones y trigger para pagar
- Medir continuamente: Métricas automatizadas en CI + retrospectivas cualitativas
- Priorizar trimestralmente: Review formal del registro de deuda con el equipo y stakeholders
- Dedicar tiempo fijo: 20% del sprint para mantenimiento, 1 sprint/trimestre para deuda mayor
- Medir el impacto: Velocity, incidentes y satisfacción del equipo antes y después
La deuda técnica no es el enemigo. Es una herramienta. Como cualquier herramienta, el problema no es usarla — es no saber cuándo dejarla.
¿Tu proyecto tiene deuda técnica que está frenando el desarrollo? Hablemos — te ayudamos a diagnosticarla, priorizarla y crear un plan de acción realista.