Saltar a contenido

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

⛶ Ver presentación completa ⬇ Archivo editable (.pptx)

9 diapositivas · se visualiza dentro del sitio (archivo editable .pptx como opción secundaria).


🎬 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 --noEmit sin errores, y npm run dev arranca 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:

EXPLICACIÓN
    ↓
CÓDIGO
    ↓
VISUAL

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

  1. Tema
  2. Fuente técnica autoritativa
  3. Regla del ISS
  4. Cómo leer este cuaderno
  5. Ruta de aprendizaje
  6. Ficha del ISS
  7. Mapa mental del ISS
  8. Mapa del backend
  9. Árbol de archivos
  10. Anatomía del código
  11. Comandos explicados
  12. Flujos
  13. Diagnóstico
  14. Recorrido del ISS, paso a paso
  15. Criterios de aceptación
  16. Evaluación
  17. Conexión con el resto del curso
  18. Glosario
  19. 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 scripts build/dev.
  • La estructura de carpetas por features: config, database/seeders, routes, shared y features/business/clients.
  • Las dependencias base: express, cors, dotenv, morgan en producción; typescript, ts-node, nodemon y los @types/* en desarrollo.
  • El tsconfig.json estricto con rootDir: ./src, outDir: ./dist y strict: true.
  • El esqueleto HTTP: src/server.ts como punto de entrada y la clase App en src/config/index.ts, con sus etapas settings, middlewares, routes, docs, errorHandling, dbConnection y listen.

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.ts que muestra el ISS es la versión consolidada del archivo: importa los modelos, Routes y Swagger que todavía no existen, marcados en el propio ISS como placeholders. En ISS-01 escribes el esqueleto de la clase App; esos imports y el sync completo se añaden con PARCHE en ISS-02…08. Si npx tsc --noEmit se 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:

(directorio de trabajo: 
 todavía sin package.json ni src/)

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 usa require/import compilado a CommonJS, así que esto evita sorpresas entre entornos.
  • "scripts" — el ISS deja solo dos: build ejecuta tsc (compila src/ a dist/) y dev arranca nodemon, que vigila src/**/*.ts y relanza ts-node -- src/server.ts en cada cambio.
  • El archivo no se escribe a mano: lo crea npm init -y y 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 dev y npm install.
  • Salida: determina cómo se ejecuta src/server.ts y 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 en src/ y la salida de npm run build cae en dist/. 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" del package.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 (script build) y ts-node (script dev), y lo valida npx 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 App desde ./config/index.
  • Define async function main() que crea new App() y ejecuta await app.listen().
  • Llama a main() al final. Es deliberadamente mínimo: toda la complejidad vive en App, para que el arranque sea trivial de leer.

Se conecta con

  • Entrada: lo ejecuta ts-node -- src/server.ts (vía nodemon en dev) y node dist/server.js (tras build).
  • Salida: llama al constructor y a listen() de App.

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() — registra morgan (logs), cors (peticiones cruzadas), express.json() y express.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 un 400 JSON 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 un sync({ 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 el sync completo 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, dotenv y (más adelante) ../database/db, ../routes/index y ../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 comprobar ts-node puede romper el script dev.

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 dev hasta 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, FK RESTRICT, í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.json con "type": "commonjs" y scripts build / dev
  • [ ] 2.2 Árbol src/ con config, 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.ts y src/config/index.ts (esqueleto App)
  • [ ] npx tsc --noEmit sin errores al cerrar el ISS

2.1 Inicializar npm y scripts

Criterios de este sub-ítem

  • [ ] package.json creado
  • [ ] Scripts build y dev definidos
mkdir app-storelab-express
cd app-storelab-express
npm init -y
mkdir -p docs

PARCHE — package.json ya existe (lo creó npm init -y).

  • Dentro de "scripts": deja solo (o añade) build y dev como 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"
}
node -e "const p=require('./package.json'); console.log(p.scripts)"

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)

HTTP (routes) -> Controller -> Service -> Repository -> Model -> Sequelize -> BD
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
find src -type d | sort

2.3 Dependencias base (Express + TypeScript)

Criterios de este sub-ítem

  • [ ] express, cors, dotenv, morgan instalados
  • [ ] 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.

npm ls --depth=0

2.4 TypeScript (tsconfig.json)

Criterios de este sub-ítem

  • [ ] tsconfig.json con rootDir: ./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
test -f tsconfig.json && npx tsc --showConfig | head -20

2.5 Servidor y App (esqueleto HTTP)

Criterios de este sub-ítem

  • [ ] Existen src/server.ts y src/config/index.ts
  • [ ] App define settings, 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 sync completo 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

npx tsc --noEmit
find src -type f | sort

Cierre del ISS

npm run dev

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.json con "type": "commonjs" y scripts build / dev
  • [ ] 2.2 Árbol src/ con config, 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.ts y src/config/index.ts (esqueleto App)
  • [ ] npx tsc --noEmit sin errores al cerrar el ISS

Evaluación

Preguntas de comprensión

  1. ¿Por qué el package.json se describe como «creado y parcheado» a la vez? Porque npm init -y lo genera con valores por defecto y el ISS lo modifica después: fija "type": "commonjs" y define los scripts build y dev. Es un archivo nuevo que además se edita en el mismo ISS.

  2. ¿Qué diferencia hay entre npm run build y npm run dev? build ejecuta tsc y compila src/ a dist/ (código listo para producción). dev ejecuta nodemon, que vigila los .ts y relanza ts-node -- src/server.ts en cada cambio (ciclo de desarrollo). El primero produce artefactos; el segundo no.

  3. ¿Por qué rootDir es ./src y outDir es ./dist? Para separar la fuente del artefacto compilado: todo el TypeScript vive en src/ y la salida JS se genera en dist/. Así npm run build no contamina la carpeta de fuentes y exclude puede ignorar dist.

  4. ¿Qué gana el proyecto activando strict: true desde 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 un undefined en producción. Como el tsconfig es la base, activarlo tarde obligaría a corregir todo el código acumulado.

  5. ¿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 (clients llega en ISS-03). El ISS-01 solo garantiza que el servidor HTTP se levanta y compila.

  6. ¿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-01 dbConnection() es un placeholder, el orden ya queda fijado en el código.

  7. ¿Qué papel cumple src/config/index.ts frente a src/server.ts? server.ts es el punto de entrada mínimo (crea y arranca). config/index.ts concentra 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
find src -type f | sort
`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 -y habría fallado.
  • Habilita: ISS-02 — Infraestructura de base de datos, que añade sequelize, .env y src/database/db.ts sobre la carpeta database/ ya creada aquí.
  • Piezas reutilizadas por el resto del curso: la clase App y su ciclo settings → middlewares → routes → docs → errorHandling → dbConnection → listen; el tsconfig estricto; los scripts build/dev; y la convención de carpetas por feature. Cada ISS posterior parchea src/config/index.ts para 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:

npx tsc --noEmit
find src -type f | sort

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:

npm run dev

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.json con "type": "commonjs" y scripts build / dev
  • [ ] Árbol src/ con config, database/seeders, routes, shared, features/business/clients
  • [ ] Dependencias Express/TS instaladas (npm ls --depth=0)
  • [ ] tsconfig.json con rootDir: ./src, outDir: ./dist, strict: true
  • [ ] src/server.ts y src/config/index.ts creados
  • [ ] npx tsc --noEmit sin errores
  • [ ] npm run dev arranca 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