Los cursos técnicos suelen fallar por lo mismo: cada lección enseña una API aislada con un ejemplo de juguete, y al terminar sabes escribir veinte fragmentos que nunca has visto encajar. Este curso funciona al revés. Todo lo que aprendas a partir de aquí construirá una única aplicación real, módulo a módulo, hasta llegar a una API en producción.
Esa aplicación es Escena Viva. Ya has visto sus datos en las lecciones anteriores; ahora vamos a formalizarla: quién es, a quién sirve, cuál es su modelo de dominio, qué convenciones adopta y —lo más importante— qué parte de ella construirás en cada uno de los doce módulos. Al terminar esta lección tendrás el esqueleto del proyecto creado, el fichero semilla datos/eventos.json escrito y un mapa claro del destino.
Contenido
- El contexto de negocio
- Los actores y lo que necesita cada uno
- El modelo de dominio
- El fichero semilla
datos/eventos.json - La estructura de carpetas del repositorio
- Hoja de ruta: qué construye cada módulo
- Convenciones del proyecto
- Nota sobre datos ficticios
- El contexto de negocio
Escena Viva es una pequeña empresa que gestiona la venta de entradas de tres espacios culturales de una ciudad de tamaño medio:
| Sala | Aforo | Perfil de programación |
|---|---|---|
| Teatro Almendra | 420 localidades | Conciertos, teatro de sala, programación estable |
| Sala Bóveda | 120 localidades | Formatos pequeños: monólogos, cantautores, micro-teatro |
| Auditorio Ribera | 900 localidades | Grandes eventos y festivales |
Hasta ahora, cada sala vendía sus entradas por su cuenta: una con una hoja de cálculo, otra por teléfono y la tercera con un sistema heredado que nadie sabe mantener. Escena Viva nace para unificarlo todo en una sola plataforma.
Los tres eventos con los que arrancamos —y que ya conoces— son:
- Concierto de Otoño (Teatro Almendra), 2 sesiones en octubre de 2026.
- Noche de Monólogos (Sala Bóveda), 3 sesiones en octubre de 2026.
- Festival de Jazz de Primavera (Auditorio Ribera), 2 sesiones en abril de 2027.
Los requisitos que marcan las decisiones técnicas del proyecto son estos:
- Picos de concurrencia brutales y muy cortos. Cuando se abre la venta de un festival, miles de personas entran a la vez durante diez minutos. El resto del mes el tráfico es modesto.
- El aforo no puede sobrevenderse. Es el requisito duro: dos personas no pueden comprar la misma butaca.
- Los precios son dinero real. No admiten errores de redondeo.
- Hay que integrarse con terceros: pasarela de pago, envío de correo, y en el futuro un lector de códigos en la puerta.
- El equipo es pequeño. Dos personas desarrollan y mantienen todo, así que la simplicidad y la reutilización de conocimiento entre cliente y servidor tienen valor real.
Los puntos 1, 4 y 5 son exactamente las razones por las que, en la primera lección, concluimos que Node.js encaja con este negocio.
- Los actores y lo que necesita cada uno
flowchart LR
A["Asistente<br/>(compra entradas)"] --> P["Plataforma<br/>Escena Viva"]
O["Organizador<br/>(programa eventos)"] --> P
D["Administrador<br/>(opera el sistema)"] --> P
P --> PG["Pasarela de pago"]
P --> CO["Servicio de correo"]
P --> BD[("Base de datos<br/>escena_viva")]
| Actor | Quién es | Qué necesita hacer |
|---|---|---|
| Asistente | El público que compra | Consultar el catálogo, ver disponibilidad, comprar entradas, recuperar su pedido, descargar sus entradas |
| Organizador | La persona responsable de una sala | Crear y editar sus eventos y sesiones, fijar aforo y precio, consultar ventas de sus eventos |
| Administrador | El equipo de Escena Viva | Ver todas las ventas, generar informes, gestionar usuarios y organizadores, anular pedidos |
Esta separación no es decorativa: es la que justificará el control de acceso basado en roles del Módulo 8. Un organizador del Teatro Almendra no debe poder ver la recaudación de la Sala Bóveda.
- El modelo de dominio
Estas son las entidades del sistema. Los nombres de la columna "identificador" son los que usaremos literalmente en el código, en español y sin tildes ni ñ.
| Entidad | Identificador | Qué representa | Atributos principales |
|---|---|---|---|
| Evento | evento |
Un espectáculo programado | id, titulo, sala, organizador, categoria, duracionMinutos, descripcion |
| Sesión | sesion |
Un pase concreto de un evento en fecha y hora | id, eventoId, fechaHora, aforo, vendidas, precioCentimos |
| Entrada | entrada |
Un derecho de admisión individual | codigo, sesionId, pedidoId, estado |
| Pedido | pedido |
Una compra: varias entradas de una o más sesiones | id, usuarioId, fecha, lineas, totalCentimos, estado |
| Usuario | usuario |
Una cuenta de la plataforma | id, email, nombre, rol, contrasenaHash |
| Organizador | organizador |
Quien programa eventos en una sala | id, nombre, salas, contacto |
| Sala | sala |
El espacio físico | id, nombre, aforoMaximo, direccion |
| Aforo | aforo |
Capacidad de una sesión (no es tabla propia) | Entero dentro de sesion |
| Precio | precioCentimos |
Importe en céntimos (no es tabla propia) | Entero dentro de sesion |
| Catálogo | catalogo |
La colección de eventos publicados | Array de evento |
Y así se relacionan:
erDiagram
SALA ||--o{ EVENTO : "acoge"
ORGANIZADOR ||--o{ EVENTO : "programa"
EVENTO ||--|{ SESION : "tiene"
SESION ||--o{ ENTRADA : "genera"
PEDIDO ||--|{ ENTRADA : "agrupa"
USUARIO ||--o{ PEDIDO : "realiza"
USUARIO }o--|| ORGANIZADOR : "puede ser"
SALA {
string id
string nombre
int aforoMaximo
}
EVENTO {
string id
string titulo
string sala
string categoria
int duracionMinutos
}
SESION {
string id
string fechaHora
int aforo
int vendidas
int precioCentimos
}
ENTRADA {
string codigo
string sesionId
string estado
}
PEDIDO {
string id
string fecha
int totalCentimos
string estado
}
USUARIO {
string id
string email
string rol
}
Tres decisiones de modelado que conviene entender desde ya:
- El aforo vive en la sesión, no en el evento. El Teatro Almendra tiene 420 localidades, pero una sesión concreta puede sacar a la venta solo 380 si hay una zona reservada para prensa. La sala aporta el máximo; la sesión, la realidad.
- La entrada es individual y tiene código propio. Un pedido de 4 entradas genera 4 registros
entrada, cada uno con su código único, porque cada uno se valida por separado en la puerta. vendidases un contador desnormalizado en la sesión. En rigor podría calcularse contando entradas, pero mostrar la disponibilidad en el catálogo debe ser instantáneo. En el Módulo 7 veremos cómo mantener ese contador coherente con transacciones.
Estados
| Entidad | Estados posibles | Notas |
|---|---|---|
pedido |
pendiente → pagado → emitido, o anulado |
pendiente reserva el aforo durante 10 minutos |
entrada |
valida → usada, o anulada |
usada la marca el lector de la puerta |
evento |
borrador → publicado → finalizado |
Solo los publicado aparecen en el catálogo público |
- El fichero semilla
datos/eventos.json
datos/eventos.jsonHasta el Módulo 7, cuando aparezca la base de datos escena_viva, este fichero es la fuente de datos de toda la aplicación. Crea datos/eventos.json con exactamente este contenido:
[
{
"id": "evt-001",
"titulo": "Concierto de Otono",
"sala": "Teatro Almendra",
"organizador": "org-almendra",
"categoria": "concierto",
"duracionMinutos": 95,
"estado": "publicado",
"descripcion": "Repertorio sinfonico de camara para abrir la temporada.",
"sesiones": [
{
"id": "ses-001-1",
"fechaHora": "2026-10-03T20:00:00",
"aforo": 420,
"vendidas": 180,
"precioCentimos": 2500
},
{
"id": "ses-001-2",
"fechaHora": "2026-10-04T19:00:00",
"aforo": 420,
"vendidas": 96,
"precioCentimos": 2200
}
]
},
{
"id": "evt-002",
"titulo": "Noche de Monologos",
"sala": "Sala Boveda",
"organizador": "org-boveda",
"categoria": "humor",
"duracionMinutos": 80,
"estado": "publicado",
"descripcion": "Cuatro comicos, formato corto y publico muy cerca.",
"sesiones": [
{
"id": "ses-002-1",
"fechaHora": "2026-10-10T21:30:00",
"aforo": 120,
"vendidas": 118,
"precioCentimos": 1800
},
{
"id": "ses-002-2",
"fechaHora": "2026-10-11T21:30:00",
"aforo": 120,
"vendidas": 45,
"precioCentimos": 1800
},
{
"id": "ses-002-3",
"fechaHora": "2026-10-17T21:30:00",
"aforo": 120,
"vendidas": 12,
"precioCentimos": 1500
}
]
},
{
"id": "evt-003",
"titulo": "Festival de Jazz de Primavera",
"sala": "Auditorio Ribera",
"organizador": "org-ribera",
"categoria": "festival",
"duracionMinutos": 240,
"estado": "publicado",
"descripcion": "Tres escenarios y doce formaciones a lo largo de dos jornadas.",
"sesiones": [
{
"id": "ses-003-1",
"fechaHora": "2027-04-17T19:00:00",
"aforo": 900,
"vendidas": 640,
"precioCentimos": 3800
},
{
"id": "ses-003-2",
"fechaHora": "2027-04-18T19:00:00",
"aforo": 900,
"vendidas": 720,
"precioCentimos": 4200
}
]
}
]Comentario campo a campo
JSON no admite comentarios, así que los ponemos aquí:
| Campo | Tipo | Por qué es así |
|---|---|---|
id (evento) |
"evt-NNN" |
Prefijo legible: al leer un registro sabes de qué es sin mirar el contexto |
titulo |
texto | Lo que ve el público |
sala |
texto | De momento el nombre; en el Módulo 7 pasará a ser una referencia a la tabla sala |
organizador |
"org-xxx" |
Referencia al organizador; sostiene el control por roles del Módulo 8 |
categoria |
texto | Sirve para filtrar y agrupar el catálogo |
duracionMinutos |
entero | En minutos, nunca en texto tipo "1h 35min": los datos se calculan, no se leen |
estado |
texto | borrador, publicado o finalizado |
descripcion |
texto | Texto comercial breve |
sesiones |
array | Anidadas dentro del evento: es la forma natural del documento JSON y la que usaremos con MongoDB |
sesiones[].id |
"ses-NNN-M" |
Incluye el número del evento: legible y trazable de un vistazo |
fechaHora |
ISO 8601 | Cadena AAAA-MM-DDTHH:mm:ss: ordenable como texto y estable al pasar por JSON |
aforo |
entero | Localidades a la venta en esta sesión |
vendidas |
entero | Contador de entradas ya vendidas |
precioCentimos |
entero | 2500 = 25,00 EUR. Nunca decimales |
Comprueba que el fichero es JSON válido antes de seguir:
node -p "require('./datos/eventos.json').length"
# 3
node -e "require('./datos/eventos.json').forEach(e => console.log(e.id, '-', e.titulo))"
# evt-001 - Concierto de Otono
# evt-002 - Noche de Monologos
# evt-003 - Festival de Jazz de PrimaveraSi el comando falla con
SyntaxError, hay una coma de más, una comilla sin cerrar o has usado comillas simples. En JSON todas las claves y todas las cadenas van entre comillas dobles, y no se admite coma tras el último elemento.
Todavía no sabemos leer este fichero desde el código con fs —eso es el Módulo 3—, pero ya está en su sitio y ya es la referencia. A partir de ahí, src/catalogo-datos.js dejará de tener los datos incrustados.
- La estructura de carpetas del repositorio
Esta es la estructura objetivo, la que tendrá el proyecto al final del curso. No la crees entera ahora: crecerá con cada módulo.
escena-viva/ ├── .env # Secretos locales (NUNCA en el repositorio) - Módulo 11 ├── .gitignore ├── .nvmrc # Versión de Node del proyecto ├── package.json # Metadatos y dependencias - Módulo 5 ├── datos/ │ ├── eventos.json # Semilla del catálogo │ └── ventas.csv # Histórico de ventas para informes - Módulo 3 ├── informes/ # Salidas generadas (ignoradas por git) ├── src/ │ ├── catalogo.js # Punto de entrada del catálogo por consola │ ├── catalogo-datos.js # Acceso a los datos del catálogo │ ├── dominio/ # Clases Evento, Sesion, Pedido - Módulo 2 │ ├── servidor/ # Servidor HTTP y rutas - Módulos 4 y 6 │ ├── middleware/ # Middleware de Express - Módulo 6 │ ├── modelos/ # Modelos de base de datos - Módulo 7 │ ├── auth/ # Autenticación y roles - Módulo 8 │ └── utiles/ # Formateo, validaciones, ayudantes └── pruebas/ # Pruebas automatizadas - Módulo 9
Principios de organización que seguiremos:
- Una responsabilidad por carpeta. Si un fichero no sabes dónde ponerlo, probablemente hace dos cosas.
src/contiene solo código;datos/solo datos;informes/solo resultados generados. Los resultados generados nunca se versionan.- Nombres de fichero en minúsculas y con guiones (
catalogo-datos.js), nunca con espacios ni mayúsculas. Linux distingue mayúsculas y minúsculas aunque tu portátil no lo haga, y eso ha roto más despliegues de los que se pueden contar.
- Hoja de ruta: qué construye cada módulo
Esta tabla es el mapa del curso. Guárdala: cada vez que empieces un módulo sabrás qué pieza de Escena Viva estás añadiendo.
| Módulo | Tema | Entrega en Escena Viva |
|---|---|---|
| 1 | Introducción | Entorno instalado, esqueleto del proyecto, catálogo por consola, semilla datos/eventos.json |
| 2 | Conceptos básicos | Clases de dominio (Evento, Sesion) separadas en módulos; carga asíncrona del catálogo; un EventEmitter que avisa cuando una sesión se agota |
| 3 | Sistema de archivos y E/S | Lectura real de datos/eventos.json con fs; generación de informes en informes/; procesado de datos/ventas.csv con streams sin cargarlo en memoria |
| 4 | HTTP y servidores web | Primer servidor propio: GET /eventos, GET /eventos/:id, alta de pedidos por POST con JSON, y consumo de una API externa de tipo de cambio |
| 5 | NPM | package.json del proyecto, dependencias, scripts npm start y npm run informe, y publicación de un paquete propio de utilidades de formato |
| 6 | Express | Migración del servidor a Express: rutas del catálogo y de pedidos, middleware de registro, validación de la compra y manejo centralizado de errores |
| 7 | Bases de datos | Sustitución del JSON por la base de datos escena_viva: modelos, CRUD de eventos y sesiones, consultas de disponibilidad, y transacciones para que el aforo nunca se sobrevenda |
| 8 | Autenticación | Registro y acceso de asistentes, hash de contraseñas, sesión con JWT, y roles: asistente, organizador y administrador |
| 9 | Pruebas y depuración | Pruebas unitarias del cálculo de aforo y precios, pruebas de integración de la API de compra, cobertura y depuración de un fallo real |
| 10 | Temas avanzados | Aguantar el pico de apertura de venta: cluster, generación de PDF en worker threads, caché de catálogo y cola de correos con Redis, y la API REST bien diseñada |
| 11 | Despliegue y DevOps | Configuración por entorno, registro estructurado, PM2, imagen Docker de la plataforma, despliegue y tubería de integración continua |
| 12 | Proyectos del mundo real | Extensiones sobre lo aprendido y cierre: de proyecto a producción |
flowchart LR
M1["M1-2<br/>Datos en memoria<br/>y consola"] --> M3["M3<br/>Ficheros<br/>JSON y CSV"]
M3 --> M4["M4-6<br/>API HTTP<br/>y Express"]
M4 --> M7["M7-8<br/>Base de datos<br/>y usuarios"]
M7 --> M9["M9-10<br/>Pruebas y<br/>rendimiento"]
M9 --> M11["M11-12<br/>Despliegue<br/>y producción"]
- Convenciones del proyecto
Estas reglas se aplican a todo el código del curso. Escritas una vez aquí, no volveremos a discutirlas.
7.1 Identificadores en español
Variables, funciones, clases, propiedades y nombres de fichero van en español, sin tildes ni ñ: precioCentimos, entradasLibres, calcularOcupacion, catalogo-datos.js.
Las excepciones son las que impone el entorno: palabras clave del lenguaje (const, class, return), APIs de Node y del navegador (readFile, createServer, map), y nombres de paquetes (express, mongoose).
¿Por qué sin tildes? Porque un identificador sesión es válido en JavaScript pero se convierte en una fuente de errores en cuanto viaja por una URL, un nombre de fichero o una cabecera HTTP. Mejor evitarlo de raíz.
Estilo: camelCase para variables y funciones, PascalCase para clases, MAYUSCULAS_CON_GUION_BAJO para constantes globales.
7.2 Fechas en formato ISO 8601
Toda fecha se almacena como cadena AAAA-MM-DDTHH:mm:ss.
const fechaHora = '2026-10-03T20:00:00'; // Bien
const fecha = '03/10/2026 20:00'; // Mal: ambiguo y no ordenableTres razones:
- Se ordena correctamente como texto, sin convertir a
Date. - No es ambiguo:
03/10/2026es 3 de octubre en España y 10 de marzo en Estados Unidos. - Sobrevive al viaje por JSON. Un objeto
Datese convierte en texto al serializar y no vuelve a serDateal deserializar; si ya guardas texto, no hay sorpresas.
El formateo a 03/10/2026 20:00 es responsabilidad de la capa de presentación, nunca del almacenamiento.
7.3 Precios en céntimos como entero
Todo importe monetario se guarda como número entero de céntimos. 2500 significa 25,00 EUR.
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(19.99 * 3); // 59.97000000000001
console.log(1999 * 3); // 5997 céntimos, exacto
console.log((5997 / 100).toFixed(2)); // '59.97'El tipo number de JavaScript es un flotante de doble precisión (IEEE 754) y no puede representar exactamente 0,1. Con dinero, ese error se acumula y acaba en un descuadre contable. Trabajando con enteros el problema desaparece: solo se divide entre 100 en el último momento, para mostrar.
Convención de nombres: cualquier campo monetario lleva el sufijo Centimos (precioCentimos, totalCentimos, descuentoCentimos). Así es imposible confundirse.
7.4 Códigos de entrada
Cada entrada tiene un código con el formato:
EV-2026-000123 │ │ └── Secuencia de 6 dígitos con ceros a la izquierda │ └─────── Año de emisión └────────── Prefijo de Escena Viva
function generarCodigoEntrada(anio, secuencia) {
// padStart rellena con ceros hasta 6 caracteres: 123 -> '000123'
return `EV-${anio}-${String(secuencia).padStart(6, '0')}`;
}
console.log(generarCodigoEntrada(2026, 123)); // EV-2026-000123¿Por qué este formato y no un identificador aleatorio largo?
- Es legible por teléfono. Alguien puede dictarlo a atención al cliente sin errores.
- Es ordenable y su año es visible a simple vista.
- Es corto para imprimirlo bajo un código de barras.
Su contrapartida es que es predecible: si conoces un código puedes adivinar el siguiente. Por eso el código nunca es la prueba de propiedad; la validación en la puerta comprueba además el estado de la entrada en la base de datos. Es una decisión consciente, del tipo que se toma constantemente en un proyecto real.
7.5 Otras convenciones
| Convención | Regla |
|---|---|
| Codificación de ficheros | UTF-8 sin BOM |
| Finales de línea | \n (LF), también en Windows |
| Indentación | 2 espacios |
| Comillas | Simples en JavaScript, dobles obligatorias en JSON |
| Punto y coma | Sí, siempre |
| Idioma de los comentarios | Español |
| Formato de los ficheros JSON | JSON.stringify(datos, null, 2) |
Datos por stdout, diagnósticos por stderr |
Siempre |
- Nota sobre datos ficticios
Todos los datos de este curso son inventados. Las salas, los eventos, los organizadores, los correos y los pedidos que aparecerán a partir del Módulo 7 no corresponden a personas ni empresas reales.
Esto no es un formalismo: es una práctica profesional que debes trasladar a tu trabajo.
- Nunca uses datos reales de clientes en desarrollo, en pruebas ni en demostraciones. Ni siquiera "solo esta vez", ni siquiera una copia de la base de datos de producción en tu portátil.
- Las bases de datos de desarrollo se pueblan con datos sintéticos, generados o anonimizados. En el Módulo 7 veremos cómo escribir semillas para eso.
- Los datos personales están regulados. Nombres, correos, teléfonos y datos de pago tienen obligaciones legales de tratamiento y minimización.
- Nunca subas secretos al repositorio: claves de la pasarela de pago, contraseñas de base de datos o tokens. Van en variables de entorno (Módulo 11) y por eso el
.gitignoreincluye.envdesde la primera lección.
Cuando en el Módulo 8 registremos al usuario [email protected], fíjate en el dominio .test: está reservado por norma para pruebas y nunca corresponderá a un buzón real.
Errores Comunes y Consejos
Error 1: guardar los precios como decimales "porque es más cómodo". Lo es durante dos semanas. Después llega el primer descuadre de un céntimo en un informe de 3.000 pedidos y no hay forma de saber de dónde sale.
Error 2: usar formatos de fecha locales en los datos.
03/10/2026 obliga a saber quién lo escribió para interpretarlo. ISO 8601 en el almacenamiento, formato local solo al mostrar.
Error 3: poner el aforo en el evento en lugar de en la sesión. Funciona hasta la primera sesión con aforo reducido, y entonces hay que migrar datos.
Error 4: dejar el fichero JSON con una única línea gigante.
Cada cambio aparece en el control de versiones como "toda la línea modificada" y las revisiones se vuelven imposibles. null, 2 siempre.
Error 5: mezclar idiomas en los identificadores.
getEventoById, listarEvents, precioPrice. Elige uno —en este curso, español— y sé sistemático.
Consejo 1: vuelve a la hoja de ruta al empezar cada módulo. Saber qué pieza estás construyendo cambia por completo la forma de estudiar una API.
Consejo 2: usa git desde el primer día. Un git commit al terminar cada lección te da un punto de retorno y un histórico de tu propio aprendizaje.
Consejo 3: no adelantes trabajo. Es tentador montar ya el servidor Express. Cada módulo introduce los conceptos en un orden pensado; saltárselo suele acabar en código que funciona sin entender por qué.
Consejo 4: cuando dudes, mira el modelo de dominio. Buena parte de las decisiones de código se responden solas mirando la tabla de entidades.
Ejercicios
Ejercicio 1: preparar el esqueleto del proyecto
Deja el proyecto listo para el Módulo 2. Debe cumplir:
- La carpeta
escena-viva/contienesrc/,datos/einformes/. datos/eventos.jsonexiste con los tres eventos y es JSON válido.informes/está bajo control de versiones aunque esté vacía (pista:.gitkeep)..gitignoreexcluyenode_modules/,.env,*.logy el contenido generado deinformes/sin excluir la carpeta..nvmrccontiene la versión LTS instalada.- Existe un
README.mdcon el nombre del proyecto, los tres eventos y los comandos para arrancar. - Un primer commit de git con todo lo anterior.
Escribe la secuencia completa de comandos y el contenido de cada fichero.
Ejercicio 2: ampliar la semilla con un cuarto evento
Añade a datos/eventos.json un cuarto evento con estas características:
- Título:
Cuentos al Anochecer - Sala: Teatro Almendra (organizador
org-almendra) - Categoría:
familiar, duración 55 minutos, estadopublicado - Tres sesiones los sábados 7, 14 y 21 de noviembre de 2026 a las 18:00
- Aforo 200 en cada sesión (el Teatro Almendra reserva parte del patio de butacas para este formato)
- Entradas vendidas: 200, 145 y 30 respectivamente
- Precio: 1200 céntimos las dos primeras y 900 la tercera
Respeta los formatos de id, de fecha y de precio del proyecto. Después, verifica con comandos node -p que:
- El fichero sigue siendo JSON válido y tiene 4 eventos.
- El catálogo tiene ahora 10 sesiones.
- El aforo total del Teatro Almendra (sumando todos sus eventos) es correcto.
- Hay exactamente una sesión agotada.
Ejercicio 3: aplicar las convenciones
Un compañero ha escrito este fragmento. Reescríbelo respetando todas las convenciones del proyecto y explica cada cambio.
var Sessions = [
{ID: "S1", date: "10/10/2026 21:30", capacity: 120, sold: 118, price: 18.00},
{ID: "S2", date: "11/10/2026 21:30", capacity: 120, sold: 45, price: 18.00}
];
function getTotalRevenue(sessions){
var total = 0
for(var i=0;i<sessions.length;i++){
total = total + (sessions[i].sold * sessions[i].price)
}
return total
}
console.log("Revenue: " + getTotalRevenue(Sessions))Soluciones
Solución 1
# 1. Estructura de carpetas
mkdir -p escena-viva/src escena-viva/datos escena-viva/informes
cd escena-viva
# 3. Mantener "informes" en el repositorio aunque este vacia
touch informes/.gitkeep
# 5. Version de Node del proyecto
node -v > .nvmrc.gitignore (punto 4). La clave es la excepción con !, para ignorar el contenido pero conservar la carpeta:
node_modules/ .env *.log # Ignoramos lo generado en informes, pero mantenemos la carpeta informes/* !informes/.gitkeep
README.md (punto 6):
# Escena Viva
Plataforma de venta de entradas para eventos culturales del Teatro Almendra,
la Sala Boveda y el Auditorio Ribera.
## Eventos en cartel
- Concierto de Otono (Teatro Almendra)
- Noche de Monologos (Sala Boveda)
- Festival de Jazz de Primavera (Auditorio Ribera)
## Requisitos
- Node.js: la version indicada en `.nvmrc`
## Puesta en marcha
nvm use
node src/catalogo.js
## Estructura
- `src/` codigo fuente
- `datos/` datos de partida (eventos.json)
- `informes/` salidas generadas (no se versionan)Verificación y primer commit (puntos 2 y 7):
node -p "require('./datos/eventos.json').length" # 3
git init
git add .
git commit -m "Esqueleto del proyecto Escena Viva con catalogo semilla"
git log --onelineSolución 2
Bloque a insertar al final del array de datos/eventos.json (recuerda la coma tras la llave de cierre del evento anterior):
{
"id": "evt-004",
"titulo": "Cuentos al Anochecer",
"sala": "Teatro Almendra",
"organizador": "org-almendra",
"categoria": "familiar",
"duracionMinutos": 55,
"estado": "publicado",
"descripcion": "Narracion oral para publico familiar a la caida de la tarde.",
"sesiones": [
{
"id": "ses-004-1",
"fechaHora": "2026-11-07T18:00:00",
"aforo": 200,
"vendidas": 200,
"precioCentimos": 1200
},
{
"id": "ses-004-2",
"fechaHora": "2026-11-14T18:00:00",
"aforo": 200,
"vendidas": 145,
"precioCentimos": 1200
},
{
"id": "ses-004-3",
"fechaHora": "2026-11-21T18:00:00",
"aforo": 200,
"vendidas": 30,
"precioCentimos": 900
}
]
}Verificaciones:
# 1. JSON valido y numero de eventos
node -p "require('./datos/eventos.json').length"
# 4
# 2. Total de sesiones
node -p "require('./datos/eventos.json').reduce((t, e) => t + e.sesiones.length, 0)"
# 10
# 3. Aforo total del Teatro Almendra: 840 (evt-001) + 600 (evt-004) = 1440
node -p "require('./datos/eventos.json').filter(e => e.sala === 'Teatro Almendra').flatMap(e => e.sesiones).reduce((t, s) => t + s.aforo, 0)"
# 1440
# 4. Sesiones agotadas
node -p "require('./datos/eventos.json').flatMap(e => e.sesiones).filter(s => s.vendidas >= s.aforo).map(s => s.id)"
# [ 'ses-004-1' ]Nota sobre el punto 4: ses-002-1 tiene 118 de 120 vendidas, así que no está agotada. La única agotada es la primera sesión del evento nuevo.
Solución 3
// src/utiles/recaudacion.js
// Calculo de la recaudacion de un conjunto de sesiones.
const sesiones = [
{ id: 'ses-002-1', fechaHora: '2026-10-10T21:30:00', aforo: 120, vendidas: 118, precioCentimos: 1800 },
{ id: 'ses-002-2', fechaHora: '2026-10-11T21:30:00', aforo: 120, vendidas: 45, precioCentimos: 1800 }
];
// Devuelve la recaudacion total en centimos (entero).
function calcularRecaudacionCentimos(sesiones) {
return sesiones.reduce(
(total, sesion) => total + sesion.vendidas * sesion.precioCentimos,
0
);
}
const totalCentimos = calcularRecaudacionCentimos(sesiones);
console.log(`Recaudacion: ${(totalCentimos / 100).toFixed(2)} EUR`);
// Recaudacion: 2934.00 EURCambios aplicados y su motivo:
| Cambio | Motivo |
|---|---|
var → const |
Ámbito de bloque y sin reasignaciones accidentales |
Sessions → sesiones |
Identificadores en español y camelCase (PascalCase se reserva para clases) |
ID, date, capacity, sold, price → id, fechaHora, aforo, vendidas, precioCentimos |
Nombres del modelo de dominio, en español |
"S1" → 'ses-002-1' |
Formato de identificador del proyecto, con prefijo y trazabilidad |
"10/10/2026 21:30" → '2026-10-10T21:30:00' |
ISO 8601: sin ambigüedad y ordenable como texto |
price: 18.00 → precioCentimos: 1800 |
Dinero como entero de céntimos; el sufijo lo hace explícito |
getTotalRevenue → calcularRecaudacionCentimos |
Español y con el sufijo que indica la unidad devuelta |
Bucle for con índice → reduce |
Más declarativo y sin variable mutable |
Concatenación con + → plantilla de cadena |
Legibilidad |
| Comillas dobles → simples | Convención de JavaScript del proyecto (las dobles quedan para JSON) |
| Punto y coma añadidos | Convención del proyecto |
| Comentarios en español añadidos | Convención del proyecto |
Comprobación del cálculo: 118 × 1800 + 45 × 1800 = 212.400 + 81.000 = 293.400 céntimos = 2.934,00 EUR. Con precios decimales el resultado habría sido el mismo en este caso concreto, pero basta un precio de 18,99 y unos cientos de operaciones para que empiecen a aparecer céntimos fantasma.
Conclusión
Escena Viva ya no es un decorado: es un proyecto con contexto, actores, modelo de dominio, datos y reglas propias. Has visto quién usa la plataforma —asistente, organizador y administrador— y cómo esa separación anticipa el control por roles; has formalizado las entidades evento, sesion, entrada, pedido, usuario, organizador y sala, con sus relaciones y sus estados; has escrito el fichero semilla datos/eventos.json que será la fuente de datos hasta que aparezca la base de datos escena_viva; y has fijado las convenciones que no volverán a discutirse: identificadores en español sin tildes, fechas ISO 8601, precios en céntimos como entero, códigos de entrada EV-2026-000123, JSON con null, 2 y datos siempre ficticios.
Sobre todo, tienes la hoja de ruta. Sabes que en el Módulo 3 el catálogo dejará de estar incrustado en el código para leerse de disco, que en el Módulo 4 se asomará por HTTP, que en el Módulo 7 se mudará a una base de datos con transacciones que impidan sobrevender el aforo, y que en el Módulo 10 aprenderás a sostener el pico de tráfico de la apertura de venta de un festival.
Con esto cerramos el Módulo 1. Tienes el entorno instalado con una versión LTS gestionada por .nvmrc, sabes ejecutar y depurar un script, manejas el REPL como laboratorio, dominas el JavaScript moderno que usaremos y tienes el proyecto en pie con su semilla de datos.
Lo que aún no sabes es cómo funciona Node.js por dentro, y es la pieza que falta para escribir código de servidor de verdad. En el Módulo 2: Conceptos Básicos abriremos la caja: la arquitectura de Node, el bucle de eventos y sus fases, los callbacks, las promesas y async/await, los eventos con EventEmitter y el sistema de módulos con require y module.exports. Ahí, por fin, el array de eventos incrustado en src/catalogo-datos.js se convertirá en un módulo reutilizable, nacerán las clases Evento y Sesion en src/dominio/, y el camarero que no espera de la primera lección dejará de ser una analogía para convertirse en el código que escribes.
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
