El oficio: mantener es la mayor parte de la vida del software
Un sistema útil se mantiene mucho más tiempo del que se construye. Las leyes de evolución del software de Meir Lehman lo resumen en dos ideas: un sistema que se usa debe cambiar continuamente o se vuelve cada vez menos útil, y su complejidad crece salvo que se haga un trabajo explícito para reducirla. Mantener bien consiste en administrar ese cambio con método: saber qué se toca, con qué riesgo, con qué red de seguridad y con qué rastro para quien venga detrás.
La norma de referencia es ISO/IEC/IEEE 14764 (proceso de mantenimiento del software), que forma parte del ciclo de vida descrito en ISO/IEC/IEEE 12207.
Tipos de mantenimiento
Reactivo
Proactivo
| Tipo | Qué lo provoca | Ejemplos | Señal de que falta |
|---|---|---|---|
| Correctivo | Un fallo detectado | Arreglar un cálculo erróneo, un cierre inesperado | Los mismos errores reaparecen |
| Adaptativo | Un cambio en el entorno | Nueva versión del sistema operativo, de una API externa, de una normativa | Las actualizaciones se descubren cuando algo deja de funcionar |
| Perfectivo | Una mejora pedida | Mejorar el rendimiento, ampliar una función | El producto no evoluciona con sus usuarios |
| Preventivo | Un riesgo futuro | Refactorizar un módulo frágil, mejorar pruebas, reducir dependencias | Cada cambio se vuelve más lento y arriesgado |
En la práctica, la parte perfectiva suele ser la mayor y la preventiva la primera que se sacrifica, con un precio que se paga después. Una cartera sana reserva capacidad explícita para cada tipo.
Del síntoma a la causa: gestión de incidencias
Ciclo de una petición
Una incidencia crítica salta la cola; el resto espera su turno.
Triaje
El triaje decide qué ocurre con cada entrada. Primero se comprueba si es reproducible, si está duplicada y si es un error, una petición de mejora o un malentendido de uso.
| Concepto | Qué mide | Quién lo decide |
|---|---|---|
| Severidad | Gravedad técnica del efecto (pérdida de datos, caída total, defecto cosmético) | Quien conoce el sistema |
| Prioridad | Cuándo se arregla, según urgencia y contexto de negocio | Quien responde del producto |
Pueden no coincidir: un error cosmético en la página de la campaña de mañana tiene severidad baja y prioridad alta.
Un buen informe de error
Título: El total del pedido no incluye los gastos de envío al elegir recogida en tienda
Entorno: Producción, versión 3.8.2, navegador de escritorio
Pasos: 1. Añadir cualquier producto al carrito.
2. Elegir "Recogida en tienda" y volver a "Envío a domicilio".
Resultado: El total no vuelve a incluir los 4,90 € de envío.
Esperado: El total incluye el envío al volver a seleccionarlo.
Frecuencia: Siempre que se alterna entre las dos opciones.
Impacto: Se están cobrando pedidos por debajo de su precio.
Adjuntos: Captura y registro de la petición.Si el informe no permite reproducir el fallo, el primer paso es pedir lo que falta, no adivinar.
Corregir con método
| Paso | Qué se hace |
|---|---|
| Reproducir | Conseguir que falle de forma controlada, en local si es posible |
| Aislar | Reducir el caso hasta encontrar la causa, no el síntoma; técnicas como bisección del historial de cambios |
| Probar | Escribir el test que falla por ese motivo y que fallará de nuevo si el error vuelve |
| Corregir | Hacer el cambio mínimo que lo arregla |
| Prevenir | Preguntar qué habría detectado el error antes: ¿una validación? ¿una prueba? ¿una alerta? |
Para la causa raíz sirven los cinco porqués y el diagrama de causa y efecto. El objetivo es encontrar el fallo de proceso o de diseño que permitió el error, no al responsable.
Incidencias en producción
Cuando el servicio está afectado, el orden cambia: primero se recupera, después se investiga.
- Detectar (alerta, informe de usuarios).
- Evaluar el impacto y asignar una persona que coordine.
- Mitigar: volver a la versión anterior, desactivar la función con un interruptor o desviar tráfico.
- Comunicar el estado a los afectados con regularidad.
- Resolver la causa con calma.
- Revisión posterior sin culpables (postmortem): cronología, causa raíz, qué funcionó, qué no, y acciones con responsable y fecha.
Métricas habituales: tiempo medio de detección y tiempo medio de recuperación. Las incidencias se estudian como tendencia, no como anécdota.
Deuda técnica
La metáfora, acuñada por Ward Cunningham, compara una solución rápida con una deuda económica: permite avanzar hoy y cobra intereses en cada cambio posterior, porque el código es más difícil de entender y de tocar.
Se sabía
No se sabía
El cuadrante de Martin Fowler recuerda que no toda la deuda es mala ni toda es consecuencia de la prisa: puede ser una decisión prudente (se entrega pronto para aprender, con un plan de pago) o imprudente (se recorta por desconocimiento o desidia).
Origen y tratamiento
| Origen | Ejemplo | Cómo tratarla |
|---|---|---|
| Deliberada | Atajo conocido para llegar a una fecha | Anotarla con fecha y motivo, y planificar su pago |
| Accidental | No se conocía una forma mejor | Corregirla al descubrirla |
| Por envejecimiento | La plataforma evolucionó y el código no | Mantenimiento adaptativo continuo |
| De conocimiento | Solo una persona entiende el módulo | Documentar, programar en pareja, rotar |
| De pruebas | Zonas sin cobertura que nadie se atreve a tocar | Añadir pruebas antes de cambiar |
Cómo priorizar el pago
No toda la deuda merece pago. Una guía: pagar donde más se cambia y más duele.
- Zonas calientes (hotspots): cruzar la frecuencia de cambios de cada fichero (rotación o churn) con su complejidad. Lo muy cambiado y muy complejo es donde el interés es mayor. Es una técnica popularizada por Adam Tornhill.
- Riesgo: lo que, si falla, causa más daño (seguridad, dinero, datos).
- Coste del retraso: lo que impide una funcionalidad ya prevista.
- Regla del boy scout: dejar cada fichero algo mejor de como se encontró, en cambios pequeños y seguros.
Hacerla visible
Lo que no se ve, no se prioriza. Se registra en un inventario de deuda con descripción, ubicación, impacto, estimación de esfuerzo y fecha. Se reserva una parte fija de la capacidad de cada iteración, en lugar de esperar a «un parón para limpiar» que nunca llega.
Trabajar con código heredado
Michael Feathers define el código heredado de forma pragmática: código sin pruebas. El problema no es la edad, sino que cambiarlo da miedo.
El método
- Identificar el punto de cambio.
- Encontrar puntos de apoyo (seams): lugares donde se puede alterar el comportamiento sin editar el código, como una interfaz o un parámetro, para poder probar.
- Escribir pruebas de caracterización: pruebas que no dicen cómo debería comportarse el código, sino cómo se comporta hoy. Fijan el comportamiento actual, incluso el discutible.
- Refactorizar con esa red de seguridad.
- Hacer el cambio funcional.
Estrategias de modernización
| Estrategia | Idea | Cuándo | Riesgo |
|---|---|---|---|
| Mejora continua | Refactorizar poco a poco según se toca | Casi siempre | Lentitud percibida |
| Estrangulador (strangler fig) | Construir lo nuevo alrededor de lo viejo y migrar rutas una a una | Sistemas grandes con fronteras claras | Convivencia prolongada de dos sistemas |
| Rama por abstracción | Introducir una interfaz y sustituir la implementación bajo ella | Cambiar un componente interno | Esfuerzo de abstraer |
| Ejecución en paralelo | Ejecutar viejo y nuevo y comparar resultados | Cálculos críticos | Coste y complejidad |
| Reescritura completa | Sustituir todo de una vez | Casi nunca | Se reproducen errores ya resueltos y se pierde conocimiento implícito |
El patrón estrangulador (Martin Fowler) es la alternativa a la reescritura: el sistema nuevo crece mientras el viejo se vacía, y en todo momento hay algo funcionando.
Versionado y publicación
Versionado semántico
SemVer 2.0.0 comunica con el número de versión el riesgo de actualizar: MAYOR.MENOR.PARCHE.
Efecto sobre el consumidor
| Sube | Cuándo | Ejemplo |
|---|---|---|
| PARCHE | Corrección compatible | 1.4.2 pasa a 1.4.3 |
| MENOR | Funcionalidad nueva compatible | 1.4.3 pasa a 1.5.0 |
| MAYOR | Cambio incompatible | 1.5.0 pasa a 2.0.0 |
Matices: la versión 0.x indica que la API todavía no es estable; los prelanzamientos se marcan con sufijo (2.0.0-beta.1); y SemVer solo funciona si hay una API pública declarada y se respeta. Para aplicaciones sin API, se usan también versionados por calendario (año y mes) o por compilación.
Qué cuenta como cambio incompatible
Eliminar o renombrar algo público, cambiar el tipo de un parámetro, endurecer una validación, modificar el formato de una respuesta, cambiar un valor por defecto que alteraba el comportamiento, subir la versión mínima de una plataforma soportada.
Mensajes de commit y changelog
Con Conventional Commits el mensaje del commit lleva un prefijo (feat, fix, refactor, docs…) y una marca para los cambios incompatibles. Facilita automatizar el cálculo de la versión y el borrador del changelog.
fix(carrito): recalcular el envío al cambiar el método de entrega
feat(api)!: devolver las fechas en formato ISO 8601
BREAKING CHANGE: los campos de fecha ya no se devuelven como marca de tiempo numérica.Sin embargo, el changelog definitivo se escribe para personas. Siguiendo Keep a Changelog:
## [3.8.3] - 2026-04-02
### Corregido
- El total del pedido vuelve a incluir los gastos de envío al alternar entre
recogida en tienda y envío a domicilio.
### Seguridad
- Se actualiza la librería de análisis de ficheros por una vulnerabilidad
de lectura fuera de rango.
## [3.8.0] - 2026-03-10
### Añadido
- Exportación de pedidos a hoja de cálculo.
### Obsoleto
- El parámetro `formato` del listado se retirará en la versión 4.0.Reglas: la versión más reciente primero, fecha en formato ISO, categorías (Añadido, Cambiado, Obsoleto, Eliminado, Corregido, Seguridad), y una sección Sin publicar arriba. Un volcado del historial de commits no es un changelog.
Flujo de una versión
Además: una versión publicada es inmutable; si hay un error, se publica una nueva. Cada versión debe poder reproducirse a partir de su etiqueta.
Estrategias de ramas y soporte
| Modelo | Rasgo | Encaja con |
|---|---|---|
| Desarrollo sobre la rama principal (trunk-based) | Cambios pequeños y frecuentes, interruptores de funciones | Servicios web con despliegue continuo |
| Ramas de versión | Cada versión mayor tiene su rama y recibe correcciones | Productos que soportan varias versiones a la vez |
| Versiones de soporte a largo plazo (LTS) | Algunas versiones reciben parches de seguridad durante años | Librerías y plataformas con muchos usuarios |
Una política de soporte escrita (qué versiones reciben correcciones y hasta cuándo) evita promesas implícitas imposibles de cumplir.
Retirar funcionalidades
Quitar algo también se diseña. Cada funcionalidad que se conserva cuesta código, pruebas y atención, pero retirarla sin cuidado rompe a quien dependía de ella.
Si el uso no baja, se amplía el plazo o se ayuda a migrar.
- Aviso con plazo proporcional al impacto (semanas para algo interno, versiones para una API pública).
- Aviso en tiempo de ejecución además de en la documentación: nadie lee las notas de versión.
- Guía de migración con ejemplos de antes y después.
- Medir el uso para saber si se puede retirar o si hace falta acompañar a alguien.
- Eliminar en una versión mayor y dejarlo anotado.
Dependencias y entorno
Gran parte del mantenimiento adaptativo consiste en seguir el ritmo de lo que no se controla: lenguajes, librerías, sistemas operativos y servicios de terceros.
| Práctica | Para qué |
|---|---|
| Fichero de bloqueo (lockfile) | Instalaciones reproducibles: mismas versiones en todas partes |
| Actualización automatizada | Una herramienta propone cambios pequeños y frecuentes |
| Pruebas automáticas fiables | Sin ellas, cada actualización es un acto de fe |
| Inventario de componentes (SBOM) | Saber qué hay dentro cuando se anuncia una vulnerabilidad |
| Calendario de fin de vida | Conocer cuándo dejan de recibir parches el lenguaje, la plataforma o la base de datos |
| Minimizar dependencias | Cada una es superficie de ataque y de mantenimiento |
La regla práctica: actualizar a menudo y en trozos pequeños. Cada versión menor de diferencia añade incertidumbre; una migración de cinco versiones mayores es un proyecto, no una tarea.
Evaluar una dependencia antes de adoptarla
- ¿Está mantenida (versiones recientes, incidencias atendidas)?
- ¿Cuántas personas la mantienen? ¿Qué pasa si desaparecen?
- ¿Cuál es su licencia y es compatible con la del proyecto?
- ¿Cuánto código transitivo arrastra?
- ¿Sería fácil sustituirla? Aislarla tras una interfaz propia reduce el coste de cambio.
Cambios seguros en producción
La mejor protección contra los errores en mantenimiento no es no cambiar, sino hacer que cambiar sea barato y reversible.
| Técnica | Qué aporta |
|---|---|
| Integración y entrega continuas | Cada cambio se compila y prueba automáticamente |
| Interruptores de funciones (feature flags) | Se despliega el código sin activarlo; se activa por tramos y se apaga sin redesplegar |
| Despliegue gradual (canario) | Se prueba con una parte pequeña del tráfico antes de generalizar |
| Azul-verde | Dos entornos idénticos; se conmuta el tráfico y se vuelve atrás al instante |
| Migraciones compatibles hacia atrás | Primero se amplía el esquema, luego se cambia el código, al final se limpia (patrón expandir y contraer) |
| Marcha atrás ensayada | Si volver atrás nunca se ha probado, no existe |
Migraciones de datos
Son el cambio más delicado porque los datos no admiten «deshacer» limpio. Principios: copia de seguridad verificada antes; migraciones idempotentes y repetibles; separar el cambio de esquema del cambio de código en despliegues distintos; ensayo con datos de tamaño real.
Medir la salud del mantenimiento
Lo que se mide orienta decisiones. Las cuatro métricas de DORA (investigación sobre rendimiento de la entrega de software) son una referencia reconocida:
| Métrica | Mide |
|---|---|
| Frecuencia de despliegue | Cada cuánto se publica |
| Tiempo de entrega de cambios | Desde el commit hasta producción |
| Tasa de fallos de cambios | Qué proporción de despliegues causa un problema |
| Tiempo de recuperación | Cuánto se tarda en restablecer el servicio tras un fallo |
Otras señales útiles: tiempo medio de resolución por tipo de incidencia, antigüedad de las incidencias abiertas, cobertura de pruebas en zonas calientes, proporción de tiempo en trabajo no planificado, versiones de dependencias por detrás de la última y número de componentes sin propietario.
Conocimiento y continuidad
Un sistema que solo entiende una persona tiene una fecha de caducidad desconocida.
- Propiedad compartida del código; nadie es dueño de un módulo en exclusiva.
- Revisión de código con intercambio de conocimiento real.
- Guardias rotativas con runbooks actualizados.
- Mapa de servicios con propietario, dependencias y nivel de criticidad.
- Registro de decisiones (ADR) que cuente por qué el sistema es como es.
- Traspasos ensayados cuando cambia el equipo.
Cuándo parar: fin de vida
Todo software llega al final. Decidirlo con tiempo es mejor que sufrirlo.
| Señal | Interpretación |
|---|---|
| El coste de mantenimiento supera el valor que aporta | Candidato a retirada o sustitución |
| La plataforma base ya no recibe parches de seguridad | Riesgo creciente: migrar o aislar |
| Nadie conoce el sistema y no hay forma de recuperar el conocimiento | Riesgo operativo alto |
| Los usuarios han migrado a otra solución | Planificar la retirada con aviso |
Una retirada ordenada incluye comunicación, exportación de datos para los usuarios, plazo de convivencia, cumplimiento de requisitos legales de conservación y borrado, y apagado verificado.
Lista de comprobación
- Cada incidencia se registra con pasos para reproducirla.
- Hay un criterio escrito de severidad y prioridad.
- Todo error corregido deja una prueba que lo demuestra.
- Existe capacidad reservada para deuda y mantenimiento preventivo.
- Las versiones siguen SemVer y cada una tiene su entrada de changelog.
- La política de soporte está escrita.
- Las dependencias se actualizan de forma regular y hay calendario de fin de vida.
- Volver atrás a la versión anterior está probado.
- Hay runbooks para los fallos más probables.
- Todo componente crítico tiene al menos dos personas que lo entienden.
Errores y antipatrones
- Arreglar el síntoma. Poner una condición para que el caso concreto no falle y dejar la causa intacta.
- Aplazar las actualizaciones hasta que se convierten en una migración de varias versiones mayores.
- Reescribir desde cero por frustración. Casi siempre se subestima el conocimiento escondido en el sistema antiguo.
- Cambios sin registro. Nadie sabe qué versión contiene qué ni por qué.
- Changelog generado y sin leer, con ruido de commits internos.
- Parches directos en producción que no vuelven a la rama principal: el siguiente despliegue los borra.
- Héroes. Una persona que lo arregla todo por la noche no es una estrategia; es un riesgo.
- Deuda sin dueño ni fecha: una lista de «mejoras pendientes» que solo crece.
- Culpar a personas en las revisiones posteriores: asegura que la próxima vez se oculte el problema.
- Eliminar sin avisar una función porque «nadie la usa» sin haber medido el uso.
- Congelar el sistema por miedo a romperlo. Un sistema sin cambios acumula riesgo adaptativo.