Módulo 1 Del webhook al trabajo que sobrevive a los fallos
Empieza aquí: NexoCare, el proyecto y cómo llamamos a tu Claude
14 min
Viernes de cierre de trimestre. El CRM de NexoCare tiene problemas y empieza a reenviar sus avisos: 1.800 entregas que en realidad son 620 eventos distintos. Un worker escribe en el CRM, se le acaba el tiempo de espera justo después, y el reintento termina con dos tareas contradictorias para la misma cuenta. Reconstruir qué pasó cuesta 96 minutos, porque el aviso, el flujo interno y el recibo del CRM no comparten ningún identificador.
NexoCare es un caso ficticio, pero cada fallo sale de comportamientos que documentan proveedores reales como Stripe, CloudEvents, AWS o Temporal. En este módulo construyes la cadena que evita esa tarde.
El caso
NexoCare vende software a otras empresas (un SaaS B2B). Cuando un cliente escribe en el CRM, el CRM avisa a tu aplicación con un webhook, una petición HTTP que otro sistema te envía cuando ocurre algo. Un modelo lee el mensaje y, si detecta intención de baja, la aplicación propone marcar la cuenta como at_risk (en riesgo) para que el equipo de Customer Success intervenga.
Hasta aquí, nada que no sepas hacer desde el nivel 2, «Construir con LLMs»: llamar al modelo, pedirle una salida con schema y usarla. Lo nuevo es lo que pasa alrededor cuando el sistema falla de verdad:
- el mismo aviso llega tres veces;
- el mensaje 19 llega antes que el 18;
- el worker (el proceso que hace el trabajo en segundo plano) se cae a mitad;
- un cliente con mucho volumen satura la cola y los demás esperan.
La cadena que vas a montar
bytes recibidos y firmados
→ schema versionado
→ inbox (bandeja de entrada) con clave source + id
→ cola con límites
→ reserva con plazo (claim con lease)
→ flujo con versiones
→ aprobación o cancelación
Cada pieza responde a una pregunta. ¿Esto lo ha enviado de verdad el CRM? ¿Ya lo había recibido? ¿Quién lo está procesando? ¿En qué paso estaba cuando se cayó? ¿Cuánto trabajo acepto antes de decir que no? Las verás de una en una en las lecciones siguientes. La cancelación entra en este módulo; la aprobación, en el siguiente.
Quién decide qué
El reparto es estricto. La aplicación conserva la identidad del cliente, los permisos, el estado del flujo y los efectos en otros sistemas. El modelo solo clasifica el mensaje y propone una categoría, un nivel de riesgo y la frase que lo justifica.
Esa separación evita que un texto generado se convierta en una autorización. Si el modelo devuelve "authorized": true, la aplicación rechaza la respuesta entera en lugar de obedecerla.
Dos caminos para la misma cadena
El proyecto tiene dos formas de recorrer la cadena, con el mismo dominio por debajo:
- La simulación, con almacenes y workers en memoria. Así ves cada contrato en la terminal sin montar una base de datos ni un broker (el servicio que gestiona colas de mensajes). Es lo que usan las lecciones 2 a 9, los tests y las demos, y responde igual en cada pasada.
- El camino real, en la lección 10. El webhook entra por HTTP, el inbox vive en PostgreSQL, la cola es BullMQ sobre Redis y el clasificador es tu Claude. Matas un worker a mitad de un trabajo y miras qué queda.
La simulación tiene un límite claro: no demuestra persistencia entre varios procesos ni sustituye a PostgreSQL, BullMQ o Temporal. Te deja saber qué estado tiene que sobrevivir. El camino real lo comprueba con procesos que mueren de verdad.
Prepara el proyecto
Proyecto de este módulo: enterprise-integration. Necesitas Node 22.12 o posterior, como pide su package.json, en macOS, Linux o Windows con WSL. Descárgalo desde la ficha del curso (apartado «El código», con tu sesión iniciada; o pídele el enlace a tu Claude conectado al curso) y, dentro de su carpeta, instala las dependencias:
npm ci
Todos los comandos del módulo se ejecutan en esa carpeta raíz. Para la simulación no necesitas clave de API ni conexión: el clasificador es determinista (responde siempre lo mismo para el mismo texto) para que los resultados se puedan repetir. Para el camino real de la lección 10 necesitas además Docker y Claude Code con tu sesión iniciada; tampoco pide clave de API. Cómo se llama a tu Claude, lo que cuesta y qué hacer si tu plan no llega está al final de esta lección, en «Cómo llamamos a tu Claude».
Comprueba que todo está en su sitio:
npm run typecheck
npm test
Qué deberías ver: el compilador de TypeScript sin errores y, al final de los tests, Test Files 12 passed (12) y Tests 102 passed (102). Ningún test sale a la red ni llama a Claude. Si algo falla aquí, revisa la instalación antes de seguir.
Estos dos comandos bastan para saber que el proyecto está sano. La comprobación completa es npm run check, la que propone el README: además del compilador y los tests, revisa el formato, ejecuta las progresiones del starter a la solución, el benchmark y las ocho demos. Tarda más y no hace falta ahora. Si lo lanzas, verás STUDENT_GREEN_INBOX_CLAIM, STUDENT_GREEN_SCHEMA_GATE y STUDENT_GREEN_EFFECT aunque no hayas tocado los ejercicios: esa parte pasa las soluciones de solucion/ por los verificadores, para comprobar que el proyecto está completo. Tus ejercicios los comprueban student:a:verify, student:schema:verify y student:b:verify.
Lo que hay dentro:
src/: el dominio (webhook, inbox, cola, máquina de estados), sin el SDK de ningún proveedor.src/live/ycompose.yaml: el camino real, con PostgreSQL, BullMQ y tu Claude.tests/: casos de fallo provocados a propósito.demos/parte-1/: las cuatro demos de este módulo.starter/ysolucion/: los ejercicios que vas a resolver y su referencia.
Lo que te llevas
Al terminar sabrás responder a una pregunta que vas a repetir mucho en este nivel:
Si el proceso cae ahora, ¿qué registro me dice cuál es el siguiente paso seguro, sin repetir nada?
Si la respuesta es «una variable en memoria», todavía no tienes un trabajo durable.
Las dos secciones que siguen son de referencia para todo el curso. Las lecciones te mandan aquí cuando las necesitas.
Cómo llamamos a tu Claude
Varias lecciones tienen un apartado opcional que llama a un modelo de verdad: tu Claude, el de tu suscripción de pago (Pro o superior), con Claude Code y tu sesión iniciada (claude y, dentro, /login). No hace falta clave de API. Los cuatro proyectos lo hacen igual, y por eso se explica una sola vez.
La llamada
Por cada llamada, el script del proyecto lanza Claude Code en modo no interactivo (claude -p) desde una carpeta temporal vacía:
claude -p "<petición>" --output-format json
--model <ID completo> --effort low
--system-prompt "<el prompt del proyecto>" --json-schema "<la forma de la respuesta>"
--tools "" --max-turns 3 --max-budget-usd <tope>
--safe-mode --strict-mcp-config --no-session-persistence
Cada opción tiene un motivo:
--output-format jsondevuelve, además de la respuesta, los tokens, el coste estimado (total_cost_usd) y la duración.--json-schemafija la forma de la respuesta, que llega enstructured_output. Fija la forma y no el contenido: lo que se ejecuta lo sigue decidiendo tu código.--tools ""deja a tu Claude sin herramientas. Solo contesta o propone.--max-turns 3y--max-budget-usdacotan los turnos y el gasto de cada llamada. Conclaude -pllamas a un agente entero, y un agente sin límites puede dar vueltas. El tope se compara con la estimación de Claude Code y puede pasarse un poco, así que deja margen. Si salta, la llamada falla y no se reintenta: repetirla lo volvería a superar.--safe-mode,--strict-mcp-configy la carpeta vacía evitan que cargue tuCLAUDE.md, tus skills, tus plugins, tus hooks o tus servidores MCP. Si los cargara, cada alumno mediría un sistema distinto.--no-session-persistenceno guarda la sesión en disco.- No usa
--bare. Ese modo nunca lee la sesión de tu plan y pide una clave de API. Por lo mismo, el script quitaANTHROPIC_API_KEYdel entorno de cada llamada.
La documentación de Claude Code anuncia que --bare pasará a ser el modo por defecto de -p en una versión futura (comprobado el 15-11-2026; mira los datos con fecha). Si un día el camino real falla por falta de sesión con tu sesión iniciada, empieza por ahí.
Dónde se cambia
Todo lo que puedes tocar está en live.config.json, en la raíz de cada proyecto, con los mismos nombres en los cuatro: model, effort, maxTurns, maxBudgetUsd y timeoutMs, más lo propio de cada uno. El modelo va con su ID completo (claude-haiku-5-5, claude-sonnet-5-5) y no con un alias como sonnet: el alias pasa a otro modelo cuando sale uno nuevo, y dos pasadas dejarían de ser comparables.
Qué queda grabado
Cada pasada se graba en evidencia/live/ del proyecto, con la fecha en el nombre. La cabecera lleva la fecha, la versión de Claude Code, el modelo, el esfuerzo, el sha256 del prompt y los argumentos exactos. Cada llamada guarda la salida completa de claude -p. Lo que esa salida cuenta del estado de tu cuenta, si lo trae (credential_expires_at, rate_limit_info), aparece como "<no se graba>".
Si no lanzas nada, cada proyecto trae sus pasadas grabadas y un comando que las vuelve a leer y recalcula sus cifras sin llamar a nadie. Cada lección te dice cuál.
Lo que cuesta
total_cost_usd es una estimación a precio de lista. Con la sesión de tu plan no se cobra aparte: se descuenta del uso de tu plan. Anthropic anunció un cambio en cómo se cobra claude -p con un plan y lo tiene en pausa (mira los datos con fecha). Si un día se cobra aparte, todos los caminos reales del curso siguen siendo opcionales y cada lección enseña la pasada grabada.
Lo que más mueve el coste de una llamada es la caché del prompt. El prompt de sistema es el mismo en todas las llamadas de un script, y en nuestras pasadas Claude Code lo guardó en la caché con duración de una hora (ephemeral_1h_input_tokens en la salida). Escribir en la caché cuesta el doble que la entrada normal. Leer de ella cuesta la décima parte en Haiku 5.5 y la vigésima en Sonnet 5.5. Tres consecuencias:
- La primera llamada de una pasada con la caché fría es la más cara, porque escribe el prompt entero.
- Repetir dentro de la hora abarata, porque las siguientes llamadas lo leen.
- Una entrada de la caché solo está disponible cuando empieza la primera respuesta. Si lanzas varias llamadas a la vez con la caché fría, la escriben todas. Por eso los scripts que lanzan en paralelo mandan primero una sola.
En la salida de cada llamada lo ves en cache_creation_input_tokens (lo escrito) y cache_read_input_tokens (lo leído).
Si tu plan no llega
De más a menos ahorro:
- Un modelo más barato.
"model": "claude-haiku-5-5"enlive.config.json. Es lo que más abarata. Cuando grabamos la misma pasada con Haiku, la lección te dice cuánto costó y qué cambió en el resultado. - Menos llamadas. Casi todos los scripts aceptan una opción para hacer una sola vuelta (
--pasadas=1o--trials=1); cada lección dice cuál. Con la caché fría ahorra menos de lo que parece, porque cada documento distinto tiene que escribir su parte. - Un tope más bajo. Baja
maxBudgetUsd. Una llamada que lo supera se corta, cuenta como fallo y no se reintenta. - Sin gastar nada. Lee la pasada grabada y recalcúlala con el comando de la lección.
Si se acaba el uso de tu plan a mitad, ningún script convierte el error en un dato. Según el proyecto, la cola lo reintenta más tarde como un fallo transitorio o la pasada se corta sin grabar nada.
Palabras que verás en inglés
El código del curso está en inglés y el texto en español. Cuando hay una palabra en español igual de precisa, el texto la usa, y verás la inglesa en el código y en las salidas:
| En el texto | En el código | Qué es |
|---|---|---|
| flujo | workflow |
El proceso de negocio con estados que sobrevive a caídas |
| reserva, con su plazo | claim, lease |
Quién está procesando un trabajo y hasta cuándo |
| huella | digest |
El hash SHA-256 de unos bytes: si cambia un byte, cambia la huella |
| responsable | owner |
La persona o el sistema que responde de una frontera o de una decisión |
| control | gate |
Una condición medible, con su umbral escrito antes de mirar, que hay que pasar para avanzar |
| manifiesto | manifest |
La lista firmada de lo que se observó, con la huella de cada pieza |
| recolector | collector |
Quien recoge unos datos: en el módulo 4, quien leyó unos bytes y dio fe de ellos; en el 5, el servicio que recibe las trazas |
| canario | canary |
Un dato trampa que no debe salir nunca, y también la versión nueva que recibe solo una parte del tráfico |
| simulacro | game day |
Un fallo provocado a propósito para comprobar que la recuperación funciona |
| traspaso | handover |
Pasar el sistema a otra persona y comprobar que puede operarlo sin ti |
Y estas se quedan en inglés porque son las que vas a encontrar en la documentación y en las herramientas:
| Palabra | Qué es |
|---|---|
| worker | El proceso que saca trabajos de la cola y los ejecuta |
| tenant | Cada empresa cliente de una plataforma compartida, con sus datos separados de los de las demás |
| inbox y outbox | La tabla de lo que ha entrado (para no procesarlo dos veces) y la de lo que falta por publicar |
| backpressure | Decir que no a tiempo cuando ya tienes más trabajo del que puedes terminar |
| fencing | Un número que sube con cada dueño nuevo de un trabajo y deja fuera las escrituras del anterior |
| fallback | La respuesta alternativa, más limitada, cuando la principal falla |
| rollback | Volver a la versión anterior |
| baseline | La versión de partida con la que comparas |
| red team | Atacar tu propio sistema para encontrar lo que falla antes que otro |
| span | Cada paso de una traza, con su inicio, su fin y sus atributos |
| SLO | El objetivo de servicio que te comprometes a cumplir, por ejemplo un 99 % de tareas bien terminadas |
| prompt | Las instrucciones y el contexto que le mandas al modelo |
| payload | Los datos que viajan en una petición, un mensaje o un trabajo |
| endpoint | Una ruta concreta de una API, como GET /ready o POST /tasks |
| timeout | El plazo máximo de espera; pasado ese plazo, dejas de esperar aunque el otro lado siga trabajando |
| health y readiness | Las dos sondas de un servicio: si el proceso está vivo y si puede recibir trabajo |
| dataset | El conjunto de casos con el que evalúas un modelo o un sistema |
| benchmark | Una medición repetible del rendimiento, con la misma carga cada vez |
| runbook | El procedimiento escrito para responder a una alerta o a un incidente |