
El versionado de software es cómo codificamos el cambio. Hazlo bien y obtendrás actualizaciones predecibles, despliegues seguros y comunicación clara entre equipos. Hazlo mal y enviarás sorpresas. A continuación, una guía clara y basada en ejemplos sobre esquemas comunes de versionado y el significado preciso de la sintaxis de rangos de npm en package.json.
Table of Contents
- Esquemas de versionado que encontrarás
- El significado exacto de los rangos de versión en package.json
- Ejemplos concretos de package.json
- Modelo mental rápido: elige rangos según el contexto
- Errores que debes evitar
- TypeScript ejecutable: valida versiones como npm
- Señales de compatibilidad a buscar
- Manual de casos de uso
- Referencias
- Resumen
- SEO Keywords
Esquemas de versionado que encontrarás
Semantic Versioning (SemVer)
Formato: MAJOR.MINOR.PATCH[-pre][+build], e.g., 2.4.1, 3.0.0-rc.1.
- MAJOR: cambios que rompen compatibilidad.
- MINOR: nuevas funcionalidades retrocompatibles.
- PATCH: correcciones retrocompatibles.
- Pre-release (
-alpha,-beta,-rc.1): candidatos de lanzamiento o builds experimentales. - Regla especial para
0.x: trátalo todo como inestable—cualquier incremento MINOR puede romper compatibilidad (0.2.0 → 0.3.0puede romper).
Calendar Versioning (CalVer)
Codifica tiempo en lugar de compatibilidad, e.g., Ubuntu 24.04 (YYYY.MM). Ideal cuando la cadencia y frescura importan más que las promesas de API (plataformas, CLIs, distros).
Incrementos de major "de marketing"
Algunos productos simplemente incrementan el major (e.g., navegadores) para mantener el ritmo con lanzamientos continuos. Es simple y evita prometer demasiada compatibilidad, pero los consumidores deben leer los changelogs cuidadosamente.
Cuándo elegir qué
- Libraries que prometen una API: prefiere SemVer.
- Apps/CLIs/distros con un calendario fijo: CalVer o majors simples funcionan bien.
- Etapas tempranas (pre-1.0.0): usa
0.xy asume inestabilidad; comunica claramente.
SemVer (de la especificación): "Given a version number MAJOR.MINOR.PATCH, increment the MAJOR version when you make incompatible API changes, the MINOR version when you add functionality in a backwards compatible manner, and the PATCH version when you make backwards compatible bug fixes." — semver.org
El significado exacto de los rangos de versión en package.json
npm (y Yarn/pnpm) usan las reglas de node-semver. Aquí está lo que significa cada forma, con ejemplos concretos que puedes razonar.
Pin exacto
"react": "18.2.0"
Solo 18.2.0 satisface. Sin actualizaciones a menos que edites el archivo (o uses overrides/resolutions).
Caret ^ (seguro por major)
"react": "^18.2.0" → permite >=18.2.0 <19.0.0.
Obtendrás 18.3.0 o 18.2.1 automáticamente, nunca 19.0.0.
Caso especial para 0.x: ^0.2.3 → >=0.2.3 <0.3.0 y ^0.0.3 → >=0.0.3 <0.0.4
Tilde ~ (seguro por minor)
"react": "~18.2.0" → >=18.2.0 <18.3.0.
Solo actualizaciones de patch (e.g., 18.2.8), sin incremento de minor.
X-ranges y wildcards
"1.2.x"→>=1.2.0 <1.3.0"1.x"→>=1.0.0 <2.0.0"*"→ cualquier versión (no lo hagas).
Rangos con comparadores
">=1.2.3 <2.0.0": rango explícito, mismo espíritu que^1.2.3pero más preciso."1.2.3 - 1.4.5": rango con guion, equivalente a>=1.2.3 <=1.4.5.
Pre-releases
"^1.2.3"no incluye1.3.0-rc.1. Los pre-releases se excluyen a menos que tu rango también mencione un pre-release o instales con flags de prerelease."^1.2.3-rc.0"→>=1.2.3-rc.0 <1.3.0(incluye RCs subsecuentes y el final1.2.3).
Workspace/enlaces locales
"my-lib": "workspace:*"o"file:../my-lib"para consumir un paquete local durante el desarrollo en monorepo.
Transitivos y enforcement
overrides(npm) /resolutions(Yarn) fuerzan una versión específica de una dependencia transitiva.enginesestablece restricciones de runtime:
"engines": { "node": ">=18.17" }
Ejemplos concretos de package.json
Aquí está lo que diferentes declaraciones de rangos permiten en la práctica:
{
"name": "versioning-demo",
"private": true,
"engines": { "node": ">=18.17" },
"dependencies": {
"react": "^18.2.0", // >=18.2.0 <19
"axios": "~1.7.2", // >=1.7.2 <1.8.0
"zod": "3.23.8", // exactly 3.23.8
"vite": "5.x", // >=5.0.0 <6.0.0
"some-lib": ">=2.4 <3" // explicit comparator range
},
"devDependencies": {
"typescript": "^5.6.3", // >=5.6.3 <6
"@types/node": "20.11.x" // >=20.11.0 <20.12.0
},
"overrides": {
"transitive-buggy-lib": "4.1.2" // force this version everywhere
}
}
Comportamientos importantes de npm
npm install somepkg@1.2.3por defecto escribirá^1.2.3enpackage.jsona menos que establezcassave-exact=true.package-lock.json(oyarn.lock/pnpm-lock.yaml) fija el árbol completo para que los builds sean reproducibles.npm ciinstala exactamente lo que dice el lockfile y falla si está desincronizado. Úsalo en CI.npm updatemueve las deps a las últimas versiones que aún satisfacen el rango.
Modelo mental rápido: elige rangos según el contexto
Aplicaciones: prefiere ^ para la mayoría de las deps, pero confía en el lockfile para determinismo; usa ~ para paquetes ultra-sensibles.
Libraries: establece peerDependencies al rango compatible más amplio (e.g., react: ">=18 <20"), mantén deps directas con ^ o comparadores, y publica cambios que rompen compatibilidad solo en incrementos MAJOR.
Pre-1.0: comunica claramente; considera pins exactos o rangos estrechos porque cualquier cambio puede romper compatibilidad.
Errores que debes evitar
- Actualizar accidentalmente una transitiva que rompe compatibilidad: rangos amplios sin lockfile pueden actualizarse bajo tus pies. Usa el lockfile y considera
overrides. - Malinterpretar
0.x:^0.2.3no permite0.3.0. - Asumir que los pre-releases se seleccionan: los rangos los excluyen a menos que el rango incluya un tag de pre-release.
- Olvidar
peerDependencies: los plugins de frameworks deben declarar peers (e.g., React, Vite) para que la app host controle la versión.
TypeScript ejecutable: valida versiones como npm
Instala el helper primero:
npm i semver
Ahora un pequeño script que puedes ejecutar con ts-node (o compilar con tsc):
// check-range.ts
import { satisfies, minVersion, coerce } from "semver";
// Prints whether a version fits a range and the minimal version for that range.
function check(range: string, version: string) {
const ok = satisfies(version, range);
const min = minVersion(range)?.version ?? "unknown";
console.log(`${version} ${ok ? "∈" : "∉"} ${range} | min allowed = ${min}`);
}
// Examples
check("^18.2.0", "18.2.5"); // true
check("^18.2.0", "19.0.0"); // false
check("~1.7.2", "1.7.9"); // true
check("~1.7.2", "1.8.0"); // false
check("^0.2.3", "0.2.9"); // true
check("^0.2.3", "0.3.0"); // false
// Pre-release behavior
check("^1.2.3", "1.3.0-rc.1"); // false
check("^1.2.3-rc.0", "1.2.3-rc.2"); // true
check(">=1.2.3-0", "1.3.0-rc.1"); // true
// Helpful: coerce loose tags like "v1.2"
console.log("coerce('v1.2') ->", coerce("v1.2")?.version); // 1.2.0
Esto refleja la misma semántica que npm usa internamente.
Señales de compatibilidad a buscar
- Retrocompatible: incremento
MINORoPATCHbajo SemVer. - Potencialmente rompiendo compatibilidad: incremento
MAJOR, o cualquier incremento en0.x. - Forward compatibility: formatos o protocolos que permiten que nuevos productores aún sean manejados por viejos consumidores (usualmente vía feature flags versionados). Documenta estos explícitamente en tu API.
Manual de casos de uso
- Producto interno: fija vía lockfile, usa
^por conveniencia, envía con changelogs y smoke tests ennpm update. - Library publicada en runtime: peers amplios, deps directas estrechas, matriz de CI a través de versiones de host soportadas.
- Herramientas CLI: CalVer o majors frecuentes; trata flags que rompen compatibilidad como cambios MAJOR incluso si el código es pequeño.
Referencias
- SemVer specification — https://semver.org/
- npm semver implementation — https://github.com/npm/node-semver
- npm: package.json reference — https://docs.npmjs.com/cli/v10/configuring-npm/package-json
- npm: semver guide — https://docs.npmjs.com/cli/v10/using-npm/semver
Resumen
El versionado de software es crítico para gestionar dependencias y comunicar cambios. Semantic Versioning (SemVer) es el estándar de facto para libraries, usando MAJOR.MINOR.PATCH para señalar cambios que rompen compatibilidad, nuevas funcionalidades y correcciones. Calendar Versioning (CalVer) funciona mejor para plataformas y herramientas donde la cadencia de lanzamiento importa más que la estabilidad de la API.
Entender la sintaxis de rangos de npm es esencial: ^1.2.3 permite actualizaciones de minor y patch (>=1.2.3 <2.0.0), mientras que ~1.2.3 solo permite patches (>=1.2.3 <1.3.0). Las reglas especiales aplican a versiones 0.x, donde los rangos con caret son mucho más restrictivos, y las versiones pre-release se excluyen de los rangos a menos que se incluyan explícitamente.
Para aplicaciones, usa rangos con caret y lockfiles para reproducibilidad. Para libraries, establece peerDependencies amplios y rompe APIs solo en incrementos de major. Siempre usa npm ci en CI/CD, aprovecha overrides para transitivos problemáticos, y mantén las dependencias actualizadas con herramientas automatizadas como Renovate o Dependabot.
Pro tip para equipos: establece save-exact=true para apps ultra-predecibles, mantén Renovate/Dependabot ejecutándose, y solo amplía rangos cuando los tests prueben que es seguro.