En la lección anterior instalaste dotenv y npm escribió ^17.2.1 en el manifiesto, no 17.2.1. Ese acento circunflejo es una decisión con consecuencias enormes: significa que tu proyecto no está pidiendo una versión, está pidiendo un rango, y que la versión concreta que acabe instalada depende de qué haya publicado el autor de dotenv el día que alguien ejecute npm install.

Eso es una bomba de relojería o una comodidad extraordinaria, según lo entiendas. Sin package-lock.json, dos personas del mismo equipo pueden instalar el mismo package.json en días distintos y acabar con árboles de dependencias diferentes. Con él, la instalación es reproducible hasta el último byte.

Esta lección tiene dos mitades que se explican mutuamente: el sistema de versiones que promete compatibilidad y el fichero que hace que esa promesa no haga falta.

Contenido

  1. SemVer: MAYOR.MENOR.PARCHE y el contrato implícito
  2. Versiones de preproducción y su orden de precedencia
  3. La tabla de rangos, con la fila que casi nadie conoce
  4. Qué instala cada rango hoy y dentro de seis meses
  5. package-lock.json: qué contiene exactamente
  6. npm ci frente a npm install
  7. Conflictos del lock en git
  8. npm dedupe y --save-exact
  9. La actualización menor que rompe
  10. De 0.1.0 a 1.0.0: versionar Escena Viva

  1. SemVer: MAYOR.MENOR.PARCHE y el contrato implícito

El versionado semántico (semver.org) es una convención que convierte tres números en una promesa verificable:

    17  .   2   .   1
     │       │       │
     │       │       └── PARCHE: correcciones compatibles
     │       └────────── MENOR:  funcionalidad nueva, compatible
     └────────────────── MAYOR:  cambios que rompen compatibilidad

Las reglas de cuándo sube cada número:

Tipo de cambio Sube Ejemplo
Corregir un fallo sin cambiar la API PARCHE 17.2.1 → 17.2.2
Añadir una función u opción nueva MENOR 17.2.1 → 17.3.0
Marcar algo como obsoleto (sin quitarlo) MENOR 17.2.1 → 17.3.0
Quitar o renombrar algo público MAYOR 17.2.1 → 18.0.0
Cambiar el comportamiento de una función existente MAYOR 17.2.1 → 18.0.0
Subir la versión mínima de Node exigida MAYOR 17.2.1 → 18.0.0
Cambiar comentarios, README o pruebas internas Nada No se publica

Al subir un número, los de su derecha vuelven a cero: de 17.2.1 a menor es 17.3.0, y a mayor es 18.0.0.

El contrato implícito es la parte que se olvida. Publicar 17.3.0 en vez de 18.0.0 no es una preferencia estética: es afirmar ante miles de proyectos que pueden actualizar sin revisar su código. Y como el 90 % de esos proyectos tiene escrito ^17.2.1, esa versión menor entrará en sus instalaciones automáticamente. Si mentiste, sus compilaciones se rompen mañana sin que ellos hayan cambiado una línea.

Esa es la razón de que semver sea, antes que una regla técnica, una cuestión de responsabilidad. Y también la razón de que no puedas fiarte del todo de él: es una convención, no algo que el registro verifique. Lo que sí verifica es el fichero de bloqueo, y por eso las dos mitades de la lección van juntas.

  1. Versiones de preproducción y su orden de precedencia

Antes de una versión estable se publican versiones de prueba, con un guion:

1.0.0-alpha
1.0.0-alpha.1
1.0.0-beta.1
1.0.0-rc.1
1.0.0

Las reglas de precedencia, definidas por la especificación: una versión con etiqueta de preproducción es siempre menor que la misma sin ella (1.0.0-rc.1 < 1.0.0, porque es un ensayo de esa versión, no una posterior); los identificadores se comparan de izquierda a derecha; los numéricos como números y los alfanuméricos alfabéticamente; un número siempre es menor que un texto (1.0.0-1 < 1.0.0-alpha); y a igualdad de los anteriores, gana quien tenga más identificadores (1.0.0-alpha < 1.0.0-alpha.1).

Una serie completa ordenada:

1.0.0-alpha  <  1.0.0-alpha.1  <  1.0.0-alpha.beta  <  1.0.0-beta
             <  1.0.0-beta.2   <  1.0.0-beta.11     <  1.0.0-rc.1  <  1.0.0

Fíjate en beta.2 < beta.11: como el identificador es numérico se compara como número, no como texto. Si se comparara como texto, 11 iría antes que 2.

Lo más importante en el día a día: las versiones de preproducción no entran nunca en un rango normal. Si tu manifiesto dice ^1.0.0 y se publica 2.0.0-beta.1, npm no la instalará. Para recibirlas hay que pedirlas explícitamente (npm install paquete@beta) o escribir un rango que las incluya. Es una protección deliberada, y es correcta.

  1. La tabla de rangos, con la fila que casi nadie conoce

Esta es la tabla que hay que saberse de memoria. Supongamos que la última publicada es 1.9.2 y que existen también 2.0.0 y 1.2.9.

Rango en package.json Nombre Permite Instala hoy
1.2.3 Exacto Solo 1.2.3 1.2.3
~1.2.3 Tilde Parches: >=1.2.3 <1.3.0 1.2.9
^1.2.3 Circunflejo Menores y parches: >=1.2.3 <2.0.0 1.9.2
1.2.x / 1.2 Comodín parcial Igual que ~1.2.0 1.2.9
1.x / 1 Comodín parcial Igual que ^1.0.0 1.9.2
>=1.2.3 <2 Rango explícito Lo que dice 1.9.2
1.2.3 - 1.5.0 Guion Ambos extremos incluidos 1.5.0
* o "" Cualquiera Cualquier versión 2.0.0
latest Etiqueta La marcada como latest 2.0.0

La forma corta de recordar las dos importantes: ~ deja subir el último número; ^ deja subir el último que no sea cero por la izquierda.

La fila que casi nadie conoce: ^0.x.y

Aquí está el detalle que produce sorpresas desagradables. El circunflejo se define como "no cambies el primer número distinto de cero", y eso da un comportamiento distinto cuando la mayor es 0:

Rango Equivale a Comentario
^1.2.3 >=1.2.3 <2.0.0 El habitual
^0.2.3 >=0.2.3 <0.3.0 Solo parches: 0.2 actúa como si fuera la mayor
^0.0.3 >=0.0.3 <0.0.4 Solo esa versión: equivale a fijarla

La lógica es sensata: en 0.x la biblioteca declara que su API todavía se mueve, así que semver trata cada versión menor como potencialmente incompatible. La consecuencia práctica sí sorprende: un npm update no mueve nada en un paquete 0.x salvo parches. Si esperabas pasar de 0.2.9 a 0.4.0 automáticamente, no ocurrirá; hay que cambiar el rango a mano.

Y el corolario para cuando publiques tú: mientras estés en 0.x no prometes nada, y quien te use lo sabe. Es exactamente por eso que Escena Viva empezó en 0.1.0 y no en 1.0.0.

  1. Qué instala cada rango hoy y dentro de seis meses

El experimento mental que aclara el problema. Hoy, con [email protected] como última publicada:

Rango Instala hoy
17.2.1 17.2.1
~17.2.1 17.2.1
^17.2.1 17.2.1

Los tres coinciden. Ahora avanza seis meses: se han publicado 17.2.5, 17.6.0 y 18.0.0. Alguien clona el repositorio, ejecuta npm install sin haber tocado el package.json y obtiene:

Rango Instala en seis meses ¿Ha cambiado tu código?
17.2.1 17.2.1 No
~17.2.1 17.2.5 No
^17.2.1 17.6.0 No
* 18.0.0 No

Tres personas con el mismo repositorio y tres árboles distintos. Ese es el problema exacto que resuelve el fichero de bloqueo.

Compara la recomendación de cada rango según el papel del proyecto:

Situación Rango recomendado Motivo
Aplicación con lock (Escena Viva) ^ El lock ya fija la versión real; el rango solo marca la política de actualización
Biblioteca publicada ^ amplio Rangos estrechos causan duplicados en los proyectos que te usan
Dependencia con historial de romper ~ o exacto Menos margen para sorpresas
Cualquier proyecto Nunca * Renuncias a toda garantía a cambio de nada

  1. package-lock.json: qué contiene exactamente

Se genera solo, en cuanto haya alguna dependencia. Este es un fragmento real, recortado:

{
  "name": "escena-viva",
  "version": "0.1.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "escena-viva",
      "version": "0.1.0",
      "license": "MIT",
      "dependencies": {
        "dotenv": "^17.2.1"
      },
      "devDependencies": {
        "prettier": "^3.6.2"
      },
      "engines": {
        "node": ">=24.5.0 <25"
      }
    },
    "node_modules/dotenv": {
      "version": "17.2.1",
      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
      "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78hjB1PYYow==",
      "engines": { "node": ">=12" },
      "funding": { "url": "https://dotenvx.com" }
    },
    "node_modules/prettier": {
      "version": "3.6.2",
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
      "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfQqFsuHLoDrIhcVYWyO0nAvqEhIkKMYQIsy0mRnNlqUXWkuVjK4X8oEeWmnCLYUEQA==",
      "dev": true,
      "bin": { "prettier": "bin/prettier.cjs" },
      "engines": { "node": ">=14" }
    }
  }
}

Campo por campo:

Campo Qué es
lockfileVersion: 3 Formato del fichero. La 3 es la de npm 7 en adelante
packages[""] El propio proyecto: copia de sus rangos declarados
version La versión exacta resuelta. Aquí no hay rangos
resolved URL exacta del tarball descargado
integrity Hash SHA-512 del contenido, en formato SRI
dev: true Marca que solo hace falta en desarrollo (permite --omit=dev)
bin Ejecutables que se enlazarán en node_modules/.bin
engines Requisitos de Node del paquete, copiados para poder validarlos

El campo integrity es el que hace que esto sea seguridad y no solo comodidad. En cada instalación npm descarga el tarball, calcula su SHA-512 y lo compara con el registrado. Si no coincide —porque alguien manipuló el registro, porque un espejo sirvió otra cosa, porque una caché se corrompió— la instalación falla. Es integridad criptográfica de todo tu árbol de dependencias, gratis.

Y de ahí la regla que no se negocia:

package-lock.json SÍ se sube al control de versiones. Siempre.

Las razones: reproducibilidad (tu portátil, el de tu compañera, la CI y el servidor instalan lo mismo, hoy y dentro de un año); seguridad, porque los hashes viajan con el repositorio; depuración, ya que git log package-lock.json te dice qué versión cambió y cuándo, que suele ser la respuesta a "esto funcionaba la semana pasada"; y auditoría, porque npm audit analiza el árbol exacto del lock y no una aproximación.

La única excepción real es una biblioteca publicada: su lock no afecta a quien la instala, porque cada proyecto resuelve su propio árbol. Aun así, muchas bibliotecas lo suben para que sus propias pruebas sean reproducibles. Escena Viva es una aplicación: lo sube sin discusión.

Y su reverso: el lock nunca se edita a mano. Es un fichero generado. Se cambia ejecutando comandos de npm.

  1. npm ci frente a npm install

Con el lock ya sobre la mesa, la comparación clave del módulo:

npm install npm ci
Lee package.json Sí Sí
Lee package-lock.json Sí, si existe Obligatorio: sin lock, falla
Modifica el lock Sí, si hace falta Nunca
Si el lock no concuerda con el manifiesto Lo actualiza en silencio Falla con error
node_modules previo Lo actualiza de forma incremental Lo borra entero y reinstala
Velocidad Más lenta (resuelve versiones) Más rápida (solo instala lo escrito)
Acepta npm ci <paquete> Sí, npm install <paquete> No existe
Uso previsto Desarrollo, añadir dependencias CI, Docker, producción

La fila decisiva es la cuarta. Si alguien edita el package.json a mano —sube un rango, añade una dependencia— y no ejecuta npm install, el lock queda desincronizado. En ese estado, npm install lo arregla en silencio; npm ci se planta:

npm error `npm ci` can only install packages when your package.json and
npm error package-lock.json are in sync. Please update your lock file with
npm error `npm install` before continuing.

Ese error es una buena noticia: la CI ha detectado que el repositorio está incoherente antes de desplegar nada. Por eso la regla operativa del módulo, que reutilizaremos en el Módulo 11:

  • En tu máquina: npm install, npm install <paquete>, npm update. Modifican el lock, y ese cambio se confirma en git como parte del trabajo.
  • En CI, Docker y producción: npm ci (con --omit=dev en producción). Nunca npm install.

  1. Conflictos del lock en git

Es inevitable: dos ramas añaden dependencias distintas, se fusionan, y git presenta un conflicto en un fichero generado de tres mil líneas.

Lo que no se hace: abrir el fichero y elegir a mano entre marcadores <<<<<<<. El lock es un grafo coherente; resolverlo por trozos produce un árbol inconsistente que "casi" funciona.

Lo que se hace: descartar el fichero y regenerarlo desde los manifiestos, que sí son legibles por humanos.

# 1. Resuelve primero el conflicto de package.json, que es pequeño y legible.
git checkout --ours package-lock.json   # o --theirs: da igual, se va a regenerar
npm install                             # regenera el lock coherente con el manifiesto fusionado
npm ci && npm test                      # verifica que el resultado instala y funciona
git add package.json package-lock.json
git commit

Alternativa igual de válida: borrar el lock y ejecutar npm install. Es más agresivo —puede subir versiones dentro de los rangos, ya que se resuelve todo de nuevo— así que la primera opción es preferible cuando quieres tocar lo mínimo.

Consejo de higiene: una dependencia nueva merece su propia confirmación, con package.json y package-lock.json juntos y nada más. Así el conflicto, si llega, es diminuto, y la revisión de código puede mirar de verdad qué entró (05-06).

  1. npm dedupe y --save-exact

npm dedupe reorganiza el árbol para compartir el máximo de copias. Tras varias instalaciones sucesivas es fácil acabar con duplicados que ya no hacen falta:

node_modules/
├── paquete-a/
│   └── node_modules/utilidad/   <- 1.4.0
└── paquete-b/
    └── node_modules/utilidad/   <- 1.5.0

Si ambos rangos admiten 1.5.0, npm dedupe sube una sola copia a la raíz y borra las anidadas. Menos disco, menos superficie de auditoría, y adiós al problema de instanceof entre copias distintas de la misma clase.

npm ls utilidad     # cuantas copias hay
npm dedupe          # reorganiza y actualiza el lock

Modifica node_modules y el lock, así que se ejecuta en desarrollo y se confirma el resultado. Nunca en producción.

--save-exact (alias -E) guarda la versión sin ^:

npm install dotenv --save-exact     # "dotenv": "17.2.1"

O como política del proyecto, en el .npmrc de 05-01:

save-exact=true

¿Compensa? La respuesta honesta es casi nunca en una aplicación con lock, porque el lock ya fija la versión real y el rango solo expresa qué política de actualización aceptas. Con ^ y lock, npm ci es igual de determinista.

Fijar versiones exactas sí compensa en tres casos concretos:

Caso Motivo
Dependencia con historial de romper en versiones menores Menos margen para sorpresas
Herramientas que afectan a la salida generada (compiladores, formateadores) Que la salida no cambie sin avisar
Entorno regulado que exige justificar cada cambio de versión Toda subida es explícita y revisable

El coste es real: con versiones exactas, npm update deja de servir y actualizar exige tocar el manifiesto paquete a paquete. En un proyecto grande eso se delega a Dependabot o Renovate (05-06).

  1. La actualización menor que rompe

El escenario que justifica todo lo anterior. Un martes, la CI de Escena Viva falla. Nadie ha tocado el código desde el viernes.

Causa: [email protected] se publicó el lunes. Tu manifiesto dice ^17.2.1, la CI no usaba lock y npm install trajo la versión nueva. Un cambio de comportamiento que el autor consideró menor —cómo interpreta las comillas en los valores del .env, por ejemplo— rompe una de tus pruebas.

Lo que hay que extraer de esa historia:

^ no es una garantía, es una expectativa. Confía en que un tercero clasifique bien su propio cambio. La mayoría lo hace; equivocarse es fácil, porque nadie conoce todos los usos de su biblioteca. Un cambio que el autor ve como corrección puede ser el comportamiento del que tú dependías.

El lock convierte esa expectativa en un hecho. Con package-lock.json en el repositorio y npm ci en la CI, el martes se habría instalado 17.2.1, igual que el viernes. La versión nueva entraría el día que alguien ejecute npm update deliberadamente, vea el cambio en el diff del lock y pase las pruebas antes de fusionar.

La diferencia no es que el fallo desaparezca: es quién controla cuándo ocurre. Sin lock, te sorprende un martes por la mañana en producción. Con lock, ocurre en una rama, con un responsable y con pruebas delante.

Y por eso el orden correcto de actualizar es siempre el mismo:

npm outdated                 # que hay disponible y de que tipo
npm update                   # sube dentro de los rangos: seguro
npm test                     # verifica (Modulo 9)
git add package-lock.json && git commit -m "Actualiza dependencias menores"

Para saltar una versión mayor, el proceso es distinto y no automatizable: leer las notas de la versión, cambiar el rango a mano, adaptar el código y probar. Rama aparte, siempre.

  1. De 0.1.0 a 1.0.0: versionar Escena Viva

Escena Viva nació en 0.1.0 porque su API se movía. Terminado el Módulo 4, tiene una API HTTP pública y estable —GET /api/eventos, POST /pedidos—, un dominio asentado y un cliente (el front-end de publico/) que depende de ella. Es el momento del salto.

npm version 1.0.0

npm version hace tres cosas, no una: cambia version en package.json y en package-lock.json, crea una confirmación de git con ese cambio (con 1.0.0 como mensaje por defecto) y crea una etiqueta de git anotada, v1.0.0, que es lo único que imprime por pantalla.

También acepta los saltos por nombre, que es como se usa a diario:

npm version patch      # 1.0.0 -> 1.0.1
npm version minor      # 1.0.1 -> 1.1.0
npm version major      # 1.1.0 -> 2.0.0
npm version 1.1.0-beta.1 --preid=beta

Si no quieres la confirmación ni la etiqueta —porque tu flujo de publicación las genera de otro modo— existe --no-git-tag-version.

Qué implica de verdad el salto a 1.0.0 en Escena Viva:

Antes (0.x) Después (1.x)
Cualquier versión menor podía romper Solo un salto a 2.0.0 puede romper
Los rangos ^0.1.0 solo admitían parches ^1.0.0 admite toda la serie 1.x
Cambiar la API era rutina Cambiar la API exige plan de migración

En concreto, a partir de ahora: quitar un campo de la respuesta de GET /api/eventos es mayor; añadir un campo nuevo es menor; cambiar el significado de error.codigo en la tabla de errores del Módulo 4 es mayor; corregir un cálculo de aforo mal hecho es parche.

Ese compromiso es el mismo que has estado exigiendo a dotenv durante toda la lección. Aquí cambias de lado de la mesa, y por eso el módulo continúa hacia la publicación.

Errores Comunes y Consejos

  • Añadir package-lock.json al .gitignore. Es el error más caro del módulo: destruye la reproducibilidad. Lo que se ignora es node_modules/, nunca el lock.
  • Resolver conflictos del lock a mano. Produce árboles incoherentes que fallan de formas raras. Regenéralo con npm install.
  • Usar npm install en la CI. Puede modificar el lock durante la compilación y desplegar algo distinto de lo que probaste. npm ci, siempre.
  • Esperar que npm update suba una versión mayor. No lo hace nunca, por diseño. Ni tampoco menores en paquetes 0.x, por la regla de ^0.x.y.
  • Publicar 1.0.0 sin querer prometer estabilidad. Si tu API todavía se mueve, quédate en 0.x: es información honesta para quien te use.
  • Poner * o latest como rango. Cualquier versión mayor entra sin avisar. No hay ningún caso en que compense.
  • Consejo: revisa el diff del lock. En una revisión de código, un lock que cambia sin que cambie package.json merece una pregunta.
  • Consejo: npm view <paquete> versions --json lista todas las versiones publicadas. Útil para ver el ritmo de lanzamientos antes de adoptar una dependencia.

Ejercicios

Ejercicio 1. Resolver rangos a mano. Un paquete tiene publicadas estas versiones: 0.9.0, 1.0.0, 1.2.0, 1.2.5, 1.9.0, 2.0.0-beta.1, 2.0.0, 2.1.0. Di qué instalaría cada uno de estos rangos: ^1.2.0, ~1.2.0, 1.2.x, >=1.0.0 <2, ^2.0.0, *, 1.2.0 - 1.9.0. Después repite el ejercicio suponiendo que las versiones publicadas son 0.1.0, 0.2.0, 0.2.5, 0.3.0 y el rango es ^0.2.0.

Ejercicio 2. Ver el lock en acción. En Escena Viva, ejecuta npm ls dotenv y anota la versión. Abre package-lock.json y localiza su version, resolved e integrity. Ahora borra node_modules, ejecuta npm ci y comprueba que la versión es idéntica. Después edita el package.json cambiando el rango a ^17.0.0 sin ejecutar npm install y lanza npm ci. Explica qué pasa y por qué es el comportamiento deseable.

Ejercicio 3. Versionar Escena Viva. Ejecuta npm version 1.0.0 con el árbol de git limpio. Comprueba con git log -1 y git tag qué ha creado exactamente. Después decide qué tipo de salto (parche, menor o mayor) corresponde a cada cambio: (a) añadir el campo salaAccesible a la respuesta de GET /api/eventos; (b) corregir el cálculo de plazas libres, que restaba mal; (c) renombrar precioCentimos a importeCentimos en la respuesta de la API; (d) cambiar UMBRAL_AFORO_BAJO de 20 a 15; (e) exigir Node 26 en engines.

Soluciones

Solución 1. Primera parte:

Rango Instala Motivo
^1.2.0 1.9.0 >=1.2.0 <2.0.0; la mayor de la serie 1
~1.2.0 1.2.5 >=1.2.0 <1.3.0; solo parches
1.2.x 1.2.5 Equivalente a ~1.2.0
>=1.0.0 <2 1.9.0 Excluye explícitamente la serie 2
^2.0.0 2.1.0 No 2.0.0-beta.1: las preproducción no entran en rangos normales
* 2.1.0 La más alta estable
1.2.0 - 1.9.0 1.9.0 Guion incluye ambos extremos

Segunda parte: ^0.2.0 instala 0.2.5, no 0.3.0. Es la regla de ^0.x.y: con la mayor a cero, el circunflejo protege también el número menor, porque 0.3.0 se considera potencialmente incompatible. Esta es la respuesta que más gente falla.

Solución 2. Tras npm ci, npm ls dotenv muestra exactamente la misma versión: el lock manda, no el rango.

Al cambiar el rango a ^17.0.0 sin instalar, npm ci falla:

npm error `npm ci` can only install packages when your package.json and
npm error package-lock.json are in sync.
npm error Invalid: lock file's [email protected] does not satisfy dotenv@^17.0.0

Es deseable porque la desincronización es un error real del repositorio, no un detalle. Alguien tocó el manifiesto sin regenerar el lock, así que nadie sabe qué árbol se pretendía desplegar. npm install lo taparía en silencio y desplegaría una resolución que nadie ha revisado; npm ci detiene el despliegue y obliga a arreglarlo en una rama. Se arregla ejecutando npm install y confirmando el lock resultante.

Solución 3. npm version 1.0.0 produce:

$ git log -1 --oneline
a1b2c3d 1.0.0

$ git tag
v1.0.0

$ git show --stat HEAD
 package.json      | 2 +-
 package-lock.json | 4 ++--

Clasificación de los cambios:

Cambio Salto Razón
(a) Añadir salaAccesible Menor → 1.1.0 Añade información; un cliente que la ignore sigue funcionando
(b) Corregir plazas libres Parche → 1.0.1 Corrige un fallo sin cambiar la forma de la respuesta
(c) Renombrar a importeCentimos Mayor → 2.0.0 Todo cliente que leyera precioCentimos se rompe
(d) UMBRAL_AFORO_BAJO de 20 a 15 Menor → 1.1.0 Cambia el comportamiento observable sin romper contratos; si esa constante estuviera documentada como parte de la API pública, sería mayor
(e) Exigir Node 26 Mayor → 2.0.0 Deja fuera entornos que antes funcionaban

El caso (d) es el interesante: la respuesta depende de si esa constante forma parte del contrato público. Esa ambigüedad es exactamente la parte difícil de semver, y la razón de que un CHANGELOG claro valga tanto como los números.

Conclusión

Ya sabes leer el lenguaje con el que el ecosistema entero se comunica. SemVer convierte tres números en una promesa: PARCHE corrige, MENOR añade sin romper, MAYOR rompe. Las versiones de preproducción son siempre menores que su estable y no entran en los rangos normales. Y de la tabla de rangos te llevas dos formas: ~ deja subir el último número, ^ el último que no sea cero por la izquierda —de ahí que ^0.2.3 solo admita parches, la regla que casi nadie conoce y que explica por qué npm update a veces parece no hacer nada.

Pero la lección de fondo es que ^ es una expectativa, no una garantía: depende de que un tercero clasifique bien su propio cambio. Quien convierte esa expectativa en un hecho es package-lock.json, con su árbol exacto, sus versiones resueltas, sus URLs y sobre todo su integrity SHA-512, que hace fallar la instalación si el contenido descargado no es byte a byte el que se registró. Por eso se sube siempre al repositorio, por eso nunca se edita a mano, y por eso sus conflictos en git se resuelven regenerándolo, no eligiendo entre marcadores.

De ahí sale la regla operativa que te acompañará hasta el final del curso: npm install en tu máquina —modifica el lock, y ese cambio se confirma y se revisa— y npm ci en la CI, en Docker y en producción, que exige el lock, lo respeta al pie de la letra, borra node_modules y falla si el manifiesto y el lock no concuerdan. Ese fallo es una buena noticia: detiene un despliegue incoherente antes de que llegue a nadie.

Escena Viva es ya 1.0.0, con su etiqueta v1.0.0 en git creada por npm version, y ese número es un compromiso: a partir de hoy, quitar un campo de la API o cambiar el significado de un error.codigo obliga a subir a 2.0.0.

En la lección siguiente, Scripts de npm y Automatización del Proyecto, llenamos el último hueco del manifiesto. El campo scripts va a convertirse en la interfaz única del proyecto —npm start, npm run dev, npm run informe, npm test— para que nadie tenga que leer el README para adivinar un comando. Y por el camino descubrirás por qué funciona una herramienta que nunca instalaste globalmente: el PATH extendido con node_modules/.bin.

Curso de Node.js: De Principiante a Avanzado

Módulo 1: Introducción a Node.js

Módulo 2: Conceptos Básicos

Módulo 3: Sistema de Archivos y E/S

Módulo 4: HTTP y Servidores Web

Módulo 5: NPM y Gestión de Paquetes

Módulo 6: Framework Express.js

Módulo 7: Bases de Datos y ORMs

Módulo 8: Autenticación y Autorización

Módulo 9: Pruebas y Depuración

Módulo 10: Temas Avanzados

Módulo 11: Despliegue y DevOps

Módulo 12: Proyectos del Mundo Real

© Copyright 2026. Todos los derechos reservados