Runbook de Actualización de Dependencias
Un runbook de cinco fases para actualizar las dependencias del proyecto de forma segura, cubriendo preparación, actualización, validación, reversión y monitoreo post-deploy.
Visión General
Las dependencias desactualizadas exponen los proyectos a vulnerabilidades de seguridad, bugs de compatibilidad y funciones faltantes. Este runbook te da un proceso repetible de cinco fases para actualizarlas con riesgo mínimo. El mismo checklist sirve para un parche CVE puntual o una ventana de mantenimiento trimestral.
Cuándo Usar
- Se publica un parche de seguridad crítico para una dependencia directa o transitiva y lo necesitas en producción hoy, no el próximo sprint.
- Se necesita una actualización de versión mayor para soporte a largo plazo, funciones nuevas o compatibilidad con el resto de tu stack.
- Ejecutas refrescos de dependencias programados por sprint o trimestre y quieres el mismo procedimiento cada vez.
- Un ingeniero junior corre su primera actualización y necesita el checklist, no el conocimiento tribal.
El proceso del runbook de un vistazo
El Runbook
# Runbook de Actualización de Dependencias
## 1. Preparación
- [ ] Identificar la dependencia y la versión objetivo
- [ ] Revisar el changelog / notas de la versión en busca de cambios incompatibles
- [ ] Comprobar issues abiertos en el repositorio de la dependencia relacionados con la actualización
- [ ] Crear una rama dedicada: `deps/upgrade-<nombre>-<version>`
- [ ] Asegurar que el CI esté verde en la rama principal actual
## 2. Actualización
- [ ] Actualizar la versión en `package.json`, `requirements.txt`, `pom.xml`, etc.
- [ ] Ejecutar el comando de instalación de dependencias
- [ ] Revisar advertencias de dependencias peer o conflictos
- [ ] Ejecutar pruebas automatizadas (unitarias, integración, lint)
- [ ] Ejecutar pruebas de humo contra un entorno local o staging
## 3. Validación
- [ ] Revisar informes de cobertura de pruebas en busca de regresiones
- [ ] Comprobar logs de la aplicación en busca de nuevas advertencias o errores
- [ ] Verificar rutas críticas del usuario manualmente si cambió el comportamiento
- [ ] Ejecutar escaneo de seguridad (`npm audit`, `safety check`, OWASP dependency check)
## 4. Plan de Reversión
- [ ] Etiquetar el último commit conocido como bueno antes del merge
- [ ] Documentar cualquier cambio manual de datos o configuración requerido
- [ ] Confirmar que la reversión puede ejecutarse en 15 minutos
## 5. Merge y Monitoreo
- [ ] Abrir un pull request con resumen del changelog
- [ ] Desplegar en staging y dejarlo madurar por 24 horas
- [ ] Desplegar en producción durante una ventana de bajo tráfico
- [ ] Monitorear tasas de error y latencia durante 48 horas después del despliegue
Cómo Funcionan las Cinco Fases
El runbook divide las actualizaciones en fases donde cada una atrapa un modo de fallo distinto. La preparación mata sorpresas: la mayoría de incidentes de actualización vienen de un changelog que nadie leyó. La actualización se queda aislada en una rama para que el diff sea revisable. La validación mezcla checks automáticos con una pasada manual sobre lo que realmente cambió. El plan de reversión existe antes de necesitarlo. “Revertir rápido” es una decisión que se toma en calma, no durante un incidente. El merge con monitoreo cierra el ciclo: el staging más 48 horas de vigilancia de errores atrapa las regresiones que los tests no ven.
Cada fase también tiene un costo si te la saltas. Salta la preparación y descubres los cambios incompatibles en producción. Salta la rama aislada y la actualización se enreda con lo que sea que estaba en vuelo esa semana. Salta la validación y “los tests pasan” cubre en silencio la única ruta que nadie ejercitó. Salta el plan de reversión y la reversión se vuelve un proyecto de arqueología. Salta el monitoreo y te enteras de la regresión por un reporte de usuario en vez de un dashboard.
Elegir la Versión Objetivo
“La última” no siempre es el objetivo correcto. Elige la versión según la intención:
- Fix de seguridad. Toma la versión más pequeña que parchee el CVE, aunque sea una rama de backport como
2.4.xen vez de3.x. Los saltos grandes bajo presión de incidente multiplican el riesgo. - Mantenimiento rutinario. La última estable está bien, pero prefiere una versión con algunas semanas de uso real sobre una publicada ayer. Deja que otro encuentre los bugs del
.0. - Migración mayor. Apunta a la línea LTS más reciente, no al borde sangrante. En frameworks, revisa primero la ventana de soporte oficial: una actualización que te deja en una versión que muere en tres meses te compra una segunda emergencia.
Dos comprobaciones antes de fijar el número. Primero, los requisitos de runtime: muchas majors suben en silencio el mínimo de Node, Python o JVM, y la actualización se convierte en dos. Segundo, el rezago del ecosistema: las definiciones de tipos, plugins y wrappers (@types/*, plugins de webpack, integraciones de ORM) suelen ir semanas detrás de una major. Si los wrappers que usas no se han puesto al día, esperar es más barato que parchear alrededor.
Comandos de Actualización por Ecosistema
Las cinco fases son las mismas en todas partes; solo cambian los comandos. Un mapa rápido de las cadenas de herramientas habituales:
| Ecosistema | Qué está desactualizado | Actualizar un paquete | Lock file | Auditoría de vulnerabilidades |
|---|---|---|---|---|
| npm | npm outdated | npm install pkg@x.y.z | package-lock.json | npm audit |
| yarn / pnpm | yarn outdated / pnpm outdated | yarn up pkg / pnpm update pkg | yarn.lock / pnpm-lock.yaml | yarn npm audit / pnpm audit |
| pip + pip-tools | pip list --outdated | pip-compile --upgrade-package pkg | requirements.txt | pip-audit |
| poetry | poetry show --outdated | poetry update pkg | poetry.lock | pip-audit sobre reqs exportados |
| Maven | mvn versions:display-dependency-updates | mvn versions:use-latest-versions | (versiones en pom.xml) | dependency-check (OWASP) |
| Go modules | go list -u -m all | go get -u pkg@vX.Y.Z | go.sum | govulncheck ./... |
| Cargo | cargo outdated | cargo update -p pkg | Cargo.lock | cargo audit |
| Bundler | bundle outdated | bundle update gem | Gemfile.lock | bundler-audit |
Sea cual sea el ecosistema, el flujo de la fase 2 es idéntico: sube el manifest, regenera el lock file, corre los tests y lee el diff del lock antes de commitear. En ese diff es donde aparecen las actualizaciones transitivas sorpresa.
Variantes
| Contexto | Enfoque | Notas |
|---|---|---|
| Parche de seguridad | Rama de vía rápida | Omitir tiempo de maduración solo para CVEs con exploits activos |
| Versión mayor | Despliegue con feature flags | Aislar el comportamiento nuevo bajo banderas durante la transición |
| Monorepo | Actualizaciones en lote | Actualizar las librerías compartidas primero, luego los consumidores |
| CVE solo transitivo | Forzar versión con overrides / resolutions | Documentar por qué existe el pin y cuándo quitarlo |
Mejores Prácticas
- Actualiza una dependencia mayor a la vez. Dos majors en un PR hacen imposible atribuir los fallos.
- Fija versiones exactas e incluye los lock files en el commit.
package-lock.jsonypoetry.lockhacen los builds reproducibles y los diffs revisables. - Usa
npm cien CI, nonpm install. Instala exactamente desde el lock file y falla cuando el archivo se desincroniza depackage.json:
# GitHub Actions
- name: Install dependencies
run: npm ci
- Automatiza el 90% aburrido. Dependabot o Renovate pueden auto-mergear bumps de parche y menores; deja los majors para humanos:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
groups:
patch-and-minor:
update-types: ["patch", "minor"]
- Lleva un calendario de deprecación. Registra las fechas de fin de soporte de frameworks y runtimes para que las actualizaciones sean planificadas, no forzadas por un CVE.
Errores Comunes
- Actualizar varias majors a la vez. Cuando fallan los tests, no puedes saber qué bump rompió las cosas.
- Ignorar advertencias de dependencias peer. Se convierten en errores de runtime en producción.
- Omitir el plan de reversión. Improvisar una reversión bajo presión extiende el downtime.
- No revisar los cambios transitivos.
git diff package-lock.jsonsuele mostrar actualizaciones sorpresa que merecen escrutinio. - Actualizar devDependencies sin un build completo. Webpack, babel o eslint pueden romper el bundle de producción aunque los tests pasen:
npm run build && npm run test
- Desestimar advertencias de deprecación. Los warnings de una versión menor se vuelven errores en la siguiente major. Captúralos y rastréalos:
npm test 2>&1 | grep -i "deprecat" > deprecation-warnings.txt
Ejemplo Práctico: Actualizar React 18 a 19
Un paseo concreto por el runbook hace tangibles las fases. Supón que la actualización es React 18 a 19 en una app con una semana de margen.
Preparación. El changelog de React 19 señala APIs eliminadas (defaultProps en componentes de función, string refs) y comportamiento cambiado en el timing de limpieza de useEffect y en ref como prop. El issue tracker del proyecto marca un problema conocido con una librería de formularios popular en 19.0.0, corregido en 19.0.2, así que el objetivo es 19.0.2, no 19.0.0. La versión de Node de la app ya cumple el requisito. Rama: deps/upgrade-react-19.
Actualización. Sube react y react-dom juntas (se publican en lockstep), más @types/react y @types/react-dom. Corre el codemod oficial para ref como prop. npm install reporta una advertencia peer de una versión vieja de una librería de gráficos que topea React en 18, así que esa librería se actualiza en la misma rama, porque un conflicto peer es un fallo de la fase 2, no un proyecto aparte.
Validación. Las suites unitarias y de integración pasan. La pasada manual ejercita las tres rutas que el changelog marcó como arriesgadas: un formulario con inputs no controlados, un modal con createPortal y un componente que dependía de defaultProps. El codemod reescribió el último automáticamente.
Reversión. El tag pre-actualización existe; la reversión es git checkout <tag> -- package.json package-lock.json && npm ci. Probada una vez en una rama desechable: cuatro minutos.
Merge y monitoreo. El PR enlaza el resumen del changelog y lista el diff del codemod por separado para que los revisores hojeen los cambios generados. Staging madura un día, producción despliega el martes por la mañana, y la tasa de errores y el INP se mantienen planos durante la ventana de 48 horas.
Manejar Cambios Incompatibles
Los cambios incompatibles son donde las actualizaciones van a morir. La defensa es aburrida pero efectiva: lee la guía de migración antes de tocar el manifest, no después de que el build se rompa. La mayoría de los proyectos maduros publican una (MIGRATION.md, docs de actualización o notas con sección “Breaking”). Si el proyecto solo publica un changelog crudo, busca APIs renombradas, opciones eliminadas, defaults que cambiaron y deprecaciones que estabas usando.
Para las majors, el trabajo mecánico suele tener tooling:
- Codemods: muchos ecosistemas publican transformaciones AST:
npx @org/codemod,ng updatede Angular, los codemods oficiales de React u OpenRewrite para Java. Córralos en una rama limpia y revisa el diff como cualquier otro cambio de código. - Capas adaptadoras: cuando llamas a la API vieja en cuarenta lugares, envuélvela. Escribe un módulo delgado con la superficie vieja que delega a la nueva, migra los call sites detrás de ella y luego bota el adaptador. Convierte un flag-day arriesgado en muchos diffs pequeños.
- Ejecución dual: para dependencias de infraestructura (clientes HTTP, serializadores, ORM), corre vieja y nueva en paralelo detrás de una bandera cuando la librería lo permita, y conmuta por entorno.
Una advertencia sobre la cobertura: si tu suite es delgada, una actualización es el peor momento para descubrirlo. Antes del salto major, añade tests de contrato sobre las rutas que tocan la dependencia: fronteras de serialización, comportamiento de reintentos, formas de error. Esos tests son la diferencia entre “los tests pasan” y “funciona”.
Coordinar Actualizaciones entre Repos
Dentro de un repo el runbook alcanza. Entre repos, con una librería compartida más sus consumidores, el orden importa más que la mecánica:
- Actualiza primero el productor. Publica la nueva versión de la librería compartida en un registry (o un tag de prerelease) antes de tocar consumidores.
- Declara una ventana de compatibilidad. Si la librería puede soportar consumidores viejos y nuevos durante un par de versiones, dilo explícitamente. Desacopla los calendarios de los equipos y quita la presión de un flag-day coordinado.
- Migra un consumidor canario. Elige el servicio de menor riesgo, córrele el runbook completo de cinco fases y déjalo madurar en producción. Sus modos de fallo te enseñan lo que los otros veinte consumidores van a encontrar.
- Agrupa a los seguidores. Cuando el canario esté limpio, actualiza los consumidores restantes en una ventana de mantenimiento por equipo, no una por servicio.
Los tests de contrato entre productor y consumidores atrapan las roturas sutiles (una forma de error cambiada, un enum reordenado) que los tests unitarios de cualquiera de los dos repos no pueden ver.
Avanzado: Automatizar las Actualizaciones
Configuración de Renovate con auto-merge seguro
{
"extends": ["config:base"],
"schedule": ["before 6am on Monday"],
"packageRules": [
{
"updateTypes": ["patch", "minor"],
"automerge": true,
"groupName": "patch and minor updates"
},
{
"updateTypes": ["major"],
"automerge": false,
"labels": ["major-upgrade", "needs-review"],
"dependencyDashboardApproval": true
}
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"],
"schedule": ["at any time"]
}
}
Script de reversión para actualizaciones npm
#!/bin/bash
set -euo pipefail
BRANCH=$(git rev-parse --abbrev-ref HEAD)
TAG="pre-upgrade-$(date +%Y%m%d-%H%M%S)"
git tag "$TAG"
echo "Created safety tag: $TAG"
# Si la actualización falla, restaura los manifests desde el tag:
# git checkout "$TAG" -- package.json package-lock.json && npm ci
echo "To rollback: git checkout $TAG -- package.json package-lock.json && npm ci"
Actualizaciones Python pineadas con pip-tools
#!/bin/bash
set -euo pipefail
# requirements.in guarda restricciones sueltas; requirements.txt es el output bloqueado
echo "package-name==2.0.0" >> requirements.in
pip-compile --upgrade-package package-name --output-file requirements.txt requirements.in
pip install -r requirements.txt --dry-run # detecta dependencias transitivas en conflicto
pytest tests/ -x
git add requirements.in requirements.txt
git commit -m "deps: upgrade package-name to 2.0.0"
Reporte de auditoría y SBOM para compliance
#!/bin/bash
set -euo pipefail
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
npm audit --json > audit-report.json
node -e "
const audit = require('./audit-report.json');
const vulns = audit.vulnerabilities || {};
const high = Object.entries(vulns).filter(([,v]) => v.severity === 'high' || v.severity === 'critical');
if (high.length > 0) {
high.forEach(([name, info]) => console.log(name + ': ' + info.severity));
process.exit(1);
}
"
Cuándo NO Actualizar
Un runbook también es permiso para esperar. Razones legítimas para aplazar:
- Un freeze de release está a días. Actualizar justo antes de un congelamiento de código mete el cambio más arriesgado en la ventana menos probada.
- La dependencia está programada para eliminarse. Si migras fuera de la librería el próximo trimestre, parchearla hoy es trabajo que vas a tirar; documenta la exposición y monitorea en su lugar.
- La actualización fuerza una mayor. Un paquete cuya nueva versión exige un runtime o framework más nuevo convierte una tarea pequeña en un proyecto. Agenda el proyecto; no lo contrabandees dentro del mantenimiento.
- Nada está roto y nada está expuesto. Una dependencia fijada en una versión vieja pero parcheada, sin CVEs y sin funciones necesarias, puede esperar al próximo ciclo. Aplazar a propósito es una decisión; olvidar no.
Sea cual sea la razón, escríbela junto a la dependencia. Un “ya lo haremos” sin documentar es como las dependencias terminan tres majors atrás.
Métricas que Vale la Pena Seguir
No puedes mejorar un proceso que no mides. Cuatro números te dicen si las actualizaciones funcionan:
- Cadencia de actualización: tiempo entre refrescos de dependencias. Si los parches se acumulan meses, las actualizaciones “rutinarias” se vuelven saltos arriesgados.
- Tiempo medio hasta el parche (MTTP): cuánto tarda un CVE publicado en llegar a producción. Para CVEs críticos con exploits activos, mídelo en días, no en semanas.
- Frescura: porcentaje de dependencias directas a una major de la última versión. Observa la tendencia, no el número absoluto.
- Tasa de actualizaciones fallidas: con qué frecuencia un bump termina revertido. Una tasa creciente suele significar que las fases 1 y 3 se están saltando, no que actualizar sea peligroso en sí.
Regístralas en lo que ya uses para métricas de ingeniería; una hoja de cálculo funciona bien. El punto es detectar la deriva antes de que un CVE fuerce la conversación.
Lectura Adicional
- Runbook Template — la estructura de runbook genérica que esta especializa.
- Changelog Template — documenta lo que cada actualización cambia para tus propios consumidores.
- On-Call Runbook Template — para cuando la actualización sale mal a las 3 a.m.
Preguntas frecuentes
¿Con qué frecuencia debo actualizar las dependencias?
Versiones de parche semanalmente o de forma automática con Dependabot/Renovate. Menores, mensualmente. Mayores, trimestralmente o antes cuando bloqueen fixes de seguridad o soporte LTS. La peor cadencia es "cuando algo se rompe"; convierte cada actualización en una emergencia.
¿Qué pasa si una dependencia transitiva tiene un CVE?
No tienes que esperar a que la dependencia directa publique un fix. Usa npm audit fix, overrides en package.json, pip-audit para Python, o dependency management de Maven para forzar una versión transitiva parcheada.
¿Debo incluir los lock files en el control de versiones?
Siempre. Los lock files te dan builds reproducibles entre máquinas y CI, y convierten las actualizaciones de dependencias en diffs revisables. npm ci, pip install -r requirements.txt y comandos similares asumen que el lock file está en el repositorio.
¿Cómo manejo una dependencia sin mantenimiento?
Evalúa alternativas mantenidas primero: una migración suele ser más barata que cargar una dependencia muerta. Si nada encaja, haz un fork (si la licencia lo permite) o inclúyela directamente en tu repositorio (vendoring), y agrégala al calendario de deprecación con fecha límite de reemplazo. Escanea la última versión publicada para conocer al menos tu exposición.
¿Cuál es la diferencia entre ~ y ^ en semver?
^1.2.3 permite cualquier versión que no cambie el dígito no-cero más a la izquierda (o sea 1.x.x), mientras ~1.2.3 permite solo cambios de parche (1.2.x). Caret vale para la mayoría de dependencias; usa tilde o pines exactos para paquetes de cuya estabilidad dependes.
¿Cuándo necesita un monorepo tooling especial?
Nx, Turborepo o Lerna justifican su complejidad cuando los paquetes comparten dependencias y las actualizaciones deben aterrizar juntas. Añaden hoisting de workspace, caching y comandos de actualización en lote. Para un puñado de paquetes, un solo package.json y el tooling normal es más simple e igual de seguro.
¿Cómo pruebo una actualización cuando la cobertura de tests es escasa?
Apóyate en el entorno, no en la suite. Despliega primero en staging y corre una pasada de humo con script sobre las rutas críticas del usuario: login, las tres llamadas API principales, el batch job que importa. Si puedes, haz un despliegue canario a una porción pequeña del tráfico de producción y observa las tasas de error unas horas antes del rollout completo. Y captura la línea base antes de la actualización: no puedes detectar una regresión de latencia del 2% sin saber el número de la semana pasada.
¿Debo actualizar a una versión mayor nueva de inmediato?
Normalmente no. A menos que parchee un CVE al que estás expuesto, dale a una major recién salida unas semanas: la .0 colecciona los reportes de bugs del mundo real, y la .1 o .2 que sigue es donde la mayoría de los equipos debería aterrizar. Usa el tiempo de espera para vigilar el issue tracker por reportes específicos de la actualización y para que @types, plugins y wrappers se pongan al día.
Recursos Relacionados
Plantilla de Runbook
Una plantilla reutilizable para runbooks operacionales: respuesta a incidentes, procedimientos de deployment y tareas rutinarias.
DocPlantilla de Changelog
Plantilla de changelog estructurada siguiendo las convenciones de Keep a Changelog para registrar versiones del proyecto.
DocPlantilla de Runbook de Guardia
Una plantilla que documenta alertas comunes y procedimientos de respuesta paso a paso para ingenieros de guardia.
DocPlantilla de Respuesta a Incidentes de Seguridad
Plantilla de respuesta a incidentes de seguridad: detección, clasificación, contención, erradicación, recuperación, comunicación y revisión post-incidente.
DocPlantilla de Reporte de Bug (Markdown + Ejemplos)
Plantilla estructurada de reporte de bugs para que los equipos reproduzcan, clasifiquen y resuelvan defectos más rápido, con ejemplo completo.
DocPlantilla de Planificación de Capacidad
Una plantilla reutilizable para planificar capacidad del sistema, estimar crecimiento y prevenir cuellos de botella de rendimiento antes de que ocurran.