Al cerrar el módulo 3 quedó una pregunta sin responder: cuántos paquetes entran de verdad en reservalia/api:a3f9c21, quién los mantiene y qué versión exacta se usó. Es el segundo de los cuatro frentes pendientes, y el más silencioso de todos: nadie abre un ticket que diga "las dependencias están mal", pero un lunes cualquiera el CI se pone rojo sin que nadie haya tocado el código, o una actualización menor cambia el comportamiento de una función y las reservas de los negocios con horario partido empiezan a calcularse mal. Esta lección convierte ese punto ciego en algo gestionado: qué garantiza exactamente un lockfile y qué no, cómo se eligen los rangos de versión, cómo se automatizan las actualizaciones sin ahogar al equipo en pull requests, cómo se auditan las dependencias transitivas, cómo se cachean en el pipeline sin romper la reproducibilidad, y cuándo una organización acaba necesitando su propio registro. Lo que no trataremos aquí son las vulnerabilidades ni los ataques a la cadena de suministro —paquetes maliciosos, npm audit como puerta de calidad, firma de artefactos—: todo eso es la lección 04-03, y se apoya en lo que construyamos hoy.
Contenido
- El inventario real de Reservalia
- Qué garantiza exactamente un lockfile
npm cifrente anpm install, y qué pasa si alguien edita el lockfile a mano- Lockfiles en otros ecosistemas
- Rangos de versión y el compromiso entre parches y reproducibilidad
- Dependencias transitivas: verlas, entenderlas y forzarlas
- Actualización automatizada: el
dependabot.ymlde Reservalia - Estrategia de actualización: qué se fusiona solo y qué se revisa
- Caché de dependencias en el pipeline y su invalidación
- Registros privados y réplicas
- Elegir una librería y jubilar las abandonadas
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- El inventario real de Reservalia
Marta pide el dato y Diego lo saca en treinta segundos:
npm ls --all --workspace apps/api | wc -l # 1 · el árbol completo
npm ls --omit=dev --all --parseable | sort -u | wc -l # 2 · solo lo que llega a producciónnpm ls --alldespliega el árbol entero, incluidas las dependencias de las dependencias. Sin--allsolo verías el primer nivel, que es la parte que ya conocías.--omit=devdescarta las herramientas de desarrollo, que no viajan dentro de la imagen.--parseableimprime rutas en lugar de un árbol, así que se pueden ordenar y contar sin duplicados.
El resultado sorprende a todo el mundo la primera vez:
apps/api |
apps/web |
Total del monorepo | |
|---|---|---|---|
| Dependencias directas de producción | 14 | 9 | 23 |
| Dependencias de desarrollo | 21 | 18 | 39 |
| Paquetes totales en el árbol | 612 | 891 | 1.147 |
| Paquetes que acaban dentro de la imagen | 418 | — | 418 |
| Mantenedores distintos implicados | ~330 | — | — |
Veintitrés decisiones conscientes se convierten en más de mil paquetes escritos por gente que el equipo no conoce, publicados en horarios que no controla y ejecutados con los mismos permisos que el código de Reservalia. El riesgo no es hipotético: en el módulo 3 vimos que el change failure rate se atascó en el 6,5 %, y una de las causas identificadas fue exactamente esto —una actualización menor de una librería de fechas que cambió el comportamiento de parseISO con zonas horarias y rompió calcularHuecos para los negocios con horario partido, sin que ninguna prueba lo detectara porque ninguna cubría ese caso—. La regla que ordena el resto de la lección: una dependencia no es código gratis, es código que has adoptado. Si falla, el cliente llama a Reservalia, no al mantenedor.
- Qué garantiza exactamente un lockfile
Un package.json declara intenciones ("express": "^4.19.2" significa "cualquier 4.x a partir de la 4.19.2"). Un package-lock.json registra hechos: la versión exacta de cada paquete del árbol, de dónde se descargó y su huella criptográfica.
{
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"engines": { "node": ">= 0.10.0" }
}
}Tres campos y tres garantías distintas. version fija qué versión se instala, así que dos instalaciones separadas por seis meses obtienen el mismo árbol. resolved fija de dónde viene, lo que importa cuando hay varios registros configurados. Y integrity es un hash del contenido del paquete: si lo descargado no coincide, npm aborta la instalación. Es la diferencia entre "pedí la versión 4.19.2" y "recibí exactamente estos bytes". Ahora la parte que casi nadie explica: lo que un lockfile NO garantiza.
| Sí garantiza | No garantiza |
|---|---|
| La misma versión de cada paquete | Que el paquete siga estando disponible (puede retirarse del registro) |
| El mismo contenido, verificado por hash | Que los scripts de instalación hagan lo mismo (compilan según la máquina) |
| El mismo árbol de resolución | La misma versión de Node, del sistema o de las librerías del SO |
| Reproducibilidad de las dependencias | Reproducibilidad de la build completa |
Por eso el lockfile es condición necesaria pero no suficiente, y por eso Reservalia lo acompaña de un .nvmrc que fija la versión de Node y de una imagen base clavada (node:20.11.0-bookworm-slim). Las tres piezas juntas —lockfile, .nvmrc, imagen base— son lo que hace que a3f9c21 signifique algo concreto.
npm ci frente a npm install, y qué pasa si alguien edita el lockfile a mano
npm ci frente a npm install, y qué pasa si alguien edita el lockfile a manonpm install |
npm ci |
|
|---|---|---|
| Lee el lockfile | Sí, pero puede modificarlo | Sí, y nunca lo modifica |
Si package.json y lockfile no concuerdan |
Resuelve y reescribe el lockfile | Falla con error |
node_modules/ previo |
Lo reutiliza y parchea | Lo borra y reinstala |
| Velocidad en CI | Menor y variable | Mayor y constante |
| Dónde usarlo | En tu portátil, al añadir un paquete | En el pipeline, siempre |
La fila decisiva es la segunda. npm ci exige que el lockfile sea coherente con el package.json, y ese comportamiento es justo lo que quieres en CI: si Diego añade "zod": "^3.23.0" al package.json y olvida subir el lockfile actualizado, el job calidad falla en veinte segundos con un mensaje claro, en vez de instalar en silencio una versión que nadie ha registrado. La verificación es gratis y elimina toda una categoría de "en mi máquina funciona". Editar el lockfile a mano es una tentación que aparece al resolver un conflicto de merge, y merece una advertencia explícita. Un package-lock.json no es una lista, es un grafo resuelto: cambiar una versión a mano no recalcula las dependencias de esa dependencia, así que puedes dejar el árbol en un estado que npm nunca habría producido —una librería que declara necesitar >=5.0.0 conviviendo con la 4.8—. El resultado son fallos en tiempo de ejecución imposibles de reproducir. La forma correcta de resolver un conflicto en el lockfile es regenerarlo:
git checkout --theirs package-lock.json # 1 · quedarse con una versión cualquiera
npm install # 2 · dejar que npm recalcule el grafo
git add package-lock.json- El contenido concreto da igual, porque se va a reescribir entero.
npm install—aquí sí, noci— recalcula el árbol a partir de lospackage.jsonya fusionados. Después conviene mirar el diff resultante: si aparecen cincuenta cambios que no esperabas, es que alguien había fijado algo a mano.
- Lockfiles en otros ecosistemas
El concepto es universal aunque los nombres cambien. Si trabajas fuera de Node, esta tabla traduce todo lo anterior:
| Ecosistema | Fichero de intenciones | Lockfile | Comando reproducible en CI |
|---|---|---|---|
| npm | package.json |
package-lock.json |
npm ci |
| Python + Poetry | pyproject.toml |
poetry.lock |
poetry install --sync |
| Python + pip | requirements.in |
requirements.txt con hashes |
pip install --require-hashes -r requirements.txt |
| Maven | pom.xml |
No hay: versiones exactas en el POM | mvn -B verify con versiones fijas |
| Gradle | build.gradle |
gradle.lockfile (hay que activarlo) |
gradle build tras --write-locks |
| Go | go.mod |
go.sum (hashes) |
go build -mod=readonly |
| Rust | Cargo.toml |
Cargo.lock |
cargo build --locked |
| PHP | composer.json |
composer.lock |
composer install --no-dev |
Dos observaciones útiles. Maven es la excepción: no tiene lockfile propio, y la reproducibilidad depende de que no uses versiones LATEST ni RELEASE y de que los bloques dependencyManagement estén completos; por eso los equipos serios de Java añaden un plugin que fija el árbol. Y el patrón de la columna derecha es siempre el mismo: existe un modo "instala exactamente lo que dice el lockfile y falla si no cuadra". Ese es el comando que va en el pipeline; cualquier otro es npm install disfrazado.
- Rangos de versión y el compromiso entre parches y reproducibilidad
El versionado semántico (semver) da a MAYOR.MENOR.PARCHE un significado contractual: mayor = cambio incompatible, menor = funcionalidad nueva compatible, parche = corrección compatible.
| Rango | Significado | Acepta | Cuándo usarlo |
|---|---|---|---|
^4.19.2 |
Compatible con 4.19.2 | 4.19.3, 4.20.0 · no 5.0.0 | Por defecto en una aplicación |
~4.19.2 |
Solo parches | 4.19.3 · no 4.20.0 | Dependencias sensibles o inestables |
4.19.2 |
Exacta | Solo esa | Herramientas cuyo comportamiento debe ser idéntico |
* o latest |
Cualquiera | Todo | Nunca |
El punto que confunde a mucha gente: con un lockfile, el rango casi no importa para la reproducibilidad. El lockfile fija la versión instalada, así que ^4.19.2 y 4.19.2 producen exactamente el mismo árbol hoy. Lo que el rango decide es qué se acepta cuando el lockfile se regenera: al ejecutar npm update, al añadir un paquete o cuando Dependabot propone una subida. Y ahí está el compromiso real. Un rango amplio (^) hace que las correcciones lleguen sin fricción, pero apuesta a que el mantenedor respeta semver, cosa que no siempre ocurre —el incidente de parseISO del apartado 1 fue justamente una versión menor con un cambio de comportamiento—. Un rango estrecho elimina las sorpresas pero convierte cada corrección en trabajo manual, y un proyecto con 23 dependencias directas fijadas a versión exacta se queda obsoleto en seis meses.
La postura de Reservalia, recomendable para la mayoría: ^ por defecto en el package.json, lockfile siempre comprometido en git, y actualizaciones que entran por pull request revisado con CI en verde, nunca por resolución automática en el momento de instalar. Así el rango describe qué es aceptable y el lockfile describe qué se usa, y pasar de uno a otro es siempre un commit visible.
- Dependencias transitivas: verlas, entenderlas y forzarlas
Una dependencia transitiva es la que no pediste: entra porque una de tus dependencias la necesita. De los 1.147 paquetes de Reservalia, 1.124 son transitivos. No aparecen en el package.json, no las eligió nadie y son la inmensa mayoría del código que se ejecuta.
npm ls date-fns # 1 · quién trae este paquete y en qué versión
npm why date-fns # 2 · la cadena de razones, en npm 9 y posteriores
npm outdated # 3 · qué está por detrás y cuántonpm ls <paquete>imprime el camino desde tus dependencias directas hasta él. Es la herramienta que responde "¿y esto de dónde ha salido?", que es la primera pregunta de casi cualquier investigación.npm whyda lo mismo en formato de explicación. Si aparecen dos versiones distintas del mismo paquete, npm ha instalado ambas —una en la raíz y otra anidada—, y eso causa errores desconcertantes cuando el paquete mantiene estado global.npm outdatedcompara lo instalado con lo publicado y separa "lo que permite tu rango" de "lo último que existe". Es el mejor termómetro de deuda de dependencias en diez segundos.
Cuando hay que forzar una versión transitiva —típicamente porque la 2.3.1 tiene un fallo y necesitas la 2.3.2, pero quien la trae aún no ha actualizado—, npm ofrece overrides:
La primera forma reemplaza esa versión en todo el árbol, vengan de donde vengan; la segunda limita el reemplazo al subárbol de un paquete concreto, que es más seguro cuando no sabes si los demás consumidores toleran el cambio. Un override es deuda técnica declarada y hay que tratarlo como tal: estás afirmando que una versión distinta de la que el mantenedor probó funciona igual, y nadie lo ha verificado más allá de tus pruebas. Reservalia se impone dos reglas: cada entrada lleva un comentario con la razón y el enlace al issue original, y la lista se revisa una vez al trimestre para quitar lo que ya sobra. El equivalente en otros ecosistemas es resolutions (Yarn), dependencyManagement (Maven) o replace (Go).
- Actualización automatizada: el
dependabot.yml de Reservalia
dependabot.yml de ReservaliaCon 1.147 paquetes, mantenerse al día a mano es imposible; no hacerlo es acumular una migración gigante para dentro de dos años. La solución es un bot que abre pull requests de actualización: Dependabot (integrado en GitHub) o Renovate (más configurable y disponible en cualquier plataforma). Reservalia elige Dependabot por estar ya en casa:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: "/" # 1 · un solo lockfile: basta la raíz
schedule:
interval: weekly
day: tuesday # 2 · martes, no lunes ni viernes
time: "06:00"
timezone: Europe/Madrid
open-pull-requests-limit: 5 # 3 · techo de PR abiertos a la vez
groups: # 4 · agrupar para no ahogar al equipo
dev-menores:
dependency-type: development
update-types: [minor, patch]
prod-parches:
dependency-type: production
update-types: [patch]
ignore:
- dependency-name: "typescript" # 5 · las mayores, a mano
update-types: [version-update:semver-major]
labels: [dependencias]
commit-message: { prefix: "chore(deps)" }
- package-ecosystem: github-actions # 6 · las acciones también son dependencias
directory: "/"
schedule: { interval: monthly }
groups:
acciones: { patterns: ["*"] }directory: "/"apunta a donde está elpackage-lock.json. Como Reservalia usa workspaces npm con un único lockfile en la raíz, una sola entrada cubreapps/api,apps/webypackages/tipos-compartidos. Con lockfiles separados haría falta una entrada por carpeta.- Martes a primera hora. El lunes el equipo vacía la bandeja del fin de semana y el viernes deja PR abiertos hasta el lunes. Es un detalle menor que cambia mucho la tasa de revisión real.
open-pull-requests-limites la protección contra el efecto avalancha. Sin él, la primera ejecución sobre un repositorio desatendido abre cuarenta pull requests, el equipo se abruma y los ignora todos: el bot pasa de ayudar a ser ruido de fondo.groupses la opción más valiosa del fichero: en vez de un PR por paquete, agrupa varios en uno. Reservalia junta todas las menores y parches de herramientas de desarrollo en un PR semanal, y los parches de producción en otro. De veinte PR se pasa a dos o tres.ignorepara las mayores de TypeScript: una subida de mayor detscpuede sacar cientos de errores nuevos, y eso es trabajo planificado, no un PR de martes.- Las acciones de GitHub son dependencias como cualquier otra, y suelen olvidarse.
uses: actions/checkout@v4es código de terceros que se ejecuta con acceso al repositorio; cómo se fijan de forma segura es asunto de la 04-03.
Un detalle que decide si todo esto funciona: una actualización automática solo es segura si el CI del módulo 2 es de fiar. El bot no lee el changelog ni entiende tu negocio; lo único que separa "actualización aplicada sin drama" de "regresión en producción" es que las pruebas cubran lo que importa. Si la suite es débil, automatizar las dependencias no reduce el riesgo: lo acelera. Por eso este frente viene después del módulo 2 y no antes.
- Estrategia de actualización: qué se fusiona solo y qué se revisa
Un bot que abre PR sin una política sobre qué hacer con ellos solo mueve el trabajo de sitio. Esta es la tabla que Reservalia escribe en el README.md:
| Tipo de actualización | Acción | Quién | Justificación |
|---|---|---|---|
| Parche de dependencia de desarrollo | Auto-merge con CI verde | Nadie | Riesgo casi nulo: no llega a producción |
| Menor de dependencia de desarrollo | Auto-merge con CI verde | Nadie | Igual; van agrupadas en el PR semanal |
| Parche de dependencia de producción | Revisión rápida y merge | Turno semanal | Suele ser una corrección; el CI cubre el resto |
| Menor de producción | Revisión real: leer el changelog | Turno semanal | Aquí vive el incidente de parseISO |
| Mayor de cualquier tipo | Ticket planificado | Marta asigna | Cambios incompatibles: es trabajo |
| Actualización de seguridad | Prioridad máxima | Nuria | Ver 04-03 |
Tres decisiones merecen explicación. El auto-merge no es dejadez: se apoya en que el pipeline bloquea el merge si algo falla —la puerta de calidad de la 04-01— y en que esas dependencias no viajan dentro de la imagen. Se activa con una línea:
Las menores de producción llevan revisión humana precisamente por el incidente del apartado 1. Semver es una promesa, no un mecanismo: un cambio de comportamiento sin cambio de API es formalmente "menor" y funcionalmente una bomba. Leer el changelog de un paquete cuesta dos minutos y es la única defensa real. Hay un turno semanal con nombre. Sin una persona asignada, las actualizaciones son responsabilidad de todos, que es lo mismo que de nadie. Reservalia rota el turno cada semana con un tiempo acotado —media hora los martes—; lo que no cabe se queda para la siguiente, pero nunca se acumula en silencio.
- Caché de dependencias en el pipeline y su invalidación
Descargar 1.147 paquetes en cada job es la parte más lenta y menos interesante del CI. La caché la elimina, pero introduce un riesgo evidente: si sirve una caché equivocada, el pipeline verifica algo que no es tu código. La solución es que la clave de la caché sea un hash del lockfile.
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm # 1 · atajo: ya usa el hash del lockfile
- run: npm ci
# Equivalente explícito, para entender qué hace el atajo:
- uses: actions/cache@v4
with:
path: ~/.npm # 2
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }} # 3
restore-keys: npm-${{ runner.os }}- # 4cache: npmensetup-nodehace exactamente lo de abajo. Es lo que Reservalia usa desde la 02-02; aquí vemos por qué funciona.- Se cachea
~/.npm, nonode_modules/. La primera es la caché de descargas de npm, independiente de la plataforma; la segunda contiene binarios compilados para un sistema concreto y enlaces internos, y restaurarla en otra máquina produce fallos difíciles de diagnosticar. Restaurar~/.npmy ejecutarnpm cies rápido y correcto. - La clave contiene el hash del lockfile. Si el lockfile cambia un solo byte, la clave cambia y la caché anterior no se usa: la invalidación es automática y no depende de que nadie se acuerde.
restore-keyses la red de seguridad: si no hay coincidencia exacta, se recupera la caché más reciente que empiece igual y npm solo descarga lo que falte. Así una actualización de dependencias no obliga a bajarlo todo desde cero.
La regla general, válida para cualquier caché del pipeline: la clave debe derivar del contenido de las entradas. Si la clave es fija (key: npm-cache), la caché nunca se invalida y acabas ejecutando pruebas contra dependencias antiguas —un verde falso, el riesgo transversal que desarrolla la 04-04—. Y una regla de higiene: la caché es un acelerador, nunca una fuente de verdad; el pipeline debe funcionar correctamente si se vacía entera, solo que más lento.
- Registros privados y réplicas
Reservalia instala directamente de registry.npmjs.org, y con 340 negocios de pago eso empieza a incomodar por tres razones concretas: si el registro público sufre una caída, no se puede desplegar —y responder a un incidente puede exigir desplegar—; no hay dónde publicar @reservalia/tipos-compartidos el día que deba compartirse con la aplicación móvil del módulo 5; y no existe ningún punto donde aplicar una política del tipo "esta librería no se usa en esta empresa".
| Opción | Qué aporta | Coste | Cuándo tiene sentido |
|---|---|---|---|
| Registro público directo | Simplicidad total | Cero | Equipos pequeños, sin paquetes propios |
| GitHub Packages | Paquetes privados con los permisos del repositorio | Muy bajo si ya usas GitHub | El siguiente paso natural de Reservalia |
| Artifactory / Nexus | Réplica del público + registro privado + políticas | Alto: es infraestructura que mantener | Varias decenas de desarrolladores o requisitos de auditoría |
| Réplica de solo lectura | Aísla de caídas y de paquetes retirados | Medio | Cuando el despliegue no puede depender de un tercero |
La configuración es un fichero, y conviene entender qué hace cada línea:
# .npmrc
@reservalia:registry=https://npm.pkg.github.com # 1 · solo el ámbito propio
//npm.pkg.github.com/:_authToken=${NPM_TOKEN} # 2 · desde variable, jamás literal
registry=https://registry.npmjs.org # 3 · el resto, del público- Solo los paquetes
@reservalia/*se buscan en el registro privado. Redirigir todo a un registro privado sin réplica configurada rompe la instalación de los otros 1.100 paquetes. - El token se lee de una variable de entorno. Un
.npmrccon un token literal comprometido en git es una filtración de credenciales, y es sorprendentemente frecuente. - El orden importa: la línea general va al final y actúa como valor por defecto.
Y un aviso sobre el conocido "paquete que desaparece": un mantenedor puede retirar una versión del registro público y romper builds que llevaban años funcionando. El integrity del lockfile no ayuda —verifica el contenido, no garantiza la disponibilidad—. Solo una réplica con copia local protege de eso, y es la razón principal por la que una organización mediana acaba montando una.
- Elegir una librería y jubilar las abandonadas
La mejor gestión de dependencias es no tener la dependencia. Antes de añadir una, Reservalia se hace cinco preguntas en este orden:
- ¿Puedo resolverlo con la biblioteca estándar? Node 20 trae
fetch, un ejecutor de pruebas,crypto.randomUUID()y bastante más. Muchas dependencias históricas hoy sobran. - ¿Cuánto código estoy adoptando? Una utilidad de veinte líneas copiada y con pruebas propias es preferible a un paquete que arrastra otros treinta.
- ¿Está viva? Commits recientes, issues que se responden, más de un mantenedor, versión mayor estable.
- ¿Cuánto costaría salir? Una librería encapsulada tras un módulo propio se sustituye en un día; una cuyos tipos aparecen en doscientos ficheros es un matrimonio.
- ¿Qué licencia tiene? Suele mirarse tarde, y es un problema legal, no técnico.
En el otro extremo están las dependencias abandonadas, con síntomas claros: sin publicaciones en dos años, issues sin respuesta, un aviso de deprecación al instalar o incompatibilidad con las versiones nuevas del lenguaje. La reacción correcta no es la urgencia —una librería estable y sin cambios puede estar sencillamente terminada— sino el registro: anotarla en una lista de riesgos, encapsularla tras una interfaz propia para poder cambiarla sin tocar doscientos ficheros, y planificar la sustitución cuando bloquee algo. Lo que no hay que hacer es descubrirlo el día que un fallo de seguridad obliga a actualizar y resulta que no hay versión a la que actualizar.
Errores Comunes y Consejos
Error 1: no comprometer el lockfile en git. Sin él, cada instalación resuelve por su cuenta y "en mi máquina funciona" vuelve a ser un argumento válido. Error 2: usar npm install en el pipeline, que puede reescribir el lockfile dentro del runner y hacer que el CI verifique un árbol distinto del que revisaste.
Error 3: editar el lockfile a mano para resolver un conflicto, dejando un grafo que npm nunca habría producido; regenéralo. Error 4: fijar todo a versión exacta "por seguridad", que congela el proyecto y convierte cada corrección en trabajo manual.
Error 5: activar Dependabot sin groups ni límite de PR. Cuarenta pull requests el primer martes, y el equipo aprende a ignorar la etiqueta dependencias para siempre. Error 6: automatizar actualizaciones con una suite de pruebas débil: no reduces el riesgo, lo aceleras. Error 7: cachear node_modules/ en lugar de ~/.npm, o usar una clave de caché fija que nunca se invalida; ambas cosas producen verdes falsos. Consejo 1: mira el árbol una vez al trimestre. npm ls --all y npm outdated en diez minutos dan una idea muy clara de por dónde llegará el próximo susto. Consejo 2: encapsula las dependencias importantes tras un módulo propio, para que sustituirlas sea trabajo de un día. Consejo 3: trata los overrides como deuda, con comentario, razón y revisión trimestral.
Ejercicios
Ejercicio 1
El CI de un equipo se pone rojo un lunes por la mañana sin que nadie haya tocado el código: el último commit es del viernes y entonces estaba verde. En el pipeline usan npm install. Explica con detalle qué ha ocurrido, por qué el package-lock.json no lo impidió y qué dos cambios lo evitan definitivamente.
Ejercicio 2
Diseña la política de actualización de dependencias para un equipo de 4 personas con una suite que cubre el 45 % del código y ninguna prueba de integración. Indica qué cambiarías respecto a la tabla del apartado 8 y por qué, y qué harías primero.
Ejercicio 3
Reservalia necesita la versión 7.6.2 de semver porque la 7.5.4 tiene un fallo, pero la trae algun-linter como transitiva y su mantenedor no ha publicado aún la corrección. Escribe la solución, explica sus riesgos y di cómo evitarías que ese apaño se quede diez años en el repositorio.
Soluciones
Solución 1. Lo ocurrido: npm install no está obligado a respetar el lockfile. Si el package.json declara rangos con ^ y durante el fin de semana se publicaron versiones nuevas, npm install puede resolverlas e instalar un árbol distinto del registrado, reescribiendo el lockfile dentro del runner —donde nadie ve el diff—. Basta con que una dependencia, directa o transitiva, publicara una versión menor con un cambio de comportamiento para que la suite falle. El lockfile no lo impidió porque estaba, pero no se estaba usando como norma: describía un árbol que el comando no tenía obligación de reproducir. Es exactamente el escenario del incidente de parseISO. Los dos cambios: (1) sustituir npm install por npm ci en todos los jobs, que instala el árbol del lockfile sin desviarse y falla si el lockfile no concuerda con el package.json; y (2) canalizar las actualizaciones por pull request con Dependabot, de modo que cada cambio del árbol sea un commit revisable, con su propio CI y con el diff del lockfile delante. Un tercer cambio complementario, más barato de lo que parece: fijar también .nvmrc y la imagen base, porque un cambio de versión de Node produce el mismo síntoma —"rojo sin tocar nada"— y se diagnostica igual de mal.
Solución 2. El dato que gobierna la respuesta es la calidad de la suite: con un 45 % de cobertura y sin pruebas de integración, el CI no es una red de seguridad suficiente, y la política del apartado 8 —que se apoya precisamente en esa red— no se puede copiar tal cual. Cambios respecto a la tabla: eliminar el auto-merge de producción por completo, incluidos los parches; mantener el auto-merge solo para dependencias de desarrollo, cuyo fallo se manifiesta en el propio pipeline y no en producción; agrupar todo en un único PR semanal para que la revisión quepa en el tiempo real de un equipo de cuatro; y bajar la frecuencia a quincenal si el equipo no llega, asumiendo el retraso de forma consciente en lugar de acumular PR ignorados.
Qué haría primero: no tocar la configuración del bot, sino la suite de pruebas. Concretamente, escribir pruebas de integración sobre los tres o cuatro caminos críticos de negocio, que es lo que convierte un CI decorativo en una puerta. Automatizar dependencias con una red agujereada es sustituir un riesgo lento y visible por otro rápido e invisible. Mientras tanto, una medida barata y muy eficaz: activar Dependabot solo para alertas de seguridad, que son las que no se pueden posponer, y dejar las de mantenimiento para cuando exista la red.
Solución 3. La solución es un override en el package.json de la raíz:
Se usa la forma anidada y no la global a propósito: limita el reemplazo al subárbol de algun-linter, de modo que si otro paquete depende de una semver antigua por una razón legítima, no se ve afectado. La forma global sería más cómoda y más arriesgada. Riesgos: se está ejecutando algun-linter con una versión de semver que su mantenedor no ha probado; si la 7.6.2 cambió algún comportamiento sutil, el fallo aparecerá dentro de una dependencia, que es el peor sitio para diagnosticarlo. Además el override es silencioso: no vuelve a fallar cuando deja de ser necesario, simplemente se queda ahí fijando una versión cada vez más antigua y bloqueando futuras actualizaciones de todo ese subárbol.
Cómo evitar que se fosilice, con tres medidas de coste casi nulo: un comentario junto a la entrada con la fecha, la razón y el enlace al issue abierto en algun-linter; una revisión trimestral de la lista completa de overrides dentro del turno de dependencias del apartado 8; y, la más eficaz, suscribirse al issue, de modo que cuando el mantenedor publique la corrección llegue un aviso y el apaño se retire el mismo día. Un override sin fecha de caducidad es indistinguible de una decisión permanente que nadie tomó.
Conclusión
El punto ciego ha dejado de serlo. Reservalia sabe que dentro de reservalia/api:a3f9c21 viajan 418 paquetes de unos 330 mantenedores, y sabe verlos, fijarlos y actualizarlos. Tiene claro qué garantiza un lockfile —versión, origen y hash— y qué no —disponibilidad, entorno, build completa—, y por eso lo acompaña de .nvmrc y de una imagen base clavada. Usa npm ci en el pipeline, con lo que un lockfile incoherente falla en veinte segundos en lugar de instalar en silencio algo que nadie revisó. Sabe que un lockfile se regenera y nunca se edita a mano, y conoce el equivalente del patrón en Poetry, Gradle, Go, Cargo y Composer. Mantiene los rangos en ^ para que las correcciones puedan llegar, apoyándose en que el lockfile decide lo que de verdad se instala. Y ve sus 1.124 dependencias transitivas con npm ls y npm why, usando overrides con comentario y fecha de revisión solo cuando no queda otra. Sobre todo, las actualizaciones han dejado de ser un evento traumático anual para convertirse en un flujo: un .github/dependabot.yml con agrupación, límite de PR y calendario razonable; una política escrita que separa lo que se fusiona solo de lo que exige leer un changelog; un turno semanal con nombre y media hora acotada; y una caché cuya clave es el hash del lockfile, de modo que se invalida sola y nunca produce un verde falso. El incidente de parseISO que contaminaba el change failure rate tiene ahora dos defensas: revisión humana para las menores de producción y una prueba que cubre el caso que se rompió.
Queda un flanco por cubrir, y es el que convierte todo lo anterior en un problema de seguridad y no solo de mantenimiento. Esas 1.147 dependencias se descargan de un registro público, se ejecutan con los permisos del pipeline y acaban dentro de una imagen que se despliega en producción; y el propio pipeline, mientras tanto, guarda credenciales sobre AWS, ejecuta acciones de terceros fijadas por etiqueta y publica los artefactos en los que todo el mundo confía. La lección Seguridad en CI/CD aborda las dos mitades del problema —la seguridad del software que pasa por el pipeline y la seguridad del pipeline mismo—, añade a Reservalia un job seguridad con una política de severidades que el equipo pueda sostener, y termina donde acaba esta cadena de confianza: el inventario firmado de lo que contiene cada artefacto.
Curso de CI/CD: Integración y Despliegue Continuo
Módulo 1: Introducción a CI/CD
- Conceptos Básicos de CI/CD
- Beneficios de CI/CD
- Herramientas Populares de CI/CD
- El Proyecto del Curso: la Aplicación que Vamos a Automatizar
- Métricas DORA: Cómo se Mide la Entrega de Software
Módulo 2: Integración Continua (CI)
- Introducción a la Integración Continua
- Configuración de un Entorno de CI
- Automatización de la Construcción
- Pruebas Automatizadas
- Calidad de Código y Análisis Estático
- Artefactos, Versionado y Promoción
- Integración con Control de Versiones
Módulo 3: Despliegue Continuo (CD)
- Introducción al Despliegue Continuo
- Automatización del Despliegue
- Infraestructura como Código y Entornos Reproducibles
- Estrategias de Despliegue
- Feature Flags, Rollback y Recuperación ante Fallos
- Monitoreo y Retroalimentación
Módulo 4: Prácticas Avanzadas de CI/CD
- Pipelines de CI/CD
- Gestión de Dependencias
- Seguridad en CI/CD
- Escalabilidad y Rendimiento
- Pipeline as Code: Plantillas, Reutilización y Pruebas del Pipeline
- Bases de Datos en el Pipeline: Migraciones Seguras
Módulo 5: Implementación de CI/CD en Proyectos Reales
- Caso de Estudio: Proyecto Web
- Caso de Estudio: Aplicación Móvil
- Caso de Estudio: Microservicios
- Caso de Estudio: Modernizar un Proyecto Legacy
Módulo 6: Herramientas y Tecnologías
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker y Kubernetes
- GitHub Actions a Fondo
- Comparativa y Criterios para Elegir Herramienta
Módulo 7: Ejercicios Prácticos
- Ejercicio 1: Configuración de un Pipeline Básico
- Ejercicio 2: Integración de Pruebas Automatizadas
- Ejercicio 3: Despliegue en un Entorno de Producción
- Ejercicio 4: Monitoreo y Retroalimentación
- Ejercicio 5: Endurecer el Pipeline con Seguridad y Secretos
- Proyecto Final: Pipeline Completo de Extremo a Extremo
