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
npm install <paquete>: qué cambia en disco y en el manifiestodependenciesfrente adevDependencies- Por qué la distinción importa de verdad en el despliegue
peerDependenciesyoptionalDependencies- Instalar una versión, un rango, una etiqueta o un repositorio git
- Instalación global: cuándo se justifica (casi nunca)
- Elegir una dependencia con criterio
- Las primeras dependencias reales de Escena Viva
- Cómo resuelve Node un paquete instalado
- Inspeccionar y mantener:
ls,outdated,update,uninstall - El árbol, el aplanamiento y las versiones duplicadas
npm install <paquete>: qué cambia en disco y en el manifiesto
npm install <paquete>: qué cambia en disco y en el manifiestoVamos con el ejemplo canónico:
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.
dependencies frente a devDependencies
dependencies frente a devDependenciesnpm 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.
- 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.
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=devsustituye al antiguo--production, que aún funciona pero está obsoleto. Verás--productionen tutoriales antiguos y enDockerfileheredados.
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.
peerDependencies y optionalDependencies
peerDependencies y optionalDependenciesHay 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.
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 |
- 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 compuestoLas 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:
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-arregloFija 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.
- Instalación global: cuándo se justifica (casi nunca)
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.
- 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.
- 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.
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:
dotenvno 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.
.envestá en el.gitignoredesde el Módulo 1. Ahí viven credenciales; no se sube nunca. Lo que sí se sube es un.env.ejemplocon las claves y valores ficticios, para que quien clone sepa qué hace falta.
- 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.
- Inspeccionar y mantener:
ls, outdated, update, uninstall
ls, outdated, update, uninstallnpm 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:
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.
- 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:
Ambos rangos son compatibles con [email protected], así que npm instala una sola copia, en la raíz:
Eso es el aplanamiento (hoisting): subir todo lo posible a la raíz para no duplicar. Ahora cambia un rango:
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_modulespesa lo que pesa. - Cuidado con
instanceofentre copias. Si dos versiones del mismo paquete definen una clase, un objeto creado por una no esinstanceofla clase de la otra. Es una fuente de errores desconcertantes en el mundo real. - El aplanamiento crea dependencias fantasma. Si
utilidadestá en la raíz denode_modules, tu propio código puede hacerrequire('utilidad')sin haberla declarado y funcionará... hasta quepaquete-acambie 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-devpor costumbre. Todo acaba endependenciesy el despliegue engorda. Antes de cada instalación, pregúntate: ¿esto se ejecuta en el servidor? Cannot find modulesolo en producción. Casi siempre es una dependencia de producción clasificada comodevDependencies. Muévela connpm install <paquete>(sin-D), que la reubica.- Borrar
node_modulescomo primer recurso. A veces hace falta, pero suele esconder el problema real. Antes, pruebanpm ls <paquete>para ver qué hay de verdad instalado. - Editar
package.jsona mano y esperar que se instale solo. Cambiar el fichero no instala nada; hay que ejecutarnpm installdespués para que el lock ynode_modulesse pongan al día. - Instalar desde una rama de git.
#maincambia bajo tus pies. Fija siempre una etiqueta o un commit. - Olvidar las comillas con
^o>.npm install paquete@>=2sin comillas hace que el shell interprete>como una redirección y te cree un fichero llamado=2. - Consejo:
npm install --dry-runmuestra 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 -lte 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:
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
- ¿Qué es Node.js?
- Instalación y Configuración del Entorno
- Tu Primer Programa en Node.js
- El REPL de Node.js
- JavaScript Moderno para Node.js
- El Proyecto del Curso: la Plataforma Escena Viva
Módulo 2: Conceptos Básicos
- Arquitectura de Node.js
- El Bucle de Eventos (Event Loop)
- Callbacks y Programación Asíncrona
- Promesas y async/await
- Eventos y EventEmitter
- Módulos CommonJS y require()
- Módulos ES e Interoperabilidad
Módulo 3: Sistema de Archivos y E/S
- Lectura y Escritura de Archivos
- El Módulo fs a Fondo
- Rutas Multiplataforma con el Módulo path
- Trabajando con Streams
- Streams de Transformación y pipeline
- Buffers y Datos Binarios
Módulo 4: HTTP y Servidores Web
- Creando un Servidor HTTP Simple
- Manejo de Solicitudes y Respuestas
- Enrutamiento Manual
- Sirviendo Archivos Estáticos
- Recibiendo Datos: Cuerpos de Petición y JSON
- Consumiendo APIs Externas desde Node.js
Módulo 5: NPM y Gestión de Paquetes
- Introducción a NPM y package.json
- Instalación y Uso de Paquetes
- Versionado Semántico y package-lock
- Scripts de npm y Automatización del Proyecto
- Creación y Publicación de Paquetes
- Seguridad y Mantenimiento de Dependencias
Módulo 6: Framework Express.js
- Introducción a Express.js
- Configuración de una Aplicación Express
- Enrutamiento en Express
- Middleware
- Middleware de Terceros Esenciales
- Validación de Datos de Entrada
- Manejo de Errores
Módulo 7: Bases de Datos y ORMs
- Introducción a las Bases de Datos
- Usando MongoDB con Mongoose
- Operaciones CRUD
- Relaciones, Poblado y Consultas Avanzadas
- Usando Bases de Datos SQL con Sequelize
- Migraciones, Transacciones y Datos de Prueba
Módulo 8: Autenticación y Autorización
- Introducción a la Autenticación
- Registro de Usuarios y Hash de Contraseñas
- Sesiones y Cookies con Passport.js
- Autenticación con JWT
- Control de Acceso Basado en Roles
- Buenas Prácticas de Seguridad en APIs
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias con Mocha y Chai
- Dobles de Prueba con Sinon
- Pruebas de Integración
- Cobertura y Automatización de las Pruebas
- Depuración de Aplicaciones Node.js
Módulo 10: Temas Avanzados
- El Módulo Cluster
- Hilos de Trabajo (Worker Threads)
- Caché y Colas de Trabajo con Redis
- Optimización del Rendimiento
- Construcción de APIs RESTful
- GraphQL con Node.js
Módulo 11: Despliegue y DevOps
- Configuración y Variables de Entorno
- Registro y Monitorización en Producción
- Usando PM2 para la Gestión de Procesos
- Empaquetado con Docker
- Desplegando en Heroku y Otras PaaS
- Integración y Despliegue Continuos
