Case studyCaso de estudio
SmartOps Agent
An AI operations agent for businesses that run on WhatsApp. It reads supplier price lists, orders and voice notes in any format, keeps the catalog up to date and tells the team only what needs action. Built solo, to the standard of a delivery to a real client.
Un agente de operaciones con IA para negocios que trabajan por WhatsApp. Lee listas de precios de proveedores, pedidos y notas de voz en cualquier formato, mantiene el catálogo al día y le avisa al equipo solo lo que requiere acción. Lo construí solo, con el estándar de una entrega a un cliente real.
45-second tour of the panel: a supplier spreadsheet in a new format stops for a person, the pipeline reads a photo of a price list, and a person takes over a chat.Recorrido de 45 segundos por el panel: una planilla de proveedor con un formato nuevo espera a una persona, el sistema lee la foto de una lista de precios y una persona toma un chat.
The problemEl problema
Small distributors and hardware stores run on WhatsApp. Suppliers send price lists as photos of printed pages, PDFs, spreadsheets in their own layouts and voice notes. Customers ask for prices and place orders in the same chats. Someone copies prices into a spreadsheet by hand, orders get lost between messages, and nobody notices a 20% increase until the margin is gone.
Las distribuidoras y ferreterías chicas trabajan por WhatsApp. Los proveedores mandan listas de precios como fotos de hojas impresas, PDF, planillas con su propio formato y audios. Los clientes consultan precios y hacen pedidos en los mismos chats. Alguien pasa los precios a mano a una planilla, los pedidos se pierden entre mensajes y nadie ve un aumento del 20% hasta que el margen ya no está.
Key decisionsDecisiones clave
- The backend decides; the AI reads. Claude extracts structured data validated against a schema. Every rule (duplicates, outliers, currency, VAT, full list or partial update) lives in tested code, never in a prompt.
- El backend decide; la IA lee. Claude extrae datos estructurados validados contra un esquema. Todas las reglas (duplicados, precios atípicos, moneda, IVA, lista completa o actualización parcial) viven en código con tests, nunca en un prompt.
- Never guess. Doubtful values, uncertain matches, new spreadsheet formats and suspicious messages become review items, with the original message beside them.
- Nunca adivinar. Los valores dudosos, las coincidencias inciertas, los formatos de planilla nuevos y los mensajes sospechosos pasan a revisión, con el mensaje original al lado.
- Spend only where it helps. A deterministic pre-filter keeps greetings and small talk away from the AI, known spreadsheet formats are read by code, and spend caps are checked before every call.
- Gastar solo donde sirve. Un filtro previo determinístico deja fuera de la IA los saludos y la charla, las planillas con formato conocido se leen por código y los topes de gasto se controlan antes de cada llamada.
- Reliable by construction. Webhooks are stored before answering, work goes through queues with retries and dead letters, and a transactional outbox hands work to n8n. Every step is idempotent.
- Confiable por diseño. Los webhooks se guardan antes de responder, el trabajo pasa por colas con reintentos y mensajes muertos, y un outbox transaccional le entrega el trabajo a n8n. Cada paso es idempotente.
- People first. A person's reply pauses the automatic answers only in that chat, and the team gets one summary per period instead of a stream of alerts.
- Las personas primero. La respuesta de una persona pausa las respuestas automáticas solo en ese chat, y el equipo recibe un resumen por período en lugar de una lluvia de alertas.
ArchitectureArquitectura
An Express 5 + TypeScript API owns the WhatsApp webhook, the business rules and every write to PostgreSQL. A worker on pg-boss queues downloads media, transcribes voice notes and sends messages. n8n orchestrates the receiver, processor and notifier agents by calling the API's internal endpoints. A bilingual, mobile-first Next.js panel opens on what waits for the person, with one-click answers, and updates live over SSE.
Una API en Express 5 + TypeScript se encarga del webhook de WhatsApp, de las reglas de negocio y de toda escritura en PostgreSQL. Un worker con colas pg-boss descarga archivos, transcribe audios y envía mensajes. n8n orquesta los agentes receptor, procesador y notificador llamando a los endpoints internos de la API. Un panel en Next.js, bilingüe y pensado primero para el teléfono, abre con lo que espera a la persona, con respuestas de un clic, y se actualiza en vivo con SSE.
Results (measured)Resultados (medidos)
- Accuracy on test data. A September PDF followed by an October photo produced exactly the expected 5 automatic price changes and 2 alerts, sent 1 doubtful product to review and left an absent product untouched.
- Precisión con datos de prueba. Un PDF de septiembre seguido de una foto de octubre dio exactamente los 5 cambios de precio automáticos y las 2 alertas esperadas, mandó 1 producto dudoso a revisión y no tocó un producto ausente.
- Prompt injection. 8 of 8 cases passed against the real model (6 attacks, 2 controls). Attacks were flagged and stopped for review, and no invented price was applied.
- Prompt injection. 8 de 8 casos correctos contra el modelo real (6 ataques, 2 controles). Los ataques quedaron marcados y frenados para revisión, y no se aplicó ningún precio inventado.
- Cost. A typical price list costs $0.012 to $0.017 to extract. A new spreadsheet format costs about $0.011 once, and $0 afterwards.
- Costo. Extraer una lista de precios típica cuesta entre USD 0,012 y 0,017. Un formato de planilla nuevo cuesta unos USD 0,011 una vez, y nada después.
- Resilience. With n8n stopped and restarted, every message was delivered once, with one run each.
- Resiliencia. Con n8n detenido y vuelto a levantar, cada mensaje se entregó una sola vez, con una sola corrida cada uno.
- Tests. 1,453 API unit and HTTP tests, 250 against a real Postgres, 247 panel unit tests and 187 browser tests on desktop Chrome, Pixel 7 and iPhone, with accessibility checks in both languages.
- Tests. 1.453 tests unitarios y HTTP de la API, 250 contra un Postgres real, 247 tests unitarios del panel y 187 tests en navegador en Chrome de escritorio, Pixel 7 e iPhone, con controles de accesibilidad en los dos idiomas.
- Infrastructure. The public demo runs with Docker Compose on an Oracle Cloud Always Free VM, at $0, with multi-architecture images (amd64 and arm64).
- Infraestructura. La demo pública corre con Docker Compose en una VM Always Free de Oracle Cloud, a costo cero, con imágenes multi-arquitectura (amd64 y arm64).
What was hard, and what I learnedLo difícil, y lo que aprendí
- Meta disabled the business account on day one. It was a false positive on a new account. While the review was resolved, I kept developing against a local WhatsApp simulator that signs webhooks and imitates the Graph API, and it is still the default way to develop.
- Meta deshabilitó la cuenta de negocio el primer día. Fue un falso positivo en una cuenta nueva. Mientras se resolvía, seguí desarrollando contra un simulador local de WhatsApp que firma los webhooks e imita la Graph API, y sigue siendo la forma de trabajo por defecto.
- "1.850" can mean 1,850 or 1.85. The system never guesses: it learns each supplier's spreadsheet format once, with a person choosing the price column, and refuses ambiguous handwritten numbers.
- "1.850" puede ser mil ochocientos cincuenta o 1,85. El sistema nunca adivina: aprende el formato de planilla de cada proveedor una vez, con una persona eligiendo la columna de precio, y rechaza los números ambiguos escritos a mano.
- Real time through a tunnel. Cloudflare's edge held GET event streams while POST streams flowed in about 75 ms, so the panel opens its stream with POST and falls back to refreshing every 30 seconds.
- Tiempo real a través de un túnel. El borde de Cloudflare retenía los streams de eventos por GET mientras que por POST fluían en unos 75 ms, así que el panel abre su stream con POST y, si falla, se actualiza cada 30 segundos.
- A panel that works is not yet a panel that is clear. Real use showed that "pending reviews" and "open alerts" read as the same thing. The panel now sorts everything into To do, Activity and System status, with each decision written as a plain question.
- Un panel que funciona todavía no es un panel claro. El uso real mostró que "revisiones pendientes" y "alertas abiertas" parecían lo mismo. Ahora el panel ordena todo en Pendientes, Novedades y Estado del sistema, con cada decisión escrita como una pregunta simple.
Want to talk about it?¿Hablamos?
I'm looking for a full-time role, on-site in Montevideo or remote, and I also take on freelance projects.
Busco un rol full-time, presencial en Montevideo o remoto, y también tomo proyectos freelance.