📚 Unidad ISS-01 · Esqueleto del proyecto — capa 🧠 APRENDER
🛠 Construir este bloque → · ✅ Condiciones de cierre (GATE) · 📝 Evaluación
Capa Página Para qué 🧠 Aprender esta página comprender, explicar y relacionar 🛠 Construir Esqueleto del proyecto ejecutar, programar y verificar ✅ GATE Cierre de la unidad condición para pasar al bloque siguiente
Mapa de correspondencias. Cada fila enlaza el mismo tema en las dos capas de la unidad; los enlaces apuntan a secciones reales del material (anclas de MkDocs, sin acentos).
| Tema | 🧠 Aprender (esta página) | 🛠 Construir (ISS técnico) |
|---|---|---|
| Ruta y ficha de la unidad | Ruta de aprendizaje · Ficha del ISS | Contenido de la unidad |
| Mapas y estructura | Mapa mental · Mapa del backend · Árbol de archivos | Contenido de la unidad |
| Recorrido y comandos | Comandos explicados · Recorrido paso a paso | Contenido de la unidad |
| Diagnóstico | Diagnóstico | Criterios de aceptación |
| Evaluación y cierre | Criterios · Evaluación · GATE | Condiciones de cierre · Cierre |
🖥 Presentación del ISS
Presentación de la unidad. Diapositivas de ISS-01 — Esqueleto del proyecto (9 diapositivas). Se visualiza aquí, dentro del sitio; no hace falta descargar nada para estudiarla.
🎬 Video explicativo
Recorrido audiovisual de la unidad. Este video presenta visualmente la construcción, la estructura, el flujo de ejecución y la verificación de ISS-01.
7:44 min · narración en español · subtítulos activables desde el reproductor.
ISS-01 — Cuaderno de aprendizaje visual
Tema
Esqueleto del proyecto: crear el proyecto npm en TypeScript con Express 5, la estructura de carpetas por features y un servidor HTTP base que arranca.
Fuente técnica autoritativa
| Archivo fuente | ../manual/02-ISS-01-esqueleto-proyecto.md |
| Estado | Solo lectura — este cuaderno no modifica el ISS |
| Alcance | 401 líneas, 15 bloques de código, 1 lista de criterios consolidados (6 ítems) |
El archivo 02-ISS-01-esqueleto-proyecto.md es la fuente autoritativa y de solo lectura. Todo su contenido técnico —comandos, versiones, archivos y criterios— aparece en este cuaderno íntegro y verbatim en la sección Recorrido del ISS, paso a paso. El ISS manda; el cuaderno explica el por qué.
Regla del ISS
Objetivo: proyecto npm + TypeScript + Express con estructura
features/y servidor HTTP base. Bloqueado por: ISS-00. Criterio de cierre:npx tsc --noEmitsin errores, ynpm run devarranca el servidor.
La condición que el propio ISS exige es doble: el proyecto debe compilar sin errores de tipos y el servidor debe levantarse. No basta con que existan los archivos: la prueba es que TypeScript los acepte y que Express escuche en el puerto. Este es el primer ISS que escribe código real, así que el listón sube: ya no se comprueba el entorno, se comprueba el proyecto.
Cómo leer este cuaderno
Cada concepto se presenta tres veces, desde tres ángulos distintos:
CONCEPTO
│
┌───────────┼───────────┐
▼ ▼ ▼
EXPLICACIÓN CÓDIGO VISUAL
│ │ │
¿qué es? ¿dónde está? ¿cómo lo
¿por qué? ¿qué hace? visualizo?
¿para qué? ¿cómo opera? ¿con qué
se relaciona?
Pregunta que responde: ¿desde qué tres ángulos voy a estudiar cada concepto de este ISS?
El recorrido de lectura es siempre el mismo:
Primero entiendes qué se construye y por qué; después ves el código exacto que lo construye (en el cuerpo incrustado del ISS); finalmente lo visualizas con diagramas y mapas para fijar cómo encajan las piezas. Y cada cuaderno contiene los mismos seis componentes:
| Componente | Dónde vive | Para qué sirve |
|---|---|---|
| Texto | todas las secciones | entender el por qué de cada decisión |
| Código | Recorrido del ISS |
ver el qué exacto, sin resúmenes |
| Diagramas | Mapa mental, Mapa del backend, Árbol de archivos, Flujos |
ver cómo se conecta todo |
| Preguntas | Evaluación |
comprobar que entendiste |
| Evaluación | Evaluación |
practicar y autoevaluarte con respuestas razonadas |
| GATE | GATE |
saber si puedes pasar al siguiente ISS |
Pregunta que responde: ¿qué seis componentes tiene el cuaderno y en qué sección busco cada uno?
Ruta de aprendizaje
Esta ruta es específica de este ISS: sigue, en orden, lo que el ISS-01 construye de verdad.
Inicializar npm (package.json)
↓
Definir scripts build y dev + type commonjs
↓
Crear la estructura de carpetas por features
↓
Instalar dependencias (Express 5 + TypeScript)
↓
Configurar tsconfig.json estricto
↓
Escribir src/server.ts (punto de entrada)
↓
Definir la clase App en src/config/index.ts
↓
Verificar tipos (npx tsc --noEmit)
↓
Arrancar el servidor (npm run dev) → GATE
Pregunta que responde: ¿en qué orden debo ejecutar los pasos del ISS-01 para no dejarme ninguno?
Fíjate en lo que no aparece todavía: no hay rutas ni controladores reales, no hay modelos, no hay conexión a base de datos y no hay migraciones. Todo eso es ISS-02 y posteriores.
Índice
- Tema
- Fuente técnica autoritativa
- Regla del ISS
- Cómo leer este cuaderno
- Ruta de aprendizaje
- Ficha del ISS
- Mapa mental del ISS
- Mapa del backend
- Árbol de archivos
- Anatomía del código
- Comandos explicados
- Flujos
- Diagnóstico
- Recorrido del ISS, paso a paso
- Criterios de aceptación
- Evaluación
- Conexión con el resto del curso
- Glosario
- GATE
Ficha del ISS
| Campo | Valor |
|---|---|
| ISS | ISS-01 |
| Título | Esqueleto del proyecto |
| Objetivo | Proyecto npm + TypeScript + Express con estructura features/ y servidor HTTP base |
| Fase | Fase I — Business |
| Tecnología principal | Node.js, npm, TypeScript 5.9 (compilado con tsc), Express 5 |
| Depende de | ISS-00 — Requisitos previos |
| Habilita | ISS-02 — Infraestructura de base de datos |
| Archivos creados | package.json, tsconfig.json, src/server.ts, src/config/index.ts y el árbol de carpetas src/ |
| Archivos parcheados | package.json (scripts build/dev y "type": "commonjs") |
| Componentes incorporados | Proyecto npm, estructura por features, dependencias base, tsconfig.json estricto, esqueleto de la clase App |
| Verificación principal | npx tsc --noEmit sin errores |
| Resultado esperado | Servidor Express escuchando en el puerto 4000 (o process.env.PORT) |
| GATE | npx tsc --noEmit OK y npm run dev arranca sin error |
Qué implementamos AHORA
Este ISS monta el andamio del backend:
- El proyecto npm (
package.json) con"type": "commonjs"y los scriptsbuild/dev. - La estructura de carpetas por features:
config,database/seeders,routes,sharedyfeatures/business/clients. - Las dependencias base:
express,cors,dotenv,morganen producción;typescript,ts-node,nodemony los@types/*en desarrollo. - El
tsconfig.jsonestricto conrootDir: ./src,outDir: ./distystrict: true. - El esqueleto HTTP:
src/server.tscomo punto de entrada y la claseAppensrc/config/index.ts, con sus etapassettings,middlewares,routes,docs,errorHandling,dbConnectionylisten.
En términos de arquitectura, esto deja HTTP → Express App funcionando. Las capas internas todavía no existen.
Qué todavía NO implementamos
Para que no confundas el estado actual con la meta del curso:
| No se implementa aquí | Llega en |
|---|---|
Conexión real a la base de datos (sequelize, .env, db.ts) |
ISS-02 |
Feature clients completo (model, repository, service, controller, routes, DTO) |
ISS-03 |
Seeders (SeedersRunner, counts, @faker-js/faker) |
ISS-04 |
Swagger (setupSwagger, /api/docs) |
ISS-05 |
Features product-types, products, sales y sus asociaciones |
ISS-06 … ISS-08 |
| Autenticación y RBAC (Fase II) | ISS-09 … ISS-15 |
Aviso de lectura. El bloque de
src/config/index.tsque muestra el ISS es la versión consolidada del archivo: importa los modelos,Routesy Swagger que todavía no existen, marcados en el propio ISS como placeholders. En ISS-01 escribes el esqueleto de la claseApp; esos imports y elsynccompleto se añaden con PARCHE en ISS-02…08. Sinpx tsc --noEmitse queja de módulos inexistentes, es exactamente eso: código que aún está por llegar, no un error tuyo.
Mapa mental del ISS
mindmap
root((ISS-01))
Objetivo
Proyecto npm
TypeScript
Servidor Express
package.json
script build
script dev
type commonjs
Estructura
config
database
seeders
routes
shared
features
business
clients
Dependencias
express
cors
dotenv
morgan
typescript
ts-node
nodemon
Servidor
server.ts
App en config
Verificación
npx tsc --noEmit
npm run dev
Pregunta que responde: ¿de qué trata el ISS-01 y qué piezas lo componen?
Mapa del backend
Esta representación responde siempre a la misma pregunta: ¿en qué parte del backend estamos? El objetivo de arquitectura del curso es HTTP → Routes → Controller → Service → Repository → Model → Sequelize → BD. En ISS-01 solo existe el extremo HTTP.
IMPLEMENTADO HASTA ESTE ISS
───────────────────────────
HTTP ✅
↓
Express App (esqueleto) ✅
├── settings (puerto)
├── middlewares (morgan, cors, json, urlencoded)
├── routes (placeholder; sin features aún)
├── docs (placeholder; Swagger llega en ISS-05)
├── errorHandling (JSON malformado → 400)
├── dbConnection (placeholder; BD llega en ISS-02)
└── listen (abre el puerto)
↓
🎯 Routes (objetivo de arquitectura)
↓
🎯 Controller
↓
🎯 Service
↓
🎯 Repository
↓
🎯 Model
↓
🎯 Sequelize
↓
🎯 Base de datos
OBJETIVO DE ARQUITECTURA
────────────────────────
HTTP → Routes → Controller → Service → Repository → Model → Sequelize → BD
⬜ Capas de negocio, sin empezar (ISS-03 en adelante)
⬜ Conexión a BD (ISS-02)
Pregunta que responde: ¿qué capas del backend existen ya y cuáles siguen siendo objetivo de arquitectura?
En este punto el proyecto ya arranca como servidor HTTP, pero todavía no atiende ninguna ruta de negocio real. La arquitectura completa de arriba es la meta del curso, no una descripción del estado actual.
Árbol de archivos
Estructura antes
Al terminar ISS-00 el terreno estaba preparado, pero no existía proyecto:
Archivos creados / modificados en este ISS
★ package.json # creado con npm init -y, parcheado con scripts/type
★ tsconfig.json # configuración de TypeScript
★ src/
├── server.ts # ★ punto de entrada
├── config/
│ └── index.ts # ★ clase App (esqueleto HTTP)
├── database/
│ └── seeders/ # ★ carpeta (sin lógica; runner en ISS-04)
├── routes/ # ★ carpeta (agregador; se llena en ISS-03+)
├── shared/
│ ├── database/ # ★ carpeta (with-transaction.ts en ISS-08)
│ ├── errors/ # ★ carpeta (app-error.ts en ISS-08/09)
│ └── http/ # ★ carpeta (base-controller.ts en ISS-03)
└── features/
└── business/
└── clients/ # ★ carpeta (capas en ISS-03)
Estructura después
app-storelab-express/
├── ★ package.json
├── ★ tsconfig.json
├── docs/ # ★ carpeta creada por el ISS
└── src/
├── ★ server.ts
├── ★ config/
│ └── ★ index.ts
├── ★ database/
│ └── ★ seeders/
├── ★ routes/
├── ★ shared/
│ ├── ★ database/
│ ├── ★ errors/
│ └── ★ http/
└── ★ features/
└── ★ business/
└── ★ clients/
Leyenda: ★ = archivo/carpeta creado o trabajado en este ISS · △ = archivo existente parcheado.
Como ves, el ISS crea las carpetas vacías de las capas futuras (comodines de estructura) pero solo escribe código en tres archivos: package.json, tsconfig.json, src/server.ts y src/config/index.ts. Las carpetas vacías existen para que la estructura del laboratorio sea visible desde el principio.
Pregunta que responde: ¿qué archivos y carpetas crea el ISS-01 y cuáles quedan vacíos a propósito?
Anatomía del código
El código completo y literal de cada archivo está en el Recorrido del ISS, paso a paso. Aquí se explica qué hace y por qué, sin repetirlo.
Archivo: package.json
Propósito
Es el contrato del proyecto: declara el tipo de módulos (commonjs), las dependencias y los dos comandos que gobiernan todo el ciclo de trabajo (build y dev).
Explicación
"type": "commonjs"— fija el sistema de módulos que usa Node para este paquete. Todo el curso usarequire/importcompilado a CommonJS, así que esto evita sorpresas entre entornos."scripts"— el ISS deja solo dos:buildejecutatsc(compilasrc/adist/) ydevarrancanodemon, que vigilasrc/**/*.tsy relanzats-node -- src/server.tsen cada cambio.- El archivo no se escribe a mano: lo crea
npm init -yy el ISS lo parchea. Por eso en el árbol aparece como creado y parcheado a la vez.
Se conecta con
- Entrada: los comandos
npm run build,npm run devynpm install. - Salida: determina cómo se ejecuta
src/server.tsy qué dependencias están disponibles.
Archivo: tsconfig.json
Propósito
Configura cómo compila TypeScript: de dónde lee, dónde escribe y con qué nivel de rigor revisa los tipos.
Explicación
rootDir: "./src"/outDir: "./dist"— el código fuente vive ensrc/y la salida denpm run buildcae endist/. Separar ambos evita mezclar fuente y artefactos.strict: true— activa el modo estricto. Es la decisión más importante del archivo: obliga a tipar bien desde el primer día y convierte errores que en JavaScript pasarían desapercibidos en errores de compilación.module: "commonjs"/target: "ES2020"— coherencia con"type": "commonjs"delpackage.json.esModuleInterop,resolveJsonModule,sourceMap,skipLibCheck,isolatedModules,forceConsistentCasingInFileNames— ajustes de compatibilidad que permiten importar paquetes CommonJS con sintaxis ESM (import express from "express"), leer JSON y depurar con mapas de fuentes.
Se conecta con
- Entrada: lo invoca
tsc(scriptbuild) yts-node(scriptdev), y lo validanpx tsc --noEmit. - Salida: define qué archivos se compilan (
include: ["src/**/*"]) y cuáles se ignoran (exclude: ["node_modules", "dist"]).
Archivo: src/server.ts
Propósito
Es el punto de entrada del backend: una función main que instancia la aplicación y la pone a escuchar.
Explicación
- Importa la clase
Appdesde./config/index. - Define
async function main()que creanew App()y ejecutaawait app.listen(). - Llama a
main()al final. Es deliberadamente mínimo: toda la complejidad vive enApp, para que el arranque sea trivial de leer.
Se conecta con
- Entrada: lo ejecuta
ts-node -- src/server.ts(víanodemonendev) ynode dist/server.js(trasbuild). - Salida: llama al constructor y a
listen()deApp.
Archivo: src/config/index.ts
Propósito
Define la clase App: el esqueleto HTTP donde se configuran puerto, middlewares, rutas, documentación, manejo de errores y arranque.
Explicación
settings()— fija el puerto:this.port || process.env.PORT || 4000.middlewares()— registramorgan(logs),cors(peticiones cruzadas),express.json()yexpress.urlencoded()(parseo del cuerpo).routes()— placeholder: aquí se registrarán las rutas de cada feature cuando existan.docs()— placeholder: aquí entrará Swagger en ISS-05.errorHandling()— traduce un JSON malformado a un400JSON limpio, evitando que se filtre un stack trace con rutas del servidor.dbConnection()— placeholder: en ISS-02 conectará y sincronizará con la base de datos.listen()— ordena el arranque: primero la BD, después abrir el puerto. El propio ISS explica por qué: abrir el puerto antes de terminar unsync({ alter: true })haría competir las sentencias DDL con las peticiones entrantes y provocaría deadlocks y errores de claves foráneas intermitentes.
Nota de lectura: el archivo mostrado es la versión consolidada. Los imports de modelos,
Routes, Swagger y elsynccompleto se incorporan por PARCHE en ISS-02…08.
Se conecta con
- Entrada: lo instancia
src/server.ts; lo parchean los ISS posteriores. - Salida: usa
express,morgan,cors,dotenvy (más adelante)../database/db,../routes/indexy../swagger/index.
Comandos explicados
Ningún comando va sin explicación. Cada grupo se copia idéntico al ISS y se desglosa con el esquema obligatorio.
Inicializar el proyecto (§2.1)
COMANDO
↓
mkdir app-storelab-express
cd app-storelab-express
npm init -y
mkdir -p docs
↓
QUÉ HACE
Crea la carpeta del proyecto, entra en ella, genera un package.json con
valores por defecto y añade una carpeta docs/.
↓
POR QUÉ SE NECESITA
npm init -y es el punto de partida de todo proyecto Node: sin package.json
no hay dónde declarar dependencias ni scripts.
↓
QUÉ CREA O MODIFICA
La carpeta app-storelab-express/, el archivo package.json (mínimo) y docs/.
↓
RESULTADO ESPERADO
Un package.json con name, version, description y license; sin scripts aún.
↓
CÓMO VERIFICARLO
node -e "const p=require('./package.json'); console.log(p.scripts)"
La salida debe incluir los dos scripts: build (tsc) y dev (nodemon…).
Crear la estructura de carpetas (§2.2)
COMANDO
↓
mkdir -p \
src/config \
src/database/seeders \
src/routes \
src/shared/errors \
src/shared/http \
src/shared/database \
src/features/business/clients
↓
QUÉ HACE
Crea de una sola vez el árbol de carpetas del proyecto.
↓
POR QUÉ SE NECESITA
La arquitectura por features necesita una carpeta por preocupación; crearla
ahora fija la convención antes de escribir código.
↓
QUÉ CREA O MODIFICA
Las carpetas src/config, src/database/seeders, src/routes, src/shared/*
y src/features/business/clients (todas vacías).
↓
RESULTADO ESPERADO
El árbol de directorios completo, sin ningún archivo todavía.
↓
CÓMO VERIFICARLO
find src -type d | sort
Instalar dependencias (§2.3)
COMANDO
↓
npm install express@^5.2.1 cors@^2.8.6 dotenv@^17.4.2 morgan@^1.12.1
npm install -D typescript@~5.9.2 ts-node@^10.9.2 nodemon@^3.1.14 \
@types/node@^22.20.3 @types/express@^5.0.6 \
@types/cors@^2.8.19 @types/morgan@^1.9.10
↓
QUÉ HACE
Instala las dependencias de producción (el servidor) y, con -D, las de
desarrollo (compilador, runner y tipos).
↓
POR QUÉ SE NECESITA
express@5 levanta el servidor; morgan/cors/dotenv son utilidades base;
typescript+ts-node compilan y ejecutan TS; los @types/* dan tipado a las
librerías que no lo traen.
↓
QUÉ CREA O MODIFICA
node_modules/ y las secciones dependencies / devDependencies del package.json.
↓
RESULTADO ESPERADO
Versiones de Express 5.x, TypeScript 5.9.x y ts-node 10.9.x instaladas.
↓
CÓMO VERIFICARLO
npm ls --depth=0
Detalle del ISS: TypeScript se fija en 5.9.x por compatibilidad con
ts-node. Subir de versión sin comprobarts-nodepuede romper el scriptdev.
Configurar TypeScript (§2.4)
COMANDO
↓
: > tsconfig.json
cat >> tsconfig.json << 'EOF'
{ ...compilerOptions... }
EOF
↓
QUÉ HACE
Vacía (o crea) tsconfig.json y escribe dentro la configuración completa.
↓
POR QUÉ SE NECESITA
TypeScript necesita saber qué compilar y con qué reglas; sin tsconfig,
ts-node no sabe resolver la ruta de src/.
↓
QUÉ CREA O MODIFICA
Crea tsconfig.json con rootDir, outDir, strict y el resto de opciones.
↓
RESULTADO ESPERADO
Un tsconfig.json válido que valida con tsc.
↓
CÓMO VERIFICARLO
test -f tsconfig.json && npx tsc --showConfig | head -20
Escribir el servidor y la App (§2.5)
COMANDO
↓
: > src/server.ts
cat >> src/server.ts << 'EOF'
...contenido del punto de entrada...
EOF
: > src/config/index.ts
cat >> src/config/index.ts << 'EOF'
...contenido de la clase App...
EOF
↓
QUÉ HACE
Crea el punto de entrada y la clase App escribiendo su contenido desde
un heredoc.
↓
POR QUÉ SE NECESITA
Son los dos únicos archivos de código del ISS: sin ellos no hay servidor.
↓
QUÉ CREA O MODIFICA
src/server.ts y src/config/index.ts (el contenido íntegro está en el
Recorrido del ISS).
↓
RESULTADO ESPERADO
Ambos archivos existen y TypeScript los acepta.
↓
CÓMO VERIFICARLO
npx tsc --noEmit && find src -type f | sort
Verificar y arrancar (Cierre)
COMANDO
↓
npx tsc --noEmit
find src -type f | sort
↓
QUÉ HACE
Comprueba los tipos sin generar archivos y lista los archivos de src/.
↓
POR QUÉ SE NECESITA
--noEmit es la verificación de cierre del ISS: si pasa, el proyecto compila.
↓
QUÉ CREA O MODIFICA
Nada (no emite salida a dist/).
↓
RESULTADO ESPERADO
Silencio: ningún error de TypeScript; y la lista completa de archivos src/.
↓
CÓMO VERIFICARLO
Que el comando termine con código de salida 0.
COMANDO
↓
npm run dev
↓
QUÉ HACE
Arranca nodemon, que vigila src/ y ejecuta ts-node -- src/server.ts.
↓
POR QUÉ SE NECESITA
Es la prueba de fuego: el servidor debe levantar y quedar escuchando.
↓
QUÉ CREA O MODIFICA
Un proceso Node en primer plano (ningún archivo).
↓
RESULTADO ESPERADO
El mensaje de arranque y el puerto 4000 a la escucha.
↓
CÓMO VERIFICARLO
Ver el log del servidor; detenerlo después con Ctrl+C.
Flujos
El arranque del servidor es un flujo sencillo pero con un orden deliberado. Este diagrama muestra la cadena npm run dev → nodemon → ts-node → server.ts → App:
sequenceDiagram
participant Dev as Tú
participant Nod as nodemon
participant TS as ts-node
participant Srv as server.ts
participant App as App
Dev->>Nod: npm run dev
Nod->>TS: ejecuta src/server.ts
TS->>Srv: main
Srv->>App: new App
App->>App: settings
App->>App: middlewares
App->>App: routes
App->>App: docs
App->>App: errorHandling
Srv->>App: await listen
App->>App: dbConnection
App-->>Dev: Servidor en puerto 4000
Pregunta que responde: ¿qué ocurre desde que ejecuto
npm run devhasta que el servidor queda escuchando?
Observa el orden: el constructor ejecuta settings → middlewares → routes → docs → errorHandling y solo después listen() llama a dbConnection() y abre el puerto. Ese «primero infraestructura, después atender peticiones» es una regla de oro que se repetirá en todos los ISS.
Diagnóstico
| Síntoma | Causa probable | Solución |
|---|---|---|
Cannot find module 'express' |
Dependencias no instaladas | Ejecutar npm install con las líneas de §2.3 |
npm run dev no existe |
Falta parchear scripts en package.json |
Añadir build y dev; comprobar con node -e "..." |
| Error de tipos pese a tener todo instalado | Se subió TypeScript por encima de 5.9.x | Fijar typescript@~5.9.2 (compatible con ts-node) |
npx tsc --noEmit señala módulos inexistentes |
Normal en el esqueleto: imports de modelos/Routes/Swagger | Son placeholders; llegan por PARCHE en ISS-02…08 |
EADDRINUSE (puerto ocupado) |
Otro proceso usa el 4000 | Detener el proceso anterior o cambiar PORT |
Recorrido del ISS, paso a paso
A partir de aquí viene el contenido técnico completo del ISS, verbatim: sus objetivos, sus criterios, sus comandos y todos sus bloques de código. Se reproduce sin resumir y sin reformatear; solo se han degradado los encabezados un nivel para que aniden bajo esta sección y se han reescrito los enlaces relativos para que abran bien desde docs/aprendizaje/. No copies los fragmentos de aquí a mano: el generador inserta el cuerpo completo automáticamente.
Fase I: Business — ISS-01 — Esqueleto del proyecto
Contexto mínimo (autosuficiente). Si abres este archivo aislado —o lo llevas a otra IA—, esto es lo que hay que saber antes de ejecutarlo. - Proyecto:
app-storelab-express-ii— Express 5 + TypeScript + Sequelize, arquitectura por features, Fase I solo Business (sin auth ni roles). - Recorrido obligatorio de una petición:HTTP → Controller → Service → Repository → Model → Sequelize → BD. Ninguna capa se salta a la siguiente. - Diseño de datos (columnas, tipos, FKRESTRICT, índices, transacciones):../bd-storelab.md, Fase I — Business. - Capas, convenciones y reglas transversales:00-contexto.md.
Este ISS Título Esqueleto del proyecto Feature / tabla estructura src/API servidor HTTP base Depende de ISS-00 — Requisitos previos Habilita ISS-02 — Infraestructura de base de datos
Contenido de este ISS
- 2.1 Inicializar npm y scripts
- 2.2 Estructura de carpetas (features)
- 2.3 Dependencias base (Express + TypeScript)
- 2.4 TypeScript (
tsconfig.json) - 2.5 Servidor y App (esqueleto HTTP)
Objetivo: proyecto npm + TypeScript + Express con estructura features/ y servidor HTTP base.
Bloqueado por: ISS-00.
Criterios de aceptación (ISS-01) — consolidados
- [ ] 2.1 Existe
package.jsoncon"type": "commonjs"y scriptsbuild/dev - [ ] 2.2 Árbol
src/conconfig,database/seeders,routes,shared,features/business/clients(auth fuera de alcance de este lab) - [ ] 2.3 Dependencias Express/TS instaladas (
npm ls --depth=0) - [ ] 2.4 Existe
tsconfig.json(rootDir: ./src,outDir: ./dist,strict: true) - [ ] 2.5 Existen
src/server.tsysrc/config/index.ts(esqueleto App) - [ ]
npx tsc --noEmitsin errores al cerrar el ISS
2.1 Inicializar npm y scripts
Criterios de este sub-ítem
- [ ]
package.jsoncreado - [ ] Scripts
buildydevdefinidos
PARCHE — package.json ya existe (lo creó npm init -y).
- Dentro de
"scripts": deja solo (o añade)buildydevcomo abajo. - Debajo de
"license"(o al mismo nivel que"scripts"): asegúrate de"type": "commonjs".
Estado esperado de esas claves:
{
"scripts": {
"build": "tsc",
"dev": "nodemon --watch src --ext ts --exec ts-node -- src/server.ts"
},
"type": "commonjs"
}
2.2 Estructura de carpetas (features)
Criterios de este sub-ítem
- [ ] Carpetas de infra y features creadas según el árbol
mkdir -p \
src/config \
src/database/seeders \
src/routes \
src/shared/errors \
src/shared/http \
src/shared/database \
src/features/business/clients
src/
├── config/
├── database/
│ └── seeders/ # solo carpeta (ISS-02 §3.3); runner en ISS-04
├── routes/
├── shared/ # reutilizable por todos los features (ISS-03-A §4.0)
│ ├── database/ # with-transaction.ts (unit of work)
│ ├── errors/ # app-error.ts
│ └── http/ # base-controller.ts
├── features/
│ └── business/
│ └── clients/ # más features en ISS-06…08; capas en ISS-03-A §4.0
└── server.ts # §2.5
| Carpeta | Uso |
|---|---|
features/business/<plural>/ |
feature por capas: model + repository + service + controller + routes (+ seeder, swagger, http, associations) |
shared/ |
utilidades transversales (AppError, BaseController, withTransaction) |
database/seeders/ |
counts + SeedersRunner (npm run db:seed) |
routes/index.ts |
Agregador de features |
config/ · database/ |
Arranque e infraestructura |
Capas de un feature — flujo obligatorio (detalle en ISS-03-A §4.0)
| Capa | Archivo (plural) | Responsabilidad |
|---|---|---|
| Controller | <plural>.controller.ts |
HTTP: req/res, status codes |
| Service | <plural>.service.ts |
reglas de negocio y transacciones |
| Repository | <plural>.repository.ts |
acceso a datos (Sequelize) |
| DTO | dto/ (un archivo por operación) |
contrato de entrada/salida (Create/Update/Patch/Response) |
| Model | <singular>.model.ts |
entidad/tabla (atributos, hooks) |
Seeders (patrón del lab)
| Pieza | Dónde |
|---|---|
| Por entidad | src/features/business/<plural>/<plural>.seeder.ts |
| Runner + counts | src/database/seeders/{index,counts}.ts → npm run db:seed |
| Datos falsos | @faker-js/faker |
2.3 Dependencias base (Express + TypeScript)
Criterios de este sub-ítem
- [ ]
express,cors,dotenv,morganinstalados - [ ]
typescript,ts-node,nodemon,@types/*instalados
npm install express@^5.2.1 cors@^2.8.6 dotenv@^17.4.2 morgan@^1.12.1
npm install -D typescript@~5.9.2 ts-node@^10.9.2 nodemon@^3.1.14 \
@types/node@^22.20.3 @types/express@^5.0.6 \
@types/cors@^2.8.19 @types/morgan@^1.9.10
TypeScript en 5.9.x por compatibilidad con
ts-node.
2.4 TypeScript (tsconfig.json)
Criterios de este sub-ítem
- [ ]
tsconfig.jsonconrootDir: ./src,outDir: ./dist,strict: true
: > tsconfig.json
cat >> tsconfig.json << 'EOF'
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"module": "commonjs",
"target": "ES2020",
"lib": ["ES2020"],
"types": ["node"],
"esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true,
"strict": true,
"skipLibCheck": true,
"moduleDetection": "force",
"isolatedModules": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
EOF
2.5 Servidor y App (esqueleto HTTP)
Criterios de este sub-ítem
- [ ] Existen
src/server.tsysrc/config/index.ts - [ ]
Appdefinesettings,middlewares,routes,dbConnection,listen(placeholders OK)
2.5.1 src/server.ts
: > src/server.ts
cat >> src/server.ts << 'EOF'
import { App } from './config/index';
async function main() {
const app = new App();
await app.listen();
}
main();
EOF
2.5.2 src/config/index.ts (esqueleto)
En ISS-01 el App es esqueleto. Los imports de modelos, associations, Routes, Swagger y el
synccompleto se añaden con PARCHE en ISS-02…08. El archivo final consolidado aparece en ISS-08.
: > src/config/index.ts
cat >> src/config/index.ts << 'EOF'
import dotenv from "dotenv";
import express, { Application, ErrorRequestHandler } from "express";
import morgan from "morgan";
var cors = require("cors");
import { sequelize, getDatabaseInfo, testConnection } from "../database/db";
import "../features/business/clients/client.model";
import "../features/business/product-types/product-type.model";
import "../features/business/products/product.model";
import "../features/business/sales/sale.model";
import "../features/business/product-sales/product-sale.model";
import "../features/business/products/products.associations";
import "../features/business/sales/sales.associations";
import "../features/business/product-sales/product-sales.associations";
// Fase II — Auth con RBAC: primero los seis modelos, después las asociaciones
// (las asociaciones referencian los modelos, no al revés).
import "../features/auth/users/user.model";
import "../features/auth/roles/role.model";
import "../features/auth/resources/resource.model";
import "../features/auth/role-users/role-user.model";
import "../features/auth/resource-roles/resource-role.model";
import "../features/auth/refresh-tokens/refresh-token.model";
import "../features/auth/rbac.associations";
import { Routes } from "../routes/index";
import { setupSwagger } from "../swagger/index";
dotenv.config();
export class App {
public app: Application;
public routePrv: Routes = new Routes();
constructor(private port?: number | string) {
this.app = express();
this.settings();
this.middlewares();
this.routes();
this.docs();
this.errorHandling();
}
private settings(): void {
this.app.set('port', this.port || process.env.PORT || 4000);
}
private middlewares(): void {
this.app.use(morgan('dev'));
this.app.use(cors());
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: false }));
}
private routes(): void {
// Fase I — Business (cada operación, modalidad JWT + RBAC)
this.routePrv.clientsRoutes.routes(this.app);
this.routePrv.productTypesRoutes.routes(this.app);
this.routePrv.productsRoutes.routes(this.app);
this.routePrv.salesRoutes.routes(this.app);
this.routePrv.productSalesRoutes.routes(this.app);
// Fase II — Auth con RBAC
// `sessionRoutes` registra los endpoints OPEN/JWT (login, refresh, logout,
// perfil, permisos); el resto son modalidad JWT + RBAC.
this.routePrv.sessionRoutes.routes(this.app);
this.routePrv.refreshTokensRoutes.routes(this.app);
this.routePrv.usersRoutes.routes(this.app);
this.routePrv.rolesRoutes.routes(this.app);
this.routePrv.resourcesRoutes.routes(this.app);
this.routePrv.roleUsersRoutes.routes(this.app);
this.routePrv.resourceRolesRoutes.routes(this.app);
}
private docs(): void {
setupSwagger(this.app);
}
/**
* Errores que ocurren **antes** de llegar a un controller o middleware.
*
* El caso típico es un cuerpo JSON malformado: `express.json()` lanza un
* `SyntaxError` que, sin manejador, cae en el de Express por defecto y responde
* 400 con un HTML que incluye el **stack trace y rutas absolutas del servidor**
* (fuga de información). Aquí se traduce a un 400 JSON limpio.
*
* Debe registrarse **después** de las rutas: Express reconoce un middleware de
* error por su aridad de 4 argumentos.
*/
private errorHandling(): void {
const bodyErrorHandler: ErrorRequestHandler = (err, _req, res, next) => {
if (err instanceof SyntaxError && "body" in err) {
res.status(400).json({ error: "Malformed JSON body" });
return;
}
next(err);
};
this.app.use(bodyErrorHandler);
}
private async dbConnection(): Promise<void> {
try {
const dbInfo = getDatabaseInfo();
console.log(`🔗 Intentando conectar a: ${dbInfo.engine.toUpperCase()}`);
const isConnected = await testConnection();
if (!isConnected) {
throw new Error(`No se pudo conectar a la base de datos ${dbInfo.engine.toUpperCase()}`);
}
// Lab: sync crea/altera tablas desde los modelos (BD limpia → snake_case desde cero).
const force = process.env.DB_SYNC_FORCE === "true";
const isMysql =
sequelize.getDialect() === "mysql" || sequelize.getDialect() === "mariadb";
if (isMysql) {
await sequelize.query("SET FOREIGN_KEY_CHECKS = 0");
}
try {
await sequelize.sync({ force, alter: !force });
} finally {
if (isMysql) {
await sequelize.query("SET FOREIGN_KEY_CHECKS = 1");
}
}
console.log(
force
? "📦 Base de datos recreada (DB_SYNC_FORCE=true)"
: "📦 Base de datos sincronizada exitosamente"
);
} catch (error) {
console.error("❌ Error al conectar con la base de datos:", error);
process.exit(1);
}
}
async listen() {
// Orden de arranque: primero la BD (conexión + `sync`), después abrir el puerto.
// Si se abre el puerto antes de terminar `sync({ alter: true })`, las sentencias
// DDL (ALTER TABLE, DROP/ADD FOREIGN KEY) compiten con las peticiones que ya
// están entrando y provocan deadlocks y errores de FK intermitentes.
await this.dbConnection();
await this.app.listen(this.app.get('port'));
console.log(`🚀 Servidor ejecutándose en puerto ${this.app.get('port')}`);
}
}
EOF
Verificación del ISS-01
Cierre del ISS
El servidor debe arrancar sin error. Detenerlo con Ctrl+C antes de continuar.
Criterios de aceptación
Los del ISS, textuales:
- [ ] 2.1 Existe
package.jsoncon"type": "commonjs"y scriptsbuild/dev - [ ] 2.2 Árbol
src/conconfig,database/seeders,routes,shared,features/business/clients(auth fuera de alcance de este lab) - [ ] 2.3 Dependencias Express/TS instaladas (
npm ls --depth=0) - [ ] 2.4 Existe
tsconfig.json(rootDir: ./src,outDir: ./dist,strict: true) - [ ] 2.5 Existen
src/server.tsysrc/config/index.ts(esqueleto App) - [ ]
npx tsc --noEmitsin errores al cerrar el ISS
Evaluación
Preguntas de comprensión
-
¿Por qué el
package.jsonse describe como «creado y parcheado» a la vez? Porquenpm init -ylo genera con valores por defecto y el ISS lo modifica después: fija"type": "commonjs"y define los scriptsbuildydev. Es un archivo nuevo que además se edita en el mismo ISS. -
¿Qué diferencia hay entre
npm run buildynpm run dev?buildejecutatscy compilasrc/adist/(código listo para producción).devejecutanodemon, que vigila los.tsy relanzats-node -- src/server.tsen cada cambio (ciclo de desarrollo). El primero produce artefactos; el segundo no. -
¿Por qué
rootDires./srcyoutDires./dist? Para separar la fuente del artefacto compilado: todo el TypeScript vive ensrc/y la salida JS se genera endist/. Asínpm run buildno contamina la carpeta de fuentes yexcludepuede ignorardist. -
¿Qué gana el proyecto activando
strict: truedesde el primer día? Que los errores de tipo se detectan en compilación en lugar de aparecer en tiempo de ejecución. Es más barato corregir un tipo en el editor que depurar unundefineden producción. Como eltsconfiges la base, activarlo tarde obligaría a corregir todo el código acumulado. -
¿Por qué el servidor arranca y sin embargo no responde ninguna ruta de negocio? Porque
routes()es un placeholder en este ISS: todavía no existe ningún feature (clientsllega en ISS-03). El ISS-01 solo garantiza que el servidor HTTP se levanta y compila. -
¿Por qué
listen()conecta a la base de datos antes de abrir el puerto? Para evitar que sentencias DDL (creación o alteración de tablas) compitan con peticiones ya entrantes, lo que produciría deadlocks y errores intermitentes. Aunque en ISS-01dbConnection()es un placeholder, el orden ya queda fijado en el código. -
¿Qué papel cumple
src/config/index.tsfrente asrc/server.ts?server.tses el punto de entrada mínimo (crea y arranca).config/index.tsconcentra la configuración del servidor (puerto, middlewares, rutas, errores, BD). Separarlos permite que el arranque sea trivial y que la configuración evolucione en un único lugar.
Ejercicios
Ejercicio 1 — Inspeccionar el proyecto. Ejecuta los comandos de verificación del ISS y explica, en dos líneas, si el proyecto cumple los criterios del GATE.
Respuesta razonada
`npx tsc --noEmit` no debe imprimir nada (cero errores). `find` debe listar, como mínimo, `src/server.ts` y `src/config/index.ts`. Si ambos se cumplen, el GATE de tipos pasa; falta la prueba de arranque con `npm run dev`.Ejercicio 2 — Predecir el fallo.
Antes de ejecutar nada, predice qué pasa si borras el "type": "commonjs" del package.json y arrancas npm run dev. Luego compruébalo.
Respuesta razonada
Sin `"type": "commonjs"`, Node trata el paquete como CommonJS por defecto igualmente (el valor por defecto ya es CommonJS), así que **en este caso concreto no cambia nada**. La lección: el ISS lo declara de forma **explícita** para que la intención sea legible y para blindar el proyecto ante cambios futuros de configuración. Explicitar los contratos evita depender de un valor por defecto que puede cambiar.Conexión con el resto del curso
- Viene de: ISS-00 — Requisitos previos, que garantizó Node, npm y un motor de base de datos accesible. Sin ese entorno,
npm init -yhabría fallado. - Habilita: ISS-02 — Infraestructura de base de datos, que añade
sequelize,.envysrc/database/db.tssobre la carpetadatabase/ya creada aquí. - Piezas reutilizadas por el resto del curso: la clase
Appy su ciclosettings → middlewares → routes → docs → errorHandling → dbConnection → listen; eltsconfigestricto; los scriptsbuild/dev; y la convención de carpetas por feature. Cada ISS posterior parcheasrc/config/index.tspara ir registrando sus rutas.
Glosario
| Término | Significado en este ISS |
|---|---|
| npm | Gestor de paquetes de Node; crea el proyecto (npm init -y) e instala dependencias (npm install). |
package.json |
Manifiesto del proyecto: tipo de módulos, dependencias y scripts. |
nodemon |
Observador de archivos: reinicia el servidor cuando cambia un .ts. |
ts-node |
Ejecuta TypeScript directamente, sin compilar a dist/ primero. |
tsc |
Compilador de TypeScript; con --noEmit solo comprueba tipos. |
tsconfig.json |
Configuración del compilador TypeScript. |
strict |
Modo estricto de comprobación de tipos. |
| Feature | Módulo de negocio autocontenido bajo src/features/business/<plural>/. |
App |
Clase que concentra la configuración y el arranque del servidor Express. |
GATE
Para cerrar el ISS-01, primero verifica los tipos:
Resultado esperado: npx tsc --noEmit termina sin imprimir errores y find lista al menos src/server.ts y src/config/index.ts.
Después arranca el servidor:
Resultado esperado: el servidor levanta y queda escuchando en el puerto 4000 (o en process.env.PORT), sin errores. Deténlo con Ctrl+C antes de continuar: el ISS-02 reutiliza el mismo puerto.
Checklist de cierre:
- [ ]
package.jsoncon"type": "commonjs"y scriptsbuild/dev - [ ] Árbol
src/conconfig,database/seeders,routes,shared,features/business/clients - [ ] Dependencias Express/TS instaladas (
npm ls --depth=0) - [ ]
tsconfig.jsonconrootDir: ./src,outDir: ./dist,strict: true - [ ]
src/server.tsysrc/config/index.tscreados - [ ]
npx tsc --noEmitsin errores - [ ]
npm run devarranca el servidor
Con todo en verde, el ISS-01 está cumplido y puedes pasar al
ISS-02 — Infraestructura de base de datos, donde nace la conexión real a la base de datos con sequelize, testConnection y sync.
Navegación de la ruta: ← ISS-00 · 🛠 Construir · ↑ Ruta Express · → ISS-01 · 🛠 Construir