En la lección anterior recorrimos cuatro proyectos muy distintos y en los cuatro apareció lo mismo: Git nunca trabaja solo. Aparecían listas de correo, plataformas de propuestas, sistemas de compilación, tuberías de integración y herramientas de seguimiento. Git es el motor, pero la experiencia diaria de usarlo la define todo lo que hay alrededor.
Esta lección trata de esa capa. No de comandos nuevos —salvo un par de piezas que faltaban—, sino de cómo encaja Git en un puesto de trabajo real: el editor, la terminal, el sistema de tickets, los linters, la plataforma de revisión. Y de dónde está el riesgo, que no es técnico sino de comprensión.
Porque hay un patrón que se repite en todos los equipos: alguien lleva dos años usando Git desde un botón de su editor, todo va bien, y el día que algo se tuerce descubre que no sabe qué hacía ese botón. Ese "Sincronizar" que resolvía todo era git pull --rebase seguido de git push, y ahora hay diecisiete commits reescritos, una rama publicada divergente y ninguna idea de por dónde empezar.
Tú ya sabes salir de eso (módulo 9 entero). Lo que vamos a hacer aquí es usar las herramientas sin renunciar a entenderlas: aprovechar lo que aportan de verdad, saber qué comando ejecutan por debajo y tener claro qué conviene seguir haciendo a mano.
Contenido
- La regla de fondo: la herramienta no sustituye al modelo mental
- Editores e IDE: qué aportan de verdad
- Qué conviene seguir haciendo en la terminal
- Clientes gráficos y visores de historial
- La terminal como entorno de Git: prompt y autocompletado
- Sistemas de tickets: cerrando la convención
GT-NNN - Herramientas de calidad: linters, formateadores y
.editorconfig - Revisión y análisis: comprobaciones de estado
format-patch,amyrequest-pullcomo puente entre mundos- Cómo elegir qué automatizar
- La regla de fondo: la herramienta no sustituye al modelo mental
Antes de nada, la regla que ordena toda la lección:
Toda herramienta que integra Git acaba ejecutando comandos de Git. Si sabes qué comando ejecuta, la herramienta te ahorra tiempo. Si no lo sabes, la herramienta te ahorra tiempo hasta el día que falla, y entonces te cuesta mucho más de lo que te ahorró.
No es un argumento contra las interfaces gráficas. Es un argumento a favor de saber leer lo que hacen. Casi todas ofrecen una consola o un registro donde muestran los comandos ejecutados: mirarla las primeras semanas es la mejor inversión de aprendizaje que existe.
Y hay una asimetría importante. Las herramientas son excelentes en lo visual y repetitivo:
- Ver diferencias con colores, por palabras, lado a lado.
- Resolver un conflicto con tres paneles en pantalla.
- Preparar cambios por fragmentos con el ratón.
- Ver el grafo de commits dibujado.
- Ver quién escribió cada línea sin salir del fichero.
Y son malas —o directamente peligrosas— en lo conceptual e irreversible:
- Cualquier cosa que reescriba el historial.
- Resolver una divergencia con el remoto.
- Recuperarse de un error.
- Cualquier operación sobre la que quieras entender exactamente qué pasó.
La regla práctica que sale de ahí, y que es el resumen de todo el módulo:
Usa la interfaz para mirar. Usa la terminal para decidir.
- Editores e IDE: qué aportan de verdad
Los editores modernos traen integración con Git de serie. Lo que sigue es qué merece la pena de verdad, ordenado por rentabilidad.
Diferencias visuales en el propio editor
El editor marca en el margen las líneas añadidas, modificadas y borradas respecto a HEAD, y permite ver la versión anterior de un vistazo. Equivale a git diff (lección 02-05) pero sin cambiar de contexto: ves el cambio dentro del fichero que estás editando, con el resaltado de sintaxis del lenguaje.
Es probablemente la integración más valiosa que existe, porque cambia un hábito: en lugar de revisar los cambios al final, los ves mientras trabajas.
Lo que aporta: contexto inmediato, diferencia por palabras dentro de una línea, revertir un fragmento concreto con un clic.
Lo que no sustituye: git diff --staged antes de confirmar. Sigue siendo el último control de calidad, y conviene hacerlo con la vista completa.
Resolución de conflictos con tres paneles
Cuando hay un conflicto (lección 03-05), un buen editor muestra tres versiones: la tuya, la de la otra rama y la base común. Poder ver la base es lo que marca la diferencia, porque un conflicto solo se entiende sabiendo de qué partían los dos lados.
Esto se puede configurar para que Git lo invoque directamente:
# Registrar una herramienta de fusión
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
# No dejar ficheros .orig por el disco
git config --global mergetool.keepBackup false
# Usarla cuando haya conflicto
git mergetoolY el equivalente para ver diferencias:
git config --global diff.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
git config --global difftool.prompt false
git difftool HEAD~1Un ajuste que conviene activar siempre, y que ya vimos en la lección 03-05:
Con zdiff3, los marcadores de conflicto incluyen la versión base entre ||||||| y =======, no solo los dos lados. Es la misma información que dan los tres paneles, pero en el fichero, y sirve tanto si resuelves en el editor como si lo haces a mano.
blame en línea
El editor muestra al final de cada línea quién la escribió, cuándo y con qué mensaje de commit. Es git blame (lección 06-03) convertido en información ambiental.
Su virtud es que elimina la fricción de preguntarse el porqué. Cuando leer el motivo de una línea cuesta cero, se lee; cuando cuesta un comando y un cambio de ventana, no se lee.
Su peligro es el mismo que ya discutimos: el blame te da la última persona que tocó la línea, que muchas veces es quien la reindentó. Cuando el resultado no tenga sentido, hay que ir a la terminal:
# Ignorar cambios de espaciado y movimientos de código
git blame -w -M -C app.js
# Ignorar los commits de reformateo masivo listados en un fichero
git blame --ignore-revs-file .git-blame-ignore-revs app.jsEse fichero .git-blame-ignore-revs en la raíz del repositorio es una lista de hashes de commits puramente cosméticos:
# Reformateo global con el nuevo formateador (GT-198) 7c4d9e2f1a3b5c6d8e0f2a4b6c8d0e2f4a6b8c0d # Migración de comillas simples a dobles (GT-203) 2f8a1c4e6b9d0f3a5c7e9b1d3f5a7c9e1b3d5f7a
Y se puede dejar activo de forma permanente:
Los editores respetan esa configuración, así que el blame en línea deja de mentir. Es un ajuste de dos minutos que mejora la vida de todo el equipo.
Preparación por fragmentos con el ratón
git add -p (lección 02-04) es una de las herramientas más útiles del curso y una de las más incómodas de usar: hay que decidir y/n/s/e sobre fragmentos que a veces hay que partir a mano. En un editor, seleccionar las líneas que van al índice es cuestión de clics, y partir un fragmento es trivial.
Aquí la interfaz gráfica gana con claridad. Si el e de add -p (editar el fragmento a mano en formato diff) te resulta hostil, esta es su alternativa razonable.
Lo demás: útil pero secundario
- Historial del fichero:
git log --follow -p fichero, con navegación cómoda. - Lista de ramas y cambio entre ellas: cómodo, pero conviene saber que el editor puede estar ejecutando
git switchogit checkoutcon distintos matices. - Stash desde el menú: equivale a
git stash push, casi nunca con mensaje. Un stash sin mensaje es un stash que en tres días no sabrás qué contiene (lección 05-04).
Tabla de correspondencias
Útil para saber siempre qué hay debajo del botón:
| Lo que ves en el editor | El comando de debajo | Cuidado con |
|---|---|---|
| "Sincronizar cambios" | git pull (+ --rebase según config) y luego git push |
Puede reescribir tus commits sin avisar |
| "Publicar rama" | git push -u origin <rama> |
Nada, es seguro |
| "Confirmar" | git commit (a veces con -a implícito) |
El -a implícito confirma cosas que no querías |
| "Confirmar y enviar" | git commit + git push |
Confirmar y publicar en un solo gesto: sin marcha atrás cómoda |
| "Descartar cambios" | git restore <fichero> |
Irreversible: no hay reflog para cambios sin confirmar |
| "Deshacer el último commit" | git reset --soft HEAD~1 (normalmente) |
Comprueba si es --soft, --mixed o --hard |
| "Actualizar rama" | git pull o git fetch + git merge/rebase |
Según la config, produce merges o reescribe |
| "Resolver en el editor" | git mergetool o edición directa |
Recuerda hacer git add del fichero resuelto |
El más peligroso de la tabla es el primero. "Sincronizar" es un botón que hace dos operaciones de red, una de las cuales puede reordenar tu historial local según un ajuste global que quizá pusiste hace un año. Merece la pena mirar una vez qué hace exactamente:
- Qué conviene seguir haciendo en la terminal
La otra mitad de la regla. Estas operaciones conviene hacerlas escribiendo el comando, y no por purismo:
| Operación | Por qué en la terminal |
|---|---|
Reescribir historial (rebase -i, commit --amend, filter-repo) |
Necesitas ver exactamente qué commits se tocan. La interfaz esconde el plan. |
| Resolver divergencias con el remoto (09-03) | Requiere decidir entre merge, rebase o --force-with-lease. No es un botón. |
Recuperar con reflog (09-04) |
Casi ninguna interfaz lo expone bien, y es la red de seguridad principal. |
push --force-with-lease |
Muchas interfaces ofrecen --force a secas, que es la variante peligrosa. |
Depurar (GIT_TRACE, check-ignore, fontanería) (09-06) |
No hay equivalente gráfico. |
bisect (06-02) |
Automatizable con --run, imposible de automatizar con clics. |
Consultas precisas de log (06-04) |
-S, -G, --follow, -L :función:fichero: expresividad que ninguna interfaz iguala. |
| Cualquier cosa que no entiendas | Si no sabes qué va a pasar, escribe el comando y lee la salida. |
La última fila es la importante. El error que hay que evitar no es usar la interfaz: es usarla para algo que no entiendes. Si sabes exactamente qué hace un botón, dale al botón; te ahorra teclear. Si no lo sabes, escribe el comando, aunque tardes más, porque estás pagando el aprendizaje una sola vez.
- Clientes gráficos y visores de historial
Los clientes gráficos dedicados van un paso más allá que la integración de un editor: son aplicaciones cuyo único trabajo es Git.
En qué son realmente superiores
El grafo. Un historial con ramas paralelas y merges es un grafo dirigido acíclico (lección 03-01), y un grafo se entiende mucho mejor dibujado que en texto. Ver de un vistazo dónde divergió una rama, qué merges hubo y qué commits son de quién es genuinamente más rápido en una interfaz.
En la terminal, el equivalente decente:
Y con los formatos de la lección 06-04, muy legible:
Pero con más de tres o cuatro ramas vivas, el dibujo ASCII se vuelve difícil de seguir. Ahí una interfaz gana.
La comparación de dos commits arbitrarios. Seleccionar dos puntos del historial y ver la diferencia es más cómodo con el ratón que recordando dos hashes.
La exploración de un repositorio ajeno. Cuando llegas nuevo a un proyecto, recorrer visualmente su historial da una intuición de cómo trabaja el equipo que cuesta más obtener leyendo texto.
Git trae los suyos
Sin instalar nada, Git incluye dos herramientas gráficas mínimas:
Son austeras, pero están en todas partes y no dependen de que nadie las mantenga. gitk --all --date-order sigue siendo una forma rápida de mirar un grafo en un servidor donde no vas a instalar nada.
El riesgo específico de los clientes gráficos
Su facilidad hace que se ejecuten operaciones costosas sin pensarlas. Arrastrar una rama sobre otra puede lanzar un rebase de veinte commits. Un menú contextual puede ofrecer "Forzar envío" al lado de "Enviar", sin distinguir entre --force y --force-with-lease (lección 09-03).
Consejo concreto: si tu cliente gráfico ofrece envío forzado, comprueba en su configuración cuál de las dos variantes usa. Si es --force a secas y no se puede cambiar, haz esa operación en la terminal. La diferencia entre las dos es el trabajo de un compañero.
- La terminal como entorno de Git: prompt y autocompletado
Si vas a decidir en la terminal, conviene que la terminal esté bien equipada. Dos ajustes con una relación esfuerzo/beneficio enorme.
Estado de Git en el prompt
Ver la rama actual y el estado del árbol en cada línea de la terminal elimina una clase entera de errores: confirmar en la rama equivocada, olvidar que hay un rebase a medias, no darte cuenta de que hay cambios sin confirmar.
Git distribuye el guion oficial. Suele estar ya instalado con Git:
# Localizarlo (la ruta varía según sistema)
ls /usr/share/git/completion/git-prompt.sh 2>/dev/null
ls /usr/share/git-core/contrib/completion/git-prompt.sh 2>/dev/null
ls /usr/lib/git-core/git-sh-prompt 2>/dev/nullY se configura en ~/.bashrc:
# Cargar el guion de prompt de Git
source /usr/lib/git-core/git-sh-prompt
# Qué información mostrar
export GIT_PS1_SHOWDIRTYSTATE=1 # * si hay cambios, + si hay preparados
export GIT_PS1_SHOWSTASHSTATE=1 # $ si hay algo en el stash
export GIT_PS1_SHOWUNTRACKEDFILES=1 # % si hay ficheros sin seguimiento
export GIT_PS1_SHOWUPSTREAM="auto" # < > = respecto al remoto
export GIT_PS1_SHOWCOLORHINTS=1 # color según el estado
# El prompt
PS1='\u@\h:\w$(__git_ps1 " (%s)")\$ 'El resultado en la práctica:
ana@portatil:~/gestor-tareas (main=)$ ana@portatil:~/gestor-tareas (GT-231-filtro-ocultas *%<)$ ana@portatil:~/gestor-tareas (GT-231-filtro-ocultas|REBASE 2/5)$
Léelos de izquierda a derecha:
main=: enmain, sincronizada con el remoto.GT-231-filtro-ocultas *%<: hay cambios sin preparar (*), ficheros sin seguimiento (%) y el remoto va por delante (<), así que toca unfetch.|REBASE 2/5: hay un rebase a medias, en el paso 2 de 5. Esta es la que más disgustos evita: es exactamente la situación en la que alguien se levanta a por café, vuelve y no entiende por quégit statusdice cosas raras (lección 09-01).
En macOS con zsh, Bruno tiene el equivalente integrado en su configuración de shell; y en Windows, Carla lo tiene de serie en Git Bash. Lo importante no es qué guion uses, sino que la rama esté siempre a la vista.
Autocompletado
Git distribuye también su guion de completado, que completa subcomandos, opciones, nombres de rama, de etiqueta y de remoto:
Con él, git switch GT-<TAB> ofrece las ramas que empiezan por GT-. En un repositorio con muchas ramas es una mejora enorme, y además evita el error de teclear mal un nombre de rama, que en Git a veces no falla sino que crea algo nuevo.
Un detalle valioso: el completado también funciona con tus alias si están definidos como alias de Git (lección 06-04):
git config --global alias.hist "log --graph --oneline --decorate --all"
git config --global alias.st "status -sb"
git config --global alias.ultimo "log -1 HEAD --stat"Frente a los alias del shell (alias gs='git status'), los alias de Git tienen tres ventajas: viajan con tu configuración a cualquier máquina, funcionan igual en Bash, zsh y Git Bash, y son visibles para quien lea tu configuración. Los alias del shell son más cortos de escribir; usa ambos, cada uno para lo suyo.
- Sistemas de tickets: cerrando la convención
GT-NNN
GT-NNNLlevamos todo el curso escribiendo GT-231 en ramas y mensajes. Toca explicar para qué sirve exactamente y qué se consigue.
Los tres puntos de contacto
flowchart LR
T["Ticket GT-231<br/>en el gestor"] --> R["Rama<br/>GT-231-filtro-ocultas"]
R --> C["Commits<br/>feat: GT-231 ..."]
C --> P["Propuesta<br/>'Cierra GT-231'"]
P --> M["Merge a main"]
M --> T2["Ticket cerrado<br/>automáticamente"]
M --> V["Etiqueta v1.4.0<br/>changelog con GT-231"]
La cadena completa de trazabilidad tiene tres eslabones que hay que mantener a mano y uno que se automatiza:
- La rama lleva el identificador.
GT-231-filtro-ocultas. Sin esto, dentro de seis meses nadie sabrá qué era esa rama. - Los commits lo llevan. Con Conventional Commits (lección 08-01), en el asunto o en un pie:
feat(tareas): GT-231 filtrar las tareas ocultas del listado Las tareas archivadas dejan de aparecer en el listado principal. Se añade la marca `oculta` y se filtra en renderizarTareas(). Refs: GT-231
- La propuesta lo declara, con la palabra clave que la plataforma reconoce (
Cierra GT-231,Closes GT-231,Fixes GT-231, según la herramienta). - Al integrar, el ticket se cierra solo y queda enlazado al commit de integración.
La pregunta que esto responde
La trazabilidad no es burocracia. Es la capacidad de responder, en treinta segundos y sin preguntar a nadie, a estas cuatro preguntas:
| Pregunta | Cómo se responde |
|---|---|
"¿Qué cambió con GT-231?" |
Buscar el identificador en el historial |
| "¿Por qué esta línea es así?" | git blame → commit → ticket → discusión completa |
"¿Está GT-231 en producción?" |
¿Está su commit en la etiqueta desplegada? |
| "¿Qué entra en la versión 1.4.0?" | Los tickets de los commits entre v1.3.0 y v1.4.0 |
Y los comandos correspondientes, todos ya conocidos:
# ¿Qué commits mencionan GT-231?
git log --grep='GT-231' --oneline
# ¿En qué ramas y etiquetas está ese commit?
git branch -a --contains 4f8a2e6
git tag --contains 4f8a2e6
# ¿Qué tickets entran en la próxima versión?
git log v1.3.0..main --format=%s | grep -oE 'GT-[0-9]+' | sort -u
# ¿Está GT-231 en la etiqueta que hay en producción?
git merge-base --is-ancestor 4f8a2e6 v1.3.0 && echo "sí" || echo "no"Ese último comando es el que se usa de verdad cuando alguien escribe "esto sigue fallando en producción": comprueba objetivamente si la corrección llegó a estar allí.
Automatizar la mitad tediosa
Escribir GT-231 en cada mensaje se olvida. Se puede automatizar con un hook (lección 06-01) que lo extraiga del nombre de la rama:
#!/bin/sh
# .git/hooks/prepare-commit-msg
# Añade el identificador del ticket, tomado del nombre de la rama.
FICHERO_MSG="$1"
ORIGEN="$2"
# No tocar merges, squashes ni mensajes ya escritos con -m editado
case "$ORIGEN" in
merge|squash) exit 0 ;;
esac
RAMA=$(git symbolic-ref --short HEAD 2>/dev/null) || exit 0
TICKET=$(printf '%s' "$RAMA" | grep -oE '^GT-[0-9]+')
[ -z "$TICKET" ] && exit 0
grep -q "$TICKET" "$FICHERO_MSG" && exit 0
# Insertar el ticket al principio de la primera línea
sed -i.bak "1s/^/$TICKET /" "$FICHERO_MSG" && rm -f "$FICHERO_MSG.bak"Y la validación del lado del servidor, que es la que garantiza que se cumple, ya la vimos en la lección 06-01 como hook update y en la 07-06 como comprobación de la tubería.
Recuerda el reparto de responsabilidades que establecimos: el hook cliente ayuda, el hook servidor obliga. Un prepare-commit-msg es una comodidad; si el requisito es real, tiene que estar comprobado también en el servidor o en la CI, porque los hooks locales no se distribuyen con el repositorio y cada cual puede saltárselos con --no-verify.
- Herramientas de calidad: linters, formateadores y
.editorconfig
.editorconfigUn linter señala problemas; un formateador impone un estilo. En un equipo, los dos deben ejecutarse de forma automática, porque el estilo discutido en las revisiones es tiempo perdido.
La pregunta interesante es dónde engancharlos, y hay tres sitios posibles.
Las tres posiciones
| Dónde | Cuándo actúa | Ventaja | Inconveniente |
|---|---|---|---|
| Editor (al guardar) | Instantáneo | Nunca ves el error | Depende de la configuración de cada persona |
Hook cliente (pre-commit) |
Al confirmar | Impide crear el commit sucio | No se distribuye; se salta con --no-verify; ralentiza |
| CI (07-06) | Al enviar / en la propuesta | Obligatorio de verdad, igual para todos | Ciclo lento: te enteras minutos después |
La combinación correcta es editor + CI, con el hook como opción del que quiera. El editor te da la respuesta inmediata, la CI garantiza el cumplimiento. El hook pre-commit es cómodo pero no puede ser la única defensa, por el motivo de siempre: no viaja con el repositorio.
Un pre-commit bien hecho tiene un detalle que casi todo el mundo olvida: debe comprobar lo que está en el índice, no lo que está en el disco. Si tienes cambios sin preparar, comprobar el fichero del disco valida algo que no es lo que vas a confirmar:
#!/bin/sh
# .git/hooks/pre-commit
# Comprueba el formato SOLO de lo que va a entrar en el commit.
# Guardar los cambios no preparados para no validarlos
git stash push --keep-index --include-untracked --quiet --message "pre-commit"
RESTAURAR=$?
FICHEROS=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|css)$')
CODIGO=0
if [ -n "$FICHEROS" ]; then
echo "$FICHEROS" | xargs npx eslint || CODIGO=1
fi
# Restaurar siempre, falle o no la comprobación
[ $RESTAURAR -eq 0 ] && git stash pop --quiet
exit $CODIGOTres cosas que merecen atención:
--diff-filter=ACMexcluye los ficheros borrados: no tiene sentido pasarle el linter a algo que ya no existe. Es el error más frecuente en estos hooks.- El
stash push --keep-indexaparta lo no preparado (lección 05-04) para que el linter vea exactamente el contenido que se va a confirmar. - La restauración se hace pase o falle la comprobación. Un hook que deja tu trabajo en el stash cuando falla es un hook que la gente desactiva.
.editorconfig
Un fichero pequeño y muy rentable, que viaja en el repositorio y que casi todos los editores respetan (algunos de serie, otros con una extensión):
# .editorconfig en la raíz de gestor-tareas
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[*.{bat,cmd}]
end_of_line = crlf
[Makefile]
indent_style = tabSu valor está en resolver en el origen lo que .gitattributes (lección 08-04) resuelve en la frontera del repositorio. Son complementarios, y conviene tener claro el reparto:
.editorconfig |
.gitattributes |
|
|---|---|---|
| Quién lo aplica | El editor, al escribir el fichero | Git, al confirmar y al extraer |
| Cuándo | Mientras editas | En add/commit/checkout |
| Si no está soportado | No pasa nada, se ignora | Git siempre lo aplica |
| Alcance | Estilo de edición | Normalización, diff, merge, filtros, exportación |
Con los dos, el problema de los finales de línea entre Ubuntu, macOS y Windows 11 —el GT-190 que resolvimos en la lección 08-04— se ataca por partida doble: el editor de Carla escribe LF desde el principio, y Git normaliza igualmente por si acaso.
- Revisión y análisis: comprobaciones de estado
Las plataformas de alojamiento tienen un mecanismo que unifica todo lo automático: los estados de commit (o comprobaciones). Cualquier sistema externo puede publicar, contra un hash de commit concreto, un veredicto: pendiente, correcto, fallido.
flowchart TD
C["Commit a1b2c3d<br/>enviado a la propuesta"] --> CI["Pruebas"]
C --> LINT["Linter"]
C --> COB["Cobertura"]
C --> SEC["Análisis de seguridad"]
C --> EST["Análisis estático"]
CI --> E["Estados del commit a1b2c3d"]
LINT --> E
COB --> E
SEC --> E
EST --> E
E --> RP["Rama protegida:<br/>¿todos en verde?"]
RP -->|sí| OK["Se puede integrar"]
RP -->|no| NO["Integración bloqueada"]
Lo importante conceptualmente: el estado se asocia a un hash de commit, no a una rama ni a una propuesta. Es coherente con todo el modelo de Git (lección 01-04): el commit es inmutable, así que un veredicto sobre él sigue siendo válido para siempre. Si reescribes la rama, los commits nuevos tienen hashes distintos y las comprobaciones se ejecutan otra vez, porque son objetos diferentes.
De ahí una consecuencia práctica que confunde a mucha gente: después de un rebase y un push --force-with-lease, todas las comprobaciones vuelven a ejecutarse. No es un fallo de la plataforma. Es que el commit ya no es el mismo.
Qué tipos de comprobación merecen ser bloqueantes
| Tipo | ¿Bloqueante? | Motivo |
|---|---|---|
| Pruebas | Sí | Un fallo es un fallo objetivo |
| Compilación / empaquetado | Sí | Si no compila, no hay nada que revisar |
| Linter | Sí | Es determinista y se corrige en segundos |
| Secretos detectados (08-05) | Sí | El coste de un falso negativo es enorme |
| Vulnerabilidades conocidas en dependencias | Según gravedad | Bloquear por todo genera fatiga y se acaba ignorando |
| Cobertura de pruebas | Con matices | Bloquear por umbral absoluto castiga a quien toca código antiguo; mejor por variación |
| Análisis estático heurístico | No | Produce falsos positivos; que informe, no que bloquee |
La regla: bloquea con lo determinista, informa con lo heurístico. Una comprobación que falla a menudo sin motivo real enseña al equipo a ignorar los rojos, y el día que el rojo es de verdad, nadie lo mira.
El detalle que hay que saber sobre la cobertura
Medir cobertura sobre el proyecto entero produce una métrica que casi no se mueve y que castiga arbitrariamente. Lo útil es medir la cobertura de las líneas que la propuesta añade o modifica, y eso se calcula, cómo no, a partir de un diff:
# Líneas que toca esta rama respecto al punto de divergencia
git diff --unified=0 origin/main...HEAD -- '*.js'Ese --unified=0 da el diff sin contexto: solo las líneas realmente cambiadas, con sus números. Es lo que consumen las herramientas de cobertura diferencial. Y otra vez los tres puntos (lección 07-02), por el mismo motivo de siempre: queremos lo que la rama aporta, no la diferencia con un main que ha avanzado.
format-patch, am y request-pull como puente entre mundos
format-patch, am y request-pull como puente entre mundosEn la lección 10-01 vimos estos comandos como el flujo nativo del kernel. Aquí interesan por otra razón: son el formato de intercambio universal entre cualquier herramienta y cualquier repositorio.
Un parche generado por format-patch es texto plano que se puede pegar en un ticket, adjuntar a un correo, guardar en un archivo o publicar en una página web. No necesita servidor, ni cuenta, ni protocolo. Y git am lo aplica conservando autoría y mensaje, cosa que copiar y pegar el código nunca hace.
Situaciones reales donde esto salva el día
1. Enviar un cambio a alguien sin acceso compartido. Un consultor externo, un compañero de otra empresa, alguien en una red aislada.
# Origen
git format-patch main --stdout > /tmp/GT-244.patch
# Destino: ver qué haría antes de aplicarlo
git apply --stat /tmp/GT-244.patch
git apply --check /tmp/GT-244.patch
# Aplicarlo conservando autoría y mensaje
git am /tmp/GT-244.patchgit apply --check es el ensayo previo: dice si el parche aplicaría limpiamente sin tocar nada. Y --stat muestra qué ficheros afectaría. Conviene ejecutar los dos antes de aplicar nada que venga de fuera.
2. Mover un commit entre dos repositorios que no se conocen. Cuando cherry-pick no sirve porque no comparten historia:
El -3 habilita la fusión a tres bandas si el contexto no coincide exactamente. Es lo que convierte un parche frágil en uno razonablemente robusto.
3. Archivar un cambio como texto legible. Un parche es autodescriptivo y sobrevive a cualquier migración de plataforma. Para cambios que hay que conservar por motivos de auditoría, es un formato mejor que un enlace a un servidor que quizá no exista en diez años.
4. Revisar sin plataforma. format-patch produce algo que se lee de arriba abajo en cualquier editor, con el mensaje del commit encima del diff. Para revisar una serie de commits en un avión, es mejor que ninguna interfaz web.
git request-pull en un flujo moderno
Ya lo vimos en la lección anterior. Aquí solo un uso concreto que es sorprendentemente útil: generar el texto de una propuesta.
Produce un resumen con el commit base, la lista de commits agrupados por autor y el diffstat. Pegado en la descripción de una propuesta grande, ahorra a quien revisa el trabajo de averiguar el alcance.
Y como pegamento entre los dos mundos: una propuesta de plataforma se puede convertir en parches, porque toda propuesta es, por debajo, un conjunto de commits en una rama.
git fetch origin refs/pull/42/head:propuesta-42
git format-patch main..propuesta-42 -o /tmp/propuesta-42/Ese refs/pull/42/head es un refspec (lección 04-01) que las plataformas exponen para acceder a las ramas de las propuestas. Sirve también para revisarlas en local:
git fetch origin refs/pull/42/head:propuesta-42
git switch propuesta-42
npm test
git diff main...propuesta-42Revisar una propuesta ejecutándola en lugar de solo leyéndola es, con diferencia, la forma más eficaz de revisar. Y es una de las cosas que la comodidad de la interfaz web ha hecho que mucha gente deje de hacer.
- Cómo elegir qué automatizar
Cerramos con el criterio, que es lo que da unidad a todo lo anterior.
Automatiza cuando
- La tarea es repetitiva y determinista: formatear, ordenar imports, comprobar el formato de un mensaje.
- El error es frecuente y detectable: olvidar el ticket, confirmar un fichero de configuración con credenciales, dejar espacios al final.
- La comprobación es objetiva: pasa o no pasa, sin criterio humano.
- El ahorro es para todo el equipo, no solo para ti.
No automatices cuando
- La operación es irreversible y contextual: reescrituras, envíos forzados, borrados.
- La decisión requiere criterio: si un cambio es una buena idea, si la abstracción es la correcta, si el mensaje explica bien el porqué.
- La automatización oculta algo que necesitas entender.
- El coste de un falso positivo es alto: si bloquea el trabajo del equipo sin motivo, se desactivará, y con ella se desactivarán las comprobaciones buenas.
El montaje recomendado para un equipo como el de gestor-tareas
Recapitulando toda la lección en una configuración concreta:
| Capa | Qué hay | Coste |
|---|---|---|
| Editor | Diferencias en el margen, blame en línea, formateo al guardar, .editorconfig |
Configuración inicial |
| Terminal | Prompt con rama y estado, autocompletado, alias de Git (06-04) | Media hora, una vez |
| Hooks cliente (06-01) | prepare-commit-msg para el ticket; pre-commit ligero opcional |
Bajo, y opcional |
| Repositorio | .gitignore, .gitattributes, .editorconfig, .git-blame-ignore-revs, CONTRIBUTING.md |
Un día, una vez |
| CI (07-06) | Pruebas, linter, secretos, formato de mensajes — bloqueantes | Mantenimiento continuo |
| Plataforma | Rama protegida, plantilla de propuesta, cierre automático de tickets | Configuración inicial |
Fíjate en que la mayor parte del beneficio está en la fila del repositorio: cuatro ficheros de texto que viajan con el proyecto y que benefician a todo el mundo sin que nadie tenga que configurar su máquina.
Errores Comunes y Consejos
Error 1: usar "Sincronizar" sin saber qué hace. Es el error de esta lección. Comprueba git config --get pull.rebase y decide conscientemente. Si el botón hace pull --rebase y tú has publicado la rama, cada sincronización puede reescribir commits que otros ya tienen (lección 05-01).
Error 2: instalar hooks que nadie más tiene. Los hooks no se distribuyen con el repositorio (lección 06-01). Si tu equipo depende de una comprobación, tiene que estar en la CI o en un hook de servidor. Los hooks locales son comodidad personal, nunca garantía.
Error 3: hooks lentos. Un pre-commit que tarda quince segundos hace que la gente confirme menos veces y con cambios más grandes, o que use --no-verify sistemáticamente. Un hook cliente debe tardar menos de dos segundos y comprobar solo los ficheros preparados.
Error 4: linter sobre el disco en lugar de sobre el índice. Si tienes cambios sin preparar, validas algo que no es lo que vas a confirmar. Usa git diff --cached --name-only --diff-filter=ACM y el truco del stash --keep-index.
Error 5: confiar el blame del editor sin .git-blame-ignore-revs. Después de un reformateo masivo, el blame te dirá que todo el fichero lo escribió quien pasó el formateador. Diez minutos de configuración lo arreglan para siempre y para todo el equipo.
Error 6: bloquear la integración con comprobaciones ruidosas. Un análisis heurístico con falsos positivos, puesto como bloqueante, produce que el equipo aprenda a ignorar los rojos. Bloquea con lo determinista, informa con lo demás.
Error 7: revisar solo por la interfaz web. Para propuestas grandes, trae la rama y ejecútala. git fetch origin refs/pull/N/head:propuesta-N y git diff main...propuesta-N dan una revisión mucho mejor que un navegador.
Consejo 1: mira el registro de comandos de tu herramienta durante una semana. Casi todas tienen una consola donde muestran lo que ejecutan. Es la forma más rápida de aprender la correspondencia entre botones y comandos.
Consejo 2: pon el estado de Git en el prompt hoy mismo. De todo lo que hay en esta lección, es lo que más errores evita por minuto invertido, sobre todo el indicador de operación en curso (|REBASE, |MERGING).
Consejo 3: mete la configuración compartida en el repositorio. .editorconfig, .gitattributes, .gitignore y .git-blame-ignore-revs son cuatro ficheros de texto que hacen que el proyecto se comporte igual para todo el mundo sin depender de la máquina de cada cual.
Consejo 4: cuando algo raro pase en la interfaz, ve a la terminal. git status, git log --oneline --graph -20 y git reflog te dicen dónde estás realmente. La interfaz muestra una interpretación; los comandos muestran el estado.
Ejercicios
Ejercicio 1: traducir botones a comandos
Carla usa su editor para todo. Describe qué está pasando en cada situación y qué comando hay debajo:
A. Pulsa "Sincronizar cambios" con tres commits locales sin enviar. Al terminar, sus tres commits tienen hashes distintos a los de antes.
B. Pulsa "Descartar cambios" en un fichero con dos horas de trabajo sin confirmar. Pregunta si puede recuperarlo con git reflog.
C. Pulsa "Deshacer el último commit" y sus cambios reaparecen como preparados en el panel de cambios.
Ejercicio 2: un hook de mensaje que no molesta
Escribe un hook commit-msg para gestor-tareas que:
- Rechace el commit si el asunto no sigue Conventional Commits (
tipo(ámbito opcional): descripción). - Rechace el commit si el asunto supera los 72 caracteres.
- Avise pero no rechace si el mensaje no menciona ningún
GT-NNN. - No moleste en merges ni en commits de
rebase/revertautomáticos.
Explica por qué el punto 3 avisa en lugar de rechazar.
Ejercicio 3: revisar una propuesta grande sin la interfaz web
Diego ha abierto la propuesta número 57 en gestor-tareas, con 14 commits y 800 líneas cambiadas. Ana quiere revisarla en serio: entender los cambios, ejecutar las pruebas y dejar comentarios fundamentados.
Escribe la secuencia de comandos que usarías, explicando qué aporta cada paso. Incluye qué harías si Diego envía una versión corregida y quieres ver solo qué ha cambiado respecto a lo que ya revisaste.
Soluciones
Solución 1
A. "Sincronizar" ha hecho pull --rebase y luego push.
Los hashes cambian porque el rebase (lección 05-01) reconstruye cada commit sobre la nueva base: cambia el padre, y como el hash de un commit incluye el de su padre (lección 01-04), cambia el hash entero.
Lo que hay debajo, según su configuración:
¿Es un problema? Depende de si la rama estaba publicada:
- Rama local, sin enviar nunca: no pasa nada, es lo deseable.
- Rama publicada y con alguien más trabajando en ella: acaba de reescribir historia compartida. El
pushposterior o bien fue rechazado, o bien fue forzado por la herramienta, dejando a los demás con una divergencia (lección 09-03).
Recomendación: que Carla compruebe conscientemente ese ajuste. pull.rebase = true es una buena opción por defecto para ramas personales, pero con un botón que lo dispara sin preguntar conviene saberlo.
B. "Descartar cambios" es git restore <fichero>, y NO, no se recupera con el reflog.
Esta es la distinción crítica de todo el módulo 9:
| ¿Recuperable? | Por qué | |
|---|---|---|
| Un commit "perdido" | Sí, con git reflog (09-04) |
Es un objeto en la base de datos |
| Contenido que estuvo preparado | Casi siempre, con git fsck --lost-found (09-04) |
git add creó un objeto blob |
| Cambios nunca preparados ni confirmados | No | Nunca llegaron a ser un objeto |
Sus dos horas de trabajo, si nunca hizo git add, no existen para Git. Solo cabe mirar el historial local de cambios del propio editor, que muchos guardan y que es la única red de seguridad en este caso.
La lección: git add no es solo "preparar para confirmar", es también "guardar una copia recuperable". Es el argumento definitivo para preparar pronto y a menudo.
C. "Deshacer el último commit" ha sido git reset --soft HEAD~1.
El commit desaparece de la rama y sus cambios quedan en el índice (por eso aparecen como preparados). Con --mixed (el defecto de reset) aparecerían como modificados pero no preparados; con --hard habrían desaparecido.
Que el editor use --soft es lo correcto para "deshacer el commit pero conservar el trabajo". Y el commit original sigue existiendo: git reflog lo muestra y git reset --hard HEAD@{1} lo restaura (lección 09-02).
Solución 2
#!/bin/sh
# .git/hooks/commit-msg
# Valida el mensaje de confirmación de gestor-tareas.
FICHERO="$1"
# Primera línea no vacía y que no sea comentario
ASUNTO=$(grep -v '^#' "$FICHERO" | sed '/^[[:space:]]*$/d' | head -1)
# --- Exclusiones: no molestar en lo automático ---
case "$ASUNTO" in
"Merge "*|"Revert "*|"fixup!"*|"squash!"*|"amend!"*)
exit 0 ;;
esac
# Tampoco durante un rebase o un merge en curso
GITDIR=$(git rev-parse --git-dir)
if [ -d "$GITDIR/rebase-merge" ] || [ -d "$GITDIR/rebase-apply" ] \
|| [ -f "$GITDIR/MERGE_HEAD" ]; then
exit 0
fi
ERROR=0
# --- Regla 1: Conventional Commits ---
PATRON='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\([a-z0-9-]+\))?!?: .+'
if ! printf '%s' "$ASUNTO" | grep -qE "$PATRON"; then
echo "ERROR: el asunto no sigue Conventional Commits."
echo " Recibido: $ASUNTO"
echo " Formato: tipo(ámbito): descripción"
echo " Tipos: feat fix docs style refactor perf test build ci chore"
ERROR=1
fi
# --- Regla 2: longitud del asunto ---
LONGITUD=$(printf '%s' "$ASUNTO" | wc -c)
if [ "$LONGITUD" -gt 72 ]; then
echo "ERROR: el asunto tiene $LONGITUD caracteres (máximo 72)."
ERROR=1
fi
# --- Regla 3: ticket, solo aviso ---
if ! grep -qE 'GT-[0-9]+' "$FICHERO"; then
echo "AVISO: el mensaje no menciona ningún ticket GT-NNN."
echo " Si el cambio corresponde a uno, añádelo con 'git commit --amend'."
fi
exit $ERRORPor qué el punto 3 solo avisa:
-
No siempre hay ticket legítimamente. Corregir una errata del
README.md, ajustar la configuración de la CI o unchorede mantenimiento no siempre tienen ticket, y obligar a inventar uno degrada el sistema de tickets: se llena de entradas falsas creadas para satisfacer al hook. -
Un hook que rechaza demasiado se desactiva. En cuanto alguien tiene que usar
--no-verifyuna vez a la semana, deja de usar el hook. Y al desactivarlo pierde también las reglas 1 y 2, que sí son valiosas. Una regla estricta de más debilita a las buenas. -
Las reglas 1 y 2 son objetivas; la 3 es contextual. El formato del asunto no admite excepción razonable. La presencia de ticket sí. Es la misma distinción del apartado 8: bloquea con lo determinista, informa con lo heurístico.
-
Si el equipo decide que el ticket es obligatorio, esa regla debe estar en el servidor o en la CI, no en un hook local. Un hook local no es una garantía para nadie más que para ti.
Las exclusiones del principio también importan: un hook que rechaza los mensajes generados por git merge o por git revert convierte operaciones normales en un calvario, y es la causa número uno de que los hooks acaben en la papelera.
Solución 3
# 1. Traer la rama de la propuesta sin depender de la interfaz web
git fetch origin refs/pull/57/head:propuesta-57
# 2. Alcance general: ¿qué toca y cuánto?
git diff --stat main...propuesta-57Los tres puntos son obligatorios (lección 07-02): dan lo que la rama aporta desde que se separó de main, no la diferencia con el main actual. Con dos puntos, cualquier avance de main aparecería como si Diego lo hubiera deshecho.
Aquí ya se aprende mucho. Catorce commits bien partidos y bien nombrados se revisan de uno en uno; catorce commits llamados "arreglos" y "wip" hay que revisarlos como un bloque, y merece un comentario pidiendo que los limpie con rebase -i (lección 05-02).
# 4. Revisar commit a commit, con el mensaje delante del diff
git log --reverse -p main..propuesta-57
# 5. Ejecutar de verdad: esto es lo que la interfaz web no puede hacer
git switch propuesta-57
npm ci && npm test
npm start # y probar el comportamiento a mano
# 6. Comprobaciones específicas
git diff main...propuesta-57 --stat -- '*.test.js' # ¿hay pruebas?
git log main..propuesta-57 --format=%B | grep -c 'GT-' # ¿referencian el ticket?
git diff main...propuesta-57 | grep -nE '(clave|token|password|secreto)' # secretosEl paso 5 es el que distingue una revisión real de una lectura. Ejecutar la rama detecta cosas que ningún diff enseña: que la interfaz queda descolocada, que hay un mensaje de error incomprensible, que una operación tarda tres segundos.
Para revisar la segunda versión, después de que Diego rehaga la rama:
# Guardar la referencia de lo que ya revisé, ANTES de traer lo nuevo
git branch propuesta-57-v1 propuesta-57
# Traer la versión corregida (forzando, porque Diego ha reescrito su rama)
git fetch origin +refs/pull/57/head:propuesta-57
# La pregunta clave: ¿qué ha cambiado respecto a lo que ya revisé?
git range-diff main propuesta-57-v1 propuesta-57git range-diff (lección 07-02) es exactamente la herramienta para esto: compara dos versiones de una misma serie de commits y muestra un diff de diffs. Emparejan los commits equivalentes y marca cada uno:
=sin cambios respecto a la versión anterior.!mismo commit pero con contenido modificado, y muestra qué cambió dentro.</>commits eliminados o añadidos en la nueva versión.
En una propuesta de 14 commits donde Diego solo ha corregido dos, range-diff te enseña esos dos y te ahorra revisar los otros doce otra vez. Es el comando que hace sostenible revisar series largas, y el motivo por el que el modelo del kernel (lección 10-01) puede permitirse llegar a la versión 5 de una serie.
El + en +refs/pull/57/head:propuesta-57 es el refspec forzado (lección 04-01): permite actualizar la referencia local aunque el avance no sea de fast-forward, que es justo lo que ocurre cuando el otro lado ha reescrito su rama.
Conclusión
Git es un motor; todo lo demás es carrocería. Esta lección ha recorrido esa carrocería con un criterio constante: usar lo que aporta, sabiendo siempre qué hay debajo.
- Los editores e IDE aportan de verdad en lo visual: diferencias en el margen, resolución de conflictos con la base común a la vista (
merge.conflictStyle = zdiff3),blameen línea y preparación por fragmentos con el ratón. Cada botón tiene un comando detrás, y el más peligroso es "Sincronizar", que puede reescribir tu historial según un ajuste que quizá no recuerdas haber puesto. - En la terminal conviene seguir haciendo todo lo irreversible o conceptual: reescrituras, divergencias,
reflog,--force-with-lease,bisect, depuración y consultas precisas delog. La regla: usa la interfaz para mirar, la terminal para decidir. - Los clientes gráficos ganan claramente dibujando el grafo y comparando puntos arbitrarios del historial. Git trae los suyos (
gitk,git gui) y están en todas partes. - La terminal bien equipada —estado de Git en el prompt, autocompletado y alias de Git (06-04)— es la mejora con mejor relación esfuerzo/beneficio del módulo. El indicador
|REBASE 2/5evita por sí solo una categoría entera de líos. - Los sistemas de tickets cierran la convención
GT-NNNque hemos usado todo el curso: rama, commits y propuesta llevan el identificador, la integración cierra el ticket, y el resultado es poder responder en treinta segundos a "¿qué cambió con GT-231?" y "¿está en producción?". - Las herramientas de calidad se enganchan en tres sitios —editor, hook cliente y CI— y la combinación sana es editor + CI: el primero da respuesta inmediata, la segunda garantiza el cumplimiento, porque los hooks locales ni se distribuyen ni obligan.
.editorconfigresuelve en el origen lo que.gitattributesresuelve en la frontera. - Las comprobaciones de estado se asocian a un hash de commit, no a una rama: por eso reescribir la rama vuelve a lanzarlas todas. Bloquea con lo determinista, informa con lo heurístico.
format-patch,amyrequest-pullson el formato de intercambio universal: mueven un cambio con su autoría y su mensaje entre repositorios que no se conocen, sin servidor ni permisos. Yrefs/pull/N/headpermite traer cualquier propuesta a local para revisarla ejecutándola, que es la única revisión que encuentra ciertos problemas.
La idea de fondo, que es la misma con la que abrimos:
Automatiza lo repetitivo y determinista. Entiende siempre qué comando hay debajo. Y reserva la terminal para lo irreversible.
Lo que viene
Hay un tipo de fichero que rompe todo lo que hemos montado hasta aquí. El editor no puede mostrar sus diferencias, la interfaz no puede resolver sus conflictos, el linter no tiene nada que decir de él y git diff responde con una línea seca: Binary files differ.
Y lo peor no es eso. Lo peor es que Git guarda cada versión entera, para siempre, y todo el que clone el repositorio se las descarga todas. Un diseñador que actualiza cinco veces un fichero de 80 MB acaba de añadir 400 MB al historial de todo el mundo, y borrarlo no sirve de nada, porque los objetos siguen ahí (lección 08-06).
En gestor-tareas ese problema aún no existe. Pero el equipo va a incorporar recursos gráficos, vídeos de demostración y los ficheros de diseño de componentes-ui, y conviene resolverlo antes de que el historial esté contaminado, no después.
La lección 10-03: Git LFS para Ficheros Grandes cierra lo que dejamos pendiente en las lecciones 06-05, 08-04 y 08-06: qué es exactamente ese filter=lfs diff=lfs merge=lfs -text que aparecía en .gitattributes, cómo se sustituye un fichero por un puntero de texto, y qué hay que saber —incluidas las limitaciones incómodas— antes de adoptarlo.
Dominando Git: De Principiante a Avanzado
Módulo 1: Introducción a Git
- ¿Qué es Git?
- Instalando Git
- Terminología Básica de Git
- El Modelo de Datos de Git
- Configurando Git
- Configuración Inicial
Módulo 2: Operaciones Básicas de Git
- Creando un Repositorio
- Clonando un Repositorio
- Flujo de Trabajo Básico de Git
- Preparando y Confirmando Cambios
- Inspeccionando Cambios con git diff
- Visualizando el Historial de Confirmaciones
Módulo 3: Ramas y Fusión
- Entendiendo las Ramas
- Creando y Cambiando Ramas
- Fusionando Ramas
- Estrategias de Fusión
- Resolviendo Conflictos de Fusión
- Gestión de Ramas
Módulo 4: Trabajando con Repositorios Remotos
- Entendiendo los Repositorios Remotos
- Agregando un Repositorio Remoto
- Autenticación con Repositorios Remotos
- Obteniendo y Extrayendo Cambios
- Enviando Cambios
- Rastreando Ramas
Módulo 5: Operaciones Avanzadas de Git
- Rebase
- Rebase Interactivo
- Cherry-Picking de Confirmaciones
- Guardando Cambios Temporales
- Etiquetando Confirmaciones
- Revirtiendo Confirmaciones
Módulo 6: Herramientas y Técnicas de Git
- Usando Git Hooks
- Git Bisect
- Git Blame
- Git Log y Alias
- Submódulos de Git
- Múltiples Copias de Trabajo con git worktree
Módulo 7: Estrategias de Colaboración y Flujo de Trabajo
- Forks y Pull Requests
- Revisiones de Código con Git
- Flujo de Trabajo Git Flow
- GitHub Flow
- Trunk Based Development
- Integración Continua con Git
Módulo 8: Mejores Prácticas y Consejos de Git
- Escribiendo Buenos Mensajes de Confirmación
- Manteniendo un Historial Limpio
- Ignorando Archivos con .gitignore
- Atributos de Fichero con .gitattributes
- Mejores Prácticas de Seguridad
- Consejos de Rendimiento
Módulo 9: Solución de Problemas y Depuración
- Problemas Comunes de Git
- Deshaciendo Cambios
- Resolviendo Divergencias con el Remoto
- Recuperando Confirmaciones Perdidas
- Tratando con Repositorios Corruptos
- Técnicas Avanzadas de Depuración
