En un proyecto de desarrollo se producen muchos archivos: código, configuraciones, scripts, datos de prueba. Todos estos artefactos los crean y modifican distintas personas a lo largo del tiempo —meses durante el desarrollo inicial, y potencialmente años durante la vida útil del producto—. Administrarlos bien es importante: la alternativa es perder información, perder tiempo rehaciendo cosas que no se encuentran, y afectar la calidad de lo que se construye.
Una vez decidida la arquitectura, debe haber un mapeo claro entre sus conceptos y los artefactos concretos: carpetas, archivos, esquemas de nombramiento. Por ejemplo, para el recurso Editorial del modelo del proyecto Book que usamos en las páginas de Backend, un mismo concepto se transforma en varios artefactos:
Con un modelo conceptual de apenas 10 clases y algunas asociaciones entre ellas, fácilmente se llega a más de 100 artefactos distintos que hay que mantener organizados, solo en el backend.
Cuando varios desarrolladores trabajan en el mismo proyecto, hay que dividirse el trabajo —por capas, por conceptos, por historias de usuario— y luego integrar lo que cada quien hizo. Una estrategia posible es esperar a que todos terminen su parte y dedicar una fase aparte, al final, solo para integrar. Suena práctica, pero trae varios problemas:
Se descubren tarde los malentendidos sobre los requerimientos o el diseño: “las piezas no encajan”.
Corregir defectos en esta etapa —sobre problemas que se introdujeron mucho antes— es más costoso y puede generar problemas nuevos.
La calidad del software es difícil de asegurar.
Las pruebas de integración llegan muy tarde en el desarrollo para ser útiles.
A este escenario se lo conoce como Integration Hell, y su consecuencia directa es retrasar las entregas porque hay que rehacer trabajo, comprometiendo la calidad.
La alternativa —hoy parte de casi cualquier metodología de desarrollo— es la Integración Continua: en vez de integrar al final, se integra de forma constante, a medida que avanza el trabajo de cada persona. La práctica nació con Extreme Programming (XP) y fue descrita por primera vez por Martin Fowler en su artículo Continuous Integration.
Cada desarrollador trabaja con su propia copia local del repositorio: clona el repositorio central, hace sus commits localmente, y cuando su trabajo está listo, hace push para que los demás lo integren con un pull. Ya viste este flujo —commits, ramas, Pull Requests— con detalle en el taller de Git; la Integración Continua se construye directamente sobre él.
Lo que agrega la Integración Continua es la frecuencia: mientras más rápido y seguido se integra el trabajo de todos al repositorio central, más rápido aparece un conflicto o un error de integración, y más barato es corregirlo.
Todo proyecto depende de librerías de terceros —y no de cualquier versión, sino de una versión específica—. Hay que asegurar que todos los desarrolladores usen exactamente las mismas.
Sin importar el lenguaje, las herramientas de manejo de dependencias comparten dos ideas:
Existe un repositorio de librerías, distinto al del código fuente del proyecto: puede ser un repositorio público de acceso universal, o uno propio de la organización.
Cada proyecto declara, en un archivo, cuáles librerías necesita y en qué versión.
En nuestro backend en Spring Boot usamos Maven, que declara las dependencias en pom.xml:
En el frontend en Angular usamos npm, que las declara en package.json:
"dependencies": {
"@angular/core": "^18.0.0"
}
En ambos casos, cualquiera que clone el repositorio y corra mvn install o npm install termina con exactamente las mismas versiones que todos los demás.
Sin un proceso de build automatizado y repetible, cada desarrollador puede terminar haciendo las cosas un poco distinto, y aparecen inconsistencias: se usan versiones de librerías que no son, no queda claro qué fuentes se empaquetaron, no se corrieron las pruebas sobre lo que se empaquetó, el servidor de destino no quedó bien configurado.
Automatizar el build significa que un solo comando —no una serie de pasos manuales— resuelve las dependencias, compila las fuentes, empaqueta la aplicación y corre las pruebas. Con Maven, ese comando es mvn clean test; con npm, npm run build y npm test. Esto ya lo puede correr cualquier desarrollador en su máquina local; lo que falta es que ocurra automáticamente, para todos, cada vez que alguien integra.
Repositorio central, dependencias declaradas y build automatizado son los ingredientes. Lo que falta es una herramienta que los conecte: que detecte automáticamente cada push al repositorio central y, sin intervención manual, dispare el build y las pruebas — y que le avise a todo el equipo si algo se rompió.
flowchart LR
A["git push"] --> B["La herramienta de CI<br/>detecta el push"]
B --> C["Descarga dependencias<br/>(mvn / npm install)"]
C --> D["Compila y empaqueta"]
D --> E["Corre las pruebas"]
E -->|"Todo pasa"| F["✅ Reporta éxito"]
E -->|"Algo falla"| G["❌ Reporta el error"]
F --> H["(Opcional)<br/>Despliega"]
Dos herramientas cubren este rol en el curso:
GitHub Actions
Corre dentro del propio GitHub: se configura con un archivo .yml dentro de .github/workflows/ en el repositorio, y no necesita infraestructura propia — GitHub la ejecuta por ti. Es la opción más simple cuando el código ya vive en GitHub.
Jenkins
Es un servidor de CI aparte, que hay que instalar y mantener (propio o en la nube), configurado con un Jenkinsfile. A cambio de ese costo de infraestructura, es independiente de dónde viva el repositorio y muy configurable para pipelines complejos.
Este mismo sitio de recursos usa GitHub Actions para correr sus pruebas en cada Pull Request. Así se ve su configuración real, en .github/workflows/test.yml:
name: Tests
on:
pull_request:
push:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Instalar dependencias
run: pnpm install --frozen-lockfile
- name: Correr las pruebas
run: pnpm test
Para un backend en Maven, el equivalente es igual de corto:
name: CI Backend
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configurar Java
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "21"
- name: Compilar y correr pruebas
run: mvn -B test
on define qué evento dispara el workflow —en ambos casos, cada push y cada Pull Request—, y steps es, en esencia, el mismo pipeline que correrías a mano: descargar dependencias, compilar, probar.
Un Jenkinsfile describe el mismo tipo de pipeline, pero lo ejecuta el propio servidor de Jenkins en vez de GitHub:
pipeline {
agent any
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Build y pruebas') {
steps { sh 'mvn clean test' }
}
stage('Empaquetar') {
steps { sh 'mvn package' }
}
stage('Desplegar') {
steps { sh './deploy.sh' }
}
}
}
Jenkins necesita que alguien instale y mantenga el servidor (propio, o en un proveedor en la nube), y que el repositorio le avise —por webhook— cuando hay un push nuevo. A cambio, es una herramienta independiente de GitHub: sirve igual si el código vive en GitLab, Bitbucket o un servidor propio.