Formación › Programar con Claude Code en 12 horas › Hora 10
Programar con Claude Code en 12 horas · hora 10 de 12
Hooks
CLAUDE.md es un consejo; un hook es una obligación. Es un programa que Claude Code ejecuta siempre en un momento concreto, y que puede bloquear una acción antes de que ocurra. Verás un hook real que protege los datos de origen, cómo lo probó Claude y lo que no cubre.
Al terminar sabrásAñadir un hook PostToolUse que actúe tras cada edición y un PreToolUse que bloquee una acción con el código de salida 2, y explicar qué cubre un hook y qué no.
60 minutos· 1 esquema· 7 preguntas· 1 ejercicio· entregable
Repaso
Comprueba De la hora 9: quieres un revisor que no pueda editar. ¿Dónde lo garantizas?
Correcto. Lo que no está en la lista, no lo puede usar.
No. El sitio no cambia lo que puede hacer.
No. Ayuda, pero no lo garantiza.
Comprueba De la hora 3: escribes en CLAUDE.md «no toques datos/». ¿Qué consigues?
No. Se lee al empezar cada sesión.
No. Es contexto: lo tiene en cuenta, no lo garantiza.
Correcto. Es contexto: lo que debe pasar siempre va en un hook.
Comprueba De la hora 7: una acción coincide con Bash(npm *) en allow y con Bash(npm publish *) en deny. ¿Qué pasa?
No. La generalidad no cambia el orden.
No. El orden en el fichero no importa.
Correcto. Deny, después ask y después allow.
Vocabulario
- Hook
- Un comando que Claude Code ejecuta automáticamente en un momento concreto de su trabajo.
- PreToolUse
- El evento de antes de usar una herramienta: el hook puede bloquear la acción.
- PostToolUse
- El evento de después de usar una herramienta, por ejemplo para formatear lo editado.
- Código de salida 2
- La forma en que un hook dice «bloquea esto»; su mensaje de error le llega a Claude como motivo.
El ejemplo: proteger los datos de origen
Antes y después Un hook que bloquea las ediciones en datos/
La petición
Configura en .claude/settings.json un hook que bloquee cualquier edición de ficheros dentro de la carpeta datos/, porque son datos de origen que nunca se deben modificar a mano. El bloqueo debe explicar el motivo. Usa un script en .claude/hooks/ que funcione con Node.js, sin jq, porque trabajamos en Windows.La sesión real
Salida real Claude Sonnet 5.5 · 24 de octubre de 2026 · sin editar · Claude Code en modo auto sobre el repositorio de práctica
Para repetirla, en el repositorio: claude --permission-mode auto y pega la petición. Se grabó con claude -p, sin conversación: lo que aquí se deniega, a ti te lo preguntaría.
Lo que hizo: 65 acciones
Bash: ls -la .claude .claude/hooks datos 2>&1; cat .claude/settings.json 2>&1; node --versionBash: mkdir -p .claude/hookscat > .claude/hooks/proteger-datos.js <<'EOF'// Hook PreToolUse: bloquea la edición de ficheros dentro de datos/.// Lee el JSON del evento por stdin; si la ruta objetivo cae bajo datos/, sale con código 2// (el mensaje de stderr se le muestra a Claude como motivo del bloqueo).const path = require('path');let entrada = '';process.stdin.setEncoding('utf8');process.stdin.on('data', (trozo) => (entrada += trozo));process.stdin.on('end', () => {let evento;try {evento = JSON.parse(entrada);} catch {process.exit(0); // entrada ilegible: no bloqueamos nada}const params = evento.tool_input || {};const destino = params.file_path || params.notebook_path;if (!destino) process.exit(0);const raiz = path.resolve(process.env.CLAUDE_PROJECT_DIR || evento.cwd || process.cwd());const carpetaDatos = path.join(raiz, 'datos');const relativa = path.relative(carpetaDatos, path.resolve(raiz, destino));// Dentro de datos/ si la ruta relativa no sube (..) ni es absoluta (otra unidad en Windows).const dentro = relativa === '' || (!relativa.startsWith('..') && !path.isAbsolute(relativa));if (!dentro) process.exit(0);console.error('Edición bloqueada: la carpeta datos/ contiene datos de origen que nunca se deben ' +'modificar a mano. Si hace falta transformarlos, genera el resultado en otra carpeta ' +'(por ejemplo salida/) sin tocar el original.');process.exit(2);});EOFcat > .claude/settings.json <<'EOF'{"hooks": {"PreToolUse": [{"matcher": "Edit|Write|MultiEdit|NotebookEdit","hooks": [{"type": "command","command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/proteger-datos.js\""}]}]}}EOFP=$(pwd -W 2>/dev/null || pwd)export CLAUDE_PROJECT_DIR="$P"for f in "$P/datos/paradas.csv" "datos/x.csv" "$P/otro/a.txt" "$P/datosfalsos/a.txt" "$P/datos/../README.md"; doecho "{\"tool_input\":{\"file_path\":\"$f\"}}" | node .claude/hooks/proteger-datos.js; echo "-> $f : exit $?"donenode -e "JSON.parse(require('fs').readFileSync('.claude/settings.json','utf8'));console.log('json ok')"Bash: mv .claude/hooks/proteger-datos.js .claude/hooks/proteger-datos.cjssed -i 's/proteger-datos\.js/proteger-datos.cjs/' .claude/settings.jsonP=$(pwd -W 2>/dev/null || pwd)export CLAUDE_PROJECT_DIR="$P"for f in "$P/datos/paradas.csv" "datos/x.csv" "$P/otro/a.txt" "$P/datosfalsos/a.txt" "$P/datos/../README.md"; doecho "{\"tool_input\":{\"file_path\":\"$f\"}}" | node .claude/hooks/proteger-datos.cjs; echo "-> $f : exit $?"donecat .claude/settings.json
El hook ya está configurado y bloquea las ediciones dentro de datos/. Probé el script a mano con rutas de ejemplo, pero no lo he visto actuar dentro de una sesión real de Claude Code.
Qué hay:
.claude/settings.json: un hookPreToolUseparaEdit,Write,MultiEdityNotebookEdit. Ejecutanode "$CLAUDE_PROJECT_DIR/.claude/hooks/proteger-datos.cjs"..claude/hooks/proteger-datos.cjs: lee el JSON del evento por stdin y mirafile_path(onotebook_path). Resuelve la ruta contra la raíz del proyecto, así que cuenta tanto la ruta absoluta como la relativa, y tambiéndatos/../x. Si cae dentro dedatos/, sale con código 2. Claude ve este mensaje por stderr: «Edición bloqueada: la carpeta datos/ contiene datos de origen que nunca se deben modificar a mano. Si hace falta transformarlos, genera el resultado en otra carpeta (por ejemplo salida/) sin tocar el original.»
Pruebas: con entradas simuladas, datos/paradas.csv (absoluta) y datos/x.csv (relativa) dan código 2. otro/a.txt, datosfalsos/a.txt y datos/../README.md dan 0.
Cosas a tener en cuenta:
.cjs: el script tiene extensión.cjsporque tupackage.jsontiene"type": "module". Con.jsfallaba con «require is not defined».- Sin
jq: solo usa Node.js, sin dependencias. - Qué no cubre: el hook solo mira las herramientas de edición. No bloquea escrituras hechas desde Bash o PowerShell, como
echo > datos/x.csvosed -i. Si quieres cubrir también eso, puedo ampliarlo, aunque detectar rutas en comandos de shell es menos fiable. - Recarga: si abres
/hookso reinicias la sesión, se recarga la configuración y el hook queda activo.
Los cambios que dejó (diff)
diff --git a/.claude/hooks/proteger-datos.cjs b/.claude/hooks/proteger-datos.cjs
new file mode 100644
index 0000000..87439e7
--- /dev/null
+++ b/.claude/hooks/proteger-datos.cjs
@@ -0,0 +1,35 @@
+// Hook PreToolUse: bloquea la edición de ficheros dentro de datos/.
+// Lee el JSON del evento por stdin; si la ruta objetivo cae bajo datos/, sale con código 2
+// (el mensaje de stderr se le muestra a Claude como motivo del bloqueo).
+const path = require('path');
+
+let entrada = '';
+process.stdin.setEncoding('utf8');
+process.stdin.on('data', (trozo) => (entrada += trozo));
+process.stdin.on('end', () => {
+ let evento;
+ try {
+ evento = JSON.parse(entrada);
+ } catch {
+ process.exit(0); // entrada ilegible: no bloqueamos nada
+ }
+
+ const params = evento.tool_input || {};
+ const destino = params.file_path || params.notebook_path;
+ if (!destino) process.exit(0);
+
+ const raiz = path.resolve(process.env.CLAUDE_PROJECT_DIR || evento.cwd || process.cwd());
+ const carpetaDatos = path.join(raiz, 'datos');
+ const relativa = path.relative(carpetaDatos, path.resolve(raiz, destino));
+
+ // Dentro de datos/ si la ruta relativa no sube (..) ni es absoluta (otra unidad en Windows).
+ const dentro = relativa === '' || (!relativa.startsWith('..') && !path.isAbsolute(relativa));
+ if (!dentro) process.exit(0);
+
+ console.error(
+ 'Edición bloqueada: la carpeta datos/ contiene datos de origen que nunca se deben ' +
+ 'modificar a mano. Si hace falta transformarlos, genera el resultado en otra carpeta ' +
+ '(por ejemplo salida/) sin tocar el original.'
+ );
+ process.exit(2);
+});
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000..1af2e04
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,15 @@
+{
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/proteger-datos.cjs\""
+ }
+ ]
+ }
+ ]
+ }
+}Qué ha pasado
Tres cosas de oficio que conviene copiar:
- Probó el hook antes de darlo por bueno: con cinco rutas simuladas, comprobando que bloquea las de
datos/(código 2) y deja pasar las demás, incluidas trampas comodatosfalsos/odatos/../README.md. - Encontró y corrigió un fallo: el script daba error porque el repositorio usa módulos modernos de JavaScript; lo renombró a
.cjsy volvió a probar. - Dijo lo que no cubre: el hook solo vigila las herramientas de edición de Claude. Un comando de Bash como
echo > datos/x.csvno pasaría por él.
Lo que esta sesión no demuestra: Claude probó el script con entradas simuladas, pero no lo vio bloquear una edición dentro de una sesión, y lo dice. Esa última prueba te toca a ti en el entregable: pide a Claude que edite datos/paradas.csv y comprueba que recibe el motivo del bloqueo.
Y un detalle que enlaza con la hora 7: en modo automático escribió los ficheros de .claude/ con un comando de Bash, y el clasificador lo permitió. En modo de aceptar ediciones, la misma escritura se denegó.
Qué son los hooks y por qué obligan
Según la documentación de Claude Code, los hooks son comandos que se ejecutan en momentos concretos del trabajo de Claude, y dan un control determinista: ocurren siempre, en lugar de depender de que el modelo decida hacerlo. Por eso lo que debe pasar siempre, sin excepciones, va en un hook y no en CLAUDE.md.
Se configuran en .claude/settings.json. Este es el ejemplo oficial para formatear tras cada edición:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}Cómo bloquea un hook: el hook recibe el evento por la entrada estándar. Si sale con código 2, la acción se bloquea y su mensaje de error le llega a Claude como motivo, para que cambie de enfoque. Según la documentación de permisos, un hook que sale con código 2 bloquea incluso antes de mirar las reglas allow.
Tras editar, el código de salida 2 no deshace nada. En PostToolUse la edición ya está hecha: salir con código 2 solo le enseña a Claude tu mensaje de error, para que lo corrija. Este hook, sin jq ni Prettier, pasa las pruebas del repositorio de práctica después de cada edición y le enseña a Claude el resultado si fallan:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npm test --silent 1>&2 || exit 2" }
]
}
]
}
}El hook Stop como puerta de calidad: un hook en el evento Stop puede ejecutar tus pruebas y no dejar que Claude termine el turno hasta que pasen. Es la versión determinista de «verifica antes de terminar» (hora 5).
Si usas Windows: los hooks de comando se ejecutan con Git Bash; si no lo tienes instalado, con PowerShell, y entonces un PreToolUse que falla puede bloquear la herramienta. Instala Git para Windows, prueba el hook antes de confiar en él y, para un hook que protege algo, declara "onFailure": "block" (desde la versión 2.1.295): así, si falla, bloquea siempre en lugar de depender del equipo.
Escribe /hooks en la sesión para ver los hooks configurados.
Practica
Clasifica ¿CLAUDE.md o hook?
- Formatear el código tras cada edición, sin excepcionesDebe pasar siempre: PostToolUse.
- Explicar la arquitectura de los informes a ClaudeEs contexto, no una acción obligatoria.
- Impedir cualquier edición en la carpeta datos/Bloquear siempre: PreToolUse con código 2.
- Preferir funciones pequeñas y nombres en españolEs una convención: se tiene en cuenta, no se obliga.
- Ejecutar las pruebas antes de que Claude dé el turno por terminadoUna puerta determinista: el evento Stop.
Comprueba lo que has aprendido
Comprueba El hook real bloquea las ediciones de Claude en datos/. ¿Qué no impide?
No. Write está en el matcher: también lo bloquea.
No. Eso es justo lo que bloquea.
Correcto. El hook solo vigila las herramientas de edición, como el propio Claude avisó.
Comprueba ¿Qué tiene que hacer un hook PreToolUse para bloquear una acción?
Correcto. Su mensaje de error le llega a Claude como motivo.
No. El texto solo no bloquea nada.
No. El 1 es un error que no bloquea: la acción sigue.
Comprueba Un hook PostToolUse sale con código 2 porque las pruebas fallan. ¿Qué ocurre con la edición?
No. Un hook no deshace nada: la herramienta ya se ejecutó.
No. Solo PreToolUse llega a tiempo de bloquear.
Correcto. PostToolUse llega después; el código 2 solo le enseña tu mensaje.
Comprueba ¿Por qué probó Claude el hook con datosfalsos/a.txt y datos/../README.md?
No. No lo pide: es buen oficio.
Correcto. Un nombre que empieza igual y una ruta que sale de la carpeta no deben bloquearse.
No. No existían: eran casos de prueba.
Entregable Dos hooks funcionando
Mínimo: añade al repositorio de práctica el hook PostToolUse de arriba y el PreToolUse que protege datos/. Abre una sesión, pide a Claude que edite datos/paradas.csv y comprueba que recibe el motivo del bloqueo. Ampliación: un hook tuyo para algo que nunca debe pasar en tu trabajo. Pega aquí la configuración, el script y lo que viste.
Está bien si: el bloqueo se ve dentro de una sesión real, no solo con entradas simuladas; has probado al menos un caso límite que no debe bloquearse; y anotas qué vía queda sin cubrir. Tiempo: unos 30 minutos el mínimo.
Lo esencial
- Un hook se ejecuta siempre: lo que debe pasar sin excepciones va en un hook, no en CLAUDE.md.
- PreToolUse puede bloquear con código 2; su mensaje le llega a Claude como motivo.
- Prueba los hooks con casos límite antes de confiar en ellos.
- Un hook de edición no vigila lo que se hace con Bash: cubre las vías que importan.
Fuentes y para seguir
- Automatizar con hooks (se abre en otra pestaña) (documentación oficial, consultada el 24/10/2026).
- Configurar permisos: precedencia entre reglas y hooks (se abre en otra pestaña).
- Buenas prácticas: hooks (se abre en otra pestaña).
Lección revisada el 24/10/2026 con la documentación oficial enlazada en sus fuentes. Revisamos los cursos cada trimestre.