← Todos los casos

Software IA a medida

Talkify — atención por WhatsApp con IA para negocios locales

Una clínica pierde citas porque nadie coge el WhatsApp a las ocho de la tarde. Construimos un micro-SaaS que contesta, consulta el calendario real del negocio y reserva — y que sabe callarse cuando el cliente se enfada. Este es el desglose técnico de cómo y, sobre todo, por qué.

10 sem del primer commit a producción
Sector: SaaS y servicios locales España
Duración: 10 semanas
Año: 2026
Servicios:
  • Producto a medida
  • Integración LLM
  • RAG
  • Arquitectura multi-tenant
  • DevOps
10 sem
del primer commit a producción
7
módulos de producto entregados
27
decisiones de arquitectura documentadas

En breve

El reto

Una clínica dental, un taller o una academia tienen el mismo problema y ninguno tiene departamento de tecnología para resolverlo: la gente pregunta por WhatsApp y nadie contesta fuera de horario. La consulta se enfría, el cliente llama a otro sitio, y la pérdida nunca aparece en ningún informe porque nadie la mide.

Las soluciones existentes fallan por los dos extremos. Los chatbots de reglas exigen que alguien configure árboles de conversación que se rompen a la primera pregunta imprevista. Las plataformas de atención al cliente serias cuestan más que el margen mensual del negocio y presuponen un equipo que las opere.

Talkify es nuestra respuesta: un micro-SaaS multi-tenant que conecta el WhatsApp del negocio, responde con IA sobre la información real de ese negocio, consulta disponibilidad contra su Google Calendar y reserva la cita. Y que, cuando la conversación se tuerce, se calla y avisa a una persona.

Lo construimos nosotros, con nuestro dinero y nuestro criterio. Eso significa que las decisiones difíciles no se pudieron esquivar echándole la culpa a un requisito del cliente.

La restricción que condicionó todo

WhatsApp no tiene una API oficial asequible para un negocio de barrio. La API oficial de WhatsApp Business quedó fuera del MVP por presupuesto, así que la conectividad se apoya en Baileys, una librería no oficial que implementa el protocolo de WhatsApp Web multidispositivo.

Esto convierte la conectividad en el riesgo operativo número uno del producto, y así está registrado en el documento de riesgos desde antes de escribir la primera línea: librería no oficial, riesgo de baneo, y desconexiones como parte normal del ciclo de vida, no como incidencia.

Todo lo que viene después es consecuencia de haber aceptado esa restricción con los ojos abiertos.

Decisión 1 — Aislar el riesgo, aunque cueste un segundo lenguaje

Nuestro estándar interno de tecnología manda Go para los servicios de backend, precisamente para no mezclar el runtime de frontend con el de backend. Baileys sólo existe en Node.js y no hay equivalente en Go con cobertura comparable.

La decisión fue escribir el worker de WhatsApp en Node.js/TypeScript como proceso separado, y dejar absolutamente todo lo demás en Go. No comparten base de datos ni espacio de proceso: se hablan por una API interna.

El resultado se ve en el reparto del código:

API Core — Go (228 ficheros) 33.139 Worker de WhatsApp — TypeScript (17 ficheros) 1.505
Líneas de código por servicio, medidas sobre el repositorio srv-talkify-app. El worker que habla el protocolo no oficial de WhatsApp — el riesgo operativo número uno del producto — es el 4% del código y corre en su propio proceso.

El componente de más riesgo del producto es el 4% del código. Una tormenta de reconexiones de Baileys no puede tumbar el API Core, porque ni siquiera vive en el mismo proceso. Es la única vez en todo el repositorio en que nos desviamos del stack por defecto, y llevó su propio ADR justificándolo.

El coste lo pagamos en otro sitio: dos ecosistemas que mantener, dos cadencias de actualización de dependencias y dos conjuntos de convenciones. Está escrito en el apartado de consecuencias negativas de esa decisión, porque un ADR que sólo lista ventajas no es un ADR.

Decisión 2 — La IA no calcula horarios

Reservar citas es el objetivo comercial del producto, y es justo donde un modelo de lenguaje es menos de fiar. Los LLM son malos en aritmética de tiempo, en respetar los límites de un horario comercial y en detectar conflictos entre varios eventos.

Así que se lo prohibimos. El modelo tiene terminantemente vedado estimar o suponer disponibilidad.

Toda operación de calendario pasa por function calling nativo — check_availability, book_appointment, cancel_appointment, list_my_appointments — y la lógica vive en Go, no en el prompt. El paquete internal/calendarclient cruza de forma determinista tres fuentes:

FuenteQué aporta
tenant_configs.scheduleEl horario de apertura real del negocio
tenant_configs.servicesLa duración de cada servicio
Google Calendar del negocioLos eventos ya existentes, en tiempo real, vía OAuth 2.0

La herramienta devuelve las horas válidas calculadas y una plantilla de texto que el modelo transmite literalmente. El modelo conversa; no decide.

El tercer punto es el que importa en el mundo real: el dueño de la peluquería sigue apuntando a los que entran por la puerta directamente en su Google Calendar de siempre. Sin sincronización bidireccional real, el sistema ofrecería huecos ya ocupados y generaría overbooking — que para un negocio pequeño es peor que no tener nada.

Los tokens de refresco de Google se guardan cifrados con AES-256-GCM.

Decisión 3 — RAG multi-tenant sin infraestructura nueva

Cada negocio necesita que la IA conozca su información: dónde aparcar, qué métodos de pago acepta, su política de cancelación, las especialidades de su equipo. Eso es recuperación aumentada, y la tentación estándar es montar una base de datos vectorial aparte.

No lo hicimos. Usamos pgvector dentro del mismo PostgreSQL, con vectores de 1536 dimensiones, índice HNSW y text-embedding-3-small.

El motivo principal no es el rendimiento, es el aislamiento. Cada trozo de conocimiento lleva tenant_id con clave foránea y borrado en cascada, y toda búsqueda por similitud está acotada por tenant_id en la propia consulta SQL. Que el conocimiento de la Clínica A llegue a un cliente de la Clínica B no es un bug aceptable: es el fin del producto.

Manteniéndolo en la base relacional, el aislamiento lo garantiza el mismo motor que ya garantiza el resto, las actualizaciones son atómicas y borrar un tenant limpia sus vectores sin código adicional. Una pieza menos de infraestructura que operar, monitorizar y pagar.

Decisión 4 — El coste no puede crecer con la conversación

Las conversaciones de WhatsApp duran semanas. Si metes el histórico entero en el prompt, pasan tres cosas a la vez: el coste por mensaje crece linealmente, la latencia se degrada, y el modelo se confunde con citas viejas cuando el cliente pregunta por algo nuevo.

La solución son dos límites, ninguno sofisticado:

  • Ventana deslizante de 10 mensajes. Al crear un mensaje, una transacción atómica borra el histórico excedente de esa conversación.
  • Caducidad por inactividad a las 4 horas. Si la conversación activa lleva más de cuatro horas parada, se cierra atómicamente y se abre una nueva. El histórico queda archivado para auditoría y analítica; el contexto del modelo arranca limpio.

Es la clase de decisión que no sale en ninguna demo y que determina si el producto tiene margen unitario.

Decisión 5 — Saber cuándo callarse

Un bot que discute con un cliente enfadado hace más daño que un bot que no existe.

El modelo dispone de la herramienta transfer_to_human(reason, summary) con cuatro motivos tipificados: cliente hostil, petición explícita de hablar con una persona, consulta fuera de alcance, y reclamación o devolución.

Cuando se dispara, la conversación pasa a needs_human y el orquestador suprime por completo las respuestas automáticas. Los mensajes siguientes del cliente se guardan en PostgreSQL para que el personal los lea, pero no se envían a OpenAI. El panel recibe una notificación en tiempo real por SSE. Sólo el dueño puede devolver la conversación a la IA desde el dashboard.

Silenciado significa silenciado. No “responde más flojito”.

Lo que decidimos no construir

Tan determinante como lo anterior. Talkify no tiene usuarios propios, ni contraseñas, ni facturación propia:

  • Identidad delegada en srv-accounts por OAuth2 + PKCE.
  • Facturación delegada en srv-billing con Stripe Checkout y Portal.

El panel web nunca llama a la API desde el navegador: todo pasa por un proxy BFF del mismo origen con cookie de sesión HttpOnly. El navegador no ve nunca un token.

Cada una de esas tres decisiones es un ADR con sus alternativas descartadas. Construir login y facturación propios habría añadido semanas y dos superficies de ataque para resolver problemas que ya estaban resueltos en casa.

Cómo se entregó

Tres ciclos, con criterio de “hecho” escrito antes de empezar cada uno:

CicloAlcanceEstado
1 — Foundation & Core ConnectInfraestructura multi-tenant y conectividad WhatsAppCompletado (jul 2026)
2 — AI Engine & Human PanelMotor de IA, RAG, citas deterministas, handoffCompletado en backend (ago 2026)
3 — Monetization & GTM LaunchFacturación, planes, pipeline comercialago 2026

Primer commit el 9 de julio de 2026. El panel web arrancó el 22 de agosto sobre el sistema de diseño “Aurora” — papel cálido de día, casi negro de noche, un solo acento naranja — en Astro con islas de Preact, CSS plano en tres capas y una CSP que sólo admite script-src 'self'.

El resultado

Un producto en producción con siete módulos funcionales, 125 rutas de API, 58 migraciones de base de datos y 74 ficheros de test en Go. La parte que asumía el riesgo protocolario quedó confinada en 1.505 líneas de TypeScript.

Y 27 decisiones de arquitectura documentadas, cada una con su contexto, sus alternativas descartadas y sus consecuencias negativas por escrito. Ese documento es el entregable del que más orgullosos estamos: dentro de un año, cuando alguien pregunte por qué el worker de WhatsApp está en Node, la respuesta estará escrita con fecha y con los argumentos que se descartaron.

Es exactamente la forma en que construimos para nuestros clientes. Talkify es la prueba de que también lo hacemos cuando el dinero es nuestro.

¿Quieres resultados similares? Hablemos.

Todo proyecto empieza con una llamada gratuita de 30 minutos. Sin pitch — solo una conversación sobre tu negocio.