learning-front

Nivel 4 · Tooling profesional: el entorno de un proyecto serio

Gestores de paquetes: npm, pnpm y yarn

Qué hace un gestor de paquetes, cómo gestiona el árbol de dependencias y en qué se diferencian npm, pnpm y yarn.

Qué resuelve un gestor de paquetes#

Ya conoces el ciclo básico: npm install descarga las dependencias declaradas en package.json y las vuelca en node_modules/. Lo que quizás no está tan claro es qué pasa exactamente ahí dentro.

Un gestor de paquetes hace tres cosas a la vez:

  1. Registro. Sabe dónde está cada paquete. npm usa registry.npmjs.org por defecto: el catálogo público de más de dos millones de paquetes.
  2. Instalación. Descarga el código que pides, pero también el código que ese código necesita, y el código que ese código necesita, y así sucesivamente.
  3. Declaración. Anota en package.json qué instalaste y con qué rango de versiones, para que cualquiera que clone el proyecto pueda reproducir lo mismo.
shell
# Instala un paquete y lo anota en "dependencies" de package.json.
npm install date-fns

# Instala una herramienta de desarrollo y la anota en "devDependencies".
# La flag -D es la abreviatura de --save-dev.
npm install -D vitest

La diferencia entre dependencies y devDependencies importa: las primeras acaban en el bundle que descarga el usuario; las segundas solo se usan mientras programas. Vite, los linters y los frameworks de tests son siempre devDependencies.

El árbol de dependencias#

Cuando instalas date-fns, no solo descargas date-fns. date-fns puede necesitar otros paquetes, que a su vez necesitan otros. Todo eso forma el árbol de dependencias:

texto
tu proyecto
├── date-fns        ← dependencia directa (la que pediste)
│   └── (ninguna)   ← date-fns no tiene dependencias propias
└── vitest          ← dependencia directa de desarrollo
    ├── @vitest/runner   ← dependencia TRANSITIVA (la pediste vitest, no esta)
    ├── vite-node        ← dependencia transitiva
    └── why-is-node-running  ← dependencia transitiva

Las dependencias transitivas son las de tus dependencias: no las pediste tú directamente, pero el gestor las instala porque algo que sí pediste las necesita. Por eso node_modules/ puede contener cientos de carpetas aunque tu package.json solo declare cinco o seis paquetes.

npm a fondo#

Instalar, actualizar y eliminar#

shell
# Instala todos los paquetes declarados en package.json (el comando de entrada al proyecto).
npm install

# Instala un paquete concreto y actualiza package.json y el lockfile.
npm install date-fns

# Instala como herramienta de desarrollo (va a devDependencies, no al bundle de producción).
npm install -D vitest

# Desinstala un paquete y lo elimina de package.json.
npm uninstall date-fns

# Actualiza un paquete a la versión más reciente que permite el rango de package.json.
npm update date-fns

Scripts#

shell
# Ejecuta el script "dev" declarado en package.json → normalmente arranca el dev server.
npm run dev

# Ejecuta el script "build" → genera la versión de producción en dist/.
npm run build

# Lista todos los scripts disponibles en el proyecto.
npm run

Los scripts son atajos. En vez de recordar vite --host --port 3000, escribes npm run dev y ya.

npx: ejecutar sin instalar#

shell
# Descarga create-vite, andamia un proyecto nuevo y lo descarta.
# No instala create-vite de forma permanente: lo usas una vez y ya está.
npx create-vite mi-proyecto

# Otro ejemplo: ejecuta la versión más reciente de prettier sobre un fichero,
# sin tener prettier instalado globalmente.
npx prettier --write src/index.js

npx es especialmente útil para herramientas que usas una vez (andamiadores) o de forma muy esporádica. No tiene sentido instalarlas de forma permanente y mantenerlas actualizadas.

El lockfile: reproducibilidad garantizada#

Cuando haces npm install date-fns, tu package.json anota algo como "date-fns": "^4.1.0". El acento circunflejo ^ significa “cualquier versión compatible con 4.x”: si mañana sale 4.2.0, alguien que clone el proyecto e instale podría obtener esa versión más nueva.

El lockfile (package-lock.json) resuelve ese problema:

json
{
  "name": "team-builder",
  "lockfileVersion": 3,
  "packages": {
    "node_modules/date-fns": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
      "integrity": "sha512-..."
    }
  }
}

El lockfile fija la versión exacta de cada paquete (incluidas las transitivas) y su hash de integridad. Esto significa que si tú tienes date-fns@4.1.0 y un compañero clona el repo e instala, también obtendrá date-fns@4.1.0. Siempre.

Sin embargo, tener lockfile no basta por sí solo: npm install puede actualizar versiones dentro del rango semver y reescribir el lockfile sin que te des cuenta. Por eso existe npm ci: reproduce el árbol exacto del lockfile sin tocarlo, y falla si hay cualquier divergencia. Es el comando que se usa en entornos limpios y en la integración continua (CI: un servidor que, en cada cambio que subes al repositorio, descarga el proyecto desde cero y lo construye y prueba de forma automática; lo verás a fondo más adelante).

El lockfile se sube a Git. Es parte del proyecto.

npm ci: instalaciones reproducibles#

shell
# Borra node_modules/ y reinstala EXACTO lo del lockfile.
# No actualiza versiones. Falla si package.json y lockfile no concuerdan.
# Es lo que ejecuta la integración continua (CI) para garantizar reproducibilidad.
npm ci

La diferencia con npm install:

ComandoActualiza lockfileFalla si hay divergenciaUso
npm installSí, puedeNoDesarrollo diario
npm ciNoCI/CD, entornos limpios

Cuando alguien incorpora cambios al proyecto, lo correcto es npm ci, no npm install: garantiza que trabajas con el árbol exacto que el equipo validó.

pnpm: el store global#

pnpm resuelve un problema práctico de npm: si tienes diez proyectos y todos usan React, npm descarga y guarda diez copias de React (una en cada node_modules/). pnpm guarda una sola vez cada versión de cada paquete en un store global y crea enlaces hacia cada proyecto.

texto
Store global (en tu carpeta de usuario)
└── react@19.2.7/    ← guardado una vez
    └── ...

tu-proyecto/node_modules/react → enlace al store
otro-proyecto/node_modules/react → el MISMO enlace al store

El resultado:

  • Menos disco. Sin duplicados.
  • Instalaciones más rápidas. Si el paquete ya está en el store, no lo descarga.
  • node_modules estricto. npm aplana el árbol de paquetes (hoisting): sube a la raíz de node_modules/ los paquetes de tus dependencias, lo que los deja accesibles en tu código aunque no los hayas declarado tú. Si esa dependencia desaparece o cambia de versión, tu código se rompe en producción sin que hayas tocado nada: son las llamadas phantom dependencies. pnpm no aplana el árbol; solo expone en node_modules/ lo que tú declaraste, y por eso elimina ese problema.

pnpm usa su propio lockfile: pnpm-lock.yaml.

shell
# Con pnpm, los comandos principales son equivalentes a npm.
# instala todo lo de package.json (sin nombre de paquete)
pnpm install
# instala un paquete concreto y lo anota en dependencies
pnpm add date-fns
# herramienta de desarrollo: va a devDependencies
pnpm add -D vitest
# ejecuta el script dev (o simplemente: pnpm dev)
pnpm run dev

Este curso usa pnpm. Cuando ves pnpm en los comandos de los ejercicios, ya sabes por qué.

yarn: existe y lo verás#

yarn fue creado por Meta (entonces Facebook) en 2016 para resolver problemas de velocidad y reproducibilidad que npm tenía entonces. npm los resolvió después.

Hay dos versiones muy distintas:

  • yarn classic (v1): La que verás en proyectos existentes. Muy similar a npm. Su lockfile es yarn.lock.
  • yarn Berry (v2+): Reescritura moderna con Plug’n’Play (PnP), que elimina node_modules/. Cambio grande; no es compatible con v1.
shell
# Instala todo lo declarado en package.json (sin nombre de paquete).
yarn

# Instala un paquete concreto y lo anota en dependencies.
yarn add date-fns

# Instala como herramienta de desarrollo (va a devDependencies).
yarn add -D vitest

# Reinstalación limpia: equivalente a npm ci, no actualiza el lockfile.
yarn install --frozen-lockfile

No insistiremos en yarn. Lo mencionamos porque lo encontrarás en proyectos heredados y necesitas saber qué es cuando lo ves. Para proyectos nuevos, el peso de 2026 está en npm y pnpm.

La misma idea, tres comandos#

La tabla de equivalencias que más usarás:

Acciónnpmpnpmyarn (classic)
Instalar todonpm installpnpm installyarn
Instalar paquetenpm install Xpnpm add Xyarn add X
Instalar devnpm install -D Xpnpm add -D Xyarn add -D X
Ejecutar scriptnpm run Xpnpm Xyarn X
Reinstalación limpianpm cipnpm install --frozen-lockfileyarn install --frozen-lockfile

La flag --frozen-lockfile es el equivalente de pnpm y yarn a npm ci: no actualiza el lockfile y falla si no concuerda con package.json.

corepack: fijar el gestor#

Imagina que tu proyecto usa pnpm, pero alguien del equipo no lo sabe y ejecuta npm install. Los lockfiles divergen. El árbol difiere. Aparecen errores difíciles de reproducir.

corepack evita eso: es una herramienta que viene con Node y que, una vez activada, lee el campo packageManager de tu package.json y fuerza que todos usen ese gestor y esa versión.

shell
# Activa corepack en tu máquina (solo una vez).
# Según tu versión de Node puede que antes necesites: npm i -g corepack
corepack enable

Luego, en tu package.json:

json
{
  "name": "team-builder",
  "packageManager": "pnpm@11.7.0"
}

La versión se fija a propósito: la gracia de packageManager es que todo el equipo use exactamente esa versión, no “la que tengan instalada”. Pon la tuya real (mírala con pnpm --version) en lugar de 11.7.0.

A partir de ahí, si alguien intenta npm install en ese proyecto, corepack lo detiene y le indica que debe usar pnpm. Todo el equipo, mismo gestor, misma versión. Sin fricción.

Comprueba lo que sabes#

Pregunta 1 de 5

¿Para qué sirve el lockfile?

Tu turno#

Este ejercicio se hace en local: toma el Team Builder con Vite que montaste en el Nivel 3 y practica el ciclo completo. Instala dependencias, trabaja con el lockfile, crea un script propio y, si te animas, fija el gestor para que todo el equipo trabaje igual. Cuando termines, despliega las soluciones y compara los comandos.

Ejercicio · hazlo en local

Mueve tu Team Builder al ciclo real de un proyecto

Sobre el Team Builder con Vite que montaste en el Nivel 3, practica el ciclo completo de un proyecto profesional: instalar dependencias distinguiendo su tipo, usar el lockfile, crear scripts propios y, si llegas al nivel excelente, fijar el gestor para que todo el equipo trabaje igual.

Paso 1: Que funcione

  • Instalas `date-fns` como dependencia de producción (`npm install date-fns`).
  • Instalas `vitest` como herramienta de desarrollo (`npm install -D vitest`).
  • Abres el lockfile (`package-lock.json`) y localizas las versiones exactas de ambos paquetes.
  • Ejecutas `npm run dev` y el proyecto sigue funcionando.

Cómo hacerlo en local

Clona el repositorio del curso, entra en la carpeta del ejercicio y abre el index.html en tu navegador. Toda tu solución va en solucion.js.

git clone <repo>
cd exercises/nivel-4/package-managers
# abre index.html en el navegador y edita solucion.js
Ver soluciones
# OK — instalar y distinguir dependencias de herramientas, sobre el Team Builder.

# Una dependencia de PRODUCCIÓN: date-fns va a "dependencies" en package.json.
# Esto significa que entrará en el bundle que descarga el usuario final.
# Cualquier librería que tu app necesite para funcionar en el navegador va aquí.
npm install date-fns

# Una herramienta de desarrollo: vitest va a "devDependencies" con la flag -D.
# Los tests solo los ejecutas tú mientras programas; el usuario nunca los ve.
# Vitest, los linters y Vite son siempre -D: nunca deben llegar a producción.
npm install -D vitest

# El lockfile (package-lock.json) ya fija las versiones exactas: ábrelo y míralo.
# Ejecuta un script declarado en package.json (atajo, no recuerdas el comando largo).
npm run dev

Por qué este nivel

  • Distingues una dependencia de producción (date-fns) de una de desarrollo (-D, vitest): solo la primera acaba en el bundle del usuario.
  • El lockfile ya fija las versiones exactas; no tienes que pensar en ello.
  • Los scripts son atajos: npm run dev en vez de recordar el comando largo.