Cierras el Nivel 3 con un dominio real del lenguaje: la sintaxis moderna (destructuring,
spread/rest, copias y acceso seguro con ?./??), cómo partir el código en módulos, las colecciones
(Sets, Maps y los métodos modernos de array y objeto), el manejo de errores y toda la asincronía
—promesas, async/await, el event loop y fetch—, más la librería estándar (regex, fechas y números).
Has escrito JavaScript moderno de verdad. Pero todo el rato has trabajado igual que al principio:
escribir ficheros y abrir un index.html en el navegador. Para aprender, perfecto. Para un
proyecto real, se queda corto enseguida. Este capítulo es el puente: por qué aparece la
maquinaria que rodea a cualquier proyecto profesional. No la dominarás aquí —eso es el Nivel 4—,
pero entenderás qué problema resuelve cada pieza, que es lo que evita que parezca magia.
Dónde se queda corto abrir un HTML a pelo#
Tu mini-app de módulos funciona abriendo el index.html. En cuanto el proyecto crece, chocas con
varios muros a la vez:
- Código de terceros. Querrás usar librerías que otros han escrito (un validador, utilidades de fechas, React). ¿Cómo las descargas, las actualizas y sabes qué versión usas? A mano es inviable.
- Imports “pelados”. Verás
import { useState } from 'react', sin ruta ni.js. El navegador no sabe dónde estáreact: los módulos nativos exigen rutas relativas con extensión. Alguien tiene que resolver ese nombre. - Muchos ficheros = muchas peticiones. Un proyecto real tiene cientos de módulos. Servirlos uno a uno al navegador es lento. Conviene juntarlos en pocos ficheros optimizados.
- Sintaxis que el navegador no entiende. TypeScript y el JSX de React no se ejecutan tal cual: hay que compilarlos a JavaScript normal antes.
- Recargar a mano. Guardar y pulsar F5 mil veces al día es fricción pura. Querrás que la página se actualice sola al guardar.
Cada muro tiene su herramienta. Son las que vas a conocer.
npm: instalar y declarar lo que usas#
npm (Node Package Manager) es dos cosas: un registro gigantesco de paquetes (código que otros publican) y una herramienta de línea de comandos para instalarlos. Viene con Node.js, que es JavaScript fuera del navegador (lo verás a fondo más adelante; por ahora, es lo que hace funcionar estas herramientas en tu máquina).
Instalar un paquete es un comando:
# Descarga el paquete date-fns y lo anota como dependencia del proyecto.
npm install date-fnspackage.json: el manifiesto del proyecto#
Ese comando no solo descarga: anota el paquete en el package.json, el fichero que describe
tu proyecto. Es lo primero que abre cualquiera que llegue al repositorio:
{
"name": "team-builder",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"date-fns": "^4.1.0"
},
"devDependencies": {
"vite": "^7.0.0"
}
}Tres bloques que importan:
scripts: atajos que ejecutas connpm run <nombre>.npm run devarranca el servidor;npm run buildgenera la versión de producción. No tienes que recordar comandos largos.dependencies: lo que tu app necesita en producción (acaba en el código que se envía al usuario).date-fnses una.devDependencies: herramientas que solo usas al desarrollar y no acaban en el producto final. Vite es una de ellas.
node_modules: dónde aterriza lo instalado#
npm install vuelca todo el código descargado en una carpeta node_modules/. Puede pesar
cientos de megas y tener miles de ficheros. Dos cosas clave:
- No se sube a Git. Va en el
.gitignore. Es enorme y, sobre todo, reconstruible: cualquiera clona el repo, ejecutanpm instally npm recreanode_modulesexacto a partir delpackage.jsony un fichero de bloqueo (el lockfile, que fija las versiones precisas). - No la tocas a mano. Es territorio de npm.
El bundler: juntar y optimizar para el navegador#
La pieza que resuelve casi todos los muros de arriba es el bundler. Toma tu grafo de módulos (tu código + las dependencias que importas) y produce pocos ficheros optimizados listos para el navegador. Por el camino:
- Resuelve los imports, incluidos los “pelados”: sabe que
import ... from 'date-fns'está ennode_modules. - Junta y minifica: menos ficheros y más pequeños, que cargan más rápido.
- Compila lo que el navegador no entiende (TypeScript, JSX) a JavaScript normal.
El bundler que usarás es Vite. Generar la versión de producción es un comando:
# Crea la carpeta dist/ con tu app optimizada, lista para subir a un hosting.
npm run builddist/ también es generado: va al .gitignore, no se versiona.
El dev server: programar con recarga en caliente#
Vite no solo construye: trae un servidor de desarrollo. En vez de abrir el index.html,
ejecutas npm run dev y te da una URL local (http://localhost:5173). Su gracia es la recarga
en caliente: guardas un fichero y el navegador refleja el cambio al instante, sin que pulses
F5 y, a menudo, sin perder el estado de la página. Se acabó el guardar-y-recargar.
# Arranca el servidor de desarrollo. Lo dejas corriendo mientras programas.
npm run devEl flujo completo, de un vistazo#
Andamiar un proyecto moderno desde cero son tres comandos:
npm create vite@latest mi-proyecto # andamia la estructura
cd mi-proyecto && npm install # instala las dependencias
npm run dev # a programar, con recarga en calienteDe esto va el Nivel 4 entero: gestores de paquetes, el package.json a fondo (con el lenguaje de
versiones, semver), Vite y los linters. Aquí solo necesitabas entender por qué existen. La
respuesta corta: porque abrir un index.html no escala, y cada una de estas piezas quita un muro
concreto de en medio.
Comprueba lo que sabes#
Pregunta 1 de 5
¿Qué es npm?
Tu turno#
Este ejercicio se hace en local: es el momento de salir del playground a un proyecto de verdad. La carpeta del ejercicio contiene el punto de partida: cinco ficheros (el HTML, el punto de entrada y los tres módulos de datos, formato y presentación) que funcionan abriendo el HTML directamente. Tu tarea es convertir esa mini-app en un proyecto Vite. Cuando lo tengas, despliega las soluciones y compara los comandos, el manifiesto y los cambios de código en cada tier.
Ejercicio · hazlo en local
Del HTML plano a un proyecto npm + Vite
En la carpeta del ejercicio tienes la mini-app de módulos que construiste en el capítulo anterior: cinco ficheros (index.html, index.js, datos.js, formato.js, render.js) que funcionan abriendo el HTML directamente. Tu tarea es convertirla en un proyecto profesional: andamiarlo con Vite, mover el código a src/, arrancarlo con el servidor de desarrollo y generar un build de producción. Los tiers más altos incorporan lo aprendido a lo largo del Nivel 3: el manejo del repositorio, la asincronía y la librería estándar.
Paso 1: El proyecto arranca
- Tienes Node instalado y andamias el proyecto con npm create vite (plantilla vanilla).
- npm install y npm run dev arrancan el servidor de desarrollo sin errores.
- Los módulos del starter viven en src/ y la lista de héroes aparece en localhost:5173 con recarga automática al guardar.
Paso 2: El proyecto está listo para el repo
- Sabes leer cada bloque del package.json: scripts, dependencies y devDependencies, y entiendes la diferencia entre ambos tipos de dependencia.
- Añades .gitignore con node_modules/ y dist/: sin él, un git add . sube todo lo generado al repositorio.
- Ejecutas npm run build y obtienes un dist/ funcional; npm run preview lo sirve para revisar antes de desplegar.
Paso 3: El proyecto usa lo aprendido en el Nivel 3
- La app carga los datos de héroes con fetch + async/await desde un JSON en public/: los datos no están hardcoded, viven fuera del código.
- El winrate se formatea con Intl.NumberFormat y un try/catch muestra un mensaje de error si la carga falla, en lugar de dejar la página en blanco.
- Los módulos se reorganizan con un barril (modulos.js) que centraliza las exportaciones públicas: src/ puede crecer sin que main.js cambie sus imports.
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-3/del-html-plano-a-un-proyecto
# abre index.html en el navegador y edita solucion.js Ver soluciones
# ════════════════════════════════════════════════════════════════════════════
# NIVEL OK — "que funcione": arrancar un proyecto de verdad con Vite
#
# Andamiar el proyecto, meter el código en src/ y verlo correr en el dev server.
# Esto sustituye a "abrir index.html a pelo".
# ════════════════════════════════════════════════════════════════════════════
# 1) Comprueba que tienes Node (incluye npm). Si no, instálalo desde nodejs.org.
node --version # debe dar v20.x o superior
npm --version
# 2) Andamia un proyecto Vite "vanilla" (JS sin framework) y entra en él.
npm create vite@latest team-builder -- --template vanilla
cd team-builder
# 3) Instala las dependencias que Vite declara en el package.json generado.
npm install
# 4) Arranca el servidor de desarrollo. Abre la URL que imprime (localhost:5173).
npm run dev
# ────────────────────────────────────────────────────────────────────────────
# Qué hacer a continuación (en tu editor, mientras dev server corre):
#
# a) Borra los ficheros de demo que genera Vite: src/counter.js, public/vite.svg, etc.
# b) Copia tus módulos del starter (datos.js, formato.js, render.js) dentro de src/.
# c) Renombra o reemplaza src/main.js con el contenido de tu index.js del starter.
# (Solo importa datos y llama a renderHeroes, sin cambiar nada más.)
# d) Guarda. El dev server recarga solo: la lista de héroes aparece en localhost:5173.
#
# Por qué esto ya es mejor que abrir index.html:
# - El servidor resuelve los módulos sin errores de CORS.
# - Recargas automáticas al guardar: no más F5.
# - El proyecto tiene un punto de entrada declarado y reproducible.
# ──────────────────────────────────────────────────────────────────────────── Por qué este nivel
- Andamias un proyecto Vite, instalas dependencias y arrancas el servidor de desarrollo.
- Mueves tus módulos a src/ y los ves correr con recarga automática al guardar.
- El salto de "abrir index.html a pelo" a "un proyecto que arranca con npm run dev".
{
"//": "NIVEL MEJOR — entender el package.json, generar un build y proteger el repo.",
"//1": "",
"//2": "Por qué supera a OK:",
"//3": " OK arranca el dev server pero no sabe leer lo que tiene delante.",
"//4": " Aqui entiendes cada bloque del package.json y completas el flujo:",
"//5": " dev (trabajar) -> build (producir) -> preview (revisar antes de subir).",
"//6": " Ademas anades .gitignore. Sin el, un 'git add .' sube node_modules/ al repo:",
"//7": " cientos de megas, miles de ficheros, que nadie deberia versionar.",
"//8": " Con .gitignore, el repo solo tiene tu codigo; npm install lo reconstruye.",
"//9": "",
"//10": "-- package.json comentado ---------------------------------------------------",
"//11": "name e version describen el proyecto. 'type: module' activa import/export ES.",
"name": "team-builder",
"version": "0.0.0",
"type": "module",
"//12": "scripts: atajos ejecutados con 'npm run <nombre>'.",
"//13": " dev -> arranca el servidor de desarrollo con recarga en caliente.",
"//14": " build -> genera dist/ con el codigo optimizado y minificado.",
"//15": " preview -> sirve dist/ localmente para revisar el resultado antes de subir.",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"//16": "dependencies: paquetes que la app necesita EN PRODUCCION (van al bundle final).",
"//17": "Se instalan con 'npm install <paquete>' y el usuario los recibe en su navegador.",
"dependencies": {},
"//18": "devDependencies: herramientas que solo usas AL DESARROLLAR (no van al bundle).",
"//19": "Se instalan con 'npm install -D <paquete>'. Vite es la mas importante ahora.",
"devDependencies": {
"vite": "^6.0.0"
},
"//20": "",
"//21": "-- .gitignore minimo -------------------------------------------------------",
"//22": "Crea un fichero .gitignore en la raiz del proyecto con este contenido:",
"//23": " node_modules/",
"//24": " dist/",
"//25": "",
"//26": "node_modules/ es enorme y reconstruible con 'npm install': nunca se versiona.",
"//27": "dist/ es generado por 'npm run build': se reconstruye con ese comando.",
"//28": "Solo tu codigo fuente va a Git; lo generado y lo instalado se reconstruye.",
"//29": "",
"//30": "-- flujo de trabajo completo -----------------------------------------------",
"//31": " npm run dev -> a programar (recarga automatica al guardar)",
"//32": " npm run build -> genera dist/ cuando quieres desplegar",
"//33": " npm run preview -> revisa dist/ antes de subirlo al servidor"
} Por qué es mejor que el anterior
- Entiendes cada bloque del package.json: scripts, dependencies y devDependencies.
- Añades .gitignore: sin él, un git add . sube node_modules/ al repo (cientos de megas que nunca deben versionarse).
- Completas el flujo: dev para trabajar, build para producir, preview para revisar antes de desplegar.
# ════════════════════════════════════════════════════════════════════════════
# NIVEL EXCELENTE — cierre real del Nivel 3
#
# Por que supera a Mejor:
# Mejor monta el proyecto y aprende la herramienta (Vite, package.json).
# Excelente usa lo que enseno todo el Nivel 3:
# - fetch + async/await para cargar datos de un JSON externo (no hardcoded).
# - Intl.NumberFormat para formatear el winrate con la locale del usuario.
# - try/catch para manejar el error de carga y no dejar la app muda.
# - Barril (modulos.js) que centraliza las exportaciones publicas.
# - .gitignore para no versionar lo generado (node_modules/, dist/).
# La app ya no tiene los datos pegados en el codigo: los carga en runtime,
# igual que lo hara de una API real en cualquier proyecto profesional.
# ════════════════════════════════════════════════════════════════════════════
# ────────────────────────────────────────────────────────────────────────────
# Estructura final del proyecto
# ────────────────────────────────────────────────────────────────────────────
#
# team-builder/
# ├── index.html <- HTML de entrada (Vite lo gestiona)
# ├── package.json <- manifiesto con scripts y devDependencies
# ├── package-lock.json <- versiones exactas instaladas (va a Git)
# ├── .gitignore <- excluye node_modules/ y dist/
# ├── node_modules/ <- dependencias instaladas (NO va a Git)
# ├── dist/ <- build de produccion (NO va a Git)
# ├── public/
# │ └── heroes.json <- datos de heroes servidos como fichero estatico
# └── src/
# ├── main.js <- punto de entrada: orquesta la carga y el render
# ├── formato.js <- modulo de formato: Intl.NumberFormat para el winrate
# ├── render.js <- modulo de presentacion: pinta y muestra errores
# └── modulos.js <- barril: re-exporta lo publico en un punto unico
# ────────────────────────────────────────────────────────────────────────────
# public/heroes.json (datos fuera del codigo; fetch los carga en runtime)
# ────────────────────────────────────────────────────────────────────────────
# Crea la carpeta public/ en la raiz del proyecto y pon este fichero dentro.
# Vite sirve public/ tal cual: fetch('/heroes.json') lo encuentra sin bundlear.
#
# [
# { "nombre": "Tracer", "rol": "Dano", "partidas": 120, "victorias": 78 },
# { "nombre": "Mercy", "rol": "Apoyo", "partidas": 200, "victorias": 130 },
# { "nombre": "Reinhardt", "rol": "Tanque", "partidas": 90, "victorias": 51 },
# { "nombre": "Genji", "rol": "Dano", "partidas": 150, "victorias": 72 },
# { "nombre": "Ana", "rol": "Apoyo", "partidas": 110, "victorias": 66 },
# { "nombre": "Winston", "rol": "Tanque", "partidas": 80, "victorias": 38 }
# ]
# ────────────────────────────────────────────────────────────────────────────
# src/formato.js
# ────────────────────────────────────────────────────────────────────────────
#
# // Modulo de formato: calcula y presenta los datos de un heroe.
#
# // INTERNO: calcula el winrate como numero entre 0 y 1.
# // No se exporta: solo lo usa fichaHeroe; nadie de fuera necesita el numero crudo.
# function winrateDe(heroe) {
# return heroe.partidas === 0 ? 0 : heroe.victorias / heroe.partidas;
# }
#
# // Formateador Intl reutilizable: porcentaje con un decimal, locale del usuario.
# // Se crea UNA VEZ fuera de la funcion para no recrearlo en cada llamada al map.
# var formatoPct = new Intl.NumberFormat("es-ES", {
# style: "percent",
# minimumFractionDigits: 1,
# maximumFractionDigits: 1,
# });
#
# // API PUBLICA: devuelve la linea de texto lista para mostrar.
# // Usa Intl en lugar de toFixed para respetar la locale del usuario.
# export default function fichaHeroe(heroe) {
# return heroe.nombre + " (" + heroe.rol + ") — " + formatoPct.format(winrateDe(heroe));
# }
# ────────────────────────────────────────────────────────────────────────────
# src/render.js
# ────────────────────────────────────────────────────────────────────────────
#
# // Modulo de presentacion: solo sabe de DOM. No carga datos ni calcula nada.
# import fichaHeroe from "./formato.js";
#
# // Pinta la lista de heroes en el contenedor del HTML.
# export function renderHeroes(lista) {
# var app = document.getElementById("app");
# if (!app) return;
# var items = lista.map(function(h) { return "<li>" + fichaHeroe(h) + "</li>"; }).join("");
# app.innerHTML = "<h2>Estadisticas del equipo</h2><ul>" + items + "</ul>";
# }
#
# // Muestra un aviso de error en lugar de dejar la pagina en blanco.
# // Error visible es siempre mejor que silencio para quien usa la app.
# export function renderError(mensaje) {
# var app = document.getElementById("app");
# if (!app) return;
# app.innerHTML = "<p>Error al cargar los heroes: " + mensaje + "</p>";
# }
# ────────────────────────────────────────────────────────────────────────────
# src/modulos.js (barril)
# ────────────────────────────────────────────────────────────────────────────
#
# // Barril: re-exporta las piezas publicas en un punto unico.
# // Quien importe de aqui no necesita conocer la estructura interna de src/.
# // Cuando src/ crezca, solo hay que actualizar este fichero; los consumidores no cambian.
# export { renderHeroes, renderError } from "./render.js";
# export { default as fichaHeroe } from "./formato.js";
# ────────────────────────────────────────────────────────────────────────────
# src/main.js
# ────────────────────────────────────────────────────────────────────────────
#
# // Punto de entrada: orquesta la carga y el render.
# // Importa del barril para no depender de rutas internas de src/.
# import { renderHeroes, renderError } from "./modulos.js";
#
# // Funcion asincrona que carga heroes desde el JSON estatico en public/.
# async function cargarYRenderizar() {
# try {
# // fetch devuelve una promesa; await espera la respuesta sin bloquear el hilo.
# var respuesta = await fetch("/heroes.json");
# // Si el servidor respondio con un error HTTP (404, 500...), lo tratamos como fallo.
# if (!respuesta.ok) {
# throw new Error("El servidor respondio con estado " + respuesta.status);
# }
# // .json() tambien es asincrono: parsea el cuerpo de la respuesta.
# var heroes = await respuesta.json();
# // Con los datos listos, los pasamos al modulo de render.
# renderHeroes(heroes);
# } catch (error) {
# // Si fetch fallo (red caida, JSON malformado, error HTTP), mostramos el mensaje.
# renderError(error.message);
# }
# }
#
# // Arrancamos la carga en cuanto el modulo se ejecuta.
# cargarYRenderizar();
# ════════════════════════════════════════════════════════════════════════════
# Comandos para montar el proyecto
# ════════════════════════════════════════════════════════════════════════════
# Andamia el proyecto y entra en el directorio.
npm create vite@latest team-builder -- --template vanilla
cd team-builder
# Instala las dependencias del package.json generado.
npm install
# Crea la carpeta public/ y el fichero de datos (luego copia el JSON de arriba).
mkdir -p public
# Arranca el servidor de desarrollo.
npm run dev
# Cuando quieras revisar el build de produccion:
npm run build
npm run preview
# .gitignore (crea este fichero en la raiz):
printf "node_modules/\ndist/\n" > .gitignore Por qué es mejor que el anterior
- La app carga los héroes con fetch + async/await desde un JSON externo: igual que lo hará de una API real.
- Intl.NumberFormat formatea el winrate respetando la locale del usuario; try/catch evita que la app quede muda si falla la carga.
- Un barril centraliza las exportaciones públicas: src/ puede crecer sin que los consumidores cambien sus imports.