Ir al contenido
Ricardo Brossard
Proyectos

TomaNota

Un asistente que toma los pedidos por WhatsApp sin poder inventar un precio.

Producto propio para comerciosEn producción

Tablero de pedidos del comercio, con columnas para pedidos nuevos, en preparación, listos y en camino, y los botones para avanzar cada uno
pruebas automáticas
+900
conversaciones simuladas contra el modelo real
1.203
herramientas para el modelo, ninguna acepta un precio
8
tablas con permisos por comercio (RLS)
46

El problema

La pizzería, la rotisería o la carnicería de barrio vende por WhatsApp. El cliente escribe como habla (“dos grandes y una mitad muzza mitad napo”), pregunta por el envío y espera que alguien le confirme. Ese alguien es el dueño, que al mismo tiempo atiende el mostrador.

En la hora pico los mensajes se acumulan, los pedidos se anotan en papel con errores en las mitades y en los totales, y a fin de mes nadie sabe cuánto entró por ese canal. Las apps de delivery cobran comisión por clientes que ya son del comercio, y un bot de respuestas armadas no entiende cómo escribe la gente.

Qué construí

TomaNota, un producto propio con tres puertas de entrada que terminan en un mismo tablero:

  • Un asistente de WhatsApp que conversa en lenguaje natural, arma el pedido con la carta real del comercio, muestra un resumen y espera que la persona toque Confirmar. Si el cliente pide hablar con alguien, o el dueño escribe desde su teléfono, se hace a un lado.
  • Una carta online con QR, para el cliente que prefiere elegir sin escribir.
  • Pedidos de mostrador, para lo que entra en persona o por teléfono.
  • La pantalla del comercio: el tablero en tiempo real con cada estado, el catálogo, las zonas de envío, los textos del asistente y un informe del mes por canal.

Por dentro son tres piezas. Un motor de conversación en TypeScript sin red ni disco, que corre igual en producción y en los tests. Un servidor en Fastify que lo conecta con WhatsApp, con Redis y con el modelo, a través de Vercel AI SDK sobre una API compatible con OpenAI. Y PostgreSQL en Supabase, con seguridad por fila en las 46 tablas: cada comercio ve solo lo suyo, y el servidor entra con un rol que no puede saltear esos permisos.

Antes de que intervenga la IA, cada mensaje pasa por controles fijos: un turno por chat a la vez (dos globos seguidos suman las dos cosas al carrito), descarte de reintentos del webhook, silencio cuando escribe el dueño y un corte a las 60 respuestas por hora que frena a dos bots contestándose en loop.

Lo más difícil

Que el modelo no pueda inventar un monto

Si el asistente dice un precio distinto al de la carta, el comercio pierde plata o discute con un cliente que tiene la captura. Lo resolví por diseño: el modelo no tiene ningún camino para escribir un precio en el pedido.

Trabaja con 8 herramientas cerradas (agregar, mitad y mitad, extra, cambiar cantidad, quitar, envío, mostrar el carrito y avisar que está listo) y solo emite ids de catálogo y cantidades. Un producto agotado ni figura entre los ids válidos. El servidor ejecuta cada herramienta, calcula y escribe el resumen: el texto que la persona confirma nunca lo redacta el modelo.

Al confirmar, una única función de la base vuelve a cotizar todo con los precios de ese momento, con una clave de idempotencia para que un reintento no duplique el pedido. Bot, carta y mostrador pasan por esa misma función.

La media pizza lo puso a prueba: en una carta real, la media no es la mitad de la entera. Encontré cuatro lugares que dividían por dos cuando faltaba el dato, incluida la regla que medía al asistente. Hoy la media tiene precio propio y, si no está cargado, ese producto se vende solo entero.

La confirmación es de la persona

La herramienta que avisa “listo” no confirma nada: le pide al sistema que muestre el resumen con Confirmar, Modificar y Cancelar. Ningún pedido existe hasta que la persona toca Confirmar. Y como el modelo puede escribir “pedido confirmado” por su cuenta, una regla cruza cada frase de cierre contra los pedidos realmente guardados en ese turno.

Cómo se prueba

Cinco capas, cada una para un tipo de error distinto:

  • Más de 900 pruebas automáticas: 847 en la suite rápida y 88 de integración contra PostgreSQL real, con el rol del servidor. Una de ellas recorre todas las funciones de la base y falla si alguna queda ejecutable sin permiso explícito.
  • 13 conversaciones completas guardadas como especificación, en cada cambio.
  • 21 suites SQL que prueban la base desde adentro: el aislamiento entre comercios y que las tres entradas cobren lo mismo.
  • Un banco de pruebas contra el modelo real, con 77 casos y 11 reglas que revisan cada respuesta: no inventa precios, no promete medios de pago que no hay, no anuncia un pedido sin confirmar. Suma 1.203 conversaciones simuladas, difíciles a propósito: inyección de prompt, descuentos que no existen, una mitad pizza mitad empanada.
  • GitHub Actions en cada push: escaneo de secretos sobre toda la historia, chequeo de tipos y la suite rápida.

Cómo funciona

  1. 01WhatsAppfirma verificada
  2. 02Orden y duplicadoscorte ante loops
  3. 03Modelo de lenguaje8 herramientas
  4. 04Servidor calculaprecio del catálogo
  5. 05Resumenlo arma el sistema
  6. 06Confirmarlo toca la persona
  7. 07Base de datosvuelve a cotizar
  8. 07Carta con QRel cliente elige
  9. 07Mostradorpersona o teléfono
  10. 08Tableroen tiempo real

Resultados

  • Tres entradas, un solo precio

    WhatsApp, la carta con QR y el mostrador cotizan con la misma función de la base y cobran siempre lo mismo.

  • El dueño toma el control

    Si escribe desde su teléfono en un chat, el asistente se calla en esa conversación durante 30 minutos.

  • Nunca queda mudo

    Con un tope de 20 segundos por llamada y 45 por turno, el cliente pasa a una persona y el dueño recibe el motivo.

  • 17 de 17 direcciones

    La zona de envío se deduce de la dirección con tramos de calle y contornos definidos por las calles que la limitan.

  • Como vende cada comercio

    Tamaños, extras con mínimo y máximo, mitad y mitad, media pizza con precio propio y venta por peso.

  • Cinco idiomas

    Detecta el idioma del cliente y lo sostiene durante todo el pedido.

Galería

Stack

Aplicación
React 18ViteTypeScriptTailwind CSSTanStack QueryPWALeaflet
Servidor e IA
Node.js 22FastifyVercel AI SDKAPI compatible con OpenAITool callingWhatsApp Cloud APIRedis
Datos
SupabasePostgreSQLRow Level SecurityRealtimeFunciones plpgsql
Calidad y despliegue
VitestSuites SQLGitHub ActionsgitleaksDocker ComposeVercel
Conversación completa con el asistente: una pizza mitad y mitad y media docena de empanadas, la zona de envío deducida de la dirección, el resumen con Confirmar, la forma de pago y el aviso del pedido nuevo al local