Integración y Despliegue Continuo (CI/CD)
Hasta ahora ejecutamos los tests nosotros. Ahora queremos que GitHub los ejecute automáticamente cada vez que subamos código.
1. ¿Qué es CI/CD? #
Cuando hacemos:
1git push
queremos que ocurra automáticamente:
1git push 2 ↓ 3GitHub recibe el código 4 ↓ 5Instala el proyecto 6 ↓ 7Tests unitarios 8 ↓ 9Tests E2E 10 ↓ 11Build 12 ↓ 13¿Todo correcto? 14 ↓ 15 ✅
Esto forma parte de CI/CD.
CI — Continuous Integration significa comprobar automáticamente que los cambios que integramos funcionan.
En nuestro proyecto:
1npm install 2npm run test:unit 3npm run test:e2e 4npm run build
CD — Continuous Delivery/Deployment consiste en preparar o publicar automáticamente la aplicación una vez que ha superado las comprobaciones.
Como ya tenemos Netlify conectado a GitHub, podemos dejar que Netlify haga el despliegue.
2. GitHub Actions #
GitHub dispone de un sistema de automatización llamado GitHub Actions.
La configuración se guarda dentro del propio repositorio en:
1.github/ 2└── workflows/
Vamos a crear:
1.github/ 2└── workflows/ 3 └── ci.yml
Nuestra estructura queda:
1taskapp/ 2├── .github/ 3│ └── workflows/ 4│ └── ci.yml 5│ 6├── src/ 7│ ├── main.js 8│ ├── style.css 9│ ├── utils.js 10│ └── utils.test.js 11│ 12├── tests/ 13│ └── taskapp.spec.js 14│ 15├── index.html 16├── package.json 17├── package-lock.json 18├── playwright.config.js 19└── .gitignore
3. Nuestro primer workflow #
Dentro de:
1.github/workflows/ci.yml
escribimos:
1name: CI 2 3on: 4 push: 5 branches: [main] 6 7 pull_request: 8 branches: [main] 9 10jobs: 11 12 test: 13 runs-on: ubuntu-latest 14 15 steps: 16 17 - name: Descargar repositorio 18 uses: actions/checkout@v4 19 20 - name: Instalar Node.js 21 uses: actions/setup-node@v4 22 with: 23 node-version: 22 24 cache: npm 25 26 - name: Instalar dependencias 27 run: npm ci 28 29 - name: Tests unitarios 30 run: npm run test:unit -- --run 31 32 - name: Instalar navegadores de Playwright 33 run: npx playwright install --with-deps 34 35 - name: Tests E2E 36 run: npm run test:e2e 37 38 - name: Construir aplicación 39 run: npm run build
Con esto ya tenemos una CI funcional.
4. Entender el archivo paso a paso #
No les daría el YAML sin más. Lo recorrería línea a línea.
Primero:
1name: CI
Simplemente estamos poniendo un nombre a nuestra automatización.
Después:
1on: 2 push: 3 branches: [main]
Significa:
Ejecuta este proceso cuando alguien haga
pushamain.
Por ejemplo:
1git push
provoca:
1GitHub 2 ↓ 3detecta push a main 4 ↓ 5ejecuta CI
También hemos añadido:
1pull_request: 2 branches: [main]
Así los tests también pueden ejecutarse cuando alguien intenta incorporar cambios mediante una Pull Request.
5. Crear una máquina #
Esta parte suele parecer más complicada de lo que realmente es.
Tenemos:
1jobs: 2 3 test: 4 runs-on: ubuntu-latest
Podemos explicarlo como:
GitHub nos presta temporalmente un ordenador para probar nuestro proyecto.
Ese ordenador utiliza Ubuntu.
1GitHub 2 ↓ 3crea una máquina Ubuntu 4 ↓ 5ejecuta nuestros comandos 6 ↓ 7termina 8 ↓ 9elimina la máquina
6. Descargar nuestro proyecto #
La máquina está inicialmente vacía.
Por eso:
1- name: Descargar repositorio 2 uses: actions/checkout@v4
Hace básicamente:
1GitHub 2 ↓ 3copia nuestro repositorio 4 ↓ 5máquina Ubuntu
7. Instalar Node.js #
Nuestro proyecto necesita Node.js.
1- name: Instalar Node.js 2 uses: actions/setup-node@v4 3 with: 4 node-version: 22 5 cache: npm
Ahora la máquina puede utilizar:
1node 2npm
8. Instalar las dependencias #
Ejecutamos:
1- name: Instalar dependencias 2 run: npm ci
Aquí aparece un concepto nuevo interesante.
Durante el desarrollo normalmente hemos utilizado:
1npm install
En CI es habitual utilizar:
1npm ci
npm ci instala las dependencias utilizando estrictamente el:
1package-lock.json
Por tanto:
1package.json 2 + 3package-lock.json 4 ↓ 5 npm ci 6 ↓ 7node_modules/
Es especialmente apropiado para instalaciones reproducibles en automatizaciones.
9. Ejecutar Vitest #
Ahora:
1- name: Tests unitarios 2 run: npm run test:unit -- --run
GitHub ejecutará nuestros tests de Vitest.
Por ejemplo:
1formatearFecha() 2 ↓ 3Vitest 4 ↓ 5 2 tests 6 ↓ 7 ✅
Si falla uno:
1❌ CI FAILED
y el proceso no continúa normalmente con los pasos posteriores.
10. Preparar Playwright #
Nuestro siguiente test necesita navegadores.
Por eso:
1- name: Instalar navegadores de Playwright 2 run: npx playwright install --with-deps
Playwright instala lo necesario para poder ejecutar los tests E2E en la máquina de GitHub.
11. Ejecutar los E2E #
Después:
1- name: Tests E2E 2 run: npm run test:e2e
Playwright hará automáticamente:
1Arrancar TaskApp 2 ↓ 3Abrir navegador 4 ↓ 5Escribir tarea 6 ↓ 7Pulsar Añadir 8 ↓ 9Comprobar resultado 10 ↓ 11 ✅
Aunque nosotros no veamos físicamente el navegador.
12. Comprobar que podemos construir la aplicación #
Finalmente:
1- name: Construir aplicación 2 run: npm run build
Eso ejecuta:
1vite build
y debe generar:
1dist/
Con esto comprobamos también que la aplicación puede construirse correctamente para producción.
13. Subir el workflow a GitHub #
Ahora hacemos exactamente lo que ya conocen:
1git add . 2 3git commit -m "Añadido CI con GitHub Actions" 4 5git push
Y ocurre algo nuevo.
GitHub detecta:
1.github/workflows/ci.yml
y ejecuta automáticamente el workflow.
14. Ver el resultado #
Entramos en el repositorio de GitHub y vamos a la pestaña Actions.
Veremos algo similar a:
1CI 2 3✓ Descargar repositorio 4✓ Instalar Node.js 5✓ Instalar dependencias 6✓ Tests unitarios 7✓ Instalar navegadores 8✓ Tests E2E 9✓ Construir aplicación
Todo verde:
1✅ CI correcta
15. Hagamos que falle #
Esto es casi obligatorio en clase.
Modificamos un test:
1expect(formatearFecha(fecha)) 2 .toBe("FECHA INCORRECTA");
Hacemos:
1git add . 2git commit -m "Prueba de CI" 3git push
Ahora GitHub ejecutará:
1npm ci 2 ↓ 3tests unitarios 4 ↓ 5 ❌
En Actions veremos el workflow en rojo.
Así el alumno entiende inmediatamente para qué sirve CI.
Corregimos el test y:
1git add . 2git commit -m "Corregido test" 3git push
GitHub vuelve a comprobarlo:
1npm ci 2 ↓ 3Vitest ✅ 4 ↓ 5Playwright ✅ 6 ↓ 7Vite build ✅ 8 ↓ 9CI ✅
16. ¿Y dónde está CD? #
Aquí conectaría con lo que ya hicimos con Netlify.
Si Netlify está conectado al repositorio:
1Nuestro ordenador 2 │ 3 │ git push 4 ▼ 5 GitHub 6 │ 7 ├──── GitHub Actions 8 │ ↓ 9 │ Vitest 10 │ ↓ 11 │ Playwright 12 │ ↓ 13 │ Build 14 │ 15 └──── Netlify 16 ↓ 17 Build 18 ↓ 19 Deploy 20 ↓ 21 🌐
Pero hay una precisión importante para explicar en clase: tal como está configurado, GitHub Actions y Netlify son procesos independientes. Que la CI falle no implica automáticamente que Netlify espere a que pase.
Si queremos una cadena estricta:
1TESTS 2 ↓ 3solo si pasan 4 ↓ 5BUILD 6 ↓ 7solo si pasa 8 ↓ 9DEPLOY
entonces podemos configurar el despliegue como parte del workflow o establecer controles adicionales.
La idea que debería llevarse el alumno #
Al terminar dibujaría esto en grande:
1 DESARROLLO 2 3HTML + CSS + Vanilla JS 4 │ 5 ▼ 6 Vite 7 │ 8 ▼ 9 TaskApp 10 │ 11 ▼ 12 Vitest 13 tests unitarios 14 │ 15 ▼ 16 Playwright 17 tests E2E 18 │ 19 ▼ 20 Git 21 │ 22 git push 23 │ 24 ▼ 25 GitHub 26 │ 27 ▼ 28 GitHub Actions 29 │ 30 ┌─────┴─────┐ 31 ▼ ▼ 32 Vitest Playwright 33 │ │ 34 └─────┬─────┘ 35 ▼ 36 Vite build 37 │ 38 ▼ 39 Netlify 40 │ 41 ▼ 42 🌐 PRODUCCIÓN
CI/CD consiste en automatizar las comprobaciones y la entrega de nuestra aplicación. En lugar de confiar en que el desarrollador recuerde ejecutar todos los pasos, GitHub puede ejecutarlos automáticamente cada vez que cambia el código.