Git en equipo y código de una IA
Programar solo y programar en equipo son oficios distintos. En equipo, el código de todos tiene que convivir sin pisarse, y cada cambio tiene que poder revisarse antes de entrar. Esta guía recorre las decisiones de Git que un equipo toma todos los días — ramas, commits, Pull Requests, conflictos — y termina con la pregunta de estos tiempos: qué hacés cuando el código no lo escribió una persona, sino una IA.
👁 — vistas · Ing. Roy Carrasco, Facultad de Ingeniería de Sistemas · UAB
Volvés al equipo de EventoUAB, ahora en pleno Sprint 1, con Valentina y Mateo programando las reservas de espacios. En cada decisión podés probar primero un camino equivocado, ver su consecuencia sin ninguna penalidad, y recién después se desbloquea el correcto.
Dos personas, un mismo archivo
Es martes del Sprint 1. Valentina está programando la historia "reservar un espacio" y Mateo la historia "cancelar una reserva". Las dos viven en el mismo archivo, reservas.js. Hasta ahora ambos hacían commits directo a main, y el viernes pasado la app dejó de arrancar porque un cambio a medias de uno pisó al otro.
Es que los dos cambian el mismo código al mismo tiempo, y ninguno puede probar lo suyo sin cargar con lo a medias del otro. Hace falta una regla de equipo sobre dónde vive el trabajo en curso.
¿Cómo organizan el trabajo en paralelo sobre el mismo archivo?
Turnarse funciona hasta que alguien se olvida de avisar, y además convierte a dos personas en una sola: mientras una programa, la otra espera sin hacer nada. El equipo pierde justo lo que Git resuelve. Probá con otra opción.
Juntar archivos a mano al final es la peor versión del problema: sin historial, sin forma de saber qué cambió cada quien, y con todos los choques descubiertos el último día. Git existe para evitar justo eso. Probá con otra opción.
Exacto. Una rama es una línea de trabajo propia: lo a medias de uno no molesta al otro, y main queda siempre funcionando. Seguí leyendo ↓
Una rama por historia
Una rama es una línea paralela de commits que parte de un punto de main. El trabajo en curso vive ahí; main solo recibe cambios ya terminados. La regla práctica del equipo: una rama por historia de usuario, con un nombre que diga qué se está haciendo.
git switch main # 1. parto de main...
git pull # ...actualizado
git switch -c feature/cancelar-reserva # 2. creo mi rama y me paso a ella
# ...programo, pruebo...
git add src/reservas.js test/reservas.test.js
git commit -m "Permite cancelar una reserva propia" # 3. guardo un paso
git push -u origin feature/cancelar-reserva # 4. subo mi rama
| Comando | Qué hace |
|---|---|
git switch -c nombre | Crea una rama nueva y te pasa a ella. |
git add archivo | Marca qué cambios van a entrar en el próximo commit. |
git commit -m "..." | Guarda un paso en la historia de tu rama, con un mensaje. |
git push | Sube tus commits al repositorio compartido. |
git pull | Trae al tuyo los cambios nuevos del repositorio compartido. |
En GitHub o GitLab se puede configurar main para que nadie pueda hacer push directo: todo entra por Pull Request. Es la forma de que la regla "main siempre funciona" no dependa de la memoria de nadie.
Une cada comando de Git con lo que hace.
El commit de 23 archivos
Valentina lleva tres días en su rama sin hacer un solo commit. Hoy tiene la reserva funcionando, pero también reformateó medio proyecto, renombró dos funciones y arregló un error de ortografía en el login. Mateo le pregunta cómo va a guardar todo eso.
¿Cómo guarda Valentina su trabajo en Git?
Un commit gigante es una caja negra: si mañana la reserva falla, nadie puede saber cuál de los 23 archivos la rompió, y deshacer solo esa parte es imposible. "Más limpia" para quien mira la lista, más ciega para quien debe encontrar un bug. Probá con otra opción.
Dividir por archivo no es dividir por intención: el cambio de reserva y el de ortografía del login pueden tocar el mismo archivo, y una función renombrada toca muchos. Y "update" no le dice nada a nadie. La cantidad de commits no mide el trabajo. Probá con otra opción.
Exacto. Un commit, un cambio lógico: si el renombre rompe algo, se deshace solo ese commit y la reserva queda intacta. Seguí leyendo ↓
Commits chicos, mensajes que sirven
El historial de commits es la bitácora del proyecto. Dentro de un mes, cuando algo falle, alguien va a leer esa lista para entender qué pasó. Un buen commit hace dos cosas: contiene un solo cambio lógico y tiene un mensaje que explica qué cambia (y, si no es obvio, por qué).
| Regla | Ejemplo en EventoUAB |
|---|---|
| Un cambio lógico por commit | El renombre de funciones va aparte de la reserva nueva. |
| Mensaje en infinitivo o imperativo, corto | "Rechaza reservas con fin anterior al inicio" |
| Dice el qué, no el "arreglé cosas" | "Corrige choque falso entre reservas contiguas" |
| El proyecto funciona después de cada commit | No se commitea una función a medias que rompe el arranque. |
git add src/reservas.js # solo la reserva nueva
git commit -m "Agrega reserva de un espacio por franja horaria"
git add src/espacios.js src/reservas.js # solo el renombre
git commit -m "Renombra buscar() a buscarEspacioLibre()"
git add src/login.js
git commit -m "Corrige ortografía del mensaje de error del login"
¿Este mensaje de commit es útil o pobre?
"Ya lo probé en mi máquina"
Mateo terminó la historia "cancelar una reserva". En su computadora funciona. Quedan dos días de sprint y el equipo tiene prisa. Tiene que decidir cómo hace llegar su trabajo a main.
¿Cómo integra Mateo su historia?
"Funciona en mi máquina" es el comienzo de muchos bugs, no el final: faltan los datos de otro, la versión de otra librería, el caso que Mateo no pensó. Sin revisión, el equipo se entera del error en la demo. Y lo que "ahorró" en espera lo paga multiplicado en arreglos. Probá con otra opción.
Un PR con todo el sprint adentro no se puede revisar de verdad: nadie lee 40 archivos con atención y la gente termina aprobando por cansancio. Además, cualquier error aparece justo cuando ya no queda tiempo de corregirlo. Probá con otra opción.
Exacto. Un PR chico es fácil de leer, y la descripción le dice al revisor qué mirar. Seguí leyendo ↓
Pull Request: pedir que otro mire antes de entrar
Un Pull Request (PR) es la solicitud de unir tu rama a main. No es un trámite: es el momento donde otra persona del equipo lee tu cambio, lo prueba y comenta. Funciona si el PR es chico y si la descripción le ahorra trabajo al revisor.
| Quién | Qué le toca |
|---|---|
| Autor | Abrir un PR chico, describir qué cambia y cómo probarlo, y responder los comentarios sin tomarlos como ataque. |
| Revisor | Leer el código, correr el cambio y contrastarlo con los criterios de aceptación de la historia. Una persona distinta del autor. |
## Qué cambia
Permite cancelar una reserva propia hasta 2 horas antes del inicio.
## Cómo probarlo
1. Reservar el salón 2 para mañana a las 10:00.
2. Entrar a "Mis reservas" y tocar "Cancelar".
3. Confirmar que el horario vuelve a aparecer libre.
## Criterios de aceptación (historia #14)
- [x] Solo el club que reservó puede cancelar.
- [x] No se puede cancelar con menos de 2 horas de anticipación.
- [ ] Se notifica a Bienestar Estudiantil (queda para otra historia).
Como regla de equipo: si un PR toca más de unos 10 archivos o mezcla más de una historia, se parte. Revisar bien no depende de la voluntad del revisor, depende del tamaño de lo que le dan.
Las marcas raras en el archivo
Valentina fusionó su rama con la última versión de main y Git frena con un aviso: CONFLICT in reservas.js. Mateo, que ya integró su historia, cambió la misma función que ella tocó. Dentro del archivo aparecen líneas con <<<<<<< y >>>>>>>.
<<<<<<< HEAD // lo que hay hoy en main (cambio de Mateo)
if (reserva.club !== usuario.club) {
throw new Error("Solo el club dueño puede modificar la reserva");
}
======= // lo que trae mi rama (cambio de Valentina)
if (!reserva.activa) {
throw new Error("La reserva ya no está vigente");
}
>>>>>>> feature/reservar-espacio
¿Qué hace Valentina con ese conflicto?
Quedarse con "lo mío" borra en silencio el control de club que escribió Mateo: la app volvería a permitir que cualquier club modifique la reserva de otro. Un conflicto no es una pelea entre dos versiones; casi siempre las dos son necesarias. Probá con otra opción.
Rehacer todo desde cero tira a la basura horas de trabajo, y no evita el problema: cuando intente integrar la versión rehecha, el mismo choque reaparece, porque Mateo y ella siguen cambiando la misma función. Probá con otra opción.
Exacto. Las dos condiciones son válidas: la solución es conservar ambas, y las pruebas confirman que no se rompió nada. Seguí leyendo ↓
Resolver un conflicto sin perder trabajo ajeno
Git marca un conflicto cuando dos ramas cambiaron las mismas líneas y no puede decidir solo cuál gana. Es normal, no una falla. Lo que importa es el criterio con el que se resuelve.
if (reserva.club !== usuario.club) {
throw new Error("Solo el club dueño puede modificar la reserva");
}
if (!reserva.activa) {
throw new Error("La reserva ya no está vigente");
}
| Paso | Qué se hace |
|---|---|
| 1. Leer | Entender qué quiso hacer cada lado, no solo cuál se ve más largo. |
| 2. Hablar | Preguntarle a quien hizo el otro cambio si no queda claro. Cinco minutos de charla evitan un bug. |
| 3. Combinar | Editar el archivo a mano y borrar todas las marcas <<<, === y >>>. |
| 4. Probar | Correr las pruebas: un archivo sin marcas puede igual estar mal combinado. |
| 5. Commitear | Recién ahí git add y el commit de resolución. |
Ramas cortas (uno o dos días), git pull a menudo desde main y PRs chicos hacen que los conflictos sean raros y pequeños. Los conflictos enormes casi siempre vienen de ramas que vivieron semanas sin sincronizarse.
"Se lo pedí a la IA y funcionó"
Mateo necesita una función que diga si dos reservas chocan en el mismo espacio. Le pide a una IA: "escribime en JavaScript una función que diga si dos reservas se pisan". Le devuelve esto, lo pega en el proyecto y lo prueba con dos reservas que se solapan: true. Perfecto.
// reservas.js
export function hayChoque(a, b) {
return a.inicio <= b.fin && b.inicio <= a.fin;
}
"Un club puede reservar un espacio que empieza justo cuando otra reserva termina (por ejemplo, 10:00–12:00 y luego 12:00–14:00)."
¿Qué hace Mateo con el código de la IA antes de abrir el PR?
Su ejemplo solapado pasa, pero el criterio de aceptación habla de reservas contiguas, y ese caso no lo probó. Con <=, la reserva 10:00–12:00 y la de 12:00–14:00 devuelven true: el sistema rechazaría reservas legítimas pegadas una a otra. "Corre sin errores" no es lo mismo que "hace lo que se pidió". Probá con otra opción.
Una IA que se revisa a sí misma comparte los mismos puntos ciegos con que escribió el código: suele responder "se ve correcto" con total seguridad. La revisión que vale es la que viene de afuera del texto del modelo — un caso concreto que se ejecuta de verdad. Probá con otra opción.
Exacto. La prueba del borde (12:00 contra 12:00) falla enseguida y muestra el bug que la demo feliz escondía. Seguí leyendo ↓
Código de una IA: tu nombre va en el commit
Cuando Mateo corre las pruebas escritas desde los criterios, la función de la IA falla justo en el caso que el equipo había anotado. Se corrige cambiando <= por <. Nadie "culpa" a la IA: en el historial de Git, el que hizo el commit es Mateo, y es él quien responde por lo que entra a main.
import test from "node:test";
import assert from "node:assert/strict";
import { hayChoque } from "./reservas.js";
const a = { inicio: 10, fin: 12 };
test("reservas solapadas chocan", () => {
assert.equal(hayChoque(a, { inicio: 11, fin: 13 }), true);
});
test("reservas contiguas NO chocan", () => {
// con la versión de la IA (<=) esta prueba falla: devuelve true
assert.equal(hayChoque(a, { inicio: 12, fin: 14 }), false);
});
export function hayChoque(a, b) {
return a.inicio < b.fin && b.inicio < a.fin; // < en vez de <=
}
| Práctica | Cómo se ve en el equipo |
|---|---|
| Dar contexto real | Pasarle a la IA la historia, los criterios de aceptación y el lenguaje del proyecto, no una frase suelta. |
| Leer antes de pegar | Poder explicar cada línea. Si no la entendés, no entra al PR. |
| Probar con los criterios | Las pruebas salen de lo que el usuario pidió, incluidos los bordes, no de lo que la IA devolvió. |
| Cuidar los secretos | Nunca pegarle claves, tokens ni datos personales reales: lo que se pega a un chat sale de tu control. |
| Declararlo en el PR | Anotar qué partes vinieron de una IA, para que el revisor mire con más atención justo esas. |
Si un token o una contraseña llega a un commit, borrar el archivo después no alcanza: queda en el historial. La única salida es revocar esa clave y generar otra. Por eso el .env va en .gitignore desde el primer commit, y por eso tampoco se pega en una conversación con una IA.
¿Se lo podés pegar a una IA, o no?
Actividades de repaso
Un repaso integrador de lo que acabás de recorrer. Cada actividad se corrige al instante; tu progreso se guarda automáticamente en este navegador.
¿Por qué conviene una rama por historia en lugar de trabajar todos directo sobre main?
Si el código lo generó una IA y las pruebas pasan, la responsabilidad por un bug en producción es de la IA y no del equipo que lo integró.
Llega un PR de 40 archivos con la descripción "varios arreglos". ¿Qué le ayudaría más al revisor?
🎯 Reto Final: las reglas Git de tu propio equipo
Ahora con el repositorio real de tu proyecto. Escribí, en no más de un párrafo por punto: 1) cómo nombran las ramas y cuándo se puede (o no) hacer push a main, 2) cómo es un buen commit y un buen PR en tu equipo, quién revisa y qué mira, 3) tu regla para usar código de una IA: qué datos nunca se le pasan y cómo se prueba lo que devuelve.
Convenciones de Git y regla de IA de tu proyecto real.
- El nombre de las ramas sigue un patrón concreto (por ejemplo
feature/…) y dice que main no recibe push directo. - Se define cuántos archivos o historias puede tener un PR como máximo antes de partirlo.
- El revisor es siempre una persona distinta del autor, y se dice qué mira (criterios de aceptación, pruebas).
- La regla de IA nombra datos que nunca se pegan (claves,
.env, datos personales reales). - La regla de IA exige una prueba escrita desde los criterios de aceptación, no solo "probar que corre".
Cheat Sheet
| Término | Idea clave |
|---|---|
| Rama | Línea paralela de commits para el trabajo en curso. Una por historia, de vida corta. |
| main protegida | Siempre funciona. Solo recibe cambios por Pull Request, no por push directo. |
| Commit | Un paso guardado en la historia. Un cambio lógico, con mensaje que dice qué cambia. |
| Pull Request | Pedido de unir una rama a main. Chico, descrito, con cómo probarlo y qué criterios cubre. |
| Revisión | La hace una persona distinta del autor: lee, corre el cambio y lo contrasta con los criterios. |
| Conflicto de merge | Dos ramas cambiaron las mismas líneas. Se leen ambos lados, se habla, se combina y se prueba. |
| Código de una IA | Se trata como un PR de un desconocido: se lee, y se prueba contra los criterios, incluidos los bordes. |
| Secretos | Nunca en un commit ni en un chat con una IA. Si se filtran, se revocan. |
| Responsabilidad | Quien hace el commit y aprueba el PR responde por el código, lo haya escrito quien lo haya escrito. |
¿Sabías que...?
🐧 Git nació en pocas semanas
Linus Torvalds empezó a escribir Git en abril de 2005, cuando el kernel de Linux se quedó sin su herramienta de control de versiones. En unos días ya se usaba para gestionar el propio desarrollo de Git. Hoy es el estándar de casi toda la industria.
🔎 git blame no es para culpar
El comando git blame muestra, línea por línea, qué commit y qué persona la cambió por última vez. Con commits chicos y mensajes claros sirve para entender el porqué de una línea, no para señalar a alguien.
🧭 Lo que viene en la próxima guía
Ya sabés cómo entra el código al proyecto. El próximo paso es decidir cuándo una historia está de verdad terminada: probar contra los criterios de aceptación y acordar una Definition of Done.