Els cursos tècnics solen fallar pel mateix: cada lliçó ensenya una API aïllada amb un exemple de joguina, i en acabar saps escriure vint fragments que no has vist mai encaixar. Aquest curs funciona a l'inrevés. Tot el que aprenguis a partir d'aquí construirà una única aplicació real, mòdul a mòdul, fins a arribar a una API en producció.
Aquesta aplicació és Escena Viva. Ja n'has vist les dades a les lliçons anteriors; ara la formalitzarem: qui és, a qui serveix, quin és el seu model de domini, quines convencions adopta i —el més important— quina part d'ella construiràs a cadascun dels dotze mòduls. En acabar aquesta lliçó tindràs l'esquelet del projecte creat, el fitxer llavor dades/esdeveniments.json escrit i un mapa clar de la destinació.
Contingut
- El context de negoci
- Els actors i el que necessita cadascun
- El model de domini
- El fitxer llavor
dades/esdeveniments.json - L'estructura de carpetes del repositori
- Full de ruta: què construeix cada mòdul
- Convencions del projecte
- Nota sobre dades fictícies
- El context de negoci
Escena Viva és una petita empresa que gestiona la venda d'entrades de tres espais culturals d'una ciutat de mida mitjana:
| Sala | Aforament | Perfil de programació |
|---|---|---|
| Teatro Almendra | 420 localitats | Concerts, teatre de sala, programació estable |
| Sala Bóveda | 120 localitats | Formats petits: monòlegs, cantautors, microteatre |
| Auditorio Ribera | 900 localitats | Grans esdeveniments i festivals |
Fins ara, cada sala venia les entrades pel seu compte: una amb un full de càlcul, una altra per telèfon i la tercera amb un sistema heretat que ningú no sap mantenir. Escena Viva neix per unificar-ho tot en una sola plataforma.
Els tres esdeveniments amb què arrenquem —i que ja coneixes— són:
- Concierto de Otoño (Teatro Almendra), 2 sessions a l'octubre del 2026.
- Noche de Monólogos (Sala Bóveda), 3 sessions a l'octubre del 2026.
- Festival de Jazz de Primavera (Auditorio Ribera), 2 sessions a l'abril del 2027.
Els requisits que marquen les decisions tècniques del projecte són aquests:
- Pics de concurrència brutals i molt curts. Quan s'obre la venda d'un festival, milers de persones hi entren alhora durant deu minuts. La resta del mes el trànsit és modest.
- L'aforament no es pot sobrevendre. És el requisit dur: dues persones no poden comprar la mateixa butaca.
- Els preus són diners de veritat. No admeten errors d'arrodoniment.
- Cal integrar-se amb tercers: passarel·la de pagament, enviament de correu, i en el futur un lector de codis a la porta.
- L'equip és petit. Dues persones desenvolupen i mantenen tot, així que la simplicitat i la reutilització de coneixement entre client i servidor tenen valor real.
Els punts 1, 4 i 5 són exactament les raons per les quals, a la primera lliçó, vam concloure que Node.js encaixa amb aquest negoci.
- Els actors i el que necessita cadascun
flowchart LR
A["Assistent<br/>(compra entrades)"] --> P["Plataforma<br/>Escena Viva"]
O["Organitzador<br/>(programa esdeveniments)"] --> P
D["Administrador<br/>(opera el sistema)"] --> P
P --> PG["Passarel·la de pagament"]
P --> CO["Servei de correu"]
P --> BD[("Base de dades<br/>escena_viva")]
| Actor | Qui és | Què necessita fer |
|---|---|---|
| Assistent | El públic que compra | Consultar el catàleg, veure disponibilitat, comprar entrades, recuperar la seva comanda, descarregar les seves entrades |
| Organitzador | La persona responsable d'una sala | Crear i editar els seus esdeveniments i sessions, fixar aforament i preu, consultar vendes dels seus esdeveniments |
| Administrador | L'equip d'Escena Viva | Veure totes les vendes, generar informes, gestionar usuaris i organitzadors, anul·lar comandes |
Aquesta separació no és decorativa: és la que justificarà el control d'accés basat en rols del Mòdul 8. Un organitzador del Teatro Almendra no ha de poder veure la recaptació de la Sala Bóveda.
- El model de domini
Aquestes són les entitats del sistema. Els noms de la columna «identificador» són els que farem servir literalment al codi, en català i sense accents ni ç.
| Entitat | Identificador | Què representa | Atributs principals |
|---|---|---|---|
| Esdeveniment | esdeveniment |
Un espectacle programat | id, titol, sala, organitzador, categoria, duracioMinuts, descripcio |
| Sessió | sessio |
Un passi concret d'un esdeveniment en data i hora | id, esdevenimentId, dataHora, aforament, venudes, preuCentims |
| Entrada | entrada |
Un dret d'admissió individual | codi, sessioId, comandaId, estat |
| Comanda | comanda |
Una compra: diverses entrades d'una o més sessions | id, usuariId, data, linies, totalCentims, estat |
| Usuari | usuari |
Un compte de la plataforma | id, email, nom, rol, hashContrasenya |
| Organitzador | organitzador |
Qui programa esdeveniments en una sala | id, nom, sales, contacte |
| Sala | sala |
L'espai físic | id, nom, aforamentMaxim, adreca |
| Aforament | aforament |
Capacitat d'una sessió (no és taula pròpia) | Enter dins de sessio |
| Preu | preuCentims |
Import en cèntims (no és taula pròpia) | Enter dins de sessio |
| Catàleg | cataleg |
La col·lecció d'esdeveniments publicats | Array d'esdeveniment |
I així es relacionen:
erDiagram
SALA ||--o{ ESDEVENIMENT : "acull"
ORGANITZADOR ||--o{ ESDEVENIMENT : "programa"
ESDEVENIMENT ||--|{ SESSIO : "te"
SESSIO ||--o{ ENTRADA : "genera"
COMANDA ||--|{ ENTRADA : "agrupa"
USUARI ||--o{ COMANDA : "fa"
USUARI }o--|| ORGANITZADOR : "pot ser"
SALA {
string id
string nom
int aforamentMaxim
}
ESDEVENIMENT {
string id
string titol
string sala
string categoria
int duracioMinuts
}
SESSIO {
string id
string dataHora
int aforament
int venudes
int preuCentims
}
ENTRADA {
string codi
string sessioId
string estat
}
COMANDA {
string id
string data
int totalCentims
string estat
}
USUARI {
string id
string email
string rol
}
Tres decisions de modelatge que convé entendre des d'ara:
- L'aforament viu a la sessió, no a l'esdeveniment. El Teatro Almendra té 420 localitats, però una sessió concreta en pot treure a la venda només 380 si hi ha una zona reservada per a premsa. La sala aporta el màxim; la sessió, la realitat.
- L'entrada és individual i té codi propi. Una comanda de 4 entrades genera 4 registres
entrada, cadascun amb el seu codi únic, perquè cadascun es valida per separat a la porta. venudesés un comptador desnormalitzat a la sessió. En rigor es podria calcular comptant entrades, però mostrar la disponibilitat al catàleg ha de ser instantani. Al Mòdul 7 veurem com mantenir aquest comptador coherent amb transaccions.
Estats
| Entitat | Estats possibles | Notes |
|---|---|---|
comanda |
pendent → pagat → emes, o anullat |
pendent reserva l'aforament durant 10 minuts |
entrada |
valida → usada, o anullada |
usada la marca el lector de la porta |
esdeveniment |
esborrany → publicat → finalitzat |
Només els publicat apareixen al catàleg públic |
- El fitxer llavor
dades/esdeveniments.json
dades/esdeveniments.jsonFins al Mòdul 7, quan aparegui la base de dades escena_viva, aquest fitxer és la font de dades de tota l'aplicació. Crea dades/esdeveniments.json amb exactament aquest contingut:
[
{
"id": "evt-001",
"titol": "Concierto de Otono",
"sala": "Teatro Almendra",
"organitzador": "org-almendra",
"categoria": "concert",
"duracioMinuts": 95,
"estat": "publicat",
"descripcio": "Repertori simfonic de cambra per obrir la temporada.",
"sessions": [
{
"id": "ses-001-1",
"dataHora": "2026-10-03T20:00:00",
"aforament": 420,
"venudes": 180,
"preuCentims": 2500
},
{
"id": "ses-001-2",
"dataHora": "2026-10-04T19:00:00",
"aforament": 420,
"venudes": 96,
"preuCentims": 2200
}
]
},
{
"id": "evt-002",
"titol": "Noche de Monologos",
"sala": "Sala Boveda",
"organitzador": "org-boveda",
"categoria": "humor",
"duracioMinuts": 80,
"estat": "publicat",
"descripcio": "Quatre comics, format curt i public molt a prop.",
"sessions": [
{
"id": "ses-002-1",
"dataHora": "2026-10-10T21:30:00",
"aforament": 120,
"venudes": 118,
"preuCentims": 1800
},
{
"id": "ses-002-2",
"dataHora": "2026-10-11T21:30:00",
"aforament": 120,
"venudes": 45,
"preuCentims": 1800
},
{
"id": "ses-002-3",
"dataHora": "2026-10-17T21:30:00",
"aforament": 120,
"venudes": 12,
"preuCentims": 1500
}
]
},
{
"id": "evt-003",
"titol": "Festival de Jazz de Primavera",
"sala": "Auditorio Ribera",
"organitzador": "org-ribera",
"categoria": "festival",
"duracioMinuts": 240,
"estat": "publicat",
"descripcio": "Tres escenaris i dotze formacions al llarg de dues jornades.",
"sessions": [
{
"id": "ses-003-1",
"dataHora": "2027-04-17T19:00:00",
"aforament": 900,
"venudes": 640,
"preuCentims": 3800
},
{
"id": "ses-003-2",
"dataHora": "2027-04-18T19:00:00",
"aforament": 900,
"venudes": 720,
"preuCentims": 4200
}
]
}
]Comentari camp a camp
JSON no admet comentaris, així que els posem aquí:
| Camp | Tipus | Per què és així |
|---|---|---|
id (esdeveniment) |
"evt-NNN" |
Prefix llegible: en llegir un registre saps de què és sense mirar el context |
titol |
text | El que veu el públic |
sala |
text | De moment el nom; al Mòdul 7 passarà a ser una referència a la taula sala |
organitzador |
"org-xxx" |
Referència a l'organitzador; sosté el control per rols del Mòdul 8 |
categoria |
text | Serveix per filtrar i agrupar el catàleg |
duracioMinuts |
enter | En minuts, mai en text tipus "1h 35min": les dades es calculen, no es llegeixen |
estat |
text | esborrany, publicat o finalitzat |
descripcio |
text | Text comercial breu |
sessions |
array | Imbricades dins de l'esdeveniment: és la forma natural del document JSON i la que farem servir amb MongoDB |
sessions[].id |
"ses-NNN-M" |
Inclou el número de l'esdeveniment: llegible i traçable d'un cop d'ull |
dataHora |
ISO 8601 | Cadena AAAA-MM-DDTHH:mm:ss: ordenable com a text i estable en passar per JSON |
aforament |
enter | Localitats a la venda en aquesta sessió |
venudes |
enter | Comptador d'entrades ja venudes |
preuCentims |
enter | 2500 = 25,00 EUR. Mai decimals |
Comprova que el fitxer és JSON vàlid abans de continuar:
node -p "require('./dades/esdeveniments.json').length"
# 3
node -e "require('./dades/esdeveniments.json').forEach(e => console.log(e.id, '-', e.titol))"
# evt-001 - Concierto de Otono
# evt-002 - Noche de Monologos
# evt-003 - Festival de Jazz de PrimaveraSi l'ordre falla amb
SyntaxError, hi ha una coma de més, una cometa sense tancar o has fet servir cometes simples. En JSON totes les claus i totes les cadenes van entre cometes dobles, i no s'admet coma després de l'últim element.
Encara no sabem llegir aquest fitxer des del codi amb fs —això és el Mòdul 3—, però ja és al seu lloc i ja és la referència. A partir d'aquí, src/cataleg-dades.js deixarà de tenir les dades incrustades.
- L'estructura de carpetes del repositori
Aquesta és l'estructura objectiu, la que tindrà el projecte al final del curs. No la creïs sencera ara: creixerà amb cada mòdul.
escena-viva/ ├── .env # Secrets locals (MAI al repositori) - Mòdul 11 ├── .gitignore ├── .nvmrc # Versió de Node del projecte ├── package.json # Metadades i dependències - Mòdul 5 ├── dades/ │ ├── esdeveniments.json # Llavor del catàleg │ └── vendes.csv # Històric de vendes per a informes - Mòdul 3 ├── informes/ # Sortides generades (ignorades per git) ├── src/ │ ├── cataleg.js # Punt d'entrada del catàleg per consola │ ├── cataleg-dades.js # Accés a les dades del catàleg │ ├── domini/ # Classes Esdeveniment, Sessio, Comanda - Mòdul 2 │ ├── servidor/ # Servidor HTTP i rutes - Mòduls 4 i 6 │ ├── middleware/ # Middleware d'Express - Mòdul 6 │ ├── models/ # Models de base de dades - Mòdul 7 │ ├── auth/ # Autenticació i rols - Mòdul 8 │ └── utils/ # Formatatge, validacions, ajudants └── proves/ # Proves automatitzades - Mòdul 9
Principis d'organització que seguirem:
- Una responsabilitat per carpeta. Si no saps on posar un fitxer, probablement fa dues coses.
src/conté només codi;dades/només dades;informes/només resultats generats. Els resultats generats no es versionen mai.- Noms de fitxer en minúscules i amb guions (
cataleg-dades.js), mai amb espais ni majúscules. Linux distingeix majúscules i minúscules encara que el teu portàtil no ho faci, i això ha trencat més desplegaments dels que es poden comptar.
- Full de ruta: què construeix cada mòdul
Aquesta taula és el mapa del curs. Guarda-la: cada vegada que comencis un mòdul sabràs quina peça d'Escena Viva estàs afegint.
| Mòdul | Tema | Lliurament a Escena Viva |
|---|---|---|
| 1 | Introducció | Entorn instal·lat, esquelet del projecte, catàleg per consola, llavor dades/esdeveniments.json |
| 2 | Conceptes bàsics | Classes de domini (Esdeveniment, Sessio) separades en mòduls; càrrega asíncrona del catàleg; un EventEmitter que avisa quan una sessió s'exhaureix |
| 3 | Sistema de fitxers i E/S | Lectura real de dades/esdeveniments.json amb fs; generació d'informes a informes/; processament de dades/vendes.csv amb streams sense carregar-lo en memòria |
| 4 | HTTP i servidors web | Primer servidor propi: GET /esdeveniments, GET /esdeveniments/:id, alta de comandes per POST amb JSON, i consum d'una API externa de tipus de canvi |
| 5 | NPM | package.json del projecte, dependències, scripts npm start i npm run informe, i publicació d'un paquet propi d'utilitats de format |
| 6 | Express | Migració del servidor a Express: rutes del catàleg i de comandes, middleware de registre, validació de la compra i gestió centralitzada d'errors |
| 7 | Bases de dades | Substitució del JSON per la base de dades escena_viva: models, CRUD d'esdeveniments i sessions, consultes de disponibilitat, i transaccions perquè l'aforament no es sobrevengui mai |
| 8 | Autenticació | Registre i accés d'assistents, hash de contrasenyes, sessió amb JWT, i rols: assistent, organitzador i administrador |
| 9 | Proves i depuració | Proves unitàries del càlcul d'aforament i preus, proves d'integració de l'API de compra, cobertura i depuració d'una errada real |
| 10 | Temes avançats | Aguantar el pic d'obertura de venda: cluster, generació de PDF en worker threads, memòria cau de catàleg i cua de correus amb Redis, i l'API REST ben dissenyada |
| 11 | Desplegament i DevOps | Configuració per entorn, registre estructurat, PM2, imatge Docker de la plataforma, desplegament i canonada d'integració contínua |
| 12 | Projectes del món real | Extensions sobre allò après i tancament: de projecte a producció |
flowchart LR
M1["M1-2<br/>Dades en memòria<br/>i consola"] --> M3["M3<br/>Fitxers<br/>JSON i CSV"]
M3 --> M4["M4-6<br/>API HTTP<br/>i Express"]
M4 --> M7["M7-8<br/>Base de dades<br/>i usuaris"]
M7 --> M9["M9-10<br/>Proves i<br/>rendiment"]
M9 --> M11["M11-12<br/>Desplegament<br/>i producció"]
- Convencions del projecte
Aquestes regles s'apliquen a tot el codi del curs. Escrites una vegada aquí, no les tornarem a discutir.
7.1 Identificadors en català
Variables, funcions, classes, propietats i noms de fitxer van en català, sense accents ni ç: preuCentims, entradesLliures, calcularOcupacio, cataleg-dades.js.
Les excepcions són les que imposa l'entorn: paraules clau del llenguatge (const, class, return), APIs de Node i del navegador (readFile, createServer, map), i noms de paquets (express, mongoose).
Per què sense accents? Perquè un identificador sessió és vàlid en JavaScript però es converteix en una font d'errors tan bon punt viatja per una URL, un nom de fitxer o una capçalera HTTP. Val més evitar-ho d'arrel.
Estil: camelCase per a variables i funcions, PascalCase per a classes, MAJUSCULES_AMB_GUIO_BAIX per a constants globals.
7.2 Dates en format ISO 8601
Tota data es desa com a cadena AAAA-MM-DDTHH:mm:ss.
const dataHora = '2026-10-03T20:00:00'; // Be
const data = '03/10/2026 20:00'; // Malament: ambigu i no ordenableTres raons:
- S'ordena correctament com a text, sense convertir a
Date. - No és ambigu:
03/10/2026és 3 d'octubre a Catalunya i 10 de març als Estats Units. - Sobreviu al viatge per JSON. Un objecte
Datees converteix en text en serialitzar i no torna a serDateen deserialitzar; si ja deses text, no hi ha sorpreses.
El formatatge a 03/10/2026 20:00 és responsabilitat de la capa de presentació, mai de l'emmagatzematge.
7.3 Preus en cèntims com a enter
Tot import monetari es desa com a nombre enter de cèntims. 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 centims, exacte
console.log((5997 / 100).toFixed(2)); // '59.97'El tipus number de JavaScript és un flotant de doble precisió (IEEE 754) i no pot representar exactament 0,1. Amb diners, aquest error s'acumula i acaba en un desquadrament comptable. Treballant amb enters el problema desapareix: només es divideix entre 100 en l'últim moment, per mostrar.
Convenció de noms: qualsevol camp monetari porta el sufix Centims (preuCentims, totalCentims, descompteCentims). Així és impossible confondre's.
7.4 Codis d'entrada
Cada entrada té un codi amb el format:
EV-2026-000123 │ │ └── Sequencia de 6 digits amb zeros a l'esquerra │ └─────── Any d'emissio └────────── Prefix d'Escena Viva
function generarCodiEntrada(any, sequencia) {
// padStart omple amb zeros fins a 6 caracters: 123 -> '000123'
return `EV-${any}-${String(sequencia).padStart(6, '0')}`;
}
console.log(generarCodiEntrada(2026, 123)); // EV-2026-000123Per què aquest format i no un identificador aleatori llarg?
- És llegible per telèfon. Algú el pot dictar a atenció al client sense errors.
- És ordenable i el seu any és visible a simple vista.
- És curt per imprimir-lo sota un codi de barres.
La contrapartida és que és predictible: si coneixes un codi pots endevinar el següent. Per això el codi no és mai la prova de propietat; la validació a la porta comprova a més l'estat de l'entrada a la base de dades. És una decisió conscient, del tipus que es pren constantment en un projecte real.
7.5 Altres convencions
| Convenció | Regla |
|---|---|
| Codificació de fitxers | UTF-8 sense BOM |
| Finals de línia | \n (LF), també a Windows |
| Indentació | 2 espais |
| Cometes | Simples en JavaScript, dobles obligatòries en JSON |
| Punt i coma | Sí, sempre |
| Idioma dels comentaris | Català |
| Format dels fitxers JSON | JSON.stringify(dades, null, 2) |
Dades per stdout, diagnòstics per stderr |
Sempre |
- Nota sobre dades fictícies
Totes les dades d'aquest curs són inventades. Les sales, els esdeveniments, els organitzadors, els correus i les comandes que apareixeran a partir del Mòdul 7 no corresponen a persones ni empreses reals.
Això no és un formalisme: és una pràctica professional que has de traslladar a la teva feina.
- No facis servir mai dades reals de clients en desenvolupament, en proves ni en demostracions. Ni tan sols «només aquesta vegada», ni tan sols una còpia de la base de dades de producció al teu portàtil.
- Les bases de dades de desenvolupament es poblen amb dades sintètiques, generades o anonimitzades. Al Mòdul 7 veurem com escriure llavors per a això.
- Les dades personals estan regulades. Noms, correus, telèfons i dades de pagament tenen obligacions legals de tractament i minimització.
- No pugis mai secrets al repositori: claus de la passarel·la de pagament, contrasenyes de base de dades o tokens. Van en variables d'entorn (Mòdul 11) i per això el
.gitignoreinclou.envdes de la primera lliçó.
Quan al Mòdul 8 registrem l'usuari [email protected], fixa't en el domini .test: està reservat per norma per a proves i no correspondrà mai a una bústia real.
Errors Comuns i Consells
Error 1: desar els preus com a decimals «perquè és més còmode». Ho és durant dues setmanes. Després arriba el primer desquadrament d'un cèntim en un informe de 3.000 comandes i no hi ha manera de saber d'on surt.
Error 2: fer servir formats de data locals a les dades.
03/10/2026 obliga a saber qui ho va escriure per interpretar-ho. ISO 8601 a l'emmagatzematge, format local només en mostrar.
Error 3: posar l'aforament a l'esdeveniment en comptes de a la sessió. Funciona fins a la primera sessió amb aforament reduït, i llavors cal migrar dades.
Error 4: deixar el fitxer JSON amb una única línia gegant.
Cada canvi apareix al control de versions com «tota la línia modificada» i les revisions es tornen impossibles. null, 2 sempre.
Error 5: barrejar idiomes als identificadors.
getEsdevenimentById, llistarEvents, preuPrice. Tria'n un —en aquest curs, el català— i sigues sistemàtic.
Consell 1: torna al full de ruta en començar cada mòdul. Saber quina peça estàs construint canvia completament la manera d'estudiar una API.
Consell 2: fes servir git des del primer dia. Un git commit en acabar cada lliçó et dona un punt de retorn i un històric del teu propi aprenentatge.
Consell 3: no avancis feina. És temptador muntar ja el servidor Express. Cada mòdul introdueix els conceptes en un ordre pensat; saltar-se'l acaba sovint en codi que funciona sense entendre per què.
Consell 4: quan dubtis, mira el model de domini. Bona part de les decisions de codi es responen soles mirant la taula d'entitats.
Exercicis
Exercici 1: preparar l'esquelet del projecte
Deixa el projecte llest per al Mòdul 2. Ha de complir:
- La carpeta
escena-viva/contésrc/,dades/iinformes/. dades/esdeveniments.jsonexisteix amb els tres esdeveniments i és JSON vàlid.informes/està sota control de versions encara que sigui buida (pista:.gitkeep)..gitignoreexclounode_modules/,.env,*.logi el contingut generat d'informes/sense excloure la carpeta..nvmrcconté la versió LTS instal·lada.- Existeix un
README.mdamb el nom del projecte, els tres esdeveniments i les ordres per arrencar. - Un primer commit de git amb tot l'anterior.
Escriu la seqüència completa d'ordres i el contingut de cada fitxer.
Exercici 2: ampliar la llavor amb un quart esdeveniment
Afegeix a dades/esdeveniments.json un quart esdeveniment amb aquestes característiques:
- Títol:
Cuentos al Anochecer - Sala: Teatro Almendra (organitzador
org-almendra) - Categoria:
familiar, durada 55 minuts, estatpublicat - Tres sessions els dissabtes 7, 14 i 21 de novembre del 2026 a les 18:00
- Aforament 200 a cada sessió (el Teatro Almendra reserva part de la platea per a aquest format)
- Entrades venudes: 200, 145 i 30 respectivament
- Preu: 1200 cèntims les dues primeres i 900 la tercera
Respecta els formats d'id, de data i de preu del projecte. Després, verifica amb ordres node -p que:
- El fitxer continua sent JSON vàlid i té 4 esdeveniments.
- El catàleg té ara 10 sessions.
- L'aforament total del Teatro Almendra (sumant tots els seus esdeveniments) és correcte.
- Hi ha exactament una sessió exhaurida.
Exercici 3: aplicar les convencions
Un company ha escrit aquest fragment. Reescriu-lo respectant totes les convencions del projecte i explica cada canvi.
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))Solucions
Solució 1
# 1. Estructura de carpetes
mkdir -p escena-viva/src escena-viva/dades escena-viva/informes
cd escena-viva
# 3. Mantenir "informes" al repositori encara que estigui buida
touch informes/.gitkeep
# 5. Versio de Node del projecte
node -v > .nvmrc.gitignore (punt 4). La clau és l'excepció amb !, per ignorar el contingut però conservar la carpeta:
node_modules/ .env *.log # Ignorem el que es genera a informes i mantenim la carpeta informes/* !informes/.gitkeep
README.md (punt 6):
# Escena Viva
Plataforma de venda d'entrades per a esdeveniments culturals del Teatro Almendra,
la Sala Boveda i l'Auditorio Ribera.
## Esdeveniments en cartell
- Concierto de Otono (Teatro Almendra)
- Noche de Monologos (Sala Boveda)
- Festival de Jazz de Primavera (Auditorio Ribera)
## Requisits
- Node.js: la versio indicada a `.nvmrc`
## Posada en marxa
nvm use
node src/cataleg.js
## Estructura
- `src/` codi font
- `dades/` dades de partida (esdeveniments.json)
- `informes/` sortides generades (no es versionen)Verificació i primer commit (punts 2 i 7):
node -p "require('./dades/esdeveniments.json').length" # 3
git init
git add .
git commit -m "Esquelet del projecte Escena Viva amb cataleg llavor"
git log --onelineSolució 2
Bloc que cal inserir al final de l'array de dades/esdeveniments.json (recorda la coma després de la clau de tancament de l'esdeveniment anterior):
{
"id": "evt-004",
"titol": "Cuentos al Anochecer",
"sala": "Teatro Almendra",
"organitzador": "org-almendra",
"categoria": "familiar",
"duracioMinuts": 55,
"estat": "publicat",
"descripcio": "Narracio oral per a public familiar a la caiguda de la tarda.",
"sessions": [
{
"id": "ses-004-1",
"dataHora": "2026-11-07T18:00:00",
"aforament": 200,
"venudes": 200,
"preuCentims": 1200
},
{
"id": "ses-004-2",
"dataHora": "2026-11-14T18:00:00",
"aforament": 200,
"venudes": 145,
"preuCentims": 1200
},
{
"id": "ses-004-3",
"dataHora": "2026-11-21T18:00:00",
"aforament": 200,
"venudes": 30,
"preuCentims": 900
}
]
}Verificacions:
# 1. JSON valid i nombre d'esdeveniments
node -p "require('./dades/esdeveniments.json').length"
# 4
# 2. Total de sessions
node -p "require('./dades/esdeveniments.json').reduce((t, e) => t + e.sessions.length, 0)"
# 10
# 3. Aforament total del Teatro Almendra: 840 (evt-001) + 600 (evt-004) = 1440
node -p "require('./dades/esdeveniments.json').filter(e => e.sala === 'Teatro Almendra').flatMap(e => e.sessions).reduce((t, s) => t + s.aforament, 0)"
# 1440
# 4. Sessions exhaurides
node -p "require('./dades/esdeveniments.json').flatMap(e => e.sessions).filter(s => s.venudes >= s.aforament).map(s => s.id)"
# [ 'ses-004-1' ]Nota sobre el punt 4: ses-002-1 té 118 de 120 venudes, així que no està exhaurida. L'única exhaurida és la primera sessió de l'esdeveniment nou.
Solució 3
// src/utils/recaptacio.js
// Calcul de la recaptacio d'un conjunt de sessions.
const sessions = [
{ id: 'ses-002-1', dataHora: '2026-10-10T21:30:00', aforament: 120, venudes: 118, preuCentims: 1800 },
{ id: 'ses-002-2', dataHora: '2026-10-11T21:30:00', aforament: 120, venudes: 45, preuCentims: 1800 }
];
// Retorna la recaptacio total en centims (enter).
function calcularRecaptacioCentims(sessions) {
return sessions.reduce(
(total, sessio) => total + sessio.venudes * sessio.preuCentims,
0
);
}
const totalCentims = calcularRecaptacioCentims(sessions);
console.log(`Recaptacio: ${(totalCentims / 100).toFixed(2)} EUR`);
// Recaptacio: 2934.00 EURCanvis aplicats i el seu motiu:
| Canvi | Motiu |
|---|---|
var → const |
Àmbit de bloc i sense reassignacions accidentals |
Sessions → sessions |
Identificadors en català i camelCase (PascalCase es reserva per a classes) |
ID, date, capacity, sold, price → id, dataHora, aforament, venudes, preuCentims |
Noms del model de domini, en català |
"S1" → 'ses-002-1' |
Format d'identificador del projecte, amb prefix i traçabilitat |
"10/10/2026 21:30" → '2026-10-10T21:30:00' |
ISO 8601: sense ambigüitat i ordenable com a text |
price: 18.00 → preuCentims: 1800 |
Diners com a enter de cèntims; el sufix ho fa explícit |
getTotalRevenue → calcularRecaptacioCentims |
Català i amb el sufix que indica la unitat retornada |
Bucle for amb índex → reduce |
Més declaratiu i sense variable mutable |
Concatenació amb + → plantilla de cadena |
Llegibilitat |
| Cometes dobles → simples | Convenció de JavaScript del projecte (les dobles queden per a JSON) |
| Punt i coma afegits | Convenció del projecte |
| Comentaris en català afegits | Convenció del projecte |
Comprovació del càlcul: 118 × 1800 + 45 × 1800 = 212.400 + 81.000 = 293.400 cèntims = 2.934,00 EUR. Amb preus decimals el resultat hauria estat el mateix en aquest cas concret, però n'hi ha prou amb un preu de 18,99 i uns centenars d'operacions perquè comencin a aparèixer cèntims fantasma.
Conclusió
Escena Viva ja no és un decorat: és un projecte amb context, actors, model de domini, dades i regles pròpies. Has vist qui fa servir la plataforma —assistent, organitzador i administrador— i com aquesta separació anticipa el control per rols; has formalitzat les entitats esdeveniment, sessio, entrada, comanda, usuari, organitzador i sala, amb les seves relacions i els seus estats; has escrit el fitxer llavor dades/esdeveniments.json que serà la font de dades fins que aparegui la base de dades escena_viva; i has fixat les convencions que no es tornaran a discutir: identificadors en català sense accents, dates ISO 8601, preus en cèntims com a enter, codis d'entrada EV-2026-000123, JSON amb null, 2 i dades sempre fictícies.
Sobretot, tens el full de ruta. Saps que al Mòdul 3 el catàleg deixarà d'estar incrustat al codi per llegir-se de disc, que al Mòdul 4 es traurà el nas per HTTP, que al Mòdul 7 es mudarà a una base de dades amb transaccions que impedeixin sobrevendre l'aforament, i que al Mòdul 10 aprendràs a sostenir el pic de trànsit de l'obertura de venda d'un festival.
Amb això tanquem el Mòdul 1. Tens l'entorn instal·lat amb una versió LTS gestionada per .nvmrc, saps executar i depurar un script, manejes el REPL com a laboratori, domines el JavaScript modern que farem servir i tens el projecte dret amb la seva llavor de dades.
El que encara no saps és com funciona Node.js per dins, i és la peça que falta per escriure codi de servidor de debò. Al Mòdul 2: Conceptes Bàsics obrirem la caixa: l'arquitectura de Node, el bucle d'esdeveniments i les seves fases, els callbacks, les promeses i async/await, els esdeveniments amb EventEmitter i el sistema de mòduls amb require i module.exports. Allà, per fi, l'array d'esdeveniments incrustat a src/cataleg-dades.js es convertirà en un mòdul reutilitzable, naixeran les classes Esdeveniment i Sessio a src/domini/, i el cambrer que no espera de la primera lliçó deixarà de ser una analogia per convertir-se en el codi que escrius.
Curs de Node.js: De Principiant a Avançat
Mòdul 1: Introducció a Node.js
- Què és Node.js?
- Instal·lació i Configuració de l'Entorn
- El Teu Primer Programa en Node.js
- El REPL de Node.js
- JavaScript Modern per a Node.js
- El Projecte del Curs: la Plataforma Escena Viva
Mòdul 2: Conceptes Bàsics
- Arquitectura de Node.js
- El Bucle d'Esdeveniments (Event Loop)
- Callbacks i Programació Asíncrona
- Promeses i async/await
- Esdeveniments i EventEmitter
- Mòduls CommonJS i require()
- Mòduls ES i Interoperabilitat
Mòdul 3: Sistema de Fitxers i E/S
- Lectura i Escriptura de Fitxers
- El Mòdul fs a Fons
- Rutes Multiplataforma amb el Mòdul path
- Treballant amb Streams
- Streams de Transformació i pipeline
- Buffers i Dades Binàries
Mòdul 4: HTTP i Servidors Web
- Creant un Servidor HTTP Simple
- Gestió de Sol·licituds i Respostes
- Enrutament Manual
- Servint Fitxers Estàtics
- Rebent Dades: Cossos de Petició i JSON
- Consumint APIs Externes des de Node.js
Mòdul 5: NPM i Gestió de Paquets
- Introducció a NPM i package.json
- Instal·lació i Ús de Paquets
- Versionat Semàntic i package-lock
- Scripts d'npm i Automatització del Projecte
- Creació i Publicació de Paquets
- Seguretat i Manteniment de Dependències
Mòdul 6: Framework Express.js
- Introducció a Express.js
- Configuració d'una Aplicació Express
- Enrutament a Express
- Middleware
- Middleware de Tercers Essencials
- Validació de Dades d'Entrada
- Gestió d'Errors
Mòdul 7: Bases de Dades i ORMs
- Introducció a les Bases de Dades
- Usant MongoDB amb Mongoose
- Operacions CRUD
- Relacions, Poblat i Consultes Avançades
- Usant Bases de Dades SQL amb Sequelize
- Migracions, Transaccions i Dades de Prova
Mòdul 8: Autenticació i Autorització
- Introducció a l'Autenticació
- Registre d'Usuaris i Hash de Contrasenyes
- Sessions i Galetes amb Passport.js
- Autenticació amb JWT
- Control d'Accés Basat en Rols
- Bones Pràctiques de Seguretat en APIs
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb Mocha i Chai
- Dobles de Prova amb Sinon
- Proves d'Integració
- Cobertura i Automatització de les Proves
- Depuració d'Aplicacions Node.js
Mòdul 10: Temes Avançats
- El Mòdul Cluster
- Fils de Treball (Worker Threads)
- Memòria Cau i Cues de Treball amb Redis
- Optimització del Rendiment
- Construcció d'APIs RESTful
- GraphQL amb Node.js
Mòdul 11: Desplegament i DevOps
- Configuració i Variables d'Entorn
- Registre i Monitoratge en Producció
- Usant PM2 per a la Gestió de Processos
- Empaquetatge amb Docker
- Desplegant a Heroku i Altres PaaS
- Integració i Desplegament Continus
