← CC3091

Integración Continua

Semestre 02, 2026

Introducción

Idea central

Si una actividad resulta costosa y dolorosa, hay que hacerla más seguido hasta volverla trivial.

  • La integración de código es la actividad más riesgosa de un proyecto con varios desarrolladores, porque es el momento en que el trabajo de todos se junta por primera vez.
  • La integración continua no es una herramienta ni un servidor, es una práctica de equipo que se sostiene con herramientas.

Integración Continua

Definición

Práctica en la que cada miembro del equipo integra su trabajo a un repositorio compartido al menos una vez al día, y cada integración se verifica con una compilación y una batería de pruebas automáticas.

  • La definición tiene tres partes inseparables, que son integrar seguido al main branch, verificar cada integración de forma automática y corregir de inmediato lo que se rompa.
  • Un equipo que tiene un servidor de CI pero integra cada dos semanas no practica integración continua, solo tiene un servidor de CI.

El infierno de la integración

Cuando cada desarrollador trabaja aislado durante semanas, el código divergente crece sin que nadie lo note.

Conflictos semánticos: Git fusiona dos archivos sin conflicto de texto y aun así produce un programa que no funciona.
Costo no lineal: el esfuerzo de fusionar crece más rápido que el tamaño del cambio.
Fase de estabilización: una etapa final impredecible dedicada solo a hacer que el sistema vuelva a funcionar.
Miedo a refactorizar: si integrar duele, nadie se atreve a mejorar la estructura del código.

Desarrollo sobre el main branch

  • Existe una sola rama principal, llamada main branch, que se mantiene siempre funcional.
  • El trabajo se hace en ramas de vida corta, de horas o de un día, que se fusionan al main branch el mismo día en que se abren.
  • Mientras más tiempo vive una rama, más divergente está del main branch y más caro resulta el merge.
  • El desarrollo sobre el main branch es un requisito de la integración continua, no una alternativa a ella.

Funcionalidades grandes

Problema

Una funcionalidad de tres semanas no cabe en una rama de un día, pero mantenerla aislada tres semanas reintroduce el problema que la práctica quiere eliminar.

  • La solución es integrar la funcionalidad incompleta pero inactiva, detrás de un interruptor de funcionalidad (feature flag) que la mantiene apagada en producción.
  • El código viaja al main branch todos los días y el usuario no lo ve hasta que el interruptor se enciende.

Prácticas de la integración continua

Un solo repositorio: todo lo necesario para construir el sistema está versionado.
Compilación automatizada: un solo comando produce el sistema ejecutable, sin pasos manuales.
Build auto-verificable: la compilación incluye las pruebas, y si alguna falla la compilación falla.
Integrar todos los días: cada desarrollador empuja su trabajo al main branch una vez al día.
Entorno de referencia: el servidor de CI construye cada commit en una máquina limpia.

Prácticas de la integración continua

Corregir el build roto de inmediato: un main branch roto bloquea a todo el equipo.
Mantener el build rápido: diez minutos o menos para la compilación de commit.
Probar en una copia de producción: menos diferencias de entorno, menos defectos escapados.
Estado visible para todos: cualquiera sabe en segundos si el main branch está en verde.
Despliegue automatizado: el artefacto verificado se promueve entre entornos con un script.

El build auto-verificable

  • La pieza que distingue a la integración continua de una compilación programada es la batería de pruebas.
  • Sin pruebas automáticas, el servidor de CI solo confirma que el código compila, y compilar no significa funcionar.
  • El pipeline solo vale lo que valen sus pruebas.

Capas de la batería de pruebas

Pruebas unitarias: muchas, rápidas y aisladas, verifican una unidad de código sin dependencias externas.
Pruebas de integración: verifican la conversación entre componentes, con base de datos o servicios reales.
Pruebas de extremo a extremo: pocas, lentas y frágiles, recorren el sistema completo desde la interfaz.

La pirámide de pruebas es una guía de proporción, con la mayoría en la base y muy pocas en la punta.

Anatomía de un pipeline de CI

flowchart LR
    A[Commit al main branch] --> B[El servidor de CI detecta el cambio]
    B --> C[Compilar]
    C --> D[Pruebas]
    D --> E[Análisis estático y dependencias]
    E --> F[Publicar artefacto]
    F --> G{Build en verde}
    G -->|Sí| H[Main branch listo para desplegar]
    G -->|No| I[Notificar y corregir de inmediato]
        

Etapas del pipeline

Disparador: un push al main branch o la apertura de un pull request.
Preparación: máquina limpia, código descargado y dependencias con versiones fijas.
Compilación: el artefacto se produce una sola vez y es el que viaja por el resto del pipeline.
Pruebas: primero las rápidas, para fallar temprano.
Análisis: linters, calidad de código y vulnerabilidades en dependencias.
Notificación: el resultado se informa por el canal que el equipo ya usa.

Servidores de integración continua

GitHub Actions: configurado en .github/workflows, con un mercado de acciones reutilizables.
GitLab CI/CD: configurado en .gitlab-ci.yml, en la nube o autoalojado.
Jenkins: autoalojado y de código abierto, con pipelines en Jenkinsfile.
CircleCI: servicio en la nube orientado a la velocidad del build.
Azure Pipelines: parte de Azure DevOps, con agentes Windows, Linux y macOS.
Bitbucket Pipelines: opción nativa para equipos que ya usan Bitbucket y Jira.

Criterio de selección

HerramientaCuándo conviene
GitHub ActionsEl código ya está en GitHub, es la opción por defecto
GitLab CI/CDSe quiere repositorio, pipelines y seguridad en un solo producto
CircleCIEl tiempo de build es una restricción medible
JenkinsHay una restricción que lo obliga, como entornos aislados o hardware propio
Advertencia

Jenkins es el más flexible y también el que más mantenimiento exige, así que solo se justifica cuando existe un equipo que pueda sostenerlo.

Herramientas de apoyo del pipeline

Compilación y dependencias: Maven, Gradle, npm, Composer, pip, Cargo.
Pruebas: JUnit, Pest, PHPUnit, pytest, Vitest, Playwright, Cypress.
Cobertura: JaCoCo, coverage.py y Codecov miden qué porción del código ejecutan las pruebas.
Análisis estático y formato: SonarQube, ESLint, Pint, Black, Checkstyle.
Seguridad: Dependabot, Renovate, OWASP Dependency-Check, Trivy, Gitleaks.
Contenedores y artefactos: Docker y registros como Artifactory guardan cada build con su versión.

Un workflow de GitHub Actions

Enunciado

Se requiere un pipeline que, en cada push al main branch y en cada pull request, instale las dependencias con versiones fijas, ejecute el análisis estático y corra la batería de pruebas de un proyecto en Node.

Un workflow de GitHub Actions


name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  verificar:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npm run lint
      - run: npm test

name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  verificar:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npm run lint
      - run: npm test
name: CI

El nombre con el que el pipeline aparece en la pestaña de Actions y en el estado de cada commit. El archivo vive en .github/workflows y está versionado junto al código, así que la definición del pipeline evoluciona con el proyecto.


name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  verificar:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npm run lint
      - run: npm test
on:
  push:
  pull_request:

Los disparadores del pipeline. Corre en cada push al main branch y en cada pull request dirigido al main branch, que son los dos momentos donde importa saber si el sistema sigue funcionando.


name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  verificar:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npm run lint
      - run: npm test
jobs:
  runs-on: ubuntu-latest

Un job es una unidad de trabajo y runs-on declara la máquina donde se ejecuta. GitHub provee una máquina limpia en cada corrida, que es lo que convierte al pipeline en un entorno de referencia y no en la laptop de alguien.


name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  verificar:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npm run lint
      - run: npm test
uses: actions/checkout@v4
uses: actions/setup-node@v4

Los pasos con uses invocan acciones reutilizables. La primera descarga el código del commit que disparó el pipeline y la segunda instala la versión exacta del lenguaje, fijada para que el build sea reproducible.


name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  verificar:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npm run lint
      - run: npm test
npm ci
npm run lint
npm test

Los pasos con run ejecutan comandos. Aquí está el build auto-verificable, con dependencias en versiones fijas, análisis estático y batería de pruebas. Si cualquier paso termina con código distinto de cero, el job falla y el commit queda marcado en rojo.

Integración en el ciclo de vida

Idea central

La integración continua no es una etapa del ciclo de vida, es una práctica que atraviesa todas sus etapas.

Requerimientos: los criterios de aceptación se escriben de forma verificable, para poder convertirse en pruebas automáticas.
Diseño: la arquitectura debe permitir probar los componentes por separado, con módulos desacoplados y dependencias inyectables.

Integración en el ciclo de vida

Construcción: el desarrollador integra al main branch a diario y el pipeline le devuelve retroalimentación en minutos en vez de semanas.
Verificación y validación: la mayor parte de las pruebas de regresión dejan de ser manuales y se ejecutan en cada commit.
Despliegue: el artefacto que el pipeline verificó es el mismo que se promueve a producción, sin recompilar.
Mantenimiento: cada defecto de producción se documenta con una prueba que falla, y esa prueba entra al pipeline para que no vuelva.

El flujo de trabajo del desarrollador

flowchart TD
    A[Rama corta desde main] --> B[Commit local y pruebas en local]
    B --> C[Traer main y resolver conflictos]
    C --> D[Pull request hacia main]
    D --> E[El pipeline verifica la combinación real]
    E --> F[Revisión de código]
    F --> G[Merge a main el mismo día]
    G --> A
        

Reglas de protección del main branch

Las políticas del repositorio impiden que la disciplina dependa de la buena memoria del equipo.

  • Pipeline obligatorio, no se permite fusionar si el build no está en verde.
  • Revisión obligatoria, al menos una aprobación de otra persona antes del merge.
  • Rama actualizada con el main branch antes de fusionarse, para que el pipeline valide la combinación real.
  • Prohibido el push directo, todo cambio entra por pull request, incluidos los del líder técnico.
  • Historial lineal con squash o rebase, para que el main branch sea legible y bisecable.

Métricas del pipeline

Medir el pipeline es la única forma de saber si la práctica funciona o solo existe en el papel.

MétricaQué indica
Frecuencia de integraciónCuántas veces al día llega trabajo al main branch
Duración del buildTecho recomendado de diez minutos
Tasa de builds exitososPorcentaje de commits que producen build en verde
Tiempo de reparaciónCuánto tarda el main branch en volver a verde
Frecuencia de despliegueCuántas veces llega software a producción
Tiempo de entrega del cambioDel commit hasta que corre en producción

Antipatrones frecuentes

La mayoría de los fracasos no vienen de la herramienta, vienen de la disciplina que la rodea.

Pruebas inestables: una prueba que pasa o falla sin que el código cambie hace que el equipo reintente en lugar de investigar.
Ramas de larga vida: mantener ramas de semanas convierte el merge en un proyecto aparte.
Main branch roto tolerado: seguir trabajando con el main branch en rojo bloquea al equipo y vuelve el pipeline decorativo.

Antipatrones frecuentes

Pipeline lento: un build de cuarenta minutos deja de ser retroalimentación y se convierte en un obstáculo.
Exceso de pruebas de interfaz: apoyar la regresión en pruebas de UI produce una batería lenta y frágil.
Pruebas dependientes del orden: comparten estado, pasan en secuencia y fallan en paralelo.
CI de fachada: el servidor configurado sin pruebas reales ni integración diaria da la apariencia sin los beneficios.

Integración, entrega y despliegue continuos

Integración continua: cada cambio se integra al main branch a diario y se verifica de forma automática.
Entrega continua: el pipeline deja listo un artefacto desplegable en cualquier momento, y una persona autoriza el paso a producción.
Despliegue continuo: todo cambio que pasa el pipeline llega a producción sin intervención humana.
Dependencia estricta

No existe entrega continua sin integración continua, ni despliegue continuo sin entrega continua.

Beneficios

La evidencia de DORA asocia la práctica con mayor frecuencia de despliegue, sistemas más estables y entregas más predecibles.

  • Los defectos se detectan minutos después de introducirse, cuando quien los causó todavía recuerda el contexto.
  • La integración deja de ser una fase costosa e impredecible y se convierte en un evento rutinario.
  • Refactorizar se vuelve seguro, porque la batería de pruebas avisa de inmediato si algo se rompió.
  • El main branch se mantiene listo para desplegar, así que la fecha de liberación pasa a ser una decisión de negocio.

Requisitos que la práctica impone

  • Un equipo comprometido y con disciplina de pruebas, porque el pipeline solo vale lo que valen sus pruebas.
  • Un entorno de compilación reproducible, sin dependencias de la máquina de nadie.
  • Inversión inicial en automatización, que se paga con el tiempo que deja de perderse en integraciones manuales.
  • No encaja bien en proyectos con colaboradores dispersos y sin coordinación, como los de código abierto clásicos.