Escena Viva ya tiene manifiesto, pero su bloque de dependencias sigue vacío. Hoy lo rompemos: instalaremos los primeros paquetes del proyecto y, sobre todo, aprenderemos a decidir cuáles merecen entrar.

Esa segunda parte importa más que la primera. Teclear npm install algo se aprende en diez segundos; saber si ese algo va a estar mantenido dentro de dos años, cuántas dependencias arrastra consigo, si su licencia es compatible con tu empresa y si el núcleo de Node ya hace lo mismo, eso es criterio de ingeniería. Y tú vienes de escribir un módulo entero —servidor, enrutador, estáticos, cliente HTTP— sin ninguna dependencia, así que ya sabes por experiencia que "instalar un paquete" no es la única respuesta posible a un problema.

Contenido

  1. npm install <paquete>: qué cambia en disco y en el manifiesto
  2. dependencies frente a devDependencies
  3. Por qué la distinción importa de verdad en el despliegue
  4. peerDependencies y optionalDependencies
  5. Instalar una versión, un rango, una etiqueta o un repositorio git
  6. Instalación global: cuándo se justifica (casi nunca)
  7. Elegir una dependencia con criterio
  8. Las primeras dependencias reales de Escena Viva
  9. Cómo resuelve Node un paquete instalado
  10. Inspeccionar y mantener: ls, outdated, update, uninstall
  11. El árbol, el aplanamiento y las versiones duplicadas

  1. npm install <paquete>: qué cambia en disco y en el manifiesto

Vamos con el ejemplo canónico:

npm install dotenv
added 1 package, and audited 2 packages in 612ms

found 0 vulnerabilities

Detrás de esas dos líneas han pasado cinco cosas concretas:

Qué cambia Detalle
package.json Aparece un bloque dependencies con "dotenv": "^17.2.1"
package-lock.json Se crea (o se actualiza) con la versión exacta resuelta y su hash
node_modules/ Se crea la carpeta con dotenv dentro y su propio package.json
node_modules/.bin/ Se enlazan los ejecutables que declare el paquete (dotenv no trae)
Caché de npm El tarball descargado queda en ~/.npm/_cacache para futuras instalaciones

Fíjate en el rango: has pedido dotenv a secas y npm ha escrito ^17.2.1, no 17.2.1. Ese acento circunflejo es el comportamiento por defecto y significa "esta versión o cualquier posterior compatible". Es el tema central de 05-03; hoy basta con saber que npm no fija versiones exactas salvo que se lo pidas.

Y una comprobación que conviene hacer una vez en la vida: ls node_modules/dotenv revela CHANGELOG.md LICENSE README.md config.js lib package.json. No hay nada exótico ahí dentro: es un proyecto Node normal, con su package.json y su código. Todo lo que has aprendido en cuatro módulos se aplica también a lo que instalas.

  1. dependencies frente a devDependencies

npm distingue dos bloques principales, y la diferencia es puramente contractual: no cambia cómo se instalan en tu máquina, cambia qué se instala en producción.

npm install dotenv               # -> dependencies
npm install --save-dev prettier  # -> devDependencies  (alias: -D)
dependencies devDependencies
Pregunta clave ¿Lo necesita el código en ejecución? ¿Lo necesito solo para desarrollar?
Se instala con npm install Sí Sí
Se instala con npm install --omit=dev Sí No
Ejemplos típicos express, mongoose, dotenv, zod eslint, prettier, mocha, nodemon
Regla mental Aparece en un require de src/ Solo aparece en scripts o en test/

La regla mental de la última fila resuelve el 95 % de los casos: si un fichero de src/ hace require de ese paquete, es una dependencia de producción. Si solo lo invocan tus scripts o tus pruebas, es de desarrollo.

Así queda el reparto en Escena Viva a lo largo del curso:

Paquete Bloque Por qué
dotenv dependencies src/config/ lo requiere al arrancar el servidor
express (M6) dependencies Es el servidor en ejecución
helmet, cors, morgan (M6) dependencies Middleware que corre en producción
mongoose (M7) dependencies Acceso a datos en ejecución
bcrypt, jsonwebtoken (M8) dependencies Autenticación en ejecución
prettier devDependencies Solo formatea código fuente
eslint devDependencies Solo analiza código fuente
mocha, chai, sinon, supertest (M9) devDependencies Solo se ejecutan en pruebas
nyc / cobertura (M9) devDependencies Solo en pruebas

Hay un caso que engaña: una herramienta que genera código que sí se ejecuta en producción. Un compilador o un empaquetador son devDependencies aunque su resultado se despliegue, porque lo que viaja al servidor es la salida, no la herramienta.

  1. Por qué la distinción importa de verdad en el despliegue

Mucha gente clasifica al azar porque "en mi máquina funciona igual". Y es verdad: en desarrollo, npm install instala ambos bloques. La factura llega en el servidor.

# En el servidor de produccion, o en el Dockerfile del Modulo 11
npm ci --omit=dev

Tres consecuencias muy concretas:

Tamaño. Un proyecto con Express, Mongoose y una batería de pruebas completa puede tener 90 MB de node_modules en desarrollo y 25 MB con --omit=dev. En una imagen Docker que se reconstruye y se descarga en cada despliegue, esa diferencia se paga en cada despliegue, todos los días.

Tiempo de instalación. Menos paquetes que descargar, verificar y extraer. En la CI del Módulo 11, donde esto ocurre en cada confirmación, se nota.

Superficie de ataque. Esta es la razón de peso. Cada paquete instalado es código ajeno que puede ejecutarse con los permisos de tu proceso. Las herramientas de desarrollo suelen ser las más pesadas en dependencias transitivas —un linter completo arrastra decenas de paquetes— y ninguna de ellas tiene motivo para estar en un servidor de producción. Sacarlas es reducir el riesgo sin perder nada. Volveremos sobre esto con datos en 05-06.

Nota histórica: --omit=dev sustituye al antiguo --production, que aún funciona pero está obsoleto. Verás --production en tutoriales antiguos y en Dockerfile heredados.

Y el corolario: si clasificas mal una dependencia de producción como devDependencies, la aplicación funciona en tu portátil y revienta en el servidor con un Error: Cannot find module. Es uno de los fallos de despliegue más frecuentes y más desconcertantes, porque el código es idéntico. Si alguna vez lo sufres, mira primero el bloque en el que está el módulo que falta.

  1. peerDependencies y optionalDependencies

Hay dos bloques más que rara vez escribirás como consumidor, pero que verás constantemente en los paquetes que instalas.

peerDependencies dice: "necesito que tú, el proyecto anfitrión, tengas instalado esto; yo no lo traigo". Su caso de uso es el de los complementos. Un plugin de ESLint declara eslint como par: no tiene sentido que el plugin traiga su propia copia de ESLint, porque entonces habría dos ESLint distintos y el plugin no vería la configuración del que de verdad se ejecuta.

"peerDependencies": {
  "eslint": ">=9.0.0"
}

Desde npm 7, las dependencias de par se instalan automáticamente si faltan, y npm falla si la versión que tienes no cumple el rango. Antes solo avisaba, y esa es la causa de los famosos ERESOLVE unable to resolve dependency tree al instalar plugins antiguos.

optionalDependencies dice: "intenta instalar esto; si falla, sigue adelante sin error". El caso clásico son los binarios nativos compilados por sistema operativo: un paquete puede ofrecer una versión rápida en C++ y una alternativa en JavaScript puro si la compilación no funciona. El código del paquete comprueba en tiempo de ejecución si el módulo opcional está disponible.

Bloque Quién lo instala Si falla
dependencies npm, siempre Error, se aborta
devDependencies npm, salvo --omit=dev Error, se aborta
peerDependencies npm 7+ si falta Error si la versión no encaja
optionalDependencies npm, si puede Se ignora y continúa

  1. Instalar una versión, un rango, una etiqueta o un repositorio git

npm install acepta un especificador después del nombre:

npm install [email protected]          # version exacta
npm install dotenv@"^17.0.0"       # rango (comillas: ^ es especial en el shell)
npm install dotenv@latest          # ultima estable publicada
npm install express@next           # etiqueta de preproduccion
npm install lodash@">=4 <5"        # rango compuesto

Las etiquetas de distribución (dist-tags) son alias con nombre que apuntan a una versión concreta. Todo paquete tiene al menos latest; muchos publican también next, beta o canary. Míralas con:

npm dist-tag ls express
beta: 5.2.0-beta.1
latest: 5.1.0

También puedes instalar desde git, algo útil cuando necesitas un arreglo que aún no está publicado, o cuando el paquete es interno y no está en ningún registro:

npm install git+https://github.com/usuario/paquete.git#v2.1.0
npm install github:usuario/paquete#rama-con-el-arreglo

Fija siempre una etiqueta o un commit (#v2.1.0), nunca una rama viva: una rama cambia bajo tus pies y arruina la reproducibilidad que el lock intenta darte. Y ten presente que un paquete instalado desde git no pasa por el registro, así que npm audit no sabe nada de él.

  1. Instalación global: cuándo se justifica (casi nunca)

npm install -g <paquete>
npm list -g --depth=0     # que tienes instalado globalmente

La lista honesta de casos en los que -g está justificado es corta: herramientas de sistema que usas a diario y no pertenecen a ningún proyecto —un cliente de despliegue, pm2 en un servidor (Módulo 11)— y casi nada más. Para todo lo demás hay dos opciones mejores: devDependencies + script de npm si la herramienta es del proyecto (ESLint, Prettier, Mocha), y npx herramienta@version si es de un solo uso, como un generador de plantillas.

El motivo de fondo, ya adelantado en 05-01, es la deriva de versiones: lo global no está declarado en ninguna parte, no viaja con el repositorio, no aparece en la CI y no se instala en la máquina de nadie más. Un proyecto cuyo README diga "primero instala X globalmente" es un proyecto que va a fallar la primera vez que alguien lo clone.

  1. Elegir una dependencia con criterio

Aquí está el corazón de la lección. Antes de escribir npm install, pásale al candidato esta lista:

Criterio Qué mirar Señal de alarma
¿Lo hace ya Node? fetch, crypto.randomUUID, node:test, structuredClone, AbortSignal Instalar uuid o node-fetch en 2026
Descargas semanales npmjs.com Menos de mil para algo de uso general
Último lanzamiento Fecha en npmjs.com o en GitHub Más de dos años sin tocar
Incidencias abiertas ¿Se responden? ¿Hay fallos críticos antiguos? Cientos abiertas, cero respuestas
Dependencias transitivas npm ls tras instalar, o packagephobia.com 200 paquetes para formatear una fecha
Tamaño instalado bundlephobia.com / packagephobia.com Decenas de MB por una utilidad
Licencia Campo license Ausente, GPL en producto cerrado (05-06)
Mantenedores ¿Una persona o una organización? Un único mantenedor sin actividad
Alternativas ¿Hay una opción estándar del ecosistema? Elegir la exótica sin motivo
Coste de escribirlo ¿Son 20 líneas tuyas? Instalar por pereza, no por complejidad

Las dos filas más productivas son la primera y la última.

La primera, porque el núcleo de Node ha crecido enormemente y una parte importante de las dependencias clásicas ya sobra:

Dependencia clásica Sustituto en el núcleo
node-fetch, axios (uso simple) fetch global (M4)
uuid crypto.randomUUID()
rimraf fs.promises.rm(ruta, { recursive: true }) (M3)
mkdirp fs.promises.mkdir(ruta, { recursive: true }) (M3)
dotenv (parcialmente) node --env-file=.env
nodemon node --watch (05-04)
mocha / jest (casos simples) node:test (M9)
chalk (casos simples) util.styleText

La última, porque a veces la mejor dependencia es la que no instalas. El caso más famoso del ecosistema es left-pad: once líneas de código, y cuando su autor lo retiró del registro en 2016 se rompieron las compilaciones de media internet, incluidas las de Babel y React. Volveremos a esa historia en 05-05, desde el punto de vista de quien publica.

Y tienes un caso propio, mucho más cercano: el Módulo 4 entero se hizo sin ninguna dependencia. Enrutador, tipos MIME, ETags, parseo de cuerpos, reintentos. No porque no existieran paquetes para eso —existen y son buenos—, sino porque el objetivo era entender el problema. Ese ejercicio te ha dejado algo valioso: cuando en el Módulo 6 instales Express, no será un acto de fe. Sabrás exactamente qué trabajo te está ahorrando y a qué precio.

La conclusión no es "no instales nada". Es que cada dependencia es una deuda: código que no controlas, que hay que actualizar, auditar y, algún día, sustituir. Instala cuando el ahorro sea claramente mayor que esa deuda.

  1. Las primeras dependencias reales de Escena Viva

Con el criterio anterior en la mano, añadimos dos paquetes. Uno de producción y uno de desarrollo.

npm install dotenv
npm install --save-dev prettier

Por qué dotenv pasa el filtro. Hasta ahora, el puerto y la clave de la API de divisas del Módulo 4 se leían de process.env, con la incomodidad de tener que exportarlas a mano en cada terminal. dotenv carga un fichero .env en process.env. Es minúsculo, tiene cero dependencias, está mantenido y es el estándar de hecho del ecosistema. Nota honesta: Node ya trae --env-file=.env, así que en un proyecto nuevo podrías prescindir de él; lo instalamos porque lo vas a encontrar en el 90 % de los proyectos reales y porque es un ejemplo perfecto de dependencia bien elegida. En el Módulo 11 compararemos las dos opciones a fondo.

Por qué prettier es de desarrollo. Formatea código fuente. Nada de lo que hace tiene sentido en un servidor. Va a devDependencies sin discusión.

Así queda el manifiesto:

{
  "name": "escena-viva",
  "version": "0.1.0",
  "private": true,
  "type": "commonjs",
  "main": "src/servidor/servidor.js",
  "engines": { "node": ">=24.5.0 <25" },
  "scripts": {
    "start": "node src/servidor/servidor.js"
  },
  "dependencies": {
    "dotenv": "^17.2.1"
  },
  "devDependencies": {
    "prettier": "^3.6.2"
  }
}

Y el uso, en un módulo nuevo que centraliza la configuración de entorno. Este fichero debe cargarse antes que cualquier otro que lea process.env:

// src/config/entorno.js
'use strict';

const path = require('node:path');
const { RAIZ } = require('./rutas.js');

// Carga .env dentro de process.env. No sobrescribe lo que ya exista:
// las variables reales del sistema siempre ganan al fichero.
require('dotenv').config({ path: path.join(RAIZ, '.env') });

// Leemos una sola vez y validamos aqui, no repartido por el codigo.
const entorno = {
  puerto: Number(process.env.PUERTO ?? 3000),
  entornoNode: process.env.NODE_ENV ?? 'desarrollo',
  claveApiDivisas: process.env.CLAVE_API_DIVISAS ?? null,
};

if (!Number.isInteger(entorno.puerto) || entorno.puerto < 1 || entorno.puerto > 65535) {
  // Diagnostico por stderr, como en todo el curso.
  console.error(`PUERTO invalido: ${process.env.PUERTO}`);
  process.exit(1);
}

module.exports = { entorno };

Puntos a subrayar:

  • dotenv no sobrescribe variables ya presentes en el entorno. Es exactamente lo que quieres: en producción manda el sistema, no un fichero olvidado en el repositorio.
  • La validación vive en un solo sitio. Un puerto inválido debe matar el proceso al arrancar, no producir un error incomprensible tres minutos después.
  • .env está en el .gitignore desde el Módulo 1. Ahí viven credenciales; no se sube nunca. Lo que sí se sube es un .env.ejemplo con las claves y valores ficticios, para que quien clone sepa qué hace falta.

  1. Cómo resuelve Node un paquete instalado

Ya sabes la respuesta desde el Módulo 2, pero ahora tiene un ejemplo real detrás. Cuando src/config/entorno.js ejecuta require('dotenv'): no empieza por ./, ../ ni /, y no es un módulo del núcleo (node:path sí lo sería), así que es un paquete. Node busca escena-viva/src/config/node_modules/dotenv (no existe), sube a escena-viva/src/node_modules/dotenv (no existe) y sube a escena-viva/node_modules/dotenv, donde lo encuentra. Entonces lee el package.json de dotenv, sigue su campo main (o su exports) hasta el fichero real, lo ejecuta una vez y lo guarda en la caché de módulos: el segundo require('dotenv') no vuelve a ejecutarlo.

Es la misma búsqueda ascendente que estudiaste, sin ninguna regla nueva. Lo único que ha cambiado es que ahora hay algo que encontrar. Y de ahí sale un detalle práctico: la ruta de tu fichero no importa. src/config/entorno.js y src/servidor/servidor.js escriben el mismo require('dotenv') sin cuentas de ../, porque la búsqueda es hacia arriba.

  1. Inspeccionar y mantener: ls, outdated, update, uninstall

npm ls muestra el árbol instalado:

npm ls              # solo el primer nivel
npm ls --all        # todo el arbol
npm ls dotenv       # quien depende de dotenv (clave en 05-06)
[email protected] /home/usuario/escena-viva
├── [email protected]
└── [email protected]

Ese npm ls <paquete> es la herramienta que usarás cuando una auditoría te avise de una vulnerabilidad en un paquete que tú nunca instalaste: te dice quién lo arrastra.

npm outdated compara lo instalado con lo publicado:

npm outdated
Package   Current  Wanted  Latest  Location            Depended by
dotenv     17.2.1  17.2.3  18.0.1  node_modules/dotenv  escena-viva

Las tres columnas dicen cosas distintas y hay que leerlas bien:

Columna Significado
Current Lo que tienes instalado ahora mismo
Wanted La versión más alta que cumple tu rango de package.json
Latest La última publicada, cumpla tu rango o no

Si Current < Wanted, un npm update lo arregla. Si Wanted < Latest, hay una versión mayor esperando y actualizar exige cambiar el rango a mano y leer las notas de la nueva versión. La razón de esa distinción es todo el contenido de 05-03.

npm update sube cada paquete a la versión más alta permitida por su rango. Nunca salta a una versión mayor: es la operación segura.

npm uninstall <paquete> (alias npm rm) lo quita de node_modules, del package.json y del lock. Sin él, un paquete que dejaste de usar sigue instalándose y sigue apareciendo en las auditorías para siempre.

  1. El árbol, el aplanamiento y las versiones duplicadas

Tus dependencias tienen dependencias. Un npm ls --all en un proyecto Express real puede ocupar cientos de líneas. Ese árbol hay que colocarlo en un sistema de ficheros plano, y ahí es donde npm toma una decisión con consecuencias.

Supón este caso:

escena-viva
├── paquete-a  -> necesita utilidad@^1.0.0
└── paquete-b  -> necesita utilidad@^1.2.0

Ambos rangos son compatibles con [email protected], así que npm instala una sola copia, en la raíz:

node_modules/
├── paquete-a/
├── paquete-b/
└── utilidad/      <- 1.4.0, compartida por los dos

Eso es el aplanamiento (hoisting): subir todo lo posible a la raíz para no duplicar. Ahora cambia un rango:

escena-viva
├── paquete-a  -> necesita utilidad@^1.0.0
└── paquete-b  -> necesita utilidad@^2.0.0

1.x y 2.x son incompatibles por definición de semver. No hay ninguna versión que satisfaga a ambos, así que npm anida:

node_modules/
├── paquete-a/
├── paquete-b/
│   └── node_modules/
│       └── utilidad/    <- 2.1.0, solo para paquete-b
└── utilidad/            <- 1.4.0, para todos los demas

Y funciona sin trucos gracias a la búsqueda ascendente del apartado 9: cuando paquete-b pide utilidad, Node encuentra primero la copia anidada y deja de buscar. Dos versiones del mismo paquete conviven en el mismo proceso sin enterarse la una de la otra.

Tres consecuencias prácticas:

  • Puede haber tres versiones de la misma cosa instaladas. Es normal, no es un fallo, y explica por qué node_modules pesa lo que pesa.
  • Cuidado con instanceof entre copias. Si dos versiones del mismo paquete definen una clase, un objeto creado por una no es instanceof la clase de la otra. Es una fuente de errores desconcertantes en el mundo real.
  • El aplanamiento crea dependencias fantasma. Si utilidad está en la raíz de node_modules, tu propio código puede hacer require('utilidad') sin haberla declarado y funcionará... hasta que paquete-a cambie su árbol y desaparezca. Esta es exactamente la trampa que pnpm evita siendo estricto.

npm dedupe intenta reorganizar el árbol para compartir más copias. Lo veremos en 05-03, junto al fichero que registra todo este árbol con precisión milimétrica: package-lock.json.

Errores Comunes y Consejos

  • Instalar sin --save-dev por costumbre. Todo acaba en dependencies y el despliegue engorda. Antes de cada instalación, pregúntate: ¿esto se ejecuta en el servidor?
  • Cannot find module solo en producción. Casi siempre es una dependencia de producción clasificada como devDependencies. Muévela con npm install <paquete> (sin -D), que la reubica.
  • Borrar node_modules como primer recurso. A veces hace falta, pero suele esconder el problema real. Antes, prueba npm ls <paquete> para ver qué hay de verdad instalado.
  • Editar package.json a mano y esperar que se instale solo. Cambiar el fichero no instala nada; hay que ejecutar npm install después para que el lock y node_modules se pongan al día.
  • Instalar desde una rama de git. #main cambia bajo tus pies. Fija siempre una etiqueta o un commit.
  • Olvidar las comillas con ^ o >. npm install paquete@>=2 sin comillas hace que el shell interprete > como una redirección y te cree un fichero llamado =2.
  • Consejo: npm install --dry-run muestra qué haría sin tocar nada. Perfecto antes de una instalación grande.
  • Consejo: mira el árbol nada más instalar. npm ls --all | wc -l te dice cuántas líneas de dependencias acabas de aceptar. A veces esa cifra sola te hace cambiar de idea.

Ejercicios

Ejercicio 1. Clasificar correctamente. Para cada paquete, decide si iría en dependencies o en devDependencies en Escena Viva y justifícalo en una frase: express, mocha, dotenv, helmet, prettier, supertest, mongoose, nodemon. Después explica qué error concreto vería el usuario si helmet estuviera mal clasificado y se desplegara con npm ci --omit=dev.

Ejercicio 2. Instalar y verificar dotenv. Instala dotenv, crea un .env con PUERTO=4000 y CLAVE_API_DIVISAS=clave-de-prueba, escribe src/config/entorno.js como en el apartado 8 y comprueba que el servidor del Módulo 4 arranca en el puerto 4000. Después ejecuta PUERTO=5000 npm start y explica en qué puerto arranca y por qué.

Ejercicio 3. Medir el coste de una dependencia. Sin instalar nada todavía, aplica la lista de comprobación del apartado 7 a express. Luego, en una carpeta desechable fuera del proyecto, ejecuta npm init -y && npm install express y compara: cuántos paquetes se han añadido (npm ls --all | wc -l), cuánto ocupa node_modules (du -sh node_modules) y qué dice npm audit. ¿Sigue mereciendo la pena?

Soluciones

Solución 1. Producción: express (es el servidor), dotenv (se requiere al arrancar), helmet (middleware que corre en cada petición), mongoose (acceso a datos en ejecución). Desarrollo: mocha y supertest (solo pruebas), prettier (solo formatea fuente), nodemon (solo reinicia en desarrollo; y además lo sustituiremos por node --watch en 05-04).

Si helmet estuviera en devDependencies, el despliegue con --omit=dev no lo instalaría y el proceso moriría al arrancar, en el require, no en la primera petición:

Error: Cannot find module 'helmet'
Require stack:
- /app/src/servidor/servidor.js

Lo desconcertante es que en local funciona perfectamente, porque allí npm install sí instaló ambos bloques. Es el argumento más contundente para clasificar bien desde el principio.

Solución 2. Con el .env en la raíz y require('./config/entorno.js') al principio del arranque, el servidor escucha en el 4000.

Con PUERTO=5000 npm start arranca en el 5000. La razón está en el apartado 8: dotenv no sobrescribe variables que ya existan en process.env. La variable de entorno real se establece antes de que el proceso arranque, así que cuando dotenv llega y ve PUERTO ya definida, la respeta.

Esa precedencia es justo lo que se quiere en producción: el .env es un valor por defecto cómodo para desarrollo, y el entorno real del contenedor o del servidor manda siempre. Si necesitaras lo contrario —raro, y casi siempre síntoma de otro problema— existe override: true.

Solución 3. Los números aproximados hoy, con Express 5:

added 66 packages, and audited 67 packages in 2s
found 0 vulnerabilities

$ du -sh node_modules
2.1M    node_modules

Unos 66 paquetes por uno instalado. Suena mucho, y la reacción inicial suele ser de rechazo. Pero aplica la lista: descargas en decenas de millones semanales, mantenido por una fundación (OpenJS) y no por una persona, licencia MIT, 2 MB en disco, quince años de historia y cero vulnerabilidades conocidas. Y el trabajo que sustituye lo conoces de primera mano: es todo el Módulo 4 y bastante más.

El contraste es la enseñanza. Sesenta y seis paquetes por Express, con ese respaldo, es una buena compra. Sesenta y seis paquetes por una función que formatea fechas y que podrías escribir en veinte líneas, no lo es. El número por sí solo no decide; decide en relación con lo que obtienes.

Conclusión

Escena Viva tiene ya sus primeras dependencias y, más importante, un criterio para elegirlas. Sabes qué toca npm install <paquete> —manifiesto, lock, node_modules, .bin y caché— y que npm escribe rangos con ^, no versiones exactas, algo que desmenuzaremos en la lección siguiente.

Distingues dependencies de devDependencies con la regla de "¿aparece en un require de src/?", y sabes que esa distinción no es burocracia: en el despliegue con npm ci --omit=dev decide el tamaño de la imagen, el tiempo de instalación y sobre todo la superficie de ataque, porque las herramientas de desarrollo son las que más código arrastran y ninguna tiene motivo para estar en un servidor. También reconoces peerDependencies —el contrato de los complementos, que npm 7 en adelante exige de verdad— y optionalDependencies, que fallan en silencio a propósito.

La lista de comprobación del apartado 7 es lo que te llevas para siempre: mira primero si el núcleo de Node ya lo hace (fetch, randomUUID, rm recursive, --watch, node:test), y después descargas, mantenimiento, transitivas, tamaño y licencia. Cada dependencia es una deuda que hay que actualizar, auditar y algún día sustituir; a veces la mejor es la que no instalas, como demostró el Módulo 4 entero y como recuerda la historia de left-pad.

Y entiendes por fin cómo se sostiene todo por debajo: require('dotenv') funciona por la misma búsqueda ascendente del Módulo 2, y el aplanamiento comparte una sola copia cuando los rangos son compatibles y anida cuando no lo son, de modo que dos versiones del mismo paquete pueden convivir en el mismo proceso —con su trampa de instanceof y sus dependencias fantasma—.

Esa frase, "cuando los rangos son compatibles", es la puerta de la lección siguiente. En Versionado Semántico y package-lock veremos qué promete de verdad ^1.2.3, por qué ^0.x.y se comporta distinto, qué instala tu proyecto dentro de seis meses sin que hayas tocado nada, y cómo package-lock.json convierte todas esas promesas en un árbol exacto, reproducible y verificado con hashes.

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