Los cursos de CI/CD suelen fallar en el mismo punto: enseñan pipelines sobre un "hola mundo" que no tiene base de datos, ni migraciones, ni dos aplicaciones que compartan código, ni un equipo con opiniones distintas. Y luego, en un proyecto real, nada encaja. Este curso hace lo contrario: construiremos, paso a paso, el pipeline completo de una aplicación con la complejidad justa para que los problemas de verdad aparezcan. Esa aplicación se llama Reservalia. En esta lección la vamos a conocer a fondo: qué hace el producto, quién forma el equipo, cómo está organizado el repositorio, qué comandos existen ya —porque el pipeline no inventa nada, solo invoca lo que el proyecto ya sabe hacer—, qué entornos hay y a qué infraestructura desplegamos. Terminaremos con la hoja de ruta del curso aplicada a Reservalia y con lo que necesitas instalado para seguirlo. Todavía no escribiremos ningún workflow: la primera pieza real de pipeline se construye en la lección 02-02.
Contenido
- Qué es Reservalia
- El equipo: Marta, Diego y Nuria
- Cómo trabajan hoy (y por qué duele)
- La estructura del repositorio
- Los
package.jsony los comandos que el pipeline invocará - El entorno de desarrollo local con Docker Compose
- Los tres entornos: dev, staging y prod
- La infraestructura AWS de destino
- La hoja de ruta del curso aplicada a Reservalia
- Cómo seguir el curso si no usas Node.js
- Qué necesitas instalado
- Errores comunes y consejos
- Ejercicios
- Conclusión
- Qué es Reservalia
Reservalia es una plataforma SaaS de reservas de cita para pequeños negocios de servicios: peluquerías, clínicas dentales, talleres mecánicos, fisioterapeutas, centros de estética. Es una empresa ficticia creada para este curso; cualquier parecido con la realidad es intencionado pero no real.
El producto tiene dos caras:
- El panel del negocio. El dueño de la peluquería configura sus servicios (corte, color, tratamiento), sus profesionales, los horarios de apertura y sus vacaciones. Consulta la agenda del día, confirma citas y ve estadísticas básicas.
- La página pública de reserva. El cliente final entra desde un enlace, ve los huecos libres, elige uno y reserva sin registrarse. Recibe una confirmación por correo y un recordatorio 24 horas antes.
Modelo de negocio: suscripción mensual por negocio, con tres planes. Ahora mismo tienen 340 negocios de pago y procesan unas 9.000 citas al mes.
1.1. Por qué Reservalia es un buen proyecto para aprender CI/CD
No es un ejemplo de juguete, y esto importa. Reservalia tiene exactamente los ingredientes que hacen interesante un pipeline:
| Ingrediente | Por qué complica el pipeline | Dónde lo trataremos |
|---|---|---|
| Base de datos con esquema evolutivo | Hay migraciones que aplicar, y aplicarlas mal rompe producción o pierde datos | 04-06 |
| Dos aplicaciones en un mismo repositorio | ¿Se prueba todo en cada cambio o solo lo afectado? | 04-04 |
| Código compartido entre ellas | Un cambio en tipos compartidos afecta a las dos | 02-03 |
| Datos sensibles (nombres, teléfonos, correos de clientes finales) | Los entornos no pueden compartir datos reales; hay que gestionar secretos | 04-03 |
| Horario comercial con picos | No se puede desplegar de cualquier manera a las 11:00 de un sábado | 03-04 |
| Terceros integrados (correo, pasarela de pago) | Hay que decidir qué se simula en pruebas y qué no | 02-04 |
| Una aplicación móvil (a partir del módulo 5) | El "despliegue" pasa por tiendas de aplicaciones, con sus reglas | 05-02 |
Si consigues automatizar Reservalia, sabrás automatizar tu proyecto.
- El equipo: Marta, Diego y Nuria
Tres personas, tres puntos de vista. Los tres aparecerán durante todo el curso, y sus discusiones son las que probablemente tendrás tú con tu equipo.
2.1. Marta — tech lead
Lleva cuatro años en Reservalia y arrastra la responsabilidad de que el producto funcione. Es quien decide qué se despliega y cuándo, y quien recibe la llamada del comercial cuando un cliente se queja.
- Qué le preocupa: que las incidencias no se repitan y poder justificar ante dirección el tiempo que el equipo dedica a "cosas que no son funcionalidades".
- Su papel en el curso: aporta el punto de vista de negocio. Es quien pregunta "¿cuánto cuesta esto y qué ganamos?" (la lección 01-02) y quien pulsará el botón de aprobación en la puerta manual del despliegue a producción.
- Su frase: "Prefiero desplegar diez veces al día y que cada despliegue me dé igual, que desplegar una vez a la semana y no dormir el jueves."
2.2. Diego — desarrollador backend
Tres años en la empresa. Escribe la mayor parte de apps/api y es, hoy, la única persona que sabe desplegar. Sabe perfectamente que el proceso actual es malo; lo que no ha tenido nunca es tiempo para arreglarlo.
- Qué le preocupa: que el pipeline le ralentice. Le da pánico un CI que tarde 40 minutos en decirle si su cambio está bien.
- Su papel en el curso: es el escéptico productivo. Cada vez que añadamos un paso al pipeline, Diego preguntará cuánto tiempo suma. Gracias a él, el pipeline final será rápido.
- Su frase: "Si el CI tarda más que ir a por un café, lo acabaré ignorando."
2.3. Nuria — SRE
Se incorporó hace ocho meses, a media jornada compartida con otro producto. Montó la infraestructura de AWS a mano, desde la consola web, porque había que salir a producción ya. Lleva las guardias.
- Qué le preocupa: que la infraestructura no esté documentada en ninguna parte salvo en su cabeza, y que un rollback hoy signifique recompilar y volver a subir por SFTP con clientes esperando.
- Su papel en el curso: aporta la perspectiva de operación. Es quien insiste en la infraestructura como código (03-03), en el rollback (03-05) y en la monitorización (03-06).
- Su frase: "Un despliegue que no se puede deshacer en cinco minutos no es un despliegue, es una apuesta."
- Cómo trabajan hoy (y por qué duele)
Recordemos el punto de partida, ahora con el detalle completo del proceso:
# El ritual del viernes en Reservalia. Duración: ~3 horas.
# Ejecutor: Diego, siempre Diego.
# 1) Se baja lo último de main (sin saber exactamente qué entra)
git checkout main && git pull
# 2) Compila en su portátil, con SU versión de Node y SUS node_modules
cd apps/api && npm install && npm run build
cd ../web && npm install && npm run build
# 3) Sube los ficheros por SFTP al único servidor de producción
sftp diego@reservalia-prod
# > put -r apps/api/dist/* /var/www/api/
# > put -r apps/web/dist/* /var/www/web/
# 4) Reinicia el proceso a mano
ssh diego@reservalia-prod 'pm2 restart api'
# 5) Aplica las migraciones pegando SQL en psql
psql -h reservalia-prod-db -U admin -d reservalia
# > ALTER TABLE citas ADD COLUMN recordatorio_enviado boolean DEFAULT false;
# 6) Comprueba a ojo que la web carga y crea una cita de prueba
# 7) Escribe en el canal del equipo: "desplegado ✅"Y los problemas de este ritual, enumerados sin piedad:
| # | Problema | Consecuencia real que ya han sufrido |
|---|---|---|
| 1 | Build en el portátil de Diego | La build no es reproducible; nadie puede recrear lo que hay en producción |
| 2 | npm install en vez de npm ci |
Una versión menor de una dependencia entró en producción sin que nadie la hubiera probado |
| 3 | Despliegue incremental por SFTP | Ficheros de versiones antiguas siguen vivos en el servidor; el estado real es desconocido |
| 4 | Ventana de despliegue sin servicio | Hay unos segundos en los que la API responde a medias |
| 5 | Migraciones a mano | Nadie sabe con certeza qué migraciones se han aplicado ni en qué orden |
| 6 | Sin registro de qué se desplegó | Ante un fallo, hay que adivinar qué cambió |
| 7 | Verificación visual | Se dio por bueno un despliegue en el que el envío de correos estaba roto |
| 8 | Bus factor de 1 | En las vacaciones de Diego en agosto, no se desplegó nada durante tres semanas |
| 9 | Rollback ≈ 1 hora | En la última incidencia, el servicio estuvo degradado 40 minutos |
| 10 | Viernes por la tarde | Dos fines de semana estropeados el último trimestre |
Cada línea de esta tabla desaparecerá en algún momento del curso. Guárdala: en la lección final (07-06) la repasaremos entera.
- La estructura del repositorio
Reservalia tiene un único repositorio en github.com/reservalia/reservalia, organizado como monorepo con workspaces de npm. Esta es su estructura tal como está hoy, antes de empezar:
reservalia/
├── .github/
│ └── workflows/ ← vacío hoy; aquí vivirán nuestros pipelines
├── apps/
│ ├── api/ ← Node.js 20 + TypeScript + Express + PostgreSQL
│ │ ├── src/
│ │ │ ├── index.ts punto de entrada del servidor
│ │ │ ├── rutas/
│ │ │ │ ├── citas.ts crear, listar y cancelar citas
│ │ │ │ ├── negocios.ts alta y configuración de negocios
│ │ │ │ └── disponibilidad.ts cálculo de huecos libres
│ │ │ ├── dominio/
│ │ │ │ ├── cita.ts reglas de negocio de una cita
│ │ │ │ └── agenda.ts solapamientos, horarios, festivos
│ │ │ └── db/
│ │ │ ├── cliente.ts conexión a PostgreSQL
│ │ │ └── migraciones/
│ │ │ ├── 0001_crear_negocios.sql
│ │ │ ├── 0002_crear_citas.sql
│ │ │ └── 0003_añadir_recordatorios.sql
│ │ ├── tests/
│ │ │ ├── unidad/ rápidos, sin base de datos
│ │ │ └── integracion/ contra PostgreSQL real
│ │ ├── Dockerfile ← lo escribiremos en 02-03
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ └── web/ ← React + Vite + TypeScript
│ ├── src/
│ │ ├── main.tsx
│ │ ├── paginas/
│ │ │ ├── PanelNegocio.tsx
│ │ │ └── ReservaPublica.tsx
│ │ └── componentes/
│ ├── tests/
│ ├── Dockerfile ← lo escribiremos en 02-03
│ ├── package.json
│ └── vite.config.ts
│
├── packages/
│ └── tipos-compartidos/ ← tipos TypeScript usados por api y web
│ ├── src/index.ts
│ └── package.json
│
├── infra/ ← infraestructura como código (módulo 3)
│ ├── terraform/
│ │ ├── modulos/
│ │ └── entornos/
│ │ ├── staging/
│ │ └── prod/
│ └── README.md
│
├── docker-compose.yml ← entorno de desarrollo local
├── package.json ← raíz del monorepo, con workspaces
├── package-lock.json ← ¡uno solo para todo el monorepo!
├── .nvmrc ← fija la versión de Node
└── README.mdTres decisiones de esta estructura merecen explicación, porque condicionan todo el pipeline:
Monorepo con un único package-lock.json. Al usar workspaces de npm, hay un solo fichero de bloqueo en la raíz que gobierna las dependencias de api, web y tipos-compartidos. Ventaja: un único npm ci instala todo de forma coherente y reproducible. Consecuencia para el pipeline: la instalación se hace una vez en la raíz, no una por aplicación.
packages/tipos-compartidos. Aquí viven los tipos que la API y la web comparten (por ejemplo, la forma de una Cita). Es lo que garantiza que si Diego cambia el modelo de datos, la web deje de compilar en CI en lugar de romperse en producción. También significa que un cambio en ese paquete obliga a probar las dos aplicaciones: es la complicación que hace interesante el "ejecutar solo lo afectado" de la lección 04-04.
.nvmrc. Un fichero de una línea que dice qué versión de Node usa el proyecto. Es la pieza más barata de reproducibilidad que existe:
Con eso, el portátil de Diego, el de Marta y el runner de CI usan exactamente la misma versión. En la lección 01-01 vimos que la versión no fijada es uno de los enemigos clásicos de la build reproducible; este fichero lo resuelve.
- Los
package.json y los comandos que el pipeline invocará
package.json y los comandos que el pipeline invocaráAquí está una idea central del curso, y conviene subrayarla:
El pipeline no inventa nada. Solo ejecuta, en una máquina limpia, los mismos comandos que tú ejecutas en tu portátil.
Por eso, antes de escribir ningún workflow, el proyecto debe tener sus comandos bien definidos. Si npm test no funciona en tu máquina, no funcionará en CI. Si funciona en tu máquina pero solo porque tienes una variable de entorno que nadie más tiene, fallará en CI. Un pipeline es, sobre todo, un revelador de suposiciones ocultas.
5.1. package.json de la raíz
{
"name": "reservalia",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"engines": {
"node": ">=20.11.0 <21"
},
"scripts": {
"build": "npm run build --workspaces --if-present",
"test": "npm run test --workspaces --if-present",
"lint": "npm run lint --workspaces --if-present",
"typecheck": "npm run typecheck --workspaces --if-present",
"dev": "docker compose up -d && npm run dev --workspace apps/api"
}
}Punto por punto, porque cada línea tiene un motivo:
"private": trueevita que el paquete raíz se publique por accidente en el registro público de npm. En un monorepo es obligatorio."workspaces"declara los subproyectos. Al ejecutarnpm cien la raíz, npm instala las dependencias de todos ellos y crea los enlaces entretipos-compartidosy las aplicaciones que lo usan."engines"documenta la versión de Node soportada. Combinado con.nvmrc, deja constancia explícita del requisito.--workspaces --if-presentejecuta el script en cada subproyecto que lo tenga definido, y no falla en los que no lo tengan. Es lo que permite que un solonpm testen la raíz pruebe la API, la web y el paquete compartido.
Con esto, el pipeline entero podría reducirse a cuatro comandos. Compruébalo:
npm ci # instalación reproducible de todo el monorepo
npm run lint # estilo y errores evidentes
npm run typecheck # coherencia de tipos entre api, web y tipos-compartidos
npm run test # pruebas de todos los subproyectos
npm run build # artefactos compiladosEstos cinco comandos son, literalmente, el esqueleto del pipeline que construiremos en el módulo 2.
5.2. package.json de apps/api
{
"name": "@reservalia/api",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc --project tsconfig.build.json",
"start": "node dist/index.js",
"test": "vitest run",
"test:unidad": "vitest run tests/unidad",
"test:integracion":"vitest run tests/integracion",
"lint": "eslint src tests --max-warnings 0",
"typecheck": "tsc --noEmit",
"migrate": "node dist/db/migrar.js",
"migrate:status": "node dist/db/migrar.js --status"
},
"dependencies": {
"@reservalia/tipos-compartidos": "*",
"express": "4.19.2",
"pg": "8.11.5",
"zod": "3.23.8"
},
"devDependencies": {
"@types/express": "4.17.21",
"eslint": "8.57.0",
"tsx": "4.7.1",
"typescript": "5.4.5",
"vitest": "1.6.0"
}
}Detalles importantes para el pipeline:
test:unidadytest:integracionestán separados. Las unitarias son rápidas y no necesitan base de datos; las de integración necesitan un PostgreSQL levantado. Esta separación es la que nos permitirá, en 02-04, dar feedback rápido a Diego: primero las rápidas, y solo si pasan, las lentas.--max-warnings 0en el lint. Sin esto, ESLint termina con código de salida 0 aunque haya avisos, y el pipeline se pondría verde con problemas dentro. Recuerda de 01-03: el pipeline solo entiende de códigos de salida.typecheckseparado debuild.tsc --noEmitcomprueba los tipos sin generar ficheros. Es rápido y puede correr en paralelo con los tests.migrateexiste como script. Hoy Diego pega SQL a mano; el proyecto ya tiene el comando, solo que nadie lo usa. En la lección 04-06 lo integraremos en el despliegue.- Versiones exactas, sin
^."express": "4.19.2"y no"^4.19.2". Junto connpm ci, garantiza que dos instalaciones den lo mismo. La estrategia completa de dependencias es materia de 04-02.
5.3. package.json de apps/web
{
"name": "@reservalia/web",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest run",
"lint": "eslint src tests --max-warnings 0",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@reservalia/tipos-compartidos": "*",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@vitejs/plugin-react": "4.2.1",
"typescript": "5.4.5",
"vite": "5.2.11",
"vitest": "1.6.0"
}
}Fíjate en que los nombres de los scripts coinciden con los de la API: build, test, lint, typecheck. Esta convención no es cosmética: es lo que hace que npm run test --workspaces funcione y lo que permitirá que el pipeline trate ambas aplicaciones con la misma lógica.
Consejo transferible: unificar los nombres de los scripts entre subproyectos es una de las inversiones más rentables antes de montar un pipeline. Si en un proyecto el comando es
npm testy en otronpm run test:ci, tu YAML se llenará de casos especiales.
- El entorno de desarrollo local con Docker Compose
Para que las pruebas de integración funcionen —tanto en el portátil como después en CI— hace falta un PostgreSQL. Reservalia lo levanta con Docker Compose:
# docker-compose.yml — entorno de desarrollo local de Reservalia
services:
db:
image: postgres:16.3 # versión FIJADA, igual que en RDS
container_name: reservalia-db
environment:
POSTGRES_USER: reservalia
POSTGRES_PASSWORD: desarrollo # ⚠️ solo local, jamás en otro entorno
POSTGRES_DB: reservalia
ports:
- "5432:5432" # accesible desde el portátil
volumes:
- datos-db:/var/lib/postgresql/data
healthcheck: # ¿está la base de datos LISTA, no solo arrancada?
test: ["CMD-SHELL", "pg_isready -U reservalia -d reservalia"]
interval: 5s
timeout: 3s
retries: 10
mailpit:
image: axllent/mailpit:v1.18 # captura los correos en local
ports:
- "1025:1025" # el SMTP al que apunta la API
- "8025:8025" # interfaz web para leerlos
volumes:
datos-db:Cuatro cosas que explicar, porque todas reaparecerán en el pipeline:
La versión está fijada: postgres:16.3. No postgres:latest. Si en local desarrollas contra PostgreSQL 16 y producción corre 15, tarde o temprano una consulta funcionará en tu máquina y fallará en producción. La versión de la base de datos es parte de la reproducibilidad.
El healthcheck es la línea más importante del fichero. Un contenedor de PostgreSQL "arrancado" no significa "listo para aceptar conexiones": hay unos segundos de inicialización. Sin healthcheck, las pruebas de integración fallan de forma intermitente porque la base de datos aún no acepta conexiones. Y eso es exactamente un test flaky, el veneno del que hablábamos en 01-02. La causa número uno de flakiness en CI es no esperar a que los servicios estén realmente listos.
Mailpit sustituye al proveedor de correo real. Reservalia envía confirmaciones y recordatorios. En local y en pruebas, esos correos no deben salir a internet: Mailpit los captura y los muestra en una web. La regla general que aplicaremos en 02-04: en pruebas, ningún servicio externo real.
La contraseña desarrollo está escrita en claro, y está bien. Porque es una base de datos local, efímera y sin datos reales. En staging y prod jamás haremos esto: allí las credenciales vienen de un gestor de secretos. Distinguir cuándo un valor es un secreto y cuándo no es parte de la materia de 04-03.
El flujo de trabajo diario de Diego, en cuatro comandos:
git clone https://github.com/reservalia/reservalia.git
cd reservalia
nvm use # lee .nvmrc → Node 20.11.0
npm ci # instala todo el monorepo de forma reproducible
docker compose up -d # levanta PostgreSQL y Mailpit
npm run dev # arranca la API en modo desarrolloCuando en el módulo 2 configuremos el CI, verás que el pipeline hace exactamente lo mismo: preparar Node, instalar dependencias, levantar los servicios necesarios y ejecutar comandos. No hay magia.
- Los tres entornos: dev, staging y prod
Un entorno (definido en 01-01) es una instancia desplegada y ejecutable del sistema, con su propia configuración y sus propios datos. Reservalia tendrá tres:
| Aspecto | dev |
staging |
prod |
|---|---|---|---|
| Para qué sirve | Que el equipo pruebe cambios en un entorno compartido y real | Ensayo general: la última verificación antes de producción | Los clientes de verdad |
| Quién lo usa | Marta, Diego, Nuria | El equipo, antes de aprobar un despliegue | 340 negocios y sus clientes |
| Qué se despliega | Cada merge a main, automáticamente |
Cada merge a main, tras pasar dev |
Solo tras aprobación manual de Marta |
| Datos | Datos ficticios generados, se pueden borrar | Datos ficticios con volumen realista | Datos reales de clientes |
| Base de datos | RDS db.t4g.micro |
RDS db.t4g.micro |
RDS db.t4g.medium, multi-AZ y con copias de seguridad |
| Instancias de la API | 1 | 1 | 2 como mínimo, con escalado automático |
| Correo | Capturado, no sale | Capturado, no sale | Proveedor real |
| Pasarela de pago | Modo simulado | Modo simulado | Modo real |
| Quién puede acceder | Solo el equipo | Solo el equipo | Público |
| Si se cae | No pasa nada | No pasa nada | Incidente |
Dos principios que rigen los tres entornos y que conviene interiorizar ya:
Principio 1: el mismo artefacto en los tres. Es la regla de promoción de 01-01. La imagen reservalia/api:a3f9c21 que se prueba en dev es exactamente la misma que llega a staging y a prod. Lo único que cambia entre entornos es la configuración inyectada por fuera:
# dev
DATABASE_URL=postgres://[email protected]:5432/reservalia
LOG_LEVEL=debug
PASARELA_PAGO_MODO=simulado
SMTP_HOST=mailpit.interno
# prod
DATABASE_URL=postgres://[email protected]:5432/reservalia
LOG_LEVEL=info
PASARELA_PAGO_MODO=real
SMTP_HOST=smtp.proveedor-correo.comPrincipio 2: los datos de producción nunca salen de producción. Copiar la base de datos de prod a staging "para probar con datos reales" es una práctica frecuente y una muy mala idea: Reservalia guarda nombres, teléfonos y correos de clientes finales. staging usa datos generados con volumen realista, no datos reales.
- La infraestructura AWS de destino
Nuria montó esto a mano desde la consola de AWS. En el módulo 3 lo pasaremos a Terraform para que sea reproducible; de momento, esta es la foto de a dónde despliega el pipeline:
flowchart TB
U["👥 Usuarios<br/>negocios y sus clientes"] --> CF["CloudFront + S3<br/>apps/web (estático)"]
U --> ALB["Application Load Balancer<br/>api.reservalia.com"]
subgraph AWS["AWS · región eu-west-1"]
ALB --> ECS
subgraph ECS["ECS Fargate · servicio reservalia-api"]
T1["Tarea 1<br/>contenedor api:a3f9c21"]
T2["Tarea 2<br/>contenedor api:a3f9c21"]
end
ECS --> RDS[("RDS PostgreSQL 16<br/>multi-AZ en prod")]
ECS --> SM["Secrets Manager<br/>credenciales de BD y APIs"]
ECS --> CW["CloudWatch Logs"]
ECR[("ECR<br/>registro de imágenes")] -.->|"descarga la imagen<br/>al desplegar"| ECS
end
GHA["⚙️ GitHub Actions"] -->|"1· publica la imagen"| ECR
GHA -->|"2· actualiza el servicio"| ECS
GHA -->|"3· sube los estáticos"| CF
style ECR fill:#d9f2d9
style GHA fill:#cfe8ff
Qué hace cada pieza y por qué está ahí:
| Pieza | Función | Por qué esta y no otra |
|---|---|---|
| ECR (Elastic Container Registry) | Guarda las imágenes Docker etiquetadas con el SHA del commit | Es el registro de artefactos: la pieza que hace posible construir una vez y promocionar |
| ECS Fargate | Ejecuta los contenedores de la API sin administrar servidores | Orquestación sin la complejidad de Kubernetes: proporcional a un equipo de tres personas (ver 01-03) |
| ALB (Application Load Balancer) | Reparte el tráfico entre las tareas y comprueba su salud | Permite despliegues sin corte: retira una tarea vieja solo cuando la nueva responde correctamente |
| RDS PostgreSQL | La base de datos gestionada | Copias de seguridad, parches y alta disponibilidad sin trabajo manual |
| S3 + CloudFront | Sirven la web compilada como ficheros estáticos | La web de Vite es HTML, CSS y JS: no necesita servidor |
| Secrets Manager | Guarda credenciales y las inyecta en tiempo de ejecución | Los secretos no viajan en la imagen ni en el repositorio (04-03) |
| CloudWatch Logs | Recoge los registros de las tareas | Base para la monitorización de 03-06 |
Y el recorrido completo de un cambio, del portátil de Diego al cliente final:
sequenceDiagram
participant D as Diego
participant GH as GitHub
participant GA as GitHub Actions
participant ECR as ECR
participant ECS as ECS Fargate
participant U as Usuario
D->>GH: push a rama + pull request
GH->>GA: trigger del pipeline
GA->>GA: npm ci · lint · typecheck · test
GA-->>GH: ✅ verde, se puede fusionar
D->>GH: merge a main
GH->>GA: trigger del pipeline de despliegue
GA->>GA: docker build → imagen etiquetada con el SHA
GA->>ECR: push de reservalia/api:a3f9c21
GA->>ECS: desplegar en dev y staging
GA->>GA: pruebas de humo en staging
GA-->>GH: ⏸️ esperando aprobación de Marta
Note over GA: puerta manual = Entrega Continua
GA->>ECS: promocionar el MISMO artefacto a prod
ECS->>U: nueva versión sirviendo tráfico
Este diagrama es el objetivo del curso. Al terminar el módulo 3, Reservalia tendrá exactamente este flujo funcionando.
- La hoja de ruta del curso aplicada a Reservalia
Qué habrá cambiado en Reservalia al final de cada módulo:
| Módulo | Qué habremos automatizado | Estado del ritual del viernes |
|---|---|---|
| 1. Introducción (estás aquí) | Nada aún: vocabulario, criterios, proyecto y métricas de partida | Intacto: 3 h, manual |
| 2. Integración Continua | Cada PR ejecuta npm ci, lint, typecheck, tests unitarios y de integración contra PostgreSQL. Se construyen imágenes Docker etiquetadas con el SHA y se publican en ECR. Se protege main para que nada entre en rojo |
El despliegue sigue siendo manual, pero ya nada llega roto a main. Diego deja de compilar a ciegas |
| 3. Despliegue Continuo | Despliegue automático a dev y staging; puerta manual para prod; infraestructura en Terraform; despliegue progresivo; feature flags; rollback automático; monitorización |
El ritual del viernes desaparece. Se despliega cuando hace falta, en minutos, y se puede deshacer |
| 4. Prácticas avanzadas | Pipeline reutilizable y probado; gestión de dependencias y actualizaciones; escaneo de seguridad y gestión de secretos; optimización de tiempo y coste; migraciones de base de datos seguras y reversibles | Diego deja de tocar psql para siempre. El pipeline baja de 14 a menos de 8 minutos |
| 5. Proyectos reales | Se incorpora apps/mobile (React Native) con su propio flujo hacia las tiendas; se estudia la separación en microservicios y cómo modernizar un legacy |
El pipeline soporta tres aplicaciones con necesidades distintas |
| 6. Herramientas | El mismo pipeline reexpresado en Jenkins, GitLab CI, CircleCI y Travis CI; contenedores y Kubernetes; GitHub Actions a fondo | El conocimiento del equipo deja de depender de una herramienta |
| 7. Ejercicios | Reconstrucción práctica de todo, de principio a fin, por tu cuenta | Lo sabes hacer tú, no solo leerlo |
| 8. Recursos | Rutas de aprendizaje, comunidades, certificaciones | — |
- Cómo seguir el curso si no usas Node.js
Reservalia usa Node.js y TypeScript, pero el curso no trata sobre Node.js. Trata sobre pipelines. Si tu día a día es Python, Java, Go, PHP, Ruby o .NET, todo lo que aprendas se aplica igual: lo único que cambia son los comandos concretos de cada paso.
Aquí está la tabla de traducción. Guárdala:
| Paso del pipeline | Node.js (el curso) | Python | Java (Maven) | Go | PHP | .NET |
|---|---|---|---|---|---|---|
| Fijar la versión | .nvmrc |
.python-version |
pom.xml + toolchain |
go.mod |
composer.json |
global.json |
| Instalar dependencias | npm ci |
pip install -r requirements.txt |
mvn dependency:go-offline |
go mod download |
composer install --no-dev |
dotnet restore |
| Fichero de bloqueo | package-lock.json |
requirements.txt fijado / poetry.lock |
pom.xml con versiones exactas |
go.sum |
composer.lock |
packages.lock.json |
| Análisis estático | eslint |
ruff / flake8 |
checkstyle / spotbugs |
go vet |
phpstan |
dotnet format |
| Comprobación de tipos | tsc --noEmit |
mypy |
(el compilador) | (el compilador) | phpstan |
(el compilador) |
| Ejecutar pruebas | npm test (vitest) |
pytest |
mvn test |
go test ./... |
phpunit |
dotnet test |
| Construir | npm run build |
(empaquetado o imagen) | mvn package |
go build |
(imagen) | dotnet publish |
| Artefacto resultante | imagen Docker | imagen Docker / wheel | .jar o imagen |
binario o imagen | imagen | imagen |
| Migraciones de BD | script migrate |
alembic upgrade head |
flyway migrate |
migrate up |
doctrine:migrations:migrate |
dotnet ef database update |
Lo que es idéntico en todos los lenguajes —y es la inmensa mayoría del curso:
- Los conceptos: trigger, job, etapa, runner, artefacto, entorno, promoción.
- El criterio de éxito o fracaso: el código de salida de cada comando.
- La estructura del pipeline: instalar → verificar → construir → publicar → desplegar.
- La estrategia de entornos y la regla de construir una vez y promocionar.
- Los contenedores: un
Dockerfilede Python y uno de Node se parecen muchísimo. - Las estrategias de despliegue, el rollback, los feature flags y la monitorización.
- Las métricas DORA y todo lo relativo a seguridad y secretos.
Sugerencia práctica: si quieres sacar el máximo partido, adapta cada ejercicio a tu propio proyecto. Cuando en 02-02 configuremos npm ci && npm test, monta en paralelo el equivalente con pip install && pytest en un repositorio tuyo. Aprenderás el doble.
- Qué necesitas instalado
Para leer el curso y entender los ejemplos: nada. Todos los ficheros están completos en las lecciones.
Para reproducir los ejemplos, que es muy recomendable:
| Herramienta | Para qué | Cómo comprobar que la tienes |
|---|---|---|
| Git | Clonar, ramificar, hacer commits | git --version |
| Cuenta de GitHub | Alojar tu repositorio y ejecutar Actions (gratis en repositorios públicos) | — |
| Node.js 20 | Ejecutar el proyecto de ejemplo | node --version |
| Docker + Docker Compose | PostgreSQL local y construcción de imágenes | docker --version y docker compose version |
| Un editor con soporte YAML | Escribir workflows sin pelearte con la indentación | — |
Opcionales, y solo a partir del módulo 3:
| Herramienta | Para qué | Nota |
|---|---|---|
| Cuenta de AWS | Desplegar de verdad en ECS y RDS | Tiene coste. Puedes seguir el módulo 3 sin ella: todos los ficheros son legibles y aplicables después |
| AWS CLI | Interactuar con AWS desde la terminal | aws --version |
| Terraform u OpenTofu | Infraestructura como código | terraform --version |
Comprobación rápida de tu entorno:
# Ejecuta esto y comprueba que no falta nada esencial.
echo "--- Imprescindible ---"
git --version || echo "❌ falta Git"
node --version || echo "❌ falta Node.js"
docker --version || echo "❌ falta Docker"
docker compose version || echo "❌ falta Docker Compose"
echo "--- Opcional (módulo 3 en adelante) ---"
aws --version || echo "ℹ️ AWS CLI no instalado (opcional)"
terraform --version || echo "ℹ️ Terraform no instalado (opcional)"Si no puedes instalar nada (por ejemplo, en un ordenador corporativo restringido): puedes seguir prácticamente todo el curso creando un repositorio en GitHub desde el navegador y editando los workflows desde la interfaz web. Los runners de GitHub ejecutan en la nube. Es menos cómodo, pero funciona.
Errores Comunes y Consejos
Error 1: querer automatizar antes de que los comandos funcionen en local. Si npm test no funciona en tu portátil, no funcionará en CI. El pipeline es un revelador de suposiciones ocultas: variables de entorno que solo tú tienes, servicios que están arrancados en tu máquina desde hace semanas, ficheros que nunca subiste al repositorio. Primero haz que funcione en una máquina limpia; luego automatízalo.
Error 2: no fijar las versiones de las herramientas. .nvmrc, engines, postgres:16.3 en lugar de postgres:latest, dependencias sin ^. Cada versión no fijada es una build que algún día dejará de funcionar sin que nadie haya tocado nada, y ese es el tipo de fallo más caro de diagnosticar.
Error 3: nombres de scripts distintos en cada subproyecto. Si apps/api usa npm test y apps/web usa npm run test:ci, tu pipeline se llenará de casos especiales. Unifica los nombres antes de escribir el primer workflow: es media hora de trabajo que ahorra días.
Error 4: copiar la base de datos de producción a staging. Es tentador ("probamos con datos reales") y es una fuga de datos personales esperando a ocurrir. Reservalia guarda teléfonos y correos de clientes finales. Genera datos ficticios con volumen realista.
Error 5: olvidar el healthcheck de los servicios auxiliares. "Contenedor arrancado" no es "servicio listo". Es la causa número uno de tests intermitentes en CI, y como vimos en 01-02, los tests flaky destruyen la confianza en el pipeline entero.
Consejo 1: si tienes un proyecto propio, úsalo en paralelo. Aplica cada lección a Reservalia y también a tu proyecto. La transferencia de conocimiento es mucho mayor cuando te enfrentas a las particularidades de tu propio código.
Consejo 2: escribe hoy tu propia tabla del ritual del viernes. Enumera todos los pasos manuales de tu proceso de despliegue actual, con su duración. Es una lista incómoda de leer y es exactamente el mapa de lo que vas a automatizar.
Consejo 3: un README.md que funcione es el primer paso de CI. Si un desarrollador nuevo puede clonar el repositorio y tener el proyecto funcionando siguiendo el README sin preguntar a nadie, tu proyecto está listo para automatizarse. Si no, arregla eso primero: el pipeline es exactamente ese README ejecutado por una máquina.
Ejercicios
Ejercicio 1: deducir el pipeline a partir del proyecto
Sin escribir nada de YAML —todavía no toca—, y usando únicamente lo que sabes del repositorio de Reservalia, responde:
- ¿En qué orden ejecutarías
npm ci,lint,typecheck,test:unidad,test:integracionybuild? Justifica el orden. - ¿Cuáles de esos pasos pueden ejecutarse en paralelo y cuáles obligatoriamente en serie?
- ¿Qué paso necesita que haya un PostgreSQL levantado, y qué implica eso para el runner?
- Un cambio toca solo
apps/web/src/paginas/PanelNegocio.tsx. ¿Sería correcto ejecutar únicamente las pruebas deapps/web? ¿Y si el cambio tocarapackages/tipos-compartidos/src/index.ts?
Ejercicio 2: diseñar la matriz de configuración por entorno
Reservalia necesita decidir, para cada valor de configuración, si es un secreto (va al gestor de secretos), una variable de entorno normal (puede estar en el repositorio) o algo que no debe existir en ese entorno. Completa la tabla y justifica los tres casos que te parezcan menos evidentes:
| Valor | dev |
staging |
prod |
|---|---|---|---|
DATABASE_URL |
|||
| Contraseña de la base de datos | |||
LOG_LEVEL |
|||
| Clave de API de la pasarela de pago | |||
SMTP_HOST |
|||
| Nombre del repositorio de ECR | |||
| Clave de firma de los tokens de sesión |
Ejercicio 3: traducir Reservalia a tu tecnología
Imagina que Reservalia estuviera escrita en Python con FastAPI en vez de Node.js con Express. Escribe:
- La estructura equivalente de
apps/api/(nombres de ficheros de configuración y de gestión de dependencias). - El equivalente de los cinco comandos del pipeline (
npm ci,lint,typecheck,test,build). - Qué partes del
docker-compose.ymlcambiarían y cuáles no. - Qué partes del curso dejarían de aplicar. (Pista: pocas.)
Soluciones
Solución al Ejercicio 1
1. Orden y justificación
1. npm ci ← imprescindible primero: sin dependencias no hay nada
2. lint · typecheck ← rápidos, detectan errores evidentes, sin infraestructura
3. test:unidad ← rápidos, sin base de datos
4. test:integracion ← lentos, requieren PostgreSQL
5. build ← solo tiene sentido si todo lo anterior está verdeEl principio que gobierna este orden se llama fail fast: coloca primero lo más rápido y lo que más probablemente falle. Si Diego se ha dejado un console.log o un tipo incompatible, quieres que lo sepa en 40 segundos, no después de esperar seis minutos a las pruebas de integración. Ordenar el pipeline al revés funciona igual de bien técnicamente, pero desperdicia el tiempo del equipo en cada fallo.
2. Paralelo frente a serie
npm cidebe ir primero y solo: todo depende de él.lint,typecheckytest:unidadpueden ir en paralelo entre sí: son independientes, no comparten estado y ninguno necesita el resultado del otro.test:integracionpuede ir en paralelo con los anteriores si el runner puede levantar PostgreSQL a la vez, aunque suele ejecutarse después para no pagar el coste de levantar la base de datos cuando algo trivial ya ha fallado. Es una decisión de compromiso entre velocidad y coste.buildva al final: construir un artefacto de código que no pasa las pruebas es tiempo y dinero tirados.
Un matiz sobre typecheck en este monorepo concreto: como apps/web depende de packages/tipos-compartidos, la comprobación de tipos de la web puede requerir que el paquete compartido esté compilado. En proyectos con referencias de proyecto de TypeScript esto se resuelve solo; en otros, obliga a un build parcial previo. Es el tipo de detalle que descubres al montar el pipeline y que resolveremos en 02-03.
3. PostgreSQL en el runner
test:integracion lo necesita. Implicaciones para el runner:
- El runner debe poder ejecutar contenedores (los runners alojados de GitHub pueden).
- Hay que esperar a que la base de datos esté lista, no solo arrancada: healthcheck o espera activa. Sin esto, tests flaky garantizados.
- Hay que aplicar las migraciones antes de ejecutar las pruebas, para que el esquema exista.
- Cada ejecución debe partir de una base de datos limpia, o el estado de una prueba contaminará a la siguiente.
4. Ejecución selectiva
- Cambio solo en
apps/web/src/paginas/PanelNegocio.tsx: sí, sería razonable ejecutar únicamente las pruebas deapps/web. La API no puede verse afectada por un cambio en un componente de React. Esto es ejecución selectiva y es una de las principales palancas de optimización (04-04). - Cambio en
packages/tipos-compartidos/src/index.ts: no. Ese paquete es una dependencia de las dos aplicaciones, así que hay que probar ambas. Este es precisamente el caso que hace peligrosa la ejecución selectiva mal implementada: si tu regla es "solo pruebo la carpeta que cambió", un cambio en el paquete compartido no probaría nada y podría romper las dos aplicaciones a la vez. La ejecución selectiva debe basarse en el grafo de dependencias, no en la ruta del fichero.
Solución al Ejercicio 2
| Valor | dev |
staging |
prod |
|---|---|---|---|
DATABASE_URL (sin contraseña) |
Variable normal | Variable normal | Variable normal |
| Contraseña de la base de datos | Secreto | Secreto | Secreto |
LOG_LEVEL |
Variable normal (debug) |
Variable normal (debug) |
Variable normal (info) |
| Clave de API de la pasarela de pago | Secreto (clave de pruebas) | Secreto (clave de pruebas) | Secreto (clave real) |
SMTP_HOST |
Variable normal | Variable normal | Variable normal |
| Nombre del repositorio de ECR | Variable normal | Variable normal | Variable normal |
| Clave de firma de tokens de sesión | Secreto | Secreto | Secreto, distinta de las otras dos |
Los tres casos menos evidentes:
DATABASE_URL. La clave está en separar la cadena de conexión de la contraseña. postgres://[email protected]:5432/reservalia no contiene ningún secreto: es un nombre de host interno que, sin credenciales y sin acceso de red, no sirve de nada. La contraseña se inyecta aparte. Meter la contraseña dentro de la URL es cómodo y convierte un valor público en un secreto, con todo lo que eso implica: no se puede loguear, no se puede poner en el repositorio, no se puede mostrar en un mensaje de error.
Clave de la pasarela de pago en dev y staging. Aunque sean claves de entorno de pruebas y no muevan dinero real, siguen siendo secretos: permiten hacer llamadas en nombre de Reservalia, consultar datos y agotar cuotas. Una clave de pruebas filtrada en un repositorio público es un incidente de seguridad menor, pero es un incidente. Regla práctica: si el proveedor la llama "clave secreta", es un secreto.
Clave de firma de tokens: distinta en cada entorno. Este es el caso más sutil y el más importante. Si staging y prod compartieran la clave de firma, un token emitido en staging —donde el equipo tiene acceso total y puede crear el usuario que quiera— sería válido en producción. Es una escalada de privilegios de manual. Los secretos criptográficos jamás se comparten entre entornos, ni siquiera "temporalmente para probar".
Un criterio general para clasificar: si al filtrarse permite a alguien hacer algo que no debería, es un secreto. Si solo revela cómo se llaman las cosas, es configuración. Volveremos sobre ello en 04-03.
Solución al Ejercicio 3
1. Estructura equivalente en Python/FastAPI
apps/api/
├── src/
│ ├── main.py punto de entrada (en vez de index.ts)
│ ├── rutas/
│ ├── dominio/
│ └── db/
│ └── migraciones/ gestionadas por Alembic
├── tests/
│ ├── unidad/
│ └── integracion/
├── pyproject.toml ← equivalente a package.json
├── poetry.lock ← equivalente a package-lock.json
├── .python-version ← equivalente a .nvmrc
├── alembic.ini configuración de migraciones
└── Dockerfile2. Los cinco comandos
| Paso | Node.js | Python/FastAPI |
|---|---|---|
| Instalar | npm ci |
poetry install --sync (respeta el lock, como npm ci) |
| Lint | npm run lint |
ruff check src tests |
| Tipos | npm run typecheck |
mypy src |
| Pruebas | npm test |
pytest |
| Construir | npm run build |
No hay compilación: el "build" es directamente docker build |
| Migraciones | npm run migrate |
alembic upgrade head |
Una observación interesante: Python no tiene paso de compilación, así que el artefacto se produce directamente al construir la imagen Docker. El pipeline tiene un paso menos, pero el resto de la estructura es idéntica.
3. Cambios en docker-compose.yml
- No cambia nada del servicio
db: PostgreSQL 16.3, su healthcheck, su volumen y sus variables son exactamente iguales. La base de datos no sabe en qué lenguaje está escrita la aplicación. - No cambia nada del servicio
mailpit: captura SMTP, sea quien sea el emisor. - Cambiaría únicamente el servicio de la aplicación, si se añadiera: la imagen base (
python:3.12-slimen vez denode:20-slim) y el comando de arranque (uvicorn src.main:app --reload).
Es un buen indicador de cuánto de este curso es independiente del lenguaje: la infraestructura de desarrollo es prácticamente la misma.
4. Qué partes del curso dejarían de aplicar
Casi ninguna. Concretamente:
- Deja de aplicar: los comandos exactos de npm, la sintaxis de
package.jsony los detalles de las herramientas concretas de JavaScript (vitest, ESLint, tsc). - Sigue aplicando íntegro: todo el vocabulario de 01-01; los beneficios y costes de 01-02; el mapa de herramientas de 01-03; las métricas DORA de 01-05; todo el módulo 3 (estrategias de despliegue, IaC, feature flags, rollback, monitorización); todo el módulo 4 salvo los ejemplos de sintaxis; el módulo 6 completo; y la estructura, el orden y la lógica de cada pipeline que construyamos.
Estimación honesta: más del 85 % del curso es independiente del lenguaje. Lo que cambia son las cadenas de texto dentro de los pasos run:.
Conclusión
Ya conoces el proyecto sobre el que trabajaremos durante todo el curso:
- Reservalia es una plataforma SaaS de reservas de cita con 340 negocios de pago, dos aplicaciones (
apps/apien Node.js + Express + PostgreSQL yapps/weben React + Vite), un paquete de tipos compartidos y un monorepo con workspaces de npm. - El equipo son Marta (tech lead, mira el negocio y el riesgo), Diego (backend, quiere un CI rápido y es el único que sabe desplegar) y Nuria (SRE, quiere infraestructura reproducible y rollback fiable). Sus tensiones son las que tendrás en tu equipo.
- El proyecto ya tiene sus comandos definidos:
npm ci,lint,typecheck,test,buildymigrate. Esta es la idea clave de la lección: el pipeline no inventa nada, solo ejecuta en una máquina limpia lo que el proyecto ya sabe hacer. Un pipeline es un revelador de suposiciones ocultas. - Hay tres entornos —
dev,stagingyprod— gobernados por dos principios: el mismo artefacto se promociona a los tres, y los datos de producción nunca salen de producción. - La infraestructura de destino es AWS: imágenes en ECR, ejecución en ECS Fargate detrás de un balanceador, datos en RDS PostgreSQL, secretos en Secrets Manager y registros en CloudWatch.
- Y sabes cómo seguir el curso con cualquier tecnología: más del 85 % es independiente del lenguaje; solo cambian los comandos dentro de cada paso.
Nos queda una última cosa antes de empezar a construir. Marta va a pedir tiempo del equipo para montar todo esto, y dentro de seis meses alguien preguntará si ha servido de algo. Para responder a eso hace falta haber medido el punto de partida antes de tocar nada. En la siguiente lección, Métricas DORA: Cómo se Mide la Entrega de Software, veremos las cuatro métricas estándar del sector —frecuencia de despliegue, lead time for changes, change failure rate y time to restore service—, cómo calcularlas a partir de datos que el propio pipeline genera, y estableceremos el cuadro de mando inicial de Reservalia: dónde están hoy y a dónde quieren llegar al final del curso.
Curso de CI/CD: Integración y Despliegue Continuo
Módulo 1: Introducción a CI/CD
- Conceptos Básicos de CI/CD
- Beneficios de CI/CD
- Herramientas Populares de CI/CD
- El Proyecto del Curso: la Aplicación que Vamos a Automatizar
- Métricas DORA: Cómo se Mide la Entrega de Software
Módulo 2: Integración Continua (CI)
- Introducción a la Integración Continua
- Configuración de un Entorno de CI
- Automatización de la Construcción
- Pruebas Automatizadas
- Calidad de Código y Análisis Estático
- Artefactos, Versionado y Promoción
- Integración con Control de Versiones
Módulo 3: Despliegue Continuo (CD)
- Introducción al Despliegue Continuo
- Automatización del Despliegue
- Infraestructura como Código y Entornos Reproducibles
- Estrategias de Despliegue
- Feature Flags, Rollback y Recuperación ante Fallos
- Monitoreo y Retroalimentación
Módulo 4: Prácticas Avanzadas de CI/CD
- Pipelines de CI/CD
- Gestión de Dependencias
- Seguridad en CI/CD
- Escalabilidad y Rendimiento
- Pipeline as Code: Plantillas, Reutilización y Pruebas del Pipeline
- Bases de Datos en el Pipeline: Migraciones Seguras
Módulo 5: Implementación de CI/CD en Proyectos Reales
- Caso de Estudio: Proyecto Web
- Caso de Estudio: Aplicación Móvil
- Caso de Estudio: Microservicios
- Caso de Estudio: Modernizar un Proyecto Legacy
Módulo 6: Herramientas y Tecnologías
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker y Kubernetes
- GitHub Actions a Fondo
- Comparativa y Criterios para Elegir Herramienta
Módulo 7: Ejercicios Prácticos
- Ejercicio 1: Configuración de un Pipeline Básico
- Ejercicio 2: Integración de Pruebas Automatizadas
- Ejercicio 3: Despliegue en un Entorno de Producción
- Ejercicio 4: Monitoreo y Retroalimentación
- Ejercicio 5: Endurecer el Pipeline con Seguridad y Secretos
- Proyecto Final: Pipeline Completo de Extremo a Extremo
