Módulo 8: Proyecto integrador: Tu primer proyecto con Claude Code
Construir con Claude Code: Implementación
Construir con Claude Code: Implementación
Objetivo del proyecto
Construir una herramienta CLI funcional usando el workflow completo de Claude Code. En esta cápsula completas la Fase 3: Build — implementarás los 3 comandos de tu CLI usando Agent mode, skills, hooks, y subagents.
Qué construiste en módulos anteriores
- Cápsula 02: Setup completo (CLAUDE.md, skills, hooks, entry point)
- Cápsula 03: Explore del proyecto + Plan de implementación aprobado
- Módulo 04: Workflow Explore → Plan → Code
- Módulo 05: Skills y hooks en acción
- Módulo 06: Subagents para delegación
Qué agregarás en este módulo
Al terminar esta cápsula tendrás:
src/utils.py/src/utils.tscon funciones de storage y formateo- 3 archivos de comandos en
src/commands/ src/cli.py/src/index.tsactualizado con los 3 comandos registrados- CLI ejecutable con
--helpy los 3 comandos funcionales - Hooks disparándose automáticamente durante la implementación
Especificaciones técnicas
Lo que Claude Code va a hacer
En esta fase, Claude Code opera en Agent mode (el modo por defecto). Tiene permiso completo para:
- Crear y modificar archivos
- Ejecutar comandos en terminal
- Instalar dependencias
- Leer archivos del proyecto
Tu rol
Tú diriges. Claude ejecuta. Tu trabajo es:
- Pedir implementación paso a paso (no todo de golpe)
- Revisar lo que Claude produce
- Dar feedback cuando algo no encaje
- Usar skills cuando aplique
- Observar hooks en acción
Regla fundamental
Implementa un comando a la vez. No pidas los 3 comandos juntos. Cada comando es un ciclo:
Pedir → Claude implementa → Verificar → Siguiente
Paso a paso guiado
Paso 1: Iniciar sesión de implementación
Si cerraste la sesión anterior, abre una nueva:
cd my-cli-project
claude
Si continuaste en la misma sesión, Claude ya tiene el contexto del plan. Si es sesión nueva, recuérdale:
Vamos a implementar la CLI que planificamos. El plan es:
1. src/utils.py — Storage y formateo
2. src/commands/add.py — Comando add
3. src/commands/list_notes.py — Comando list
4. src/commands/search.py — Comando search
5. src/cli.py — Registrar comandos
Empecemos con utils.py. Impleméntalo según el plan.
Paso 2: Implementar utils (la base)
Prompt
Implementa src/utils.py con las funciones de storage y formateo
que planificamos:
- load_notes(): cargar notas del archivo JSON
- save_notes(): guardar notas al archivo JSON
- get_notes_path(): retornar path al archivo de storage
- format_note(): formatear una nota para display
Sigue las convenciones de CLAUDE.md.
Qué observar durante la implementación
Mientras Claude trabaja, observa:
- Claude lee CLAUDE.md — usa las convenciones definidas ahí
- Claude crea el archivo — escribe
src/utils.pycon las funciones - Hook PostToolUse se dispara — después de crear el archivo, el linter (ruff/eslint) corre automáticamente
El hook en acción se ve así en la terminal:
Claude wrote src/utils.py
[Hook: PostToolUse] Running: ruff check --fix src/utils.py
All checks passed!
Si el linter encuentra errores de estilo:
[Hook: PostToolUse] Running: ruff check --fix src/utils.py
Found 2 errors (2 fixed)
Claude ve este output y sabe que el hook corrigió los errores. No necesitas hacer nada.
Verificar utils
Después de que Claude cree el archivo, verifica:
Muéstrame el contenido de src/utils.py y confirma que las
funciones tienen type hints y docstrings.
Claude te muestra el código. Revisa que:
- Las funciones tienen las firmas correctas
- Hay type hints en parámetros y return types
- El storage usa el path definido en el plan
- El manejo de errores es apropiado (archivo no existe, JSON inválido)
Si algo no te gusta:
La función format_note no incluye el tag en el output.
Corrígelo — el formato debería ser: #42 Texto [tag] timestamp
Claude modifica el archivo. El hook de linter corre de nuevo automáticamente.
Paso 3: Implementar el primer comando
Prompt
Implementa el primer comando: add. Crea src/commands/add.py
siguiendo el plan. El comando debe:
- Recibir el texto de la nota como argumento
- Aceptar --tag para categorizar
- Guardar la nota usando utils.save_notes
- Mostrar confirmación con el ID de la nota creada
Después, regístralo en src/cli.py.
Hook en acción
Cuando Claude crea src/commands/add.py, el hook PostToolUse corre:
Claude wrote src/commands/add.py
[Hook: PostToolUse] Running: ruff check --fix src/commands/add.py
All checks passed!
Cuando Claude modifica src/cli.py para registrar el comando:
Claude wrote src/cli.py
[Hook: PostToolUse] Running: ruff check --fix src/cli.py
All checks passed!
Verificar el primer comando
Pide a Claude que ejecute el comando:
Ejecuta el comando add para verificar que funciona:
python -m src.cli add "Mi primera nota" --tag test
Claude ejecuta y deberías ver algo como:
$ python -m src.cli add "Mi primera nota" --tag test
Note created: #1
Verifica que se creó el archivo de storage:
¿Se creó el archivo .notes.json? Muéstrame su contenido.
Claude lee el archivo y te muestra:
{
"notes": [
{
"id": 1,
"text": "Mi primera nota",
"tag": "test",
"created_at": "2026-02-28T10:30:00"
}
],
"next_id": 2
}
Si todo se ve bien, continúa con el segundo comando.
Paso 4: Implementar el segundo comando usando el skill
Aquí es donde el skill /create-command demuestra su valor.
Usando el skill
/create-command list_notes
El comando list debe:
- Listar todas las notas
- Filtrar por tag con --tag
- Soportar output JSON con --json
- Mostrar output formateado por defecto
Qué pasa cuando usas el skill
- Claude lee
.claude/skills/create-command/SKILL.md - Incorpora las instrucciones del skill como contexto
- Crea
src/commands/list_notes.pysiguiendo el template del skill - Registra el comando en
src/cli.py - El hook de linter corre automáticamente
El skill asegura consistencia: el formato del archivo, la estructura del comando, el manejo de errores, y el registro en cli.py siguen el mismo patrón que el primer comando.
Verificar con el skill
Si el skill incluye un checklist (como el que creaste en la Cápsula 02), Claude lo sigue:
Verifica el checklist del skill:
- [ ] Archivo creado en src/commands/
- [ ] Type hints en parámetros
- [ ] Docstring como help text
- [ ] Errores con ClickException
- [ ] Registrado en cli.py
Probar el segundo comando
Ejecuta estos comandos para verificar list:
python -m src.cli add "Segunda nota" --tag work
python -m src.cli add "Tercera nota" --tag personal
python -m src.cli list
python -m src.cli list --tag work
python -m src.cli list --json
Deberías ver:
$ python -m src.cli list
#1 Mi primera nota [test] Feb 28, 10:30
#2 Segunda nota [work] Feb 28, 10:32
#3 Tercera nota [personal] Feb 28, 10:33
$ python -m src.cli list --tag work
#2 Segunda nota [work] Feb 28, 10:32
$ python -m src.cli list --json
[
{"id": 1, "text": "Mi primera nota", "tag": "test", ...},
{"id": 2, "text": "Segunda nota", "tag": "work", ...},
{"id": 3, "text": "Tercera nota", "tag": "personal", ...}
]
Paso 5: Verificar la implementación con subagent
Antes de implementar el tercer comando, verifica que todo lo implementado hasta ahora es coherente. Claude Code puede usar su subagent de Explore para esto:
Revisa todo lo que hemos implementado hasta ahora.
Verifica que:
1. utils.py es usado correctamente por add.py y list_notes.py
2. Las convenciones de CLAUDE.md se respetan
3. No hay imports sin usar o funciones muertas
4. El error handling es consistente entre comandos
Solo analiza, no modifiques nada todavía.
Claude usa su capacidad de análisis para revisar los archivos y darte un reporte:
## Revisión de implementación
### Consistencia ✅
- Ambos comandos usan utils.load_notes() y utils.save_notes()
- Naming sigue snake_case como define CLAUDE.md
- Error handling con click.ClickException en ambos
### Issues encontrados
1. ⚠️ list_notes.py importa json pero no lo usa cuando --json
no está activo. No es un error, pero ruff lo marcará como
import no usado si --fix no lo resuelve.
→ Mover el import dentro del if --json
2. ✅ add.py: sin issues
3. ✅ utils.py: sin issues
4. ✅ cli.py: ambos comandos registrados correctamente
### Recomendación
Corregir el import de json en list_notes.py antes de continuar.
Si hay issues, pide que los corrija:
Corrige el import de json en list_notes.py como sugieres.
Paso 6: Implementar el tercer comando
Implementa el tercer comando: search. Crea src/commands/search.py.
El comando debe:
- Recibir un query como argumento
- Buscar en el texto de todas las notas (case-insensitive)
- Mostrar las notas que coincidan con el mismo formato que list
- Mostrar "No se encontraron notas" si no hay resultados
Claude crea el archivo, el hook corre el linter, y el comando se registra en cli.py.
Probar el tercer comando
Prueba search con estos casos:
python -m src.cli search "nota"
python -m src.cli search "PRIMERA"
python -m src.cli search "inexistente"
Resultados esperados:
$ python -m src.cli search "nota"
#1 Mi primera nota [test] Feb 28, 10:30
#2 Segunda nota [work] Feb 28, 10:32
#3 Tercera nota [personal] Feb 28, 10:33
$ python -m src.cli search "PRIMERA"
#1 Mi primera nota [test] Feb 28, 10:30
$ python -m src.cli search "inexistente"
No se encontraron notas con "inexistente"
Paso 7: Ejecutar la CLI completa
Ahora que los 3 comandos están implementados, verifica la CLI completa:
Ejecuta la CLI con --help para ver todos los comandos disponibles.
$ python -m src.cli --help
Usage: cli [OPTIONS] COMMAND [ARGS]...
notes-cli - Herramienta de notas desde la terminal.
Options:
--version Show the version and exit.
--help Show this message and exit.
Commands:
add Agregar una nueva nota.
list Listar todas las notas.
search Buscar notas por texto.
Verifica el help de cada comando:
Ejecuta --help para cada comando: add, list, y search.
$ python -m src.cli add --help
Usage: cli add [OPTIONS] TEXT
Agregar una nueva nota.
Options:
--tag TEXT Tag para categorizar la nota
--help Show this message and exit.
$ python -m src.cli list --help
Usage: cli list [OPTIONS]
Listar todas las notas.
Options:
--tag TEXT Filtrar por tag
--json / -j Output en formato JSON
--help Show this message and exit.
$ python -m src.cli search --help
Usage: cli search [OPTIONS] QUERY
Buscar notas por texto.
Options:
--help Show this message and exit.
Manejo de errores durante la implementación
Error: Claude genera código que no funciona
Si un comando falla al ejecutarse:
El comando search falla con "ModuleNotFoundError: No module
named 'src.commands.search'". ¿Qué pasó?
Claude diagnostica y corrige. Causas comunes:
- Falta
__init__.pyen el directoriocommands/ - Import path incorrecto
- Nombre de archivo no coincide con el import
Error: Hook de linter falla
Si el hook muestra errores que no se corrigen automáticamente:
[Hook: PostToolUse] Running: ruff check --fix src/commands/search.py
src/commands/search.py:15:5: F811 Redefinition of unused `result`
1 error remaining (not auto-fixable)
Claude ve este output y debería corregir el error automáticamente. Si no lo hace:
El hook de ruff encontró un error F811 en search.py.
Corrígelo.
Error: El output no se ve como esperabas
El output de list no está alineado. Las columnas no coinciden.
Ajusta format_note para que use un ancho fijo de columna.
Claude ajusta la función de formateo.
Error: El storage no persiste
Las notas desaparecen entre ejecuciones. ¿El archivo .notes.json
se está creando correctamente?
Claude investiga y corrige el path o la lógica de save.
Cuándo guiar vs dejar que Claude decida
Guía a Claude cuando:
| Situación | Ejemplo de guía |
|---|---|
| La decisión afecta la UX | "Quiero que el output tenga colores" |
| El plan es específico | "Usa el formato #42 como planificamos" |
| Hay una preferencia personal | "Prefiero exceptions sobre return codes" |
| El comportamiento es ambiguo | "Si no hay notas, muestra un mensaje, no una lista vacía" |
Deja que Claude decida cuando:
| Situación | Ejemplo |
|---|---|
| Detalles de implementación | Cómo parsear el JSON internamente |
| Naming de variables locales | Nombres de variables dentro de funciones |
| Imports y dependencias menores | Qué función de pathlib usar |
| Orden de operaciones internas | En qué orden hacer las validaciones |
La regla de oro
Guía el "qué" y el "cómo se ve". Deja que Claude resuelva el "cómo funciona internamente".
Tips para prompts efectivos durante implementación
Sé incremental
❌ "Implementa toda la CLI con los 3 comandos, utils, tests,
y registra todo en cli.py"
✅ "Implementa src/utils.py con las funciones de storage"
Da contexto cuando cambias de paso
✅ "Utils está listo y funciona. Ahora implementa el comando
add siguiendo el plan."
Incluye criterios de verificación
✅ "Implementa search. Cuando termines, ejecútalo con
'python -m src.cli search nota' para verificar."
Referencia el plan
✅ "Según el plan, search debe ser case-insensitive. Asegúrate
de implementar eso."
Pide que ejecute para verificar
✅ "Ejecuta la CLI con --help para verificar que los 3 comandos
aparecen."
Estado final después de esta cápsula
Tu proyecto debería verse así:
my-cli-project/
├── CLAUDE.md
├── .claude/
│ ├── skills/
│ │ ├── create-command.md
│ │ └── add-tests.md
│ └── settings.json
├── src/
│ ├── __init__.py
│ ├── cli.py ← Actualizado con 3 comandos
│ ├── commands/
│ │ ├── __init__.py
│ │ ├── add.py ← Comando add
│ │ ├── list_notes.py ← Comando list
│ │ └── search.py ← Comando search
│ └── utils.py ← Storage y formateo
├── tests/
│ └── __init__.py ← (vacío, tests en cápsula 05)
├── .notes.json ← Archivo de storage (datos de prueba)
├── pyproject.toml
└── .gitignore
Verificación final
Ejecuta esta secuencia completa para verificar que todo funciona:
# Limpiar datos de prueba
rm -f .notes.json
# Verificar help
python -m src.cli --help
# Agregar notas
python -m src.cli add "Comprar café" --tag personal
python -m src.cli add "Revisar PR #42" --tag work
python -m src.cli add "Estudiar Claude Code" --tag learning
# Listar todas
python -m src.cli list
# Filtrar por tag
python -m src.cli list --tag work
# Output JSON
python -m src.cli list --json
# Buscar
python -m src.cli search "Claude"
python -m src.cli search "CAFÉ"
python -m src.cli search "inexistente"
Si todos los comandos funcionan correctamente, la Fase 3 está completa.
Troubleshooting específico
"ModuleNotFoundError"
# Verifica que estás en el directorio correcto
pwd
# Verifica que el paquete está instalado
pip install -e .
# Verifica que __init__.py existe en todos los directorios
find src -name "__init__.py"
"Click no reconoce el comando"
Verifica que el comando está registrado en cli.py:
from src.commands.add import add
from src.commands.list_notes import list_notes
from src.commands.search import search
cli.add_command(add)
cli.add_command(list_notes, "list")
cli.add_command(search)
Nota el "list" como segundo argumento — renombra el comando de list_notes a list en la CLI.
"JSON decode error"
El archivo .notes.json puede estar corrupto. Elimínalo y empieza de nuevo:
rm .notes.json
python -m src.cli add "Test"
"Permission denied al crear archivo"
Verifica permisos del directorio:
ls -la .
"Commander/Click no reconoce las opciones"
Verifica que los decoradores o métodos de definición de opciones son correctos:
Python — Click:
@click.option("--tag", default=None, help="Tag para filtrar")
TypeScript — Commander:
.option("--tag <tag>", "Tag para filtrar")
El error más común es olvidar el tipo del argumento en Commander (<tag> para obligatorio, [tag] para opcional).
"El hook no corre ruff/eslint"
Verifica que el linter está instalado:
# Python
ruff --version
# TypeScript
npx eslint --version
Si no está instalado:
# Python
pip install ruff
# TypeScript
npm install --save-dev eslint
Context window durante la implementación
Cómo se consume el contexto
Cada interacción con Claude Code consume tokens del context window. Durante la implementación, el contexto se llena con:
- CLAUDE.md (~500 tokens, siempre presente)
- Archivos leídos por Claude (cada archivo ~200-500 tokens)
- Output de comandos ejecutados
- Tu conversación (cada mensaje)
- Skills cargados (cuando los invocas)
Señales de que el contexto se está llenando
- Claude empieza a "olvidar" decisiones anteriores
- Las respuestas se vuelven más genéricas
- Claude repite preguntas que ya respondiste
Estrategias para gestionar el contexto
| Estrategia | Cuándo usarla |
|---|---|
| Implementar paso a paso | Siempre — no pidas todo de golpe |
Usar /compact | Cuando la conversación lleva 15+ mensajes |
| Nueva sesión | Si Claude empieza a "olvidar" el plan |
| Referir a CLAUDE.md | "Sigue las convenciones de CLAUDE.md" en vez de repetirlas |
| Referir al plan | "Según el plan..." en vez de re-explicar la arquitectura |
Si necesitas nueva sesión a mitad de implementación
CLAUDE.md persiste entre sesiones — no necesitas reconfigurarlo. Simplemente inicia la nueva sesión y recuérdale dónde estabas:
Estamos implementando una CLI de notas. Ya completé utils.py
y el comando add. Necesito implementar list_notes y search
según las convenciones del proyecto. Lee el código existente
y continúa.
Claude lee los archivos existentes y retoma donde quedaste.
Checklist de completitud
Antes de avanzar a la Cápsula 05, verifica:
-
src/utils.pyimplementado con funciones de storage y formateo -
src/commands/add.pyimplementado y registrado -
src/commands/list_notes.pyimplementado y registrado -
src/commands/search.pyimplementado y registrado - CLI ejecutable con
--helpmostrando los 3 comandos - Comando
addcrea notas correctamente - Comando
listmuestra notas con filtro y JSON - Comando
searchbusca case-insensitive - Hooks de linter se dispararon durante la implementación
- Skill
/create-commandse usó al menos 1 vez
Resumen
Lo que hiciste en esta cápsula:
- Implementaste utils.py — la base de storage y formateo que usan todos los comandos
- Implementaste 3 comandos — add, list, search — cada uno en su propio archivo
- Usaste el skill
/create-commandpara crear al menos un comando con consistencia - Verificaste hooks en acción — el linter corrió automáticamente después de cada archivo
- Usaste el subagent de Explore para verificar la implementación a mitad de camino
- Ejecutaste la CLI completa y verificaste que los 3 comandos funcionan
La implementación siguió el plan que diseñaste en la cápsula anterior. No improvisaste la arquitectura — la ejecutaste.
Lecciones de la fase Build
Tres cosas que deberías haber notado durante la implementación:
-
Claude Code es más efectivo con instrucciones incrementales. "Implementa utils.py" produce mejor código que "implementa toda la CLI". Esto es el patrón multi-turn que aprendiste en el Módulo 04.
-
Los skills hacen que el segundo comando sea más fácil que el primero. El template del skill
/create-commandgarantiza consistencia sin que tú tengas que recordar todos los detalles. La inversión de tiempo en crear skills paga dividendos inmediatos. -
Los hooks son una red de seguridad silenciosa. No los invocas, no piensas en ellos — simplemente funcionan. Cada archivo que Claude escribe pasa por el linter automáticamente. Es calidad de código sin esfuerzo.
Estas tres lecciones aplican a cualquier proyecto, no solo a CLIs. Cuando trabajes en tu próximo proyecto con Claude Code, aplica el mismo patrón: incremental, con skills, con hooks.
Siguiente cápsula: 05 - Tests, commit y entregar — donde generarás tests, revisarás el código, harás commit, y completarás el proyecto.