[0:00]En este vídeo vamos a construir una aplicación de inteligencia artificial con Claude Code que podéis vender a negocios locales por cientos o miles de euros. Y todo esto sin escribir ni una sola línea de código. Y no, no estamos hablando de una demo básica. Más bien, vamos a crear un sistema real capaz de responder a cientos de clientes automáticamente por WhatsApp. Gestionar conversaciones, agendar citas, sincronizar con Google Calendar y permitir la intervención humana desde un dashboard profesional. Y todo esto que estáis viendo lo vamos a construir paso a paso y en directo. Y lo mejor es que, aunque en este vídeo lo hagamos para una clínica dental, lo podéis reutilizar exactamente con la misma estructura para gimnasios, inmobiliarias, restaurantes, academias o prácticamente cualquier negocio local que tenga estas necesidades. Así que vamos con la demo. Como veis por aquí, este es el agente conversacional que hemos creado en este vídeo para una clínica dental, donde podéis solicitar los servicios que ofrece esta clínica. Tener información relativa a todo lo necesario por parte del cliente final para tomar una buena decisión, podéis agendar, modificar reservas y hacer absolutamente todo lo que haría una agente humano a nivel conversacional. Y esto lo hacemos con un dashboard totalmente automatizado, donde tenemos conversaciones en los últimos 30 días, citas esta semana, citas hoy, bot en pausa, que esto lo entenderemos posteriormente. Lo que nos permite cuantificar el impacto que está teniendo en el negocio, este bot en particular, que es muy importante para justificarlo comercialmente. Aquí tenemos las últimas conversaciones, donde tenemos el historial de conversaciones con todos y cada uno de los clientes. Y además, tenemos la posibilidad no solo de ver el historial, sino de activar y desactivar individualmente el bot para cada una de las conversaciones, para que de esta forma podamos intervenir nosotros como seres humanos. Es decir, si vemos que hay una excepción o algo que se sale de la norma, podemos desactivarlo y interactuar enviando, por ejemplo, un hola qué tal, como estáis viendo aquí, y diferenciándolo en color, que esto es una locura, sobre todo para darle más tranquilidad a vuestros clientes. Si venimos a la parte de citas, tenemos el calendario sincronizado con todo nuestro Google Calendar para tener de un solo vistazo todas las agendas y la información mínima necesaria relevante, ¿vale? Para poder tener en un vistazo todo lo que necesitamos. Y desde personalización, tenemos la opción de ajustarlo para cada uno de nuestros clientes de una forma muy rápida y sencilla. Podemos ajustar el tono del bot, el System Prom con las características, reglas y el rol, y algo como, por ejemplo, preguntar si el paciente es nuevo al agendar, que esto puede ser importante para vuestro caso de uso. En la parte de negocio, podemos darle la información del negocio con el nombre, la dirección, el teléfono y las preguntas frecuentes, junto con las políticas. Horario, ¿vale? Fácilmente modificable gracias a estas opciones que nos vienen aquí, todo muy intuitivo y visual. Servicios, para que podamos modificarlos en cualquier momento, ampliarlos, reducirlos, introducir nuevos cambios, etcétera, con la duración para poder reservar los slots en tiempo, el nombre y la descripción del servicio. Y los mensajes, donde podemos configurar el saludo inicial y el mensaje de transferencia a un ser humano, en caso de que el usuario, el cliente, quiera hablar con un agente humano. En integraciones, tenemos de un solo vistazo todas las integraciones que estamos utilizando en nuestro sistema, para que esto sea sencillísimo tanto de mantener como de conectar con diferentes clientes, y de nuevo, esto es una auténtica locura. Venga, pues vamos a montar el proyecto desde cero y mostrando todos y cada uno de los pasos. Todos los recursos necesarios para el desarrollo de este proyecto los vais a tener en la descripción de forma totalmente gratuita dentro de nuestra comunidad de WhatsApp para que podéis seguirlo todo paso a paso. Lo primero que vamos a hacer es abrir una carpeta, en este caso yo estoy utilizando Antigravity como ID, donde voy a tener Cloud Code para el desarrollo de todo el proyecto. Vamos a darle un nombre, en este caso le voy a llamar WhatsApp agente o clínica, va, vamos a decirle. Vamos a decir crear, abrir. Y de momento vamos a cerrar el la extensión de agente que nos viene por defecto por parte de Google para abrir una nueva sesión de Claude Code, como comentábamos. Vale, lo primero que vamos a hacer es instalar aquellas skills que necesitamos para que nuestro proyecto sea tanto lo más estético como lo más funcional posible desde un inicio. Para eso vamos a poner barra Manage plugins para poder acceder a los plugins de la marketplace oficial de Antropic, vais a necesitar instalar Claude directamente desde vuestro terminal. Si no nos va a aparecer nada aquí y os vais a frustrar muchísimo, ¿vale? Una vez estáis aquí dentro, veréis que de las primeras que nos aparece es esta de aquí de Frontend Design. Básicamente la vamos a instalar, sin más. Aquí le voy a decir que me lo instale simplemente para mí a nivel global en todos los proyectos, aunque podéis instalarlo localmente en este proyecto.
[4:37]Fijaos que cuando lo instalamos, nos pide reiniciar, vamos a hacerle caso. Y perfecto, ya lo tenemos. Ahora lo que vamos a hacer es instalar dos skills, que os voy a dejar los enlaces de el GitHub en el grupo de WhatsApp, junto con los Proms y el Cloud.md del proyecto. Y básicamente le voy a decir, instala estas dos skills en el proyecto presente siguiendo las buenas prácticas de Anthropic.
[5:12]Aquí le voy a decir que este es el primero y este es el segundo. Vale, esto básicamente son dos skills que lo que nos dan son las directrices de cómo utilizar Supabase, es decir, la base de datos que vamos a utilizar para el registro conversacional y las sesiones activas de los usuarios cuando conversan a través de este agente de WhatsApp. Y por otro lado, la otra skill nos va a dar las directrices de funcionamiento, es decir, una nos va a dar las directrices de mejores prácticas y otra de funcionamiento general. Aquí le vamos a dar permisos y una vez lo tenga instalado, volvemos. Puntualizar también que yo para este proyecto estoy utilizando la versión más potente actualmente de Claude. Es decir, Opus 4.7 en la versión Max, es decir, con el máximo razonamiento posible, para que de esta manera no tenga ningún problema. Al final, pensad que cuando estamos hablando de proyectos que vamos a implementar directamente en nuestros clientes, es muy importante que no escatimemos en recursos. A menos que estemos haciendo un producto mínimo viable o nuestra situación sea pues muy vulnerable a nivel económico, os recomiendo que invirtáis fuertes, porque de verdad, esto puede valer miles de dólares en cuanto a desarrollo y lo estáis haciendo en cuestión de horas, minutos. Entonces, no escatiméis a menos que vuestra situación sea muy jodida, no hablando mal y claro. Venga, perfecto, como veis aquí, ya nos ha instalado las skills correctamente, además lo ha hecho siguiendo el directorio oficial de Claude, de Claude Code. Si os fijáis, tenemos la carpeta .code y de ella nace la carpeta skills con las dos skills que le hemos solicitado, tanto la de Supabase como la de Supabase Postgres mejores prácticas, ¿bien? Entonces, esto ya lo tenemos. Ahora, simplemente, vamos a limpiar esta conversación para que no nos ocupe espacio la ventana de contexto, con un clear. Y lo que vamos a hacer es poner el Cloud.md, que os hemos hecho para todos vosotros, donde vamos a tener las directrices de desarrollo del proyecto para que todos funcione a la perfección. Para esto, simplemente venís a la carpetita, donde tendréis el Cloud.md y el Prom, y en este caso, vamos a arrastrar el Cloud.md aquí, a la raíz. Vale, muy importante que quede aquí tal cual, y si os fijáis, este Cloud.md lo que tiene es un pequeño Prom super concreto, donde le decimos que es un ingeniero full stack senior, con un objetivo determinado, que va a crear un sistema SaaS multi-tenant que actúa como agente IA de atención al cliente vía WhatsApp para una clínica dental. Con la flexibilidad de adaptarse a otros negocios que necesiten agendamiento de citas. La plataforma incluye un panel web para que el dueño del negocio gestione todo. Sigue las instrucciones al pie de la letra, cuando algo no esté especificado, toma decisiones razonables o pregúntale al usuario. Todo información sensible en .env o .env.local. No le pidas al usuario ninguna credencial, en su lugar, crea .env.example para que él sepa cómo colocar las variables de entorno. Si hay Git, asegúrate de que el .gitignore contenga la información sensible. RLS en las tablas de Supabase. No introduzcas dependencias no listadas sin justificación. Backend: Node.js, Frontend: Next.js 16.2.0 con App Router y TypeScript en modo strict. Base de datos: Supabase (PostgreSQL), Comunicación cliente-servidor: gRPC, Despliegues: Vercel + Supabase, Lenguajes: TypeScript, Frameworks/librerías UI: Radix UI (por su accesibilidad y minimalismo), Tailwind CSS, Framer Motion. IA / orquestación del agente: Vercel AI SDK 6 (+ @ai-sdk/anthropic), Modelo LLM: Anthropic Claude Sonnet 4.6 (model string exacto: claude-sonnet-4-6). WhatsApp: WhatsApp Cloud API oficial de Meta (Graph API), Calendario: Google Calendar API vía OAuth 2.0, Deploy: Vercel (serverless / fluid compute, runtime Node.js para webhooks). Notas importantes sobre el stack (verificadas contra la documentación oficial vigente): Next.js 16.2.0 usa App Router por defecto. Server Components por defecto. Aprovecha Server Actions para mutaciones desde el dashboard. Cache Components es opt-in en Next.js 16, el caché ya no es implícito, así que no asumas caché donde no se ha declarado. WhatsApp Cloud API: no es la versión On-Premises, que Meta está deprecando. Endpoints contra https://graph.facebook.com/v2.0/{PHONE_NUMBER_ID}/messages (v2.0 es la versión más reciente lanzado por Meta el 18 de febrero de 2026). No uses v2.3.0 ni inferiores: están cerca del fin de soporte (Meta mantiene cada versión ~2 años). Centraliza la versión en una constante. GRAPH_API_VERSION para futuras migraciones. Autenticación con System User Access Token (no token personal) con permisos whatsapp_business_messaging y whatsapp_business_management. El webhook necesita endpoint público HTTPS (no autofirmado) que maneje: GET /api/webhooks/whatsapp para la verificación (`hub.mode=subscribe`, comprobando `hub.verify_token`). POST /api/webhooks/whatsapp para los eventos. Debe responder <200 lo antes posible y procesar el mensaje en background; si tardas, Meta reintenta hasta 7 días. Verifica la firma `X-Hub-Signature-256` con HMAC-SHA256 usando el `APP_SECRET`. Implementa idempotencia por `message.id` (Meta puede reenviar el mismo evento). Nueva TLS / Trust store: desde el 31 de marzo de 2026 Meta firma sus llamadas a webhooks con una nueva CA interna. Si despliegas en Vercel esto está cubierto por tu trust store gestionado, pero si en algún momento se autohospeda (Docker, EC2, etc.), el sistema debe confiar en la nueva Meta Internal CA o el handshake TLS fallará y dejarás de recibir eventos. Documéntalo en el README. Vercel AI SDK 6: usa `generateText` / `streamText` con `tools` definidos mediante `tool( { inputSchema: z.object({...}), execute: async ... } )`. Para el loop multi-step usa el parámetro `stopWhen: stepCountIs(N)` para permitir varios turnos de tool-use seguidos. Modelo: instancia con `anthropic('claude-sonnet-4-6')`. No uses alias tipo `claude-sonnet-latest` en producción. Arquitectura general: Cliente WhatsApp -> Meta Cloud API -> webhook -> Next.js /api/webhooks/whatsapp -> Verifica firma HMAC, Persiste mensaje (Supabase). Si bot activo: Agente IA (AI SDK + Sonnet 4.6). tool: `get_available_slots`, tool: `book_appointment`, tool: `collect_patient_info`, tool: `request_human_handoff`. Google Calendar: FreeBusy query para devolver 3 slots libres respetando la duración del servicio. Devuelve ISO strings en la timezone del `organization`. Google Calendar: crea evento en Google Calendar y registro en `appointments`. Idempotente: si ya existe una cita en ese slot para ese contacto, no duplica. Si `bot.active` es `false`, envía `handoff_message` y debería notificar al dueño (vía Supabase Realtime al dashboard para esta primera versión deja un TODO para notificar al dueño). Esquema de base de datos (Supabase / Postgres): Moviliza RLS en todas las tablas y crea políticas que filtren por `organization_id` del usuario autenticado. SQL: `organizations`: cuenta del negocio (clínica dental, otro negocio, etc.). `id` uuid pk default `gen_random_uuid()`, `name` text not null, `slug` text unique not null, `timezone` text not null default `America/Mexico_City`, `created_at` timestampz default `now()`. `profiles`: extends `auth.users` de Supabase, vincula a `organization`. `organization_id` uuid fk references `organizations(id)` on delete cascade, `full_name` text, `role` text check (`role` in ('owner', 'staff')) default 'owner', `created_at` timestampz default `now()`. `whatsapp_configs`: credenciales por organización. `organization_id` uuid pk references `organizations(id)` on delete cascade, `phone_number_id` text (WhatsApp Phone Number ID), `waba_id` text (WhatsApp Business Account ID), `access_token` encrypted text not null, `verify_token` encrypted text not null, `app_secret` encrypted text not null, `updated_at` timestampz default `now()`. `contacts`: clientes que han escrito por WhatsApp. `id` uuid pk default `gen_random_uuid()`, `organization_id` uuid references `organizations(id)` on delete cascade, `wa_phone` text not null, `full_name` text, `is_new_patient` boolean, `created_at` timestampz default `now()`, unique (`organization_id`, `wa_phone`). `conversations`: hilo por contacto. `id` uuid pk default `gen_random_uuid()`, `organization_id` uuid references `organizations(id)` on delete cascade, `contact_id` uuid references `contacts(id)` on delete cascade, `bot_active` boolean default `true`, `last_message_at` timestampz not null default `now()`, `created_at` timestampz default `now()`. `messages`: cada mensaje (entrante o saliente). `id` uuid pk default `gen_random_uuid()`, `conversation_id` uuid references `conversations(id)` on delete cascade, `organization_id` uuid references `organizations(id)` on delete cascade, `wa_message_id` text (id de Meta, para idempotencia). `direction` text check (`direction` in ('inbound', 'outbound')) not null, `sender` text check (`sender` in ('contact', 'bot', 'human')) not null, `content` text, `raw_jsonb` jsonb, `created_at` timestampz default `now()`, unique (`wa_message_id`). `appointments`: citas agendadas. `id` uuid pk default `gen_random_uuid()`, `organization_id` uuid references `organizations(id)` on delete cascade, `contact_id` uuid references `contacts(id)` on delete cascade, `service` text not null, `starts_at` timestampz not null, `ends_at` timestampz not null, `google_event_id` text, `status` text check (`status` in ('confirmed', 'cancelled', 'completed')) default 'confirmed', `is_new_patient` boolean, `full_name` text, `phone` text not null, `notes` text, `created_at` timestampz default `now()`. Índices recomendados en `messages(conversation_id, created_at desc)`, `conversations(organization_id, last_message_at desc)`, `appointments(organization_id, starts_at)`. Flujo de conversación del agente IA: Comportamiento esperado. `1. El cliente escribe a WhatsApp -> llega al webhook -> si `conversation.bot_active` = `true` -> invoca al agente. `2. El agente saluda y pregunta el motivo (solo si es el primer mensaje del hilo o ha pasado un umbral de inactividad). `3. Si quiere una cita, pregunta servicio. Servicios por defecto es clínica dental: `limpieza`, `empaste`, `blanqueamiento`. Pero deben venir de la config para servir para otros negocios. `4. Sugiere 3 huecos libres de la próxima semana llamando a la tool `get_available_slots` (ver más arriba). `5. Recolecta los datos necesarios uno a uno, con preguntas claras y breves. `6. Confirma cita SOLO cuando tengas todos los datos requeridos. `7. Si el agente no entiende o el cliente lo pide -> llama a `request_human_handoff`, que pone `conversations.bot_active` = `false` y envía el `handoff_message` al cliente. A partir de ese punto el agente no responde a ese hilo hasta que el dueño reactive el bot desde el dashboard. Datos a recoger por cita: Nombre completo (del cliente, recolectado en chat), Teléfono (lo obtiene automáticamente del payload del webhook, no se pregunta), Servicio (elegido entre los configurados), Fecha/hora preferida (debe coincidir con un slot disponible real), ¿Es nuevo paciente? (si/no, configurable en plantilla por si otro negocio no lo necesita). Tools del agente (todas en `/lib/agent/tools/`): Define con `tool({ description, inputSchema: z.object({...}), execute: async ... })`. `-- get_available_slots` -> `input: { service: string, days_ahead?: number = 7 }`. Lee `business_hours` y consulta Google Calendar (FreeBusy query) para devolver 3 slots libres respetando la duración del servicio. Devuelve ISO strings en la `timezone` del `organization`. `-- book_appointment` -> `input: { full_name, starts_at, is_new_patient }`. Crea evento en Google Calendar y registro en `appointments`. Idempotente: si ya existe una cita en ese slot para ese contacto, no duplica. `-- save_contact_info` -> `input: { full_name, is_new_patient }`. Actualiza `contacts`. `-- request_human_handoff` -> `input: { reason?: string }`. Setea `bot.active` = `false`, envía `handoff_message`, y debería notificar al dueño (vía Supabase Realtime al dashboard para esta primera versión deja un TODO para notificar al dueño). Configura el agente con `stopWhen: stepCountIs(N)` para permitir varias rondas de tools, y `temperature: 0.3`. App web (Next.js 16.2.0): Estructura de rutas (App Router). `/app/ (marketing)` -> `landing` pública. `/auth/ (auth)` -> `login/page.tsx`, `signup/page.tsx`, `callback/route.ts` -> Supabase auth callback. `/app/ (app)` -> `shell` con `sidebar` (Phosphor Icons). `dashboard/page.tsx`, `citas/page.tsx`, `conversaciones/page.tsx`, `conversaciones/[id]/page.tsx`, `personalización/page.tsx`, `integraciones/page.tsx` -> conectar WhatsApp y Google Calendar. `/api/webhooks/whatsapp/route.ts` (con `export const runtime = 'nodejs'` (no `edge`; necesita `crypto` nativo para HMAC y la operación puede tomar tiempo) y `export const dynamic = 'force-dynamic'`). GET: verificación. Lee `mode`/`token`/`challenge` de `query` string. Para multi-tenant: el `verify_token` debe identificar a la `organization`. Usa el formato `{org_slug}:{secret}` o un mapeo en 80. POST: Lee raw body (sin parsear) para verificar firma `X-Hub-Signature-256`. Resuelve `organization` a partir de `phone_number_id` del `payload`. Devuelve 200 inmediatamente. En `after()` / `waitUntil()`: procesa el mensaje. `upsert contact` (por `organization_id`, `wa_phone`). `upsert conversation`. `insert message` con `wa_message_id` (ignorar si ya existe). Si `bot_active`: llamar al agente. `enviar respuesta` vía Graph API y `registrar message outbound`. Usa `after()` de Next.js 16 para el procesamiento asíncrono tras responder 200. Variables de entorno: Supabase (NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY). Anthropic (Claude Sonnet 4.6) (ANTHROPIC_API_KEY). Google OAuth (Calendar) (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_OAUTH_REDIRECT_URI). Cifrado de tokens en BD (AES-256-GCM) (ENCRYPTION_KEY). Genera con `pnpm gen:key` (o `node scripts/gen-encryption-key.mjs`). Debe ser exactamente 32 bytes en base64 (44 caracteres terminados en `=`). App: URL pública de la app, sin trailing slash. En prod, usa el dominio Vercel. (NEXT_PUBLIC_APP_URL).



