Módulo 1: De tu máquina al pipeline: por qué CI

3. Qué es la Integración Continua

Descripción

Al terminar esta lección vas a poder definir la Integración Continua (CI) con precisión, distinguirla de lo que no es, y reconocer sus tres pilares. La lección 2 te dejó con el problema en carne viva —"en mi máquina funciona", el mismo test dando dos veredictos según el entorno—; esta lección le pone nombre y forma a la solución. En una frase: la Integración Continua es correr tu suite de tests automáticamente, en un entorno limpio y reproducible, en una máquina neutral compartida, cada vez que alguien sube un cambio al repositorio. Cada palabra de esa frase carga peso, y vamos a desarmarla pieza por pieza, porque entender qué hace automática a la integración, qué hace limpio al entorno y qué hace neutral a la máquina es entender por qué el CI cierra justo las grietas que un verde local deja abiertas.

También vas a ver de dónde viene el nombre —"integrar" no es una palabra decorativa, describe un acto concreto: juntar el trabajo de todos, seguido, y verificarlo cada vez—; qué no es el CI, para no pedirle lo que no da; y cuál es la plataforma que usaremos como canónica en la guía —GitHub Actions— junto con sus equivalentes. Y vas a ver, honestamente presentado, cómo se leería el log de CI corriendo nuestra suite de Reservo: el mismo pytest que ya corres, envuelto por las etapas de un runner.

Conexión con el módulo: esta es la lección-bisagra del módulo. La 1 planteó el porqué; la 2 mostró el problema; esta define la solución. Lo que viene se apoya en esta definición: la lección 4 explica por qué conviene que ese entorno automático atrape el fallo cuanto antes (el bucle de feedback); la 5 abre el pipeline por dentro (sus etapas); la 6 dice qué protege (la rama verde). Aquí todavía no escribes el YAML —eso es el módulo 2—; aquí entiendes qué es la cosa que ese YAML va a describir.

El sistema de agua de un edificio

Piénsalo así. En un edificio de departamentos, cada quien podría conseguir su propia agua: bajar a la calle con cubetas, llenarlas en una llave pública, subirlas, y confiar en que el agua está limpia porque "a mí no me ha caído mal". Funcionaría… hasta que a alguien le cae mal, y para entonces ya la tomó. Nadie sabría si el agua está buena hasta que alguien se enferma. La verificación —"¿está limpia?"— sería manual, ocasional, y tarde.

Por eso los edificios no funcionan así. Tienen un sistema central: el agua entra por un solo lugar, pasa automáticamente por un filtro y un análisis antes de llegar a las llaves, y lo hace cada vez que corre, no cuando alguien se acuerda de revisar. El sistema es compartido: no es "el agua de Ana" y "el agua de Beto", es el agua del edificio, la misma para todos, verificada en un punto neutral por el que pasa todo. Si el análisis detecta algo, cierra la llave antes de que el agua sucia llegue a un vaso. Nadie tiene que acordarse de nada; el sistema no se cansa ni olvida.

El CI es ese sistema central para el código. Sin él, cada desarrollador "consigue su propia agua": corre los tests en su máquina, cuando se acuerda, y confía en su verde local —hasta que a alguien "le cae mal" en otra máquina o en producción—. Con CI, el código pasa automáticamente por la suite, en un punto neutral y compartido por el que pasan todos los cambios, cada vez que alguien sube algo. Y si la suite detecta un problema, el CI "cierra la llave" —marca el cambio en rojo— antes de que el código roto llegue a la rama principal. Los tres adjetivos del sistema de agua —automático, compartido, cada-vez— son exactamente los tres pilares del CI.

Integración Continua: correr la suite automáticamente (nadie tiene que acordarse), en un entorno limpio y reproducible (no tu máquina, con sus supuestos), en un punto neutral y compartido (el mismo para todo el equipo), en cada cambio que se sube (no de vez en cuando).

Los tres pilares, uno por uno

La definición es densa a propósito. Cada pilar cierra una de las grietas que la lección 1 nombró en un verde local. Vale la pena verlos por separado.

Pilar 1: automática — cierra la grieta del olvido

Una suite que depende de que un humano se acuerde de correrla, tarde o temprano, no se corre. No por descuido: por prisa, por un viernes a las seis, por "es un cambio de una línea, seguro no rompe nada". El CI quita al humano de la ecuación: la suite se dispara sola cuando ocurre un evento —típicamente un push a una rama o la apertura de un pull request—. No hay un "¿corriste los tests?" en la revisión, porque la respuesta siempre es sí, la máquina los corrió. La palabra "continua" del nombre vive aquí: la integración no es un evento que se agenda, es algo que pasa continuamente, en cada cambio, sin fricción.

Pilar 2: entorno limpio y reproducible — cierra la grieta del entorno

Este es el pilar que responde directamente a la lección 2. El runner del CI arranca vacío: una máquina recién creada, sin tus variables de shell, sin los paquetes que instalaste una vez y olvidaste, con una versión de Python que el proyecto declara explícitamente. Sobre esa base limpia, instala solo lo que el proyecto dice que necesita, y corre ahí la suite. Por eso el CI es el "caso B" de la lección 2 hecho rutina: la máquina neutra que descubre los supuestos invisibles. "Reproducible" es la otra mitad: como el entorno se construye desde una declaración escrita —qué versión, qué dependencias—, dos corridas del CI parten del mismo punto, y cualquiera puede reconstruir ese entorno para reproducir un fallo (el arte del módulo 3).

Pilar 3: neutral y compartido — cierra la grieta del aislamiento

El CI no corre en la máquina de nadie: corre en una máquina de nadie y de todos, un punto por el que pasan todos los cambios de todos los desarrolladores. Eso lo vuelve una verdad compartida. Cuando el CI dice "verde", no es "verde para Ana"; es "verde en el entorno común", y ese veredicto vale para el equipo entero. Cuando dice "rojo", nadie puede responder "pues en la mía funciona" y cerrar el caso, porque el CI no es "la de nadie": es el escenario neutral. Este pilar es el que convierte el testing de un acto individual en una garantía de equipo, y es la base de lo que la lección 6 llamará "la rama principal siempre verde".

De dónde viene el nombre: integrar

Vale la pena entender la palabra, porque explica el "por qué" histórico. Integrar es juntar el trabajo de distintas personas en un solo cuerpo de código que funcione. En los equipos de antes —y en los que todavía no hacen CI— cada quien trabajaba semanas en su rama, aislado, y al final venía el temido "día de la integración": todos juntaban su código de golpe, y aparecían montañas de conflictos y roturas que nadie había visto venir, porque cada pieza se había probado sola pero nunca junta. Ese día podía costar días de dolor.

La idea de la Integración Continua fue: en vez de integrar de golpe cada varias semanas, integrar seguido —idealmente varias veces al día— y verificar cada integración automáticamente con la suite. Si juntas el trabajo de todos muchas veces al día y una máquina corre los tests en cada juntada, los problemas aparecen de a uno, pequeños, el mismo día que se crearon, en vez de acumularse en una avalancha. "Continua" se opone a "de vez en cuando"; "integración" es el acto de juntar y verificar. El CI, entonces, no es solo "correr tests en un servidor": es una disciplina de integrar seguido y verificar cada vez, y el pipeline de tests es la herramienta que la hace posible.

Qué NO es la Integración Continua

Definir algo por lo que no es evita pedirle lo que no da. El CI no es:

  • No escribe tus tests. El CI corre la suite que ya tienes; no la inventa. Si tu suite no prueba el descuento pro, el CI tampoco lo va a probar —solo va a correr, fielmente, los tests que existen—. Escribir buenos tests es la guía de fundamentos; el CI hereda la calidad de tu suite, no la crea.
  • No es un sello de "código correcto". Un CI verde significa "los tests que hay pasaron en el entorno limpio", no "el código no tiene bugs". Un bug en un caso que ningún test toca pasa el CI sin despeinarse. El CI es tan bueno como tu suite.
  • No es despliegue. Correr los tests en cada cambio (CI) es distinto de enviar el código verificado a producción (CD). Son cosas separadas, y la lección 7 las distingue. Esta guía se enfoca en el CI de los tests.
  • No es una plataforma específica. GitHub Actions es una forma de hacer CI, la que usaremos; pero el CI es un concepto, no un producto. GitLab CI, CircleCI, Jenkins, Buildkite y otros hacen lo mismo con otra sintaxis. Aprende el concepto y la plataforma será un detalle.

La plataforma canónica: GitHub Actions (y sus primos)

En esta guía usamos GitHub Actions como la plataforma de CI, por dos razones prácticas: es la más extendida hoy, y vive dentro de tu repositorio de GitHub, así que no tienes que salir a configurar un servidor aparte. En el módulo 2 vas a escribir su archivo de configuración —un YAML que vive en .github/workflows/— y a leer sus logs.

Pero conviene que sepas, desde ya, que el concepto es portátil. Todas estas plataformas hacen lo mismo —arrancar una máquina limpia, instalar, correr tu suite, reportar verde o rojo—; solo cambia la sintaxis del archivo que lo describe:

PlataformaDónde vive la configNombre del archivo típico
GitHub Actions (la nuestra)En el repo, .github/workflows/ci.yml
GitLab CI/CDEn el repo, raíz.gitlab-ci.yml
CircleCIEn el repo, .circleci/config.yml
JenkinsEn el repo o en el servidorJenkinsfile

Lo que aprendas aquí —qué es un pipeline, sus etapas, qué protege, cómo leer su log— se traduce a cualquiera de ellas. Elegimos GitHub Actions para tener una sintaxis concreta que tocar, no porque el CI dependa de ella.

Ejemplo trabajado: cómo se leería el log del CI

Aquí toca una honestidad, porque es la regla de esta guía. El pytest de Reservo lo corremos de verdad en local, y su salida es la que ya viste: real, medida. El log de un CI de GitHub Actions, en cambio, lo mostramos como contenido honesto —"así se ve, así se leería"—, porque ejecutar un CI real necesita un runner de GitHub que aquí no tenemos. No te vamos a inventar números: el corazón del log es exactamente la salida de pytest que sí corrimos; lo que lo rodea es la envoltura que un runner le pone.

La idea clave, y por eso el CI no da miedo, es esta: el CI corre el mismo comando que tú corres. Cuando en tu máquina tecleas python3 -m pytest y ves 12 passed, el CI teclea ese mismo comando en su máquina limpia. Su log es tu salida de pytest, envuelta en unas líneas que cuentan qué etapa está ejecutando. Así se leería, para nuestra suite de Reservo en verde:

Run python -m pytest
============================= test session starts ==============================
platform linux -- Python 3.12.7, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/runner/work/reservo/reservo
collected 12 items

test_pricing.py ....                                                     [ 33%]
test_refunds.py .....                                                    [ 75%]
test_scheduling.py ...                                                   [100%]

============================== 12 passed in 0.05s ==============================

Qué esperar, línea por línea. La primera línea, Run python -m pytest, es del runner: te dice qué comando está ejecutando en esta etapa (en el log real de GitHub aparece como un paso plegable que puedes abrir). Todo lo de abajo es pytest puro, idéntico a tu salida local, con dos diferencias reveladoras: platform linux en vez de darwin —el runner corre en Linux, no en tu Mac— y Python 3.12.7 en vez de tu 3.14.0 —el runner usa la versión que el proyecto declaró—. Esas dos diferencias son, ni más ni menos, dos de las fugas de entorno de la lección 2, ahora controladas: el CI no depende de tu Mac ni de tu versión, corre en un entorno declarado y explícito. Y la ruta rootdir: /home/runner/work/... te confirma que esto no corrió en tu máquina, sino en un runner limpio de nadie.

Ahora, ¿qué pasaría si el código estuviera roto —el descuento pro mal, como en la lección 2—? El CI correría el mismo comando y su log terminaría así:

Run python -m pytest
...
FAILED test_pricing.py::test_price_by_tier_and_hours[pro-3h] - assert 7500 == 6000
============================== 1 failed, 11 passed in 0.05s ===============================
Error: Process completed with exit code 1.

Fíjate en la última línea: Error: Process completed with exit code 1. Esa es la magia mínima del CI, y es el tema de la lección 5: pytest terminó con código de salida 1 (rojo), el runner leyó ese 1, y marca todo el pipeline como fallido. En tu máquina, ese 1 lo ignorabas; el CI lo convierte en un check rojo que bloquea el merge. El CI no entiende de tests ni de descuentos: entiende de un número —0 o 1— que pytest le entrega al terminar. Todo lo demás es plomería alrededor de ese número.

Errores comunes

Creer que el CI corre "algo distinto" de lo que corres tú. Qué pasa: alguien trata el CI como una caja negra mágica que hace una verificación misteriosa, distinta y más estricta que su pytest local, y por eso le tiene miedo o desconfía de sus resultados. Por qué pasa: el CI se ve envuelto en logs y configuración, así que parece otra cosa. Cómo detectarlo: si no sabrías decir qué comando exacto corre tu CI, lo estás tratando como magia. Cómo corregirlo: recuerda que el CI corre el mismo comando que tú —python -m pytest—, solo que en una máquina limpia. Lo único distinto es el entorno, no la verificación. Cuando el CI y tú difieren, la diferencia está en el entorno (lección 2 y módulo 3), no en un "pytest secreto".

Pedirle al CI que garantice que el código es correcto. Qué pasa: alguien ve el check verde y concluye "el CI lo aprobó, el código está bien", y baja la guardia sobre la calidad de su suite. Por qué pasa: un verde grande y tranquilizador se siente como una garantía total. Cómo detectarlo: si confías en el verde del CI más de lo que confiarías en tu propia suite corrida en local, te estás engañando —es la misma suite—. Cómo corregirlo: el CI verde vale exactamente lo que valen tus tests. Si tu suite no cubre un caso, el CI tampoco. El CI garantiza "estos tests pasan en un entorno limpio", no "el código es correcto". Subir la calidad de la suite es la guía de fundamentos; ponerle puertas de cobertura al CI es el módulo 6 de esta.

Confundir la plataforma con el concepto. Qué pasa: alguien aprende GitHub Actions y cree que "CI" es GitHub Actions, así que se siente perdido si el equipo usa GitLab o Jenkins. Por qué pasa: uno aprende con una herramienta concreta y la confunde con la idea. Cómo detectarlo: si crees que cambiar de plataforma significa reaprender el CI desde cero, confundiste la sintaxis con el concepto. Cómo corregirlo: separa las dos capas. El concepto —máquina limpia, instala, corre la suite, reporta con un código de salida— es idéntico en todas. La sintaxis del archivo cambia. Aprende bien el concepto en este módulo y GitHub Actions en el módulo 2, y traducir a otra plataforma será leer su documentación una tarde.

Ejercicios

Ejercicio 1 — Empareja el pilar con la grieta. La lección 1 nombró tres grietas de un verde local: el olvido, el entorno y el aislamiento. Empareja cada una con el pilar del CI que la cierra (automática / entorno limpio / neutral y compartido) y explica el emparejamiento en una frase.

Ver solución
  • Olvido → automática. El pilar de correr la suite sola en cada push/PR cierra la grieta del olvido: nadie tiene que acordarse, porque la máquina no olvida.
  • Entorno → entorno limpio y reproducible. El runner que arranca vacío, con versión y dependencias declaradas, cierra la grieta del entorno: corre el "caso B" de la lección 2 por rutina, sacando a la luz los supuestos invisibles.
  • Aislamiento → neutral y compartido. El punto por el que pasan todos los cambios, que no es la máquina de nadie, cierra la grieta del aislamiento: su verde vale para el equipo, no solo para quien lo corrió.

La lección: el CI no es un truco monolítico; son tres mecanismos, cada uno respondiendo a una grieta distinta. Entender qué pilar cierra qué grieta es entender por qué el CI resuelve "en mi máquina funciona" y no solo lo esconde.

Ejercicio 2 — Diagnostica desde el log. Miras el log de CI de un compañero y ves esta cabecera: platform linux -- Python 3.10.6, pytest-9.1.1. En tu máquina, la suite pasa con Python 3.14.0. El CI está rojo con un error de que cierta función no existe. Sin ver más, (a) ¿cuál es la fuga de entorno más probable? (b) ¿por qué tu verde local no te avisó? (c) ¿qué cierra esta grieta a futuro, y en qué módulo se ve?

Ver solución
  • (a) La versión de Python. El CI corre en 3.10.6 y tú en 3.14.0. Una función que existe en 3.14 y no en 3.10 (o que cambió) explica que allá falle y aquí no. La cabecera lo delata: la línea platform ... -- Python X es justo el dato que revela esta fuga.
  • (b) Porque tu entorno tenía la versión "buena". Tu 3.14 tiene la función; nunca ejerciste el caso de la versión vieja. Tu verde solo prueba "pasa en 3.14", no "pasa en 3.10". Es un caso puro de "en mi máquina funciona" por diferencia de versión.
  • (c) Lo cierra declarar y controlar la versión —y, mejor aún, probar en varias a la vez con la matriz, que es el módulo 4 de esta guía—. Reproducir el fallo instalando localmente esa versión es el módulo 3.

La lección: el log del CI no es solo un veredicto, es un diagnóstico. Su cabecera te dice el entorno exacto donde falló, y comparar ese entorno con el tuyo suele apuntar directo a la fuga.

Ejercicio 3 — Traduce entre plataformas. Un equipo usa GitLab CI, no GitHub Actions. Un compañero dice: "entonces lo que aprendimos del CI no nos sirve, es todo distinto". Argumenta por qué se equivoca, nombrando tres cosas que son idénticas entre las dos plataformas y una que cambia.

Ver solución

Se equivoca porque confunde la sintaxis con el concepto. Tres cosas idénticas entre GitHub Actions y GitLab CI (y cualquier otra plataforma de CI):

  1. El modelo: una máquina limpia arranca, instala las dependencias declaradas, corre tu suite y reporta verde o rojo. Es el mismo en las dos.
  2. El disparador: la suite se corre automáticamente en un evento del repositorio (un push, un merge request / pull request). Mismo pilar.
  3. La señal de veredicto: las dos leen el código de salida del proceso de tests (0 = verde, 1 = rojo) para decidir si el pipeline pasa. Mismo mecanismo (lección 5).

Lo que cambia es la sintaxis y ubicación del archivo de configuración: GitHub Actions usa un YAML en .github/workflows/; GitLab CI usa .gitlab-ci.yml en la raíz, con palabras clave distintas. Cambia el "cómo se escribe", no el "qué hace".

La lección: cambiar de plataforma de CI es aprender un dialecto, no un idioma nuevo. Lo que este módulo te enseña —el concepto— es lo mismo en todas; lo que el módulo 2 te enseña —el YAML de GitHub Actions— es el dialecto que elegimos.

Resumen y siguiente paso

En esta lección le pusiste nombre y forma a la solución del problema de la lección 2. La Integración Continua es correr tu suite automáticamente, en un entorno limpio y reproducible, en una máquina neutral y compartida, en cada cambio que se sube al repositorio. Sus tres pilares cierran, uno por uno, las tres grietas de un verde local: la automática cierra el olvido, el entorno limpio cierra la diferencia de entorno, y la máquina neutral cierra el aislamiento. El nombre no es decorativo: "integrar" es juntar el trabajo de todos, y "continua" es hacerlo seguido y verificarlo cada vez, para que los problemas aparezcan pequeños y a tiempo en vez de en una avalancha.

Viste qué no es el CI —no escribe tus tests, no garantiza corrección, no es despliegue, no es una plataforma— y cuál usaremos como canónica: GitHub Actions, con GitLab CI y otros como primos que hacen lo mismo con otra sintaxis. Y viste, honestamente presentado, cómo se leería el log del CI corriendo nuestra suite: el mismo pytest que ya corres, en platform linux -- Python 3.12.7, terminando con el veredicto que el runner lee del código de salida.

Antes de avanzar deberías poder: definir el CI en una frase con sus tres pilares; explicar de dónde viene la palabra "integración"; nombrar dos cosas que el CI no es; y decir por qué el CI corre "el mismo comando que tú".

Lo que sigue es entender por qué vale tanto la pena que ese entorno automático atrape el fallo cuanto antes. En la lección 4 vas a ver el bucle de feedback y la curva del costo: por qué el mismo bug cuesta centavos si lo cazas al escribirlo y una fortuna si llega al cliente, y cómo el CI acorta ese bucle acercando el aviso al momento en que tienes el contexto todavía caliente.

Recursos