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.ts con funciones de storage y formateo
  • 3 archivos de comandos en src/commands/
  • src/cli.py / src/index.ts actualizado con los 3 comandos registrados
  • CLI ejecutable con --help y 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:

  1. Pedir implementación paso a paso (no todo de golpe)
  2. Revisar lo que Claude produce
  3. Dar feedback cuando algo no encaje
  4. Usar skills cuando aplique
  5. 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:

  1. Claude lee CLAUDE.md — usa las convenciones definidas ahí
  2. Claude crea el archivo — escribe src/utils.py con las funciones
  3. 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

  1. Claude lee .claude/skills/create-command/SKILL.md
  2. Incorpora las instrucciones del skill como contexto
  3. Crea src/commands/list_notes.py siguiendo el template del skill
  4. Registra el comando en src/cli.py
  5. 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__.py en el directorio commands/
  • 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ónEjemplo 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ónEjemplo
Detalles de implementaciónCómo parsear el JSON internamente
Naming de variables localesNombres de variables dentro de funciones
Imports y dependencias menoresQué función de pathlib usar
Orden de operaciones internasEn 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

EstrategiaCuándo usarla
Implementar paso a pasoSiempre — no pidas todo de golpe
Usar /compactCuando la conversación lleva 15+ mensajes
Nueva sesiónSi 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.py implementado con funciones de storage y formateo
  • src/commands/add.py implementado y registrado
  • src/commands/list_notes.py implementado y registrado
  • src/commands/search.py implementado y registrado
  • CLI ejecutable con --help mostrando los 3 comandos
  • Comando add crea notas correctamente
  • Comando list muestra notas con filtro y JSON
  • Comando search busca case-insensitive
  • Hooks de linter se dispararon durante la implementación
  • Skill /create-command se usó al menos 1 vez

Resumen

Lo que hiciste en esta cápsula:

  1. Implementaste utils.py — la base de storage y formateo que usan todos los comandos
  2. Implementaste 3 comandos — add, list, search — cada uno en su propio archivo
  3. Usaste el skill /create-command para crear al menos un comando con consistencia
  4. Verificaste hooks en acción — el linter corrió automáticamente después de cada archivo
  5. Usaste el subagent de Explore para verificar la implementación a mitad de camino
  6. 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:

  1. 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.

  2. Los skills hacen que el segundo comando sea más fácil que el primero. El template del skill /create-command garantiza consistencia sin que tú tengas que recordar todos los detalles. La inversión de tiempo en crear skills paga dividendos inmediatos.

  3. 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.