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

  1. El contexto de negocio
  2. Los actores y lo que necesita cada uno
  3. El modelo de dominio
  4. El fichero semilla datos/eventos.json
  5. La estructura de carpetas del repositorio
  6. Hoja de ruta: qué construye cada módulo
  7. Convenciones del proyecto
  8. Nota sobre datos ficticios

  1. 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:

  1. 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.
  2. El aforo no puede sobrevenderse. Es el requisito duro: dos personas no pueden comprar la misma butaca.
  3. Los precios son dinero real. No admiten errores de redondeo.
  4. Hay que integrarse con terceros: pasarela de pago, envío de correo, y en el futuro un lector de códigos en la puerta.
  5. 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.

  1. 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.

  1. 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.
  • vendidas es 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

  1. El fichero semilla datos/eventos.json

Hasta 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 Primavera

Si 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.

  1. 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.

  1. 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"]

  1. 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 ordenable

Tres razones:

  1. Se ordena correctamente como texto, sin convertir a Date.
  2. No es ambiguo: 03/10/2026 es 3 de octubre en España y 10 de marzo en Estados Unidos.
  3. Sobrevive al viaje por JSON. Un objeto Date se convierte en texto al serializar y no vuelve a ser Date al 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

  1. 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 .gitignore incluye .env desde 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:

  1. La carpeta escena-viva/ contiene src/, datos/ e informes/.
  2. datos/eventos.json existe con los tres eventos y es JSON válido.
  3. informes/ está bajo control de versiones aunque esté vacía (pista: .gitkeep).
  4. .gitignore excluye node_modules/, .env, *.log y el contenido generado de informes/ sin excluir la carpeta.
  5. .nvmrc contiene la versión LTS instalada.
  6. Existe un README.md con el nombre del proyecto, los tres eventos y los comandos para arrancar.
  7. 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, estado publicado
  • 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:

  1. El fichero sigue siendo JSON válido y tiene 4 eventos.
  2. El catálogo tiene ahora 10 sesiones.
  3. El aforo total del Teatro Almendra (sumando todos sus eventos) es correcto.
  4. 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 --oneline

Solució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 EUR

Cambios 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

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