Llevamos seis módulos aplazando esta lección. En la 03-02 dijimos que el reflog explica cómo Git recuerda por dónde ha pasado HEAD. En la 03-06 prometimos que borrar una rama con -D casi nunca es irreversible, y remitimos aquí. En la 05-01 dijimos que un rebase desastroso se puede deshacer, y remitimos aquí. En la 05-04, que un stash drop accidental es recuperable. En la 09-02, que los commits que un reset --hard deja atrás siguen ahí. Y en la 09-03, que origin/main@{1} guarda dónde estaba el servidor antes de la reescritura.
Todas esas promesas se apoyan en la misma pieza, y es hora de desarrollarla entera.
El reflog es probablemente la funcionalidad de Git con mejor relación entre lo que salva y lo poco que se conoce. Mucha gente lleva años usando Git sin haberlo ejecutado nunca, y es exactamente el comando que convierte "he perdido tres días de trabajo" en "he tardado dos minutos en recuperarlo".
Empecemos por lo más importante: si has llegado aquí en pánico, no has perdido nada, siempre que lo que buscas llegara a ser un commit. Sigue leyendo con calma.
Contenido
- Qué es el reflog y dónde vive
- Local y temporal: los dos límites que hay que conocer
- Leer
git reflog HEAD@{n}frente aHEAD@{tiempo}- El patrón universal de recuperación
- Recetario:
reset --hard, rama borrada, rebase, merge,--amend - Cuando el reflog no basta:
git fsck --lost-found - Recuperar un stash eliminado
- Los límites reales: lo que no se puede recuperar
- Higiene: no destruir tu propia red de seguridad
- Qué es el reflog y dónde vive
El reflog es el registro local de todos los movimientos de cada referencia.
Cada vez que una referencia —HEAD, una rama, una rama de seguimiento remota— cambia de valor, Git escribe una línea en un fichero de texto anotando de dónde venía, adónde va, quién lo hizo y por qué.
Míralo directamente:
4f8a2e6c9b1d5e3a b52c9d1e4a7f3c8b Ana Ferrer <[email protected]> 1785312041 +0200 commit: GT-241 base del filtro b52c9d1e4a7f3c8b 7d3a8f4a2c6e9b1d Ana Ferrer <[email protected]> 1785312388 +0200 commit: GT-241 calcular el contador 7d3a8f4a2c6e9b1d 4f8a2e6c9b1d5e3a Ana Ferrer <[email protected]> 1785312551 +0200 reset: moving to HEAD~2
Cada línea tiene: hash anterior, hash nuevo, identidad, marca de tiempo, y descripción de la operación. Es texto plano, sin magia.
La estructura de directorios:
.git/logs/HEAD .git/logs/refs/heads/main .git/logs/refs/heads/GT-241 .git/logs/refs/remotes/origin/main .git/logs/refs/stash
| Fichero | Registra los movimientos de |
|---|---|
.git/logs/HEAD |
HEAD: todo lo que has hecho, incluidos los cambios de rama |
.git/logs/refs/heads/<rama> |
Solo esa rama |
.git/logs/refs/remotes/origin/<rama> |
La rama de seguimiento: lo que trajo cada fetch (09-03) |
.git/logs/refs/stash |
La pila de stashes (05-04) |
Y aquí está la clave de todo el módulo. Recupera la idea de la lección 09-01:
Un commit sobrevive mientras algo lo alcance. Las entradas del reflog cuentan como referencias.
Por eso git gc no elimina un commit que aparece en el reflog, aunque ninguna rama lo apunte. El reflog no es solo un registro: es lo que mantiene vivos los objetos huérfanos. Esa es la razón técnica de que la recuperación funcione.
flowchart LR
subgraph refs["Referencias que mantienen vivo un commit"]
R1["Ramas<br/>refs/heads/*"]
R2["Etiquetas<br/>refs/tags/*"]
R3["Ramas remotas<br/>refs/remotes/*"]
R4["Stash<br/>refs/stash"]
R5["REFLOG<br/>.git/logs/*"]
end
C["commit b52c9d1"]
R1 -.-> C
R5 ==>|"aunque las demás<br/>desaparezcan"| C
- Local y temporal: los dos límites que hay que conocer
Antes del recetario, las dos limitaciones. No entenderlas es lo que hace que alguien confíe en el reflog cuando no debe.
Es LOCAL
El reflog no viaja. No se publica con push, no se descarga con fetch, y un git clone no lo trae.
Una sola entrada. Todo el historial de movimientos que Ana tenía en su máquina se queda en su máquina.
Tres consecuencias prácticas:
- El reflog de Ana no puede rescatar el desastre de Bruno. Cada uno tiene el suyo.
- El reflog no es una copia de seguridad. Si el disco muere, muere con él. Las copias de seguridad reales son
git bundley los propios clones del equipo (lección 09-05). - Un clon recién hecho no tiene red de seguridad. El primer
reset --harddesafortunado en un clon nuevo sí puede dejarte sin nada que recuperar, porque no hay entradas anteriores.
Y una consecuencia positiva, la del apartado 9 de la lección 09-03: si un compañero fuerza y destruye una rama del servidor, tu reflog de origin/rama conserva dónde estaba, y con él se restaura.
Es TEMPORAL
Las entradas caducan. Dos parámetros lo gobiernan:
git config --get gc.reflogExpire # por defecto: 90 días
git config --get gc.reflogExpireUnreachable # por defecto: 30 días| Parámetro | Se aplica a | Valor por defecto |
|---|---|---|
gc.reflogExpire |
Entradas cuyo commit sigue siendo alcanzable | 90 días |
gc.reflogExpireUnreachable |
Entradas cuyo commit ya no es alcanzable desde ninguna rama | 30 días |
La segunda es la que importa: los commits realmente huérfanos tienen 30 días. Después, un git gc los purga definitivamente.
La caducidad no ocurre sola: la ejecuta git gc (lección 08-06), que Git lanza automáticamente cuando se acumulan objetos sueltos. En la práctica, tienes semanas, no meses, y desde luego no años.
Puedes alargar el plazo:
# Un año para todo, en tus repositorios de trabajo
git config --global gc.reflogExpire "1 year"
git config --global gc.reflogExpireUnreachable "1 year"
# O por rama, con un patrón
git config gc.refs/heads/main.reflogExpire neverCuesta unos megabytes y compra mucha tranquilidad.
Y lo contrario, que nunca debes ejecutar salvo que sepas exactamente qué haces (lección 08-06):
# DESTRUYE la red de seguridad de todo este módulo
git reflog expire --expire=now --all
git gc --prune=now
- Leer
git reflog
git reflog7d3a8f4 HEAD@{0}: reset: moving to HEAD~2
9c4e7b2 HEAD@{1}: commit: GT-241 estilos del indicador
3e7f1a8 HEAD@{2}: commit: GT-241 calcular el contador
8a1f6c3 HEAD@{3}: rebase (finish): returning to refs/heads/GT-241
8a1f6c3 HEAD@{4}: rebase (pick): GT-241 base del filtro
2f8c6e1 HEAD@{5}: rebase (start): checkout origin/main
b52c9d1 HEAD@{6}: checkout: moving from main to GT-241
7d3a8f4 HEAD@{7}: pull: Fast-forward
4f8a2e6 HEAD@{8}: commit: chore: configuración de eslintCómo se lee:
HEAD@{0}es siempre lo último que pasó. El orden es del presente hacia el pasado.- El hash de cada línea es dónde quedó
HEADDESPUÉS de esa operación. Este detalle es crucial y produce el error más frecuente: si quieres el estado anterior a la operación deHEAD@{0}, necesitas el hash deHEAD@{1}. - La descripción dice qué comando lo provocó.
El vocabulario de las descripciones:
| Descripción | Qué la produjo |
|---|---|
commit: |
git commit |
commit (amend): |
git commit --amend |
commit (initial): |
El primer commit del repositorio |
checkout: moving from X to Y |
git switch o git checkout |
reset: moving to X |
git reset |
pull: / merge X: |
Una integración |
rebase (start/pick/finish): |
Las fases de un rebase |
cherry-pick: |
git cherry-pick |
revert: |
git revert |
branch: Created from X |
git branch o git switch -c |
clone: from X |
El clon inicial |
fetch: forced-update |
Alguien reescribió la rama remota (09-03) |
El reflog de una rama concreta
9c4e7b2 GT-241@{0}: commit: GT-241 estilos del indicador
3e7f1a8 GT-241@{1}: commit: GT-241 calcular el contador
8a1f6c3 GT-241@{2}: rebase (finish): refs/heads/GT-241 onto 2f8c6e1
b52c9d1 GT-241@{3}: branch: Created from HEADMucho más limpio que el de HEAD cuando sabes qué rama te interesa, porque no incluye los cambios de rama.
Formatos útiles
# Con fechas relativas: imprescindible para orientarse
git reflog --date=relative -10
# Con fecha y hora exactas
git reflog --date=iso -10
# Con el mensaje del commit además de la operación
git reflog --format='%h %gd %gs | %s' -10
# Solo lo de una rama, con gráfico
git log --walk-reflogs --oneline GT-2417d3a8f4 HEAD@{hace 3 minutos}: reset: moving to HEAD~2
9c4e7b2 HEAD@{hace 41 minutos}: commit: GT-241 estilos del indicador
3e7f1a8 HEAD@{hace 2 horas}: commit: GT-241 calcular el contador--date=relative es el que más ayuda en una recuperación: "lo tenía justo antes de comer" se traduce directamente a una entrada.
Y git log -g (o --walk-reflogs), que recorre el reflog mostrando cada entrada como un commit completo:
Útil cuando no reconoces un commit por su mensaje y necesitas ver qué ficheros tocaba.
HEAD@{n} frente a HEAD@{tiempo}
HEAD@{n} frente a HEAD@{tiempo}Dos sintaxis que se parecen y significan cosas distintas.
| Sintaxis | Qué es | Ejemplo |
|---|---|---|
HEAD@{2} |
La entrada número 2 del reflog (contando desde 0) | Dos operaciones atrás |
HEAD@{2.hours.ago} |
Dónde estaba HEAD hace dos horas |
Según el reloj |
HEAD~2 |
Dos commits atrás en el grafo | Recorre padres, no reflog |
HEAD^2 |
El segundo padre (solo en fusiones) | Otra rama de la fusión |
main@{5} |
Quinta entrada del reflog de main |
— |
main@{yesterday} |
Dónde estaba main ayer |
— |
origin/main@{1} |
Dónde estaba la rama remota antes del último fetch | 09-03 |
@{-1} |
La rama anterior en la que estuviste | Lo que usa git switch - |
La distinción crítica, y merece un ejemplo:
git log --oneline HEAD~3 -1 # tres commits atrás en la historia
git log --oneline HEAD@{3} -1 # donde estaba HEAD hace tres operacionesHEAD~3 recorre el grafo: sigue la cadena de padres. HEAD@{3} recorre tu historia personal: puede estar en otra rama, en un commit huérfano, en cualquier sitio donde hayas estado.
Cuando has hecho un reset, HEAD~1 te lleva al padre del commit actual (que ya no es lo que buscabas) y HEAD@{1} te lleva a donde estabas antes del reset (que sí lo es).
Las formas temporales aceptan expresiones muy naturales:
git log --oneline main@{yesterday} -3
git log --oneline main@{"1 week ago"} -3
git log --oneline main@{2026-07-28.09:00:00} -3
git diff main@{1.day.ago} mainY el aviso que Git da cuando te pasas de plazo:
No es un error: te está diciendo que ha usado la entrada más antigua disponible. Si la fecha que pediste es anterior, el resultado no es el que crees.
- El patrón universal de recuperación
Todas las recetas del apartado siguiente son el mismo procedimiento. Apréndelo una vez.
flowchart TD
A["1. git reflog (o git reflog show rama)"] --> B["2. Localizar la entrada:<br/>usar la descripción y --date=relative"]
B --> C["3. VERIFICAR el candidato:<br/>git show hash --stat<br/>git log --oneline hash -5"]
C --> D{"¿Es lo que buscaba?"}
D -->|No| B
D -->|Sí| E["4. git branch rescate hash"]
E --> F["5. Comprobar en la rama nueva"]
F --> G["6. Integrar: merge, cherry-pick,<br/>o reset --hard si estás seguro"]
El paso 4 es el que hay que interiorizar, y es la diferencia entre una recuperación tranquila y un segundo susto:
# BIEN: crea un puntero nuevo. No mueve nada de lo que ya tienes
git branch rescate 9c4e7b2
# PEOR: mueve tu rama actual, y si te equivocas de hash pierdes de vista lo de ahora
git reset --hard 9c4e7b2git branch es puramente aditivo. Si el hash era el equivocado, borras la rama y pruebas otro. Nada se ha movido. Con reset --hard cada intento fallido añade una capa más de confusión.
Y el paso 3, verificar, evita el error de recuperar el commit equivocado:
git show 9c4e7b2 --stat # ¿qué ficheros tocaba?
git log --oneline 9c4e7b2 -5 # ¿qué historia tiene detrás?
git diff HEAD 9c4e7b2 --stat # ¿en qué se diferencia de lo que tengo ahora?Y para inspeccionar sin comprometerse a nada:
git switch --detach 9c4e7b2 # mirar el proyecto en ese estado
# ...abrir ficheros, ejecutar la aplicación...
git switch - # volver
- Recetario:
reset --hard, rama borrada, rebase, merge, --amend
reset --hard, rama borrada, rebase, merge, --amend6.1. Deshacer un git reset --hard
La situación. Ana quería quitar un commit y escribió HEAD~3.
No has perdido nada (salvo lo que estuviera sin confirmar, lección 09-02).
4f8a2e6 HEAD@{0}: reset: moving to HEAD~3
9c4e7b2 HEAD@{1}: commit: GT-241 estilos del indicador
3e7f1a8 HEAD@{2}: commit: GT-241 calcular el contador
8a1f6c3 HEAD@{3}: commit: GT-241 base del filtro
2f8c6e1 HEAD@{4}: checkout: moving from main to GT-241HEAD@{1} (9c4e7b2) es la punta que había justo antes del reset.
git show 9c4e7b2 --stat # verificar
git branch rescate-GT-241 9c4e7b2
git log --oneline rescate-GT-241 -49c4e7b2 (rescate-GT-241) GT-241 estilos del indicador 3e7f1a8 GT-241 calcular el contador 8a1f6c3 GT-241 base del filtro 2f8c6e1 chore: configuración de eslint
Recuperado. Ahora, si quieres que GT-241 vuelva a ser eso:
El atajo, si acabas de hacerlo y no has hecho nada más (lección 09-02):
O directamente con la sintaxis del reflog:
6.2. Recuperar una rama borrada con -D
Esto cierra la promesa de la lección 03-06.
Primer regalo: Git imprime el hash al borrar. Si lo tienes en el buffer de la terminal, la recuperación es inmediata:
Si has cerrado la terminal, hay dos caminos.
A. El reflog de HEAD (funciona si estuviste en esa rama):
b52c9d1 HEAD@{hace 3 horas}: checkout: moving from GT-247 to main
b52c9d1 HEAD@{hace 3 horas}: commit: GT-247 validar el formularioB. Buscar por mensaje entre todos los commits huérfanos (funciona siempre):
O directamente sobre la base de objetos:
Y el detalle importante que se olvida: el reflog de la propia rama se borra con ella.
.git/logs/refs/heads/GT-247 ya no existe. Por eso hay que buscar en el reflog de HEAD, que sí sobrevive.
-Dcasi nunca es irreversible. Los commits siguen en.git/objects, alcanzables desde el reflog deHEAD, durante al menos 30 días. La promesa de la lección 03-06 queda cumplida.
Y la excepción, para ser honestos: si la rama se creó, se le hicieron commits y se borró sin que HEAD pasara nunca por ella (por ejemplo, con git branch X <hash> y luego git branch -D X), el reflog de HEAD no la registra. En ese caso, git fsck del apartado 7.
6.3. Rescatar un rebase que salió mal
Esto cierra la promesa de la lección 05-01.
Bruno hizo un rebase -i de diez commits, aplastó los que no debía y terminó con --continue.
4b8e2c9 HEAD@{0}: rebase (finish): returning to refs/heads/GT-241
4b8e2c9 HEAD@{1}: rebase (squash): GT-241 el filtro completo
8f2d6a1 HEAD@{2}: rebase (squash): GT-241 base del filtro
6e2b9c7 HEAD@{3}: rebase (pick): GT-241 base del filtro
2f8c6e1 HEAD@{4}: rebase (start): checkout origin/main
9c4e7b2 HEAD@{5}: checkout: moving from main to GT-241La entrada clave es rebase (start): la anterior a ella es el estado exacto previo al rebase. Aquí, HEAD@{5} → 9c4e7b2.
Los diez commits originales, intactos.
Un atajo que casi nadie conoce: durante un rebase, Git guarda la posición inicial en ORIG_HEAD, y también en una referencia especial:
git rev-parse ORIG_HEAD
cat .git/rebase-merge/orig-head 2>/dev/null # solo mientras el rebase está en marchaY si el rebase aún no ha terminado, la salida es mucho más simple:
Devuelve todo exactamente al estado previo. Úsalo siempre que puedas: es más limpio que recuperar después.
Para comparar el resultado con el original y decidir qué hacer, git range-diff (lección 07-02):
6.4. Deshacer un merge que no debiste hacer
Si acabas de hacerlo y no hay nada encima:
merge siempre escribe ORIG_HEAD antes de fusionar. Es la salida canónica.
Si ya has hecho más cosas encima:
c7e2a91 HEAD@{0}: commit: GT-241 ajustar el contador
2b9e6c1 HEAD@{1}: merge funcionalidad/experimental: Merge made by the 'ort' strategy.
7d3a8f4 HEAD@{2}: commit: GT-241 estilos del indicadorHEAD@{2} es el estado previo a la fusión. Pero cuidado: si vuelves ahí, pierdes también el commit c7e2a91 que hiciste después. Lo correcto es rescatarlo aparte:
git branch antes-del-merge 7d3a8f4
git switch antes-del-merge
git cherry-pick c7e2a91 # traer solo el commit bueno (lección 05-03)Y si el merge ya está publicado, nada de reflog: la respuesta es git revert -m 1 (lección 05-06).
Un caso frecuente y desconcertante: git merge --abort no funciona porque la fusión terminó bien; solo era mala idea. --abort sirve mientras hay conflictos sin resolver, no después.
6.5. Recuperar un commit sobrescrito por --amend
Carla hizo git commit --amend y se dio cuenta de que ha perdido parte del mensaje original —o peor, de que el commit anterior tenía cambios que el nuevo no.
No has perdido nada. --amend no modifica el commit: crea uno nuevo y mueve la rama (lección 09-01). El original sigue en la base de datos.
3f8a1d6 HEAD@{0}: commit (amend): GT-241 calcular el contador sobre la lista completa
8f4c2a9 HEAD@{1}: commit: GT-241 calcular el contador
b52c9d1 HEAD@{2}: commit: GT-241 base del filtroHEAD@{1} (8f4c2a9) es el commit original, antes del --amend.
# Ver el mensaje original completo
git show 8f4c2a9 --stat
git log -1 --format=%B 8f4c2a9
# Comparar los dos
git diff 8f4c2a9 3f8a1d6Y las formas de recuperar, según lo que necesites:
# A. Volver del todo al original
git reset --hard 8f4c2a9
# B. Solo recuperar el mensaje
git commit --amend -m "$(git log -1 --format=%B 8f4c2a9)"
# C. Recuperar un fichero concreto que se perdió en el amend
git restore --source=8f4c2a9 --staged --worktree app.js
# D. Ver qué se perdió exactamente
git diff 3f8a1d6 8f4c2a96.6. Recuperar tras un checkout a otra rama con commits en detached HEAD
Retomando el apartado 5.7 de la lección 09-01: hiciste commits en detached HEAD y te fuiste.
7d3a8f4 HEAD@{0}: checkout: moving from 9c4e7b2 to main
9c4e7b2 HEAD@{1}: commit: prueba del algoritmo alternativo
3e7f1a8 HEAD@{2}: commit: esbozo del filtro por fecha
4f8a2e6 HEAD@{3}: checkout: moving from main to 4f8a2e6La línea checkout: moving from 9c4e7b2 to main te da el hash de donde saliste.
La tabla de referencia rápida
| Desastre | Dónde mirar | Comando de rescate |
|---|---|---|
reset --hard de más |
git reflog -5, entrada anterior al reset: |
git branch rescate HEAD@{1} |
Rama borrada con -D |
El hash que imprimió -D, o git reflog | grep <rama> |
git branch <rama> <hash> |
| Rebase desastroso | Entrada anterior a rebase (start) |
git branch antes 9c4e7b2 / git rebase --abort si sigue en marcha |
| Merge indeseado | ORIG_HEAD, o entrada anterior a merge |
git reset --hard ORIG_HEAD |
--amend que se llevó algo |
Entrada commit: anterior a commit (amend): |
git restore --source=<hash> <fichero> |
| Commits en detached HEAD | checkout: moving from <hash> to <rama> |
git branch <nombre> <hash> |
| Rama remota reescrita por otro | git reflog show origin/<rama> |
git branch antigua origin/<rama>@{1} |
| Stash eliminado | git fsck --unreachable | grep commit |
Apartado 8 |
| Nada aparece en el reflog | git fsck --lost-found |
Apartado 7 |
- Cuando el reflog no basta:
git fsck --lost-found
git fsck --lost-foundEl reflog registra los movimientos de referencias. Hay objetos que llegan a la base de datos sin que ninguna referencia se mueva, y para esos hace falta la otra herramienta.
Checking object directories: 100% (256/256), done. dangling commit 9c4e7b2e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b dangling commit 3e7f1a8b6d2e9a5f1c7b3d8e4a6f2c9b7d3a8f4a dangling blob 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b dangling tree 8a1f6c3d5e2b9c7f4a6d1e8b3c5f7a9d2e4b6c8a
Y además crea un directorio con enlaces:
Qué es cada cosa
| Tipo | Qué es | Por qué aparece |
|---|---|---|
| dangling commit | Un commit que ninguna referencia alcanza | reset, rama borrada, rebase, --amend, stash eliminado |
| dangling blob | Contenido de un fichero sin árbol que lo referencie | Un git add cuyo commit nunca llegó a hacerse |
| dangling tree | Un directorio sin commit que lo use | Un commit a medias, o el árbol de un stash |
| unreachable X | Igual, pero contando el reflog | Con --unreachable se ven también los del reflog |
dangling es normal e inofensivo. No es un síntoma de corrupción. Es lo que se espera en cualquier repositorio activo. La distinción entre eso y los errores de verdad (missing, broken link) es la lección 09-05.
Cómo inspeccionar los candidatos
Un fsck puede devolver decenas de líneas. El trabajo es identificar cuál es el tuyo.
# Los commits colgantes, con fecha, autor y mensaje, ordenados por fecha
git fsck --lost-found --no-progress 2>/dev/null \
| awk '/dangling commit/ {print $3}' \
| xargs -I{} git log -1 --format='%ci %h %an %s' {} \
| sort -r2026-07-30 18:22:41 +0200 9c4e7b2 Ana Ferrer GT-241 estilos del indicador 2026-07-30 17:51:03 +0200 3e7f1a8 Ana Ferrer GT-241 calcular el contador 2026-07-28 11:04:17 +0200 8a1f6c3 Bruno Salas WIP en GT-238
Eso convierte una lista de hashes en información utilizable. Guárdalo como alias:
git config --global alias.perdidos '!git fsck --lost-found --no-progress 2>/dev/null | awk "/dangling commit/ {print \$3}" | xargs -I{} git log -1 --format="%ci %h %an %s" {} | sort -r'Inspección individual, con las herramientas de fontanería de la lección 01-04:
tree 8a1f6c3d5e2b9c7f4a6d1e8b3c5f7a9d2e4b6c8a parent 2f8c6e1a4b7d9c3e5f2a8b6d1c9e4f7a3b5d2c8e author Ana Ferrer <[email protected]> 1785312041 +0200 committer Ana Ferrer <[email protected]> 1785312041 +0200 GT-241 estilos del indicador de pendientes
# El diff completo
git show 9c4e7b2
# Solo los ficheros
git show --stat 9c4e7b2
# Qué contenía un blob colgante
git cat-file -p 6f2b9d4 | head -20
# De qué tipo es un objeto cualquiera
git cat-file -t 8a1f6c3Los blobs colgantes: el add que nunca se confirmó
Este es el caso que salva más trabajo del que la gente espera. Si preparaste un fichero con git add y luego lo perdiste, el contenido está en la base de datos (lección 09-01, tabla del apartado 1).
# Buscar entre los blobs colgantes el que contenga una cadena que recuerdes
for b in $(git fsck --lost-found --no-progress 2>/dev/null | awk '/dangling blob/ {print $3}'); do
if git cat-file -p "$b" 2>/dev/null | grep -q "calcularPendientes"; then
echo "=== $b ==="
git cat-file -p "$b" | head -5
fi
done=== 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b ===
function calcularPendientes(tareas) {
return tareas.filter(t => !t.completada).length;
}Buscar por una cadena que recuerdes es mucho más eficaz que revisar los blobs uno a uno.
- Recuperar un stash eliminado
Esto cierra la promesa de la lección 05-04.
Primer regalo, otra vez: drop imprime el hash. Con él:
Y si fue git stash clear, que borra toda la pila sin imprimir nada, hay dos caminos.
A. El reflog del stash, si aún existe:
stash clear borra la referencia, pero el fichero de log a veces sobrevive hasta el siguiente gc. Merece la pena mirarlo primero.
B. Buscar los commits del stash entre los inalcanzables:
git fsck --unreachable --no-progress | grep commit | awk '{print $3}' \
| xargs -I{} git log -1 --format='%h %ci %s' {} \
| grep -i "WIP on\|On .*:"b52c9d1 2026-07-30 16:12:44 +0200 WIP on GT-241: 7d3a8f4 GT-241 base del filtro 8f4c2a9 2026-07-29 09:31:20 +0200 On main: pruebas del contador
Los stashes se reconocen por su mensaje: WIP on <rama>: (los automáticos) o On <rama>: <mensaje> (los que creaste con -m).
Un stash es internamente un commit de fusión con dos o tres padres, y merece verlo porque explica cómo funciona:
| Padre | Qué contiene |
|---|---|
1º (7d3a8f4) |
El HEAD en el momento del stash |
2º (4f8a2e6) |
El índice (lo que estaba preparado) |
3º (9c4e7b2) |
Los ficheros sin seguimiento, si usaste -u |
Y la recuperación:
# Aplicarlo directamente
git stash apply b52c9d1
# O devolverlo a la pila para tratarlo con normalidad
git stash store -m "recuperado: GT-241 a medias" b52c9d1
git stash list# Ver qué contenía sin aplicarlo
git show b52c9d1 # los cambios del directorio de trabajo
git show b52c9d1^2 # lo que estaba preparado
git show b52c9d1^3 # los ficheros sin seguimiento (si los hay)
- Los límites reales: lo que no se puede recuperar
Toca ser honestos. Hay dos fronteras que ninguna técnica de esta lección cruza.
Frontera 1: lo que nunca entró en la base de datos
Ya lo vimos en la lección 09-01, y es la limitación fundamental:
| Situación | ¿Recuperable? |
|---|---|
| Un commit, aunque la rama se borrara | Sí |
Un git add sin confirmar |
Sí: hay blob |
| Un stash, incluso eliminado | Sí: son commits |
Un fichero modificado y destruido con git restore |
No |
Un fichero destruido por reset --hard sin add previo |
No |
Un fichero sin seguimiento borrado por git clean |
No |
Cuando estás en uno de los tres últimos casos, Git ya no puede ayudarte. Lo que queda es fuera de Git:
1. El historial local de tu editor. Es la vía que más veces funciona y la que menos se prueba.
- VS Code: paleta de comandos → "Local History: Find Entry to Restore". Guarda versiones de cada fichero que has editado, independientemente de Git.
- IntelliJ / WebStorm / Eclipse: menú contextual del fichero → Local History → Show History. Guarda semanas.
- Vim: si tienes
set undofile, el historial de deshacer persiste entre sesiones (:earlier 1h).
2. Copias de seguridad del sistema: Time Machine en macOS, instantáneas de Btrfs/ZFS, versiones anteriores en Windows, copias en la nube.
3. Buscar en /tmp y en los ficheros de intercambio del editor (.swp de Vim, ~ de otros).
Frontera 2: lo que ya pasó por gc
Si se ejecutó git gc --prune=now después de que el commit quedara huérfano, el objeto ha desaparecido físicamente. Si además se ejecutó git reflog expire --expire=now --all, no hay ni registro.
Comprueba si aún está antes de rendirte:
Ese mensaje significa que el objeto ya no existe. Pero antes de darlo por perdido, mira fuera de tu máquina:
# ¿Lo tiene el servidor?
git fetch origin '+refs/*:refs/remotes/copia/*'
git log --all --oneline | grep -i "GT-241"
# ¿Lo tiene un compañero? (su clon es una copia casi completa)
git remote add bruno /ruta/o/url/al/clon/de/bruno
git fetch bruno
git log --oneline bruno/GT-241Esa es la red de seguridad del modelo distribuido, y es el tema central de la lección 09-05.
Y la prevención, que es lo que de verdad resuelve esto
# 1. Confirma pronto y a menudo. Un "wip" cada media hora lo cambia todo.
git commit -am "wip"
# 2. Antes de cualquier operación arriesgada, un marcador. Cuesta dos segundos.
git branch respaldo-$(date +%H%M)
# 3. Antes de limpiar, un stash con ficheros sin seguimiento incluidos
git stash push -u -m "por si acaso"
# 4. Alarga el reflog en tus repositorios de trabajo
git config --global gc.reflogExpire "1 year"
git config --global gc.reflogExpireUnreachable "1 year"
# 5. Publica tus ramas, aunque estén a medias
git push -u origin GT-241La quinta es la más infravalorada. Una rama publicada está en dos máquinas. Ningún accidente local puede con eso.
- Higiene: no destruir tu propia red de seguridad
Cierra la lección lo que no hay que hacer.
1. git reflog expire --expire=now --all. Vacía el registro. Todo lo huérfano queda desprotegido.
2. git gc --prune=now. Elimina inmediatamente los objetos inalcanzables. Tiene su uso legítimo (tras un filter-repo de la lección 08-05, donde borrar es justo el objetivo), y ninguno en un día normal.
3. Confiar en el reflog como copia de seguridad. Es local, temporal y muere con el disco. No es una copia de seguridad.
4. Recuperar con reset --hard en lugar de git branch. Cada intento fallido añade confusión. branch no mueve nada.
5. Trabajar meses sin publicar una rama. Un clon nuevo tiene el reflog vacío; una rama sin publicar existe en un solo disco.
Y una comprobación de salud de treinta segundos, para saber si tu red de seguridad está donde crees:
echo "Entradas en el reflog de HEAD: $(git reflog | wc -l)"
echo "Entrada más antigua: $(git reflog --date=iso | tail -1)"
echo "reflogExpire: $(git config --get gc.reflogExpire || echo '90 días (por defecto)')"
echo "reflogExpireUnreachable: $(git config --get gc.reflogExpireUnreachable || echo '30 días (por defecto)')"
echo "Objetos colgantes: $(git fsck --lost-found --no-progress 2>/dev/null | grep -c dangling)"
echo "Ramas sin publicar: $(git for-each-ref --format='%(refname:short) %(upstream)' refs/heads | awk '$2==""{print $1}' | tr '\n' ' ')"Errores Comunes y Consejos
Error 1: no probar git reflog. Es el primer comando ante cualquier "he perdido commits", y la mayoría de la gente no lo ejecuta nunca.
Error 2: confundir HEAD@{1} con HEAD~1. El primero es dónde estabas hace una operación; el segundo es el commit padre. Tras un reset, son cosas completamente distintas.
Error 3: coger el hash de la entrada equivocada. El hash de una línea es dónde quedó HEAD después de esa operación. Para el estado previo a HEAD@{0}, necesitas HEAD@{1}.
Error 4: recuperar con reset --hard en lugar de git branch. Si el hash era el equivocado, con branch borras la rama y pruebas otra; con reset cada intento complica más el estado.
Error 5: creer que el reflog viaja en un clone. No viaja. El clon nuevo tiene una sola entrada y ninguna red de seguridad.
Error 6: confiar en el reflog para siempre. 90 días para lo alcanzable, 30 para lo huérfano, y gc los aplica. En la práctica, semanas.
Error 7: asustarse con los dangling de git fsck. Son normales en cualquier repositorio activo. Lo grave es missing y broken link (lección 09-05).
Error 8: dar por perdido un stash. stash drop imprime el hash, stash clear deja los commits huérfanos, y ambos se recuperan con git fsck --unreachable buscando WIP on.
Error 9: rendirse sin mirar fuera de Git. El historial local del editor recupera lo que Git no puede, y casi nadie lo prueba.
Consejo 1: el patrón es siempre el mismo. Reflog → verificar con git show → git branch rescate <hash>.
Consejo 2: git reflog --date=relative. "Lo tenía antes de comer" se traduce directamente a una entrada.
Consejo 3: define el alias git perdidos. El día que lo necesites no vas a recordar la tubería de fsck y awk.
Consejo 4: git rebase --abort mientras puedas. Mucho más limpio que recuperar después.
Consejo 5: alarga el reflog a un año. Dos líneas de configuración global y unos megabytes.
Consejo 6: publica tus ramas. Una rama en dos máquinas es inmune a los accidentes de una.
Ejercicios
Ejercicio 1: el recetario completo
Sobre un repositorio de pruebas con app.js, estilos.css e index.html:
- Crea seis commits en
mainy una ramaGT-241con tres commits propios. - Provoca cuatro desastres seguidos, sin recuperar entre uno y otro:
a.
git reset --hard HEAD~2enGT-241. b.git switch main && git branch -D GT-241. c. Ungit commit --amendenmainque borre parte del mensaje. d. Ungit mergede una rama experimental que no querías. - Ejecuta
git reflog --date=relativey anota, para cada desastre, qué entrada usarías. - Recupera las cuatro cosas usando siempre
git branch, nuncareset. - Verifica cada recuperación con
git show --statygit diff.
Ejercicio 2: los límites de la recuperación
- En un repositorio nuevo, crea tres situaciones: un commit, un fichero preparado con
git add, y un fichero modificado solo en disco. - Ejecuta
git reset --hard HEAD~1. - Recupera el commit con el reflog y el fichero preparado con
git fsck --lost-found+git cat-file -p. - Documenta por qué el tercero es irrecuperable, comprobándolo con
git fsckygit reflog. - Crea un stash con
-u, elimínalo congit stash cleary recupéralo congit fsck --unreachable. - Comprueba con
git show --format='%p' -sque el stash tiene tres padres e inspecciona cada uno.
Ejercicio 3: la caducidad y la higiene
- Crea un repositorio con diez commits y provoca cinco commits huérfanos con
reset. - Comprueba que aparecen en
git fsck --lost-found. - Ejecuta
git reflog expire --expire=now --ally vuelve a mirargit reflogygit fsck. Explica la diferencia entre las dos salidas. - Ejecuta
git gc --prune=nowy comprueba congit cat-file -t <hash>que los objetos han desaparecido de verdad. - Repite el experimento configurando antes
gc.reflogExpireUnreachable "1 year"y explica qué cambia. - Escribe la comprobación de salud del apartado 10 en un guion e interpreta su salida en un repositorio real tuyo.
Soluciones
Solución 1:
rm -rf /tmp/p9-04 && mkdir /tmp/p9-04 && cd /tmp/p9-04 && git init -q -b main
git config user.name "Ana Ferrer"; git config user.email "[email protected]"
for i in 1 2 3 4 5 6; do echo "linea $i" >> app.js; git add .; git commit -q -m "chore: commit $i"; done
git switch -q -c GT-241
echo "// filtro" > filtro.js && git add . && git commit -q -m "GT-241 base del filtro"
echo "// contador" >> filtro.js && git commit -q -am "GT-241 calcular el contador"
echo ".pendiente{}" > estilos.css && git add . && git commit -q -m "GT-241 estilos del indicador"
git rev-parse --short HEAD# 2a. reset --hard
git reset -q --hard HEAD~2
# 2b. borrar la rama
git switch -q main
git branch -D GT-241# 2c. amend destructivo
git commit -q --amend -m "chore"
# 2d. merge indeseado
git switch -q -c experimental HEAD~1
echo "experimento" > experimento.txt && git add . && git commit -q -m "experimento que no vale"
git switch -q main
git merge -q --no-ff experimental -m "Merge experimental"
git log --oneline -3c7e2a91 (HEAD -> main) Merge experimental 2b9e6c1 (experimental) experimento que no vale 3f8a1d6 chore
c7e2a91 HEAD@{hace 4 segundos}: merge experimental: Merge made by the 'ort' strategy.
3f8a1d6 HEAD@{hace 12 segundos}: checkout: moving from experimental to main
2b9e6c1 HEAD@{hace 18 segundos}: commit: experimento que no vale
7d3a8f4 HEAD@{hace 25 segundos}: checkout: moving from main to experimental
3f8a1d6 HEAD@{hace 33 segundos}: commit (amend): chore
8f4c2a9 HEAD@{hace 40 segundos}: checkout: moving from GT-241 to main
6e2b9c7 HEAD@{hace 48 segundos}: reset: moving to HEAD~2
9c4e7b2 HEAD@{hace 55 segundos}: commit: GT-241 estilos del indicador| Desastre | Entrada a usar | Hash |
|---|---|---|
a. reset --hard |
La anterior al reset: |
9c4e7b2 |
b. branch -D |
El hash impreso, o el checkout: moving from GT-241 |
8a1f6c3 (lo que imprimió -D) |
c. --amend |
La commit: anterior a commit (amend): |
8f4c2a9 |
d. merge |
La anterior al merge, o ORIG_HEAD |
3f8a1d6 |
Fíjate en el matiz de (b): tras el reset --hard, GT-241 apuntaba a 6e2b9c7, y eso es lo que borró -D (8a1f6c3 en el ejemplo del mensaje). Pero lo que Ana quiere es el estado previo al reset, 9c4e7b2. El reflog las conserva las dos.
# 4. Recuperar, siempre con branch
git branch rescate-GT-241-completa 9c4e7b2
git branch rescate-amend 8f4c2a9
git branch rescate-antes-merge 3f8a1d6
git branchNinguna de esas operaciones ha movido main. Todo lo que había sigue donde estaba, y ahora además hay tres puntos de acceso a lo recuperado.
9c4e7b2 (rescate-GT-241-completa) GT-241 estilos del indicador 8a1f6c3 GT-241 calcular el contador 6e2b9c7 GT-241 base del filtro 7d3a8f4 chore: commit 6
Los tres commits, incluidos los dos que el reset --hard había dejado atrás.
El mensaje original, antes de que el --amend lo dejara en chore.
Lo único que aportó el merge indeseado. Para deshacerlo:
Y para restaurar GT-241 de verdad:
Solución 2:
rm -rf /tmp/p9-04b && mkdir /tmp/p9-04b && cd /tmp/p9-04b && git init -q -b main
echo "base" > app.js && git add . && git commit -q -m "c1"
# 1. Las tres situaciones
echo "COMMIT" >> app.js && git commit -q -am "c2: confirmado"
echo "PREPARADO" > estilos.css && git add estilos.css
echo "SOLO EN DISCO" >> app.js
# 2. El desastre
git reset -q --hard HEAD~13f8a1d6 HEAD@{0}: reset: moving to HEAD~1
8f4c2a9 HEAD@{1}: commit: c2: confirmado
3f8a1d6 HEAD@{2}: commit (initial): c1# 4. El tercero
git fsck --lost-found --no-progress | wc -l
git reflog | grep -c "SOLO EN DISCO"
grep -rl "SOLO EN DISCO" .git/ 2>/dev/null | wc -lUn solo objeto colgante (el del add), ninguna entrada de reflog, y ni un byte en todo .git. La línea SOLO EN DISCO nunca se convirtió en objeto: no se calculó su SHA, no se comprimió, no se escribió. Git no puede devolver lo que jamás recibió. Las únicas vías serían el historial local del editor o una copia del sistema de ficheros.
# 5. El stash
echo "trabajo a medias" >> app.js
echo "fichero nuevo" > borrador.txt
git stash push -u -q -m "GT-241 a medias"
git stash list
git stash clear
git stash listgit fsck --unreachable --no-progress | grep commit | awk '{print $3}' \
| xargs -I{} git log -1 --format='%h %ci %s' {} | grep -i "on main"Recuperado íntegro, incluido el fichero sin seguimiento.
git cat-file -t b52c9d1
git show --stat b52c9d1^2 # el índice
git show --stat b52c9d1^3 # los sin seguimientoTres padres exactamente como decía el apartado 8: HEAD, el índice y los ficheros sin seguimiento. Un stash es un commit, y por eso sobrevive a stash clear.
Solución 3:
rm -rf /tmp/p9-04c && mkdir /tmp/p9-04c && cd /tmp/p9-04c && git init -q -b main
for i in $(seq 1 10); do echo "linea $i" >> app.js; git add .; git commit -q -m "commit $i"; done
git rev-parse --short HEAD
git reset -q --hard HEAD~5# 2. Los huérfanos
git fsck --lost-found --no-progress | grep -c "dangling commit"
git reflog | head -33f8a1d6 HEAD@{0}: reset: moving to HEAD~5
9c4e7b2 HEAD@{1}: commit: commit 10
8f4c2a9 HEAD@{2}: commit: commit 9# 3. Vaciar el reflog
git reflog expire --expire=now --all
git reflog | wc -l
git fsck --lost-found --no-progress | grep -c "dangling commit"La diferencia clave: el reflog está vacío, pero los objetos siguen ahí. reflog expire borra el registro, no los objetos. Todavía se pueden recuperar con fsck... pero ya no sabes cuál era la punta ni en qué orden estaban, porque esa información vivía en el reflog. Recuperable, sí; cómodo, no.
# 4. Ahora sí, la purga
git gc --prune=now -q
git fsck --lost-found --no-progress | grep -c "dangling commit"
git cat-file -t 9c4e7b2Ahora sí ha desaparecido de verdad. El objeto ya no existe en .git/objects. Ninguna técnica de esta lección lo recupera; solo un clon ajeno o una copia de seguridad.
# 5. Con reflog largo
rm -rf /tmp/p9-04d && mkdir /tmp/p9-04d && cd /tmp/p9-04d && git init -q -b main
git config gc.reflogExpire "1 year"
git config gc.reflogExpireUnreachable "1 year"
for i in $(seq 1 10); do echo "l $i" >> app.js; git add .; git commit -q -m "c$i"; done
git rev-parse --short HEAD > /tmp/punta.txt
git reset -q --hard HEAD~5
git gc --prune=now -q
git reflog | wc -l
git cat-file -t "$(cat /tmp/punta.txt)"El gc --prune=now no ha podido purgar nada, porque las entradas del reflog siguen vigentes y el reflog cuenta como referencia. Ese es el mecanismo exacto que mantiene vivos los objetos huérfanos, y por eso alargar la caducidad protege de verdad.
# 6. Comprobación de salud
cat > /tmp/salud-git.sh <<'EOF'
#!/usr/bin/env bash
echo "Entradas en el reflog de HEAD: $(git reflog | wc -l)"
echo "Entrada más antigua: $(git reflog --date=iso | tail -1)"
echo "reflogExpire: $(git config --get gc.reflogExpire || echo '90 días (defecto)')"
echo "reflogExpireUnreachable: $(git config --get gc.reflogExpireUnreachable || echo '30 días (defecto)')"
echo "Objetos colgantes: $(git fsck --lost-found --no-progress 2>/dev/null | grep -c dangling)"
echo "Ramas sin publicar: $(git for-each-ref --format='%(refname:short) %(upstream)' refs/heads | awk '$2==""{print $1}' | tr '\n' ' ')"
EOF
chmod +x /tmp/salud-git.sh && /tmp/salud-git.shEntradas en el reflog de HEAD: 11
Entrada más antigua: 3f8a1d6 HEAD@{2026-07-31 11:02:14 +0200}: commit (initial): c1
reflogExpire: 1 year
reflogExpireUnreachable: 1 year
Objetos colgantes: 5
Ramas sin publicar: mainLa última línea es la que más información da en un repositorio real: cada rama sin upstream existe en un solo disco.
Conclusión
El reflog es la razón por la que casi nada se pierde en Git, y saberlo cambia por completo la relación con los comandos destructivos.
- El reflog registra cada movimiento de cada referencia, en texto plano bajo
.git/logs/. Y no es solo un registro: sus entradas cuentan como referencias, y por eso mantienen vivos los commits que ninguna rama alcanza. - Es local: no viaja en un
clone, no se publica, y muere con el disco. No es una copia de seguridad. Y es temporal: 90 días para lo alcanzable, 30 para lo huérfano, quegcaplica. HEAD@{n}no esHEAD~n. El primero recorre tu historia personal de movimientos; el segundo recorre el grafo. Tras unreset, son cosas distintas, y confundirlos es el error más común.- El patrón de recuperación es siempre el mismo:
git reflog→ verificar congit show --stat→git branch rescate <hash>. Crear una rama es aditivo y no mueve nada;reset --hardcomplica cada intento fallido. - El recetario cubre todo lo que este curso había ido prometiendo: deshacer un
reset --hard(entrada anterior alreset:), recuperar una rama borrada con-D(el hash que imprime, o el reflog deHEAD, porque el de la rama se borra con ella), rescatar un rebase (entrada anterior arebase (start), o--abortsi sigue en marcha), deshacer unmerge(ORIG_HEAD) y recuperar lo que se llevó un--amend(la entradacommit:anterior). - Cuando el reflog no basta,
git fsck --lost-found: los objetos dangling son normales, y entre ellos están los commits huérfanos y los blobs de ungit addque nunca se confirmó.git cat-file -pygit showlos inspeccionan. - Un stash es un commit con dos o tres padres (
HEAD, índice, sin seguimiento), y por eso sobrevive adropy aclear: se busca porWIP onentre los inalcanzables y se devuelve congit stash store. - Los límites son reales: lo que nunca fue objeto (modificaciones sin
add, ficheros borrados porclean) y lo que ya pasó porgc --prune. Ahí solo salvan el historial local del editor, una copia del sistema o el clon de un compañero. - Y la prevención que hace innecesaria toda la lección: confirmar a menudo, poner un
git branchantes de lo arriesgado,stash push -uantes de limpiar, alargar el reflog a un año y publicar las ramas.
Hasta aquí, todo se apoyaba en una premisa: que la base de datos de objetos estuviera sana. Los objetos existían, los hashes cuadraban y Git podía leerlos.
¿Y cuándo no? Cuando git status responde error: object file .git/objects/4f/8a2e6... is empty, cuando una referencia apunta a un objeto que no existe, cuando el índice está corrupto y Git se niega a hacer absolutamente nada. Eso ya no es un problema de referencias mal puestas: es un problema de integridad, y tiene su propio diagnóstico, sus propias reparaciones y —lo más importante— un criterio claro para saber cuándo hay que dejar de intentar arreglarlo y volver a clonar.
Continúa en la lección 09-05: Tratando con Repositorios Corruptos.
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
