ecoplagas.online es el sistema interno de EcoPlagas, una fumigadora de Costa Rica. Ahí la empresa agenda, vende, despacha, cobra, controla bodega y capacita. Esta guía lo explica desde cero: qué hace cada parte, con qué piezas se hizo y cómo se conectan.
Por Pablo Paniagua, LATAM Code Labs. Guía abierta para aprender: se puede leer, citar y reutilizar (ver permisos). Hay una versión en texto plano para leer con una IA. La segunda parte, con cada módulo al detalle, es el plano completo.
288pantallas (páginas) dentro del sistema
169funciones en la nube
37tareas que corren solas con horario
7.843versiones publicadas desde el 17 de enero de 2026
Antes de ver los módulos, estas son las palabras que se repiten en todo el documento. Ninguna requiere saber programar.
Página web
Un archivo de texto con instrucciones que el navegador (Chrome, Safari) convierte en pantalla. Cada módulo del sistema, como Agenda o Cartera, es una página. Se escriben en tres lenguajes: HTML dice qué hay (un botón, una tabla), CSS dice cómo se ve (color, tamaño) y JavaScript dice qué hace (al tocar el botón, guardar la cita).
Base de datos
Donde se guarda la información para que no se pierda al cerrar la pantalla. Usamos Firestore, de Google. Funciona como un archivero: cada cajón es una colección (citas, empleados, productos) y cada ficha dentro es un documento. Cuando alguien cambia una ficha, todas las pantallas abiertas que la muestran se actualizan solas.
Función en la nube
Un pedazo de programa que no corre en el teléfono de nadie, sino en una computadora de Google que está siempre encendida. Se usa para lo que no debe depender del empleado: mandar un WhatsApp, revisar que una regla se cumpla o hablar con otro proveedor usando una clave secreta.
Tarea programada
Una función en la nube con reloj. Por ejemplo, cada hora a los 5 minutos baja cuánto se gastó en publicidad; cada 5 minutos revisa el GPS de los vehículos.
Disparador
Una función que se despierta sola cuando pasa algo en la base de datos. Ejemplo: se crea una cita y, 3 minutos después, una función manda la imagen al grupo de Ventas.
API
La ventanilla por donde un sistema le pide algo a otro. Meta (WhatsApp, Facebook) tiene una API: nuestro sistema le dice "mandá este mensaje a este número" y Meta lo entrega. Cada proveedor de esta lista se conectó por su API.
Webhook
Lo contrario de una API: es el otro sistema el que nos avisa. Cuando un cliente escribe por WhatsApp, Meta llama a una dirección nuestra y entrega el mensaje en ese instante.
Llave o token
Una contraseña larga que identifica al sistema ante un proveedor. Viven guardadas como secretos en la nube y nunca en las páginas, para que nadie las pueda copiar desde el navegador.
Reglas de seguridad
El portero de la base de datos. Antes de leer o escribir cualquier ficha, Firestore revisa quién lo pide y si tiene permiso. Por ejemplo: nadie, ni gerencia, puede borrar una cita.
Repositorio y publicación
Todo el código vive en GitHub, que guarda cada versión con fecha, autor y descripción. Al subir un cambio, un robot de GitHub lo publica en ecoplagas.online en un par de minutos. Así se sabe siempre qué cambió, cuándo y por qué, y se puede volver atrás.
Modelo de lenguaje (IA)
Un programa entrenado con mucho texto que puede leer, escribir y conversar. Usamos Claude, de Anthropic, para los vendedores virtuales y para revisar textos antes de que lleguen al cliente.
02
Cómo está armado
Todo el sistema son cuatro capas. La de arriba es lo que ve el empleado; la de abajo, los servicios de otras empresas con los que conversa.
La pantalla nunca habla directo con un proveedor: pasa por una función en la nube, que es la única que conoce la llave.
Por qué se eligió así
Sin servidores propios. Google (Firebase) se encarga de que todo esté encendido, respaldado y rápido. No hay una computadora en la oficina que se pueda apagar.
Sin paso de armado. Las páginas se escriben en HTML, CSS y JavaScript directos, con Bootstrap para los componentes comunes. Lo que está en el repositorio es exactamente lo que se publica, lo que hace fácil encontrar y arreglar errores.
En vivo. Firestore avisa de cada cambio a las pantallas abiertas, así que si Ventas mueve una cita, el técnico la ve movida sin recargar.
Un permiso por módulo. Cada pantalla pide una clave de permiso (agenda, cartera, gps360...). En la ficha del empleado se marcan las que tiene.
03
Cómo se trabaja
La mayor parte del código se escribió con Claude Code, una herramienta de inteligencia artificial que programa a partir de instrucciones en español. El ciclo de cada cambio es este:
Se describe el problema del negocio, con datos reales. Ejemplo: "los vendedores aprietan Enviar a Ventas un minuto después de crear la cita, casi siempre".
Se miden los datos antes de decidir. En ese ejemplo, de 458 citas solo 1 se editó en los primeros 30 minutos. Eso justificó automatizarlo.
Se escribe el cambio en la página o en la función correspondiente, imitando el estilo del código que ya existe.
Se prueba. Las reglas de seguridad se prueban contra un emulador (una copia de mentira de la base de datos) con más de 140 casos. El bot de ventas tiene 105 pruebas automáticas.
Se sube a GitHub con una descripción en español. Un robot lo publica en ecoplagas.online en minutos.
Se verifica en vivo, entrando al sitio con un navegador automático como lo haría un usuario.
El equipo reporta con el botón naranja del bicho que está en todas las páginas: la descripción, un pantallazo y los datos técnicos del momento llegan juntos a Gerencia.
04
Ventas y CRM
CRM significa gestor de relaciones con clientes: el lugar donde vive cada conversación con un cliente posible (lead) hasta que compra.
Embudo del vendedor
Qué hace
Cada vendedor ve sus chats de WhatsApp y Messenger ordenados por etapa: nuevo, en conversación, cotizado, agendado. Responde desde ahí mismo.
Cómo se construyó
Meta entrega cada mensaje entrante por webhook a una función, que lo guarda en Firestore. La pantalla escucha esa colección y el chat aparece al instante.
Para responder, la pantalla llama a la función whatsappSend, que es la única con la llave de WhatsApp y que aplica todas las reglas antes de enviar.
Messenger se sumó con una tarea que cada minuto pregunta a Meta por mensajes nuevos de la página de Facebook y los guarda en las mismas colecciones que WhatsApp. Así el reparto, el embudo y las métricas los tratan igual.
Reparto automático de leads
Qué hace
Decide a qué vendedor le toca cada cliente nuevo, sin que nadie lo elija a mano.
Cómo se construyó
Equidad: entre los vendedores conectados (con actividad real en los últimos minutos) va uno a cada uno. Si entra o sale alguien, el conteo vuelve a cero para no castigar a quien llegó después.
Nivelación semanal: le toca a quien lleva menos leads en la semana, respetando el turno de cada uno (diurno, temprano, nocturno, domingo).
Por turno: si el dueño del lead no está en turno y otro vendedor de turno le contesta al cliente, el lead pasa a ese vendedor.
Lead frío: un lead de otro vendedor con más de 3 días sin ningún mensaje pasa a quien lo vuelve a contactar.
Las reglas corren en las funciones de la nube dentro de transacciones, para que dos vendedores no se queden con el mismo cliente al mismo tiempo.
Política de promesas
Qué hace
Impide que se le prometa a un cliente lo que la ley obliga a cumplir y no se puede garantizar: "erradicación total", "100 %", "nunca más".
Cómo se construyó
Una lista de frases prohibidas basada en la Ley 7472 (lo ofrecido obliga).
Un revisor que entiende trampas: quita tildes, letras repetidas, números escritos con letras y errores de una o dos letras.
Lo aplica el servidor antes de enviar, así que no se puede saltar desde el teléfono. Si el mensaje trae una promesa, no sale y el vendedor ve por qué.
Llamada obligatoria a leads de Google
Qué hace
Cuando un vendedor responde por primera vez a un lead de Google, arranca un reloj de 3 horas para llamarlo.
Cómo se construyó
Se midió que el 65 % de las ventas de Google cierran en las primeras 3 horas. La función de envío marca la hora de inicio y el vencimiento; el embudo muestra un reloj y, al vencer, un aviso rojo. La llamada se documenta con resultado, duración y pantallazo.
05
Agenda y rutas
La agenda es la pantalla central: cada cita con su cliente, servicio, monto, ruta, técnicos y vehículo.
Agenda y ruteador
Qué hace
Muestra el día como una línea de tiempo por ruta. El ruteador asigna técnicos y vehículos a cada ruta.
Cómo se construyó
Cada cita es un documento en Firestore con su día, hora y datos. El "día operacional" va de 5:00 a. m. a 4:59 a. m., para que el turno nocturno no se parta en dos fechas.
Cada cambio queda en un historial que solo crece: nadie puede borrar lo que pasó.
Mapas con Leaflet (una librería gratuita) y polígonos de provincias, cantones y distritos.
Compás: dónde cabe una cita
Qué hace
El vendedor pega la ubicación del cliente y la herramienta le dice en qué hueco de qué ruta agrega menos manejo.
Cómo se construyó
Se analizaron 2.491 citas: el manejo era un tercio del tiempo, y lo que más ahorraba era elegir bien el día y la ruta, no el orden.
Con 2.387 trayectos reales de Google se sacó una fórmula simple para estimar minutos de manejo a partir de la distancia, con error típico de 2,5 minutos.
Para cada hueco calcula el desvío: ir de la cita anterior al cliente y del cliente a la siguiente, menos lo que ya se manejaba.
Envío automático a Ventas
Qué hace
Tres minutos después de crear una cita, el grupo de Ventas recibe una imagen con la cita y el mapa de la ruta con tiempos de manejo.
Cómo se construyó
Un disparador espera 3 minutos, relee la cita para confirmar que está completa, dibuja la imagen en la nube con un navegador sin pantalla (Puppeteer y Chromium), pide los tiempos de manejo a OSRM (un servicio gratuito de rutas sobre OpenStreetMap) y la manda por WhatsApp.
06
GPS de la flota
Cada vehículo tiene un GPS que reporta su posición. El sistema convierte esos puntos en información útil.
Qué hace
GPS 360: el día de un vehículo como un muro: paradas en citas, en la central o fuera de ruta, con mapa.
Alertas por WhatsApp: exceso de velocidad (más de 90 km/h sostenido), paradas fuera de ruta y llegadas tardías a la primera cita.
Velocidad por empleado: un ranking diario en Reportes.
Cómo se construyó
Un receptor guarda los puntos de cada vehículo cada 2 minutos en Firestore.
Una "parada" es estar 5 minutos dentro de 60 metros. Si cae a menos de 500 m de una cita, es esa cita.
Para no acusar a nadie por echar gasolina, se cargaron 334 gasolineras de OpenStreetMap: una parada corta ahí no cuenta como fuera de ruta.
Los saltos falsos del aparato (un 159 km/h que duró un instante) se filtran comparando distancia y tiempo entre puntos.
El punto se le atribuye al técnico según la ruta que tenía asignada ese vehículo a esa hora en el ruteador.
07
Inventario
Bodega principal, bodegas personales de cada técnico y bodegas de vehículo, con cada movimiento registrado.
Qué hace
Transferencias entre bodegas, gastos por cita, auditorías de conteo y arqueos.
Saldos mínimos: cada bodega tiene un kit según su tipo (diurno, nocturno, termitero, vehículo) y la pantalla dice qué le falta.
Reposición: recomienda el pedido semanal.
Cómo se construyó
Todo movimiento pasa por una sola función compartida que resta de un lado y suma del otro al mismo tiempo, para que nunca quede producto "en el aire".
Reposición mide el consumo real de 28 días (lo que salió y no volvió, sin contar los traslados), le suma un colchón según qué tan a saltos se usa cada producto y apunta a 14 días de cobertura.
08
Planilla y personal
Qué hace
Registro de horas, planilla mensual e histórico.
Liquidación de colaboradores con aguinaldo, vacaciones, preaviso y cesantía.
Cuentas por cobrar.
Fichas de empleado con permisos, departamento y nombre preferido.
Cómo se construyó
La liquidación copia exactamente el método de la hoja de cálculo que usaba Gerencia: promedio de los salarios brutos de los últimos 6 meses, salario diario y cada rubro multiplicado por sus días. La diferencia es que los salarios ya no se digitan: se calculan en vivo desde la planilla del sistema, y cualquier dato se puede corregir a mano antes de imprimir.
09
Ministerio de Salud y capacitación
Una empresa fumigadora tiene que demostrar que su personal está capacitado y que sus productos y bodegas cumplen la norma.
Qué hace
8 cursos obligatorios del Decreto 38335 con vigencia de 12 meses y constancia imprimible.
20 videos de capacitación narrados (2 h 38 min en total) y 52 cuestionarios.
Manual de procedimientos, plan de residuos y plan de emergencias.
Módulo de cumplimiento: cada requisito con responsable, fecha y evidencia; hojas de seguridad y registros de cada producto.
Certificados de fumigación con código que el cliente puede verificar en línea.
Cómo se construyó
Las preguntas de los cursos legales son de situación ("estás en la ruta y pasa esto") y la respuesta correcta se reparte entre las opciones para que no se pueda adivinar.
Un curso se da por aprobado con 90 %; la vigencia cuenta desde el último intento aprobado. Gerencia ve en un panel quién está al día.
Los videos se generaron con láminas en alta definición y narración con voz sintética de ElevenLabs.
10
Clientes y calidad
Qué hace
Portal del cliente: el cliente entra y ve sus sedes, servicios, reportes y próximas visitas.
Garantías: el cliente llena un formulario con fotos desde un enlace, sin cuenta.
Encuestas de satisfacción después del servicio.
Tickets de citas en rojo: un reporte grave abre un caso al vendedor.
Cómo se construyó
El portal del cliente no se reescribió a mano: un programa copia la ficha de cliente de Cartera y solo le cambia cómo inicia sesión. Así lo que ve el cliente es lo mismo que ve el equipo.
El formulario de garantía funciona con un enlace que lleva un código único y difícil de adivinar. Ese código es la llave: solo quien lo tiene ve ese caso.
Las fotos se comprimen en el teléfono del cliente antes de subirse, para que funcione con señal débil.
11
Números del negocio
Qué hace
Reporte de canales: cuánto se invirtió en cada plataforma de publicidad y cuánto vendió cada una (retorno sobre la inversión, ROAS).
Conversión por vendedor: de cada 100 leads, cuántos compran, semana por semana.
Táctico y estrategia: metas del mes y tendencias de largo plazo.
Cómo se construyó
Una tarea corre cada hora: le pide a Meta el gasto del día por API y recibe el de Google desde un pequeño programa que vive dentro de la cuenta de Google Ads y nos lo envía.
Cada venta se conecta con su origen mediante un código que viaja en el primer mensaje de WhatsApp desde el sitio web.
Las ventas se devuelven a Google y Meta como conversiones, para que sus algoritmos aprendan qué clientes compran de verdad.
12
Inteligencia artificial
La IA se usa donde ahorra tiempo o evita errores, siempre con reglas que la limitan.
Vendedores virtuales Melissa y Adrián
Qué hace
Atienden por WhatsApp: preguntan lo necesario, cotizan, proponen espacios reales en la agenda y crean la cita.
Cómo se construyó
Una máquina de estados (cada conversación sabe en qué paso va) más el modelo Claude para entender y redactar.
Para proponer horarios usa el mismo motor de espacios del sistema y valida contra la agenda antes de crear la cita.
Ubica al cliente en su distrito con mapas de polígonos y revisa si la zona tiene cobertura.
Topes, opción de no recibir más mensajes y paso a un humano cuando hace falta. 105 pruebas automáticas.
Simulador de campo
Qué hace
El técnico practica por voz con un cliente difícil (un chef molesto, una señora con bebé y mascotas). Al final recibe una calificación.
Cómo se construyó
Cuatro personajes sacados de casos reales, cada uno con una voz distinta de ElevenLabs. La conversación se transcribe y se califica con IA contra lo que el técnico debía decir.
Revisión de la nota al cliente
Qué hace
Antes de que el cliente vea la nota del técnico, la IA revisa que no tenga groserías, juicios sobre su casa, datos de terceros ni promesas prohibidas.
Cómo se construyó
Un disparador manda la nota a Claude con instrucciones estrictas: si no hay problema, el texto queda letra por letra; si hay, cambia solo esa frase. Si la corrección no queda limpia, la nota no se publica.
Producción de contenido
Qué hace
Anuncios de video, videos de capacitación, video semanal de la reunión de técnicos y artes de catálogo.
Cómo se construyó
Guiones escritos con IA, voz con ElevenLabs (incluida una voz clonada), imágenes con Gemini de Google y armado del video con FFmpeg, un programa gratuito para editar video por instrucciones.
13
Servicios externos y APIs
El sistema no hace todo solo: se apoya en servicios de otras empresas, cada uno experto en una cosa (mandar WhatsApp, generar voz, calcular rutas). Esta sección explica cada uno: qué es, para qué lo usamos y cómo se conectó.
Cómo funciona una conexión, con un ejemplo. Cuando el sistema necesita que Melissa diga una frase en voz alta, una función en la nube le manda a ElevenLabs el texto y el código de la voz, junto con nuestra llave secreta. ElevenLabs responde con un archivo de audio. Esa conversación de pedido y respuesta es la API. Todas las conexiones de esta sección funcionan igual: cambia quién responde y qué devuelve.
Cómo se protegen las conexiones
20 llaves secretas guardadas en el gestor de secretos de Google. Solo las funciones de la nube las pueden leer; ninguna página ni teléfono las conoce.
Los mensajes que entran se verifican. Cuando Meta nos avisa de un WhatsApp nuevo, el aviso trae una firma que se comprueba con una llave compartida. Un aviso falso se descarta.
Las conexiones de entrada tienen su propia clave. El programa de Google Ads que nos envía el gasto, por ejemplo, tiene que presentar una clave que solo él conoce.
Cada llave tiene el mínimo permiso. La de publicidad puede leer campañas pero no mandar WhatsApp, y al revés.
Si se quiere construir algo parecido
Cada ficha de esta sección termina con Para replicarlo: los pasos para conectar ese servicio en un proyecto propio. Antes de abrir cuentas, este es el orden que funcionó:
Empezar por un problema medible del negocio, no por la herramienta. "Los vendedores tardan en mandar la cita" es un problema; "quiero usar IA" no lo es. Escribir qué número debería cambiar.
Guardar los datos primero. Antes de automatizar, que cada cita, lead o movimiento quede en la base de datos con fecha y responsable. Sin datos no hay forma de saber si el cambio sirvió.
Montar la base: un proyecto de Firebase, un repositorio en GitHub y la publicación automática. Con eso ya se puede publicar una página con inicio de sesión.
Conectar un servicio a la vez, siempre desde una función en la nube y con la llave guardada como secreto. Nunca poner una llave dentro de una página.
Probar con casos reales y medir el número del paso 1 antes y después.
Pedirle a una IA que programe (Claude Code, por ejemplo) funciona mejor si se le da el problema, los datos de ejemplo y las reglas del negocio por escrito, y se le pide que pruebe lo que hace antes de publicarlo.
La receta que se repite
Casi todas las conexiones tienen la misma forma: la pantalla le pide algo a una función propia, la función agrega la llave secreta, llama al servicio externo y devuelve solo lo necesario. Este es el esqueleto, con Claude como ejemplo:
// functions/index.js (Firebase Functions, Node.js)const { onCall, HttpsError } = require("firebase-functions/v2/https");
const { defineSecret } = require("firebase-functions/params");
// La llave se guarda una vez con: firebase functions:secrets:set ANTHROPIC_API_KEYconst llave = defineSecret("ANTHROPIC_API_KEY");
exports.resumirNota = onCall({ secrets: [llave] }, async (req) => {
// 1. Solo usuarios con sesiónif (!req.auth) throw new HttpsError("unauthenticated", "Inicie sesión");
// 2. Llamar al servicio externo con la llave, que nunca sale del servidorconst r = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": llave.value(),
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify({
model: "NOMBRE-DEL-MODELO",
max_tokens: 300,
system: "Resumí la nota del técnico en una frase, sin inventar nada.",
messages: [{ role: "user", content: req.data.nota }],
}),
});
if (!r.ok) throw new HttpsError("internal", "El servicio no respondió");
// 3. Devolver solo lo que la pantalla necesitaconst datos = await r.json();
return { resumen: datos.content[0].text };
});
Desde la página se llama con httpsCallable(functions, "resumirNota")({ nota }). Cambiando la dirección, las cabeceras y el cuerpo, el mismo esqueleto sirve para ElevenLabs, Meta o cualquier otro servicio de esta lista.
La base: Google y GitHub
Google Firebase
Qué es
El conjunto de servicios de Google para construir aplicaciones sin administrar servidores.
Para qué
Todo el sistema vive aquí. Hosting sirve las 288 páginas; Firestore es la base de datos; Cloud Functions corre las 169 funciones; Authentication maneja el inicio de sesión; Storage guarda fotos, audios, PDF y pantallazos.
Cómo se conectó
Las páginas cargan las librerías oficiales de Firebase directamente desde los servidores de Google, sin instalar nada. Las funciones se escriben en Node.js (JavaScript para servidores) y se publican junto con el resto del código.
Para replicarlo
Crear el proyecto en console.firebase.google.com con una cuenta de Google.
Pasar el proyecto al plan Blaze (pago por uso). Es obligatorio para tener funciones y para que esas funciones llamen a otros servicios. Conviene poner de entrada una alerta de presupuesto en Google Cloud.
Instalar Node.js y luego la herramienta de Firebase: npm install -g firebase-tools, después firebase login y firebase init en la carpeta del proyecto (elegir Hosting, Firestore y Functions).
Escribir las reglas de Firestore desde el día uno, cerradas por defecto, y probarlas con el emulador (firebase emulators:start).
Publicar con firebase deploy.
Lo que más cuesta en la factura no es el cómputo sino la transferencia de datos: pantallas que leen colecciones enteras. Leer solo lo necesario (filtrar por fecha, paginar) desde el principio.
Google Cloud Speech-to-Text
Qué es
El servicio de Google que convierte una grabación de voz en texto.
Para qué
Pasar a texto los audios que se dictan en el sistema, para poder leerlos y buscarlos.
Cómo se conectó
El audio ya está guardado en Storage, así que la función le pasa a Google la dirección del archivo en vez de enviarlo. Se usa el modo largo, porque el modo rápido solo acepta un minuto y un dictado de dos minutos lo rebasa.
Para replicarlo
En console.cloud.google.com, dentro del mismo proyecto de Firebase, activar "Cloud Speech-to-Text API".
Las funciones de Firebase ya tienen una identidad de Google (cuenta de servicio), así que no hace falta otra llave: se pide un token de acceso desde la función.
Para audios de más de un minuto, guardarlos primero en Storage y usar el modo longrunningrecognize con la dirección gs:// del archivo.
Indicar idioma es-CR (o el del país) para mejorar la precisión.
Google BigQuery
Qué es
Un almacén de datos de Google para hacer consultas sobre millones de filas.
Para qué
Recibe el detalle diario de la factura de Google Cloud, para saber qué parte del sistema cuesta más.
Cómo se conectó
Exportación automática de facturación activada desde la consola de Google.
Para replicarlo
En Google Cloud, menú Facturación, entrar a "Exportación de facturación".
Activar "Exportación de costos de uso estándar" hacia un conjunto de datos de BigQuery del mismo proyecto.
Esperar un día y consultar con SQL qué servicio y qué recurso gasta más.
GitHub
Qué es
El servicio más usado del mundo para guardar código con su historial completo.
Para qué
Cada cambio queda registrado con fecha, autor y descripción (7.843 a la fecha). Si algo se rompe, se ve qué cambió y se vuelve atrás.
Cómo se conectó
Con GitHub Actions: un robot que, cada vez que entra un cambio, publica las páginas en Firebase y, si se tocaron funciones, publica también las funciones. Los cambios hacen fila para que uno viejo nunca pise a uno nuevo.
Para replicarlo
Crear un repositorio privado y subir la carpeta del proyecto.
En Google Cloud, crear una cuenta de servicio con permiso para publicar en Firebase, bajar su llave JSON y guardarla en GitHub como secreto del repositorio (Settings, Secrets).
Agregar un archivo de flujo en .github/workflows/ que, en cada cambio a la rama principal, instale firebase-tools y corra firebase deploy con esa cuenta.
Agregar concurrency al flujo para que dos publicaciones seguidas hagan fila y la más vieja no pise a la nueva.
Activar una alerta de facturación en GitHub: si el pago falla, las Actions se detienen sin avisar en el sitio.
Mensajería y llamadas
WhatsApp Cloud API (Meta)
Qué es
La versión oficial de WhatsApp para empresas, que se maneja por programa en vez de por la app del teléfono.
Para qué
La línea oficial de ventas: recibir y contestar clientes desde el CRM, mandar plantillas (mensajes aprobados por Meta para escribir primero), y que los bots Melissa y Adrián atiendan.
Cómo se conectó
Entrada: un webhook. Meta llama a nuestra función en el momento en que llega un mensaje, con su firma; la función lo verifica, lo guarda y baja fotos y audios a Storage.
Salida: la función whatsappSend, que revisa reglas (promesas prohibidas, clientes que pidieron no recibir mensajes, reparto por turno) antes de pedirle a Meta que envíe.
Plantillas: se crean por API, incluida la imagen de encabezado, que se sube por la API de carga de archivos de Meta.
Para replicarlo
Tener un Business Manager de Meta (business.facebook.com) y, si es posible, verificar la empresa con sus documentos: sin verificación los límites de envío son bajos.
En developers.facebook.com, crear una app tipo Business y agregarle el producto WhatsApp.
Agregar el número de teléfono. Ese número no puede estar al mismo tiempo en la app normal de WhatsApp: hay que darlo de baja ahí primero.
Crear un usuario del sistema en Business Manager y generar un token permanente con los permisos de WhatsApp. El token temporal de la consola vence en 24 horas.
Configurar el webhook: una función pública que responda la verificación inicial con un código que uno inventa, y que en cada aviso compruebe la firma X-Hub-Signature-256 con el secreto de la app.
Enviar con POST https://graph.facebook.com/v21.0/{id-del-número}/messages.
Para escribirle primero a un cliente, o después de 24 horas sin que escriba, hace falta una plantilla aprobada por Meta. Responder dentro de esas 24 horas no requiere plantilla.
Messenger (Meta)
Qué es
El chat de la página de Facebook.
Para qué
Que los mensajes de Facebook entren al mismo CRM que WhatsApp y se repartan igual.
Cómo se conectó
Una tarea programada cada minuto le pregunta a Meta por conversaciones nuevas, guarda cada chat con un identificador que empieza por fb_ y copia los adjuntos. Al responder, la misma función de WhatsApp detecta ese prefijo y envía por Messenger.
Para replicarlo
En la misma app de Meta, agregar Messenger y conectar la página de Facebook.
Pedir los permisos pages_messaging y pages_read_engagement y generar un token de página.
Elegir cómo entran los mensajes: por webhook (al instante) o preguntando cada minuto a /{página}/conversations, que es más simple de montar y no se pierde nada si el webhook falla.
Responder con POST /{página}/messages. Igual que WhatsApp, fuera de las 24 horas hay restricciones.
WhatsApp de empresa (whatsapp-web.js)
Qué es
Una librería gratuita que maneja WhatsApp Web de forma automática, como si una persona lo tuviera abierto.
Para qué
Monitorear y contestar los teléfonos de empresa de los vendedores, y mandar los avisos internos a grupos: alertas de GPS, citas a Ventas, reportes.
Cómo se conectó
Un agente propio corre en un servidor Linux rentado en Costa Rica (CR Servers), con una sesión por teléfono. El sistema no le habla directo: escribe el mensaje en una cola de Firestore y el agente lo recoge y lo envía. Si el servidor se reinicia, la cola espera.
Para replicarlo
Rentar un servidor Linux pequeño que esté siempre encendido (un VPS) e instalar Node.js y Chrome.
Instalar la librería: npm install whatsapp-web.js. Al arrancar muestra un código QR que se escanea con el teléfono, como WhatsApp Web.
Guardar la sesión en disco para no escanear de nuevo en cada reinicio.
No mandar directo desde el sistema: escribir los mensajes en una cola en la base de datos y que el agente los envíe uno por uno, marcando cada uno como enviado.
Advertencia: no es un producto oficial de WhatsApp. Usarlo para envíos masivos o a desconocidos puede terminar en el bloqueo del número. Sirve para teléfonos propios y avisos internos; para clientes, la vía correcta es la API oficial.
Twilio
Qué es
Un proveedor de llamadas y mensajes de texto por programa.
Para qué
Llamar a clientes desde el CRM, también a teléfonos fijos o sin WhatsApp.
Cómo se conectó
Construido y probado, con llamada desde el navegador. Está en pausa: Twilio no vende números de Costa Rica y la verificación del número local nunca timbró en los operadores ticos.
Para replicarlo
Antes de construir, revisar en twilio.com si venden números del país. Para Costa Rica no venden, y eso fue lo que dejó este proyecto en pausa.
Crear la cuenta, cargar saldo y activar los permisos geográficos de llamadas al país.
Comprar un número o verificar uno propio como identificador de llamada.
Para llamar desde el navegador se usa su Voice SDK con un token de acceso que genera una función.
Publicidad y medición
Meta Marketing API y Conversions API
Qué es
Las APIs de Meta para manejar anuncios de Facebook e Instagram y para informarle qué pasó después del clic.
Para qué
Bajar el gasto de cada campaña cada hora, crear campañas y anuncios, y devolverle a Meta las ventas reales.
Cómo se conectó
Una tarea cada hora consulta el gasto con una llave de solo lectura. Otra tarea cada 6 horas cruza las citas con monto contra la conversación que las originó y le envía a Meta un evento de compra. Así Meta aprende a mostrar anuncios a gente que compra, no solo a gente que escribe.
Para replicarlo
En la app de Meta, pedir los permisos ads_read (leer gasto) y ads_management (crear campañas).
Generar el token desde un usuario del sistema con acceso a la cuenta publicitaria.
Gasto: GET /act_{cuenta}/insights con el rango de fechas y el nivel (campaña, conjunto o anuncio).
Ventas: enviar un evento Purchase a /{píxel}/events con el monto y el teléfono del cliente cifrado con SHA-256. Meta nunca recibe el dato en claro.
Para unir la venta con el anuncio, guardar desde el primer mensaje de qué anuncio vino la conversación.
Facebook e Instagram (Graph API)
Qué es
La API general de Meta para páginas y cuentas.
Para qué
Publicar fotos, videos, reels y carruseles, y leer comentarios.
Cómo se conectó
Con la llave de la página de Facebook, que también controla la cuenta de Instagram vinculada.
Para replicarlo
Conectar la cuenta de Instagram (tipo empresa) a la página de Facebook.
Pedir los permisos pages_manage_posts e instagram_content_publish.
Facebook: POST /{página}/photos o /videos.
Instagram: se publica en dos pasos. Primero se crea el contenido con una URL pública del archivo; cuando Meta termina de procesarlo, se publica.
Google Ads
Qué es
La plataforma de anuncios en el buscador de Google y en YouTube.
Para qué
Saber el gasto por hora y devolverle a Google las ventas que generó cada clic.
Cómo se conectó
Gasto: un pequeño programa que vive dentro de la cuenta de Google Ads corre cada hora y nos envía los números a una función, con su propia clave.
Ventas: el sitio guarda el identificador de cada clic de Google junto al código de la conversación. Una función publica una lista de ventas que Google Ads descarga sola de forma programada.
Para replicarlo
Gasto: en Google Ads, Herramientas, Acciones masivas, Scripts. Crear un script que lea el gasto del día y lo mande con UrlFetchApp.fetch a una función propia, con una clave en la cabecera. Programarlo cada hora.
Ventas: en el sitio, guardar el parámetro gclid que trae cada clic y pasarlo con la conversación.
Crear en Google Ads una acción de conversión de tipo "Importación".
Publicar un archivo CSV en una dirección HTTPS con gclid, fecha y monto de cada venta, y programar en Google Ads la importación diaria desde esa dirección.
Este camino evita tramitar el acceso a la API de Google Ads, que pide un token de desarrollador con aprobación.
TikTok Ads y Microsoft Ads
Qué es
Las plataformas de anuncios de TikTok y del buscador Bing.
Para qué
Campañas que llevan a WhatsApp (TikTok) y campañas de búsqueda (Bing).
Cómo se conectó
Se administran desde sus plataformas. Sus visitas se distinguen en el reporte de canales por la marca que trae el enlace del anuncio.
Para replicarlo
Poner parámetros utm_source y utm_campaign en todos los enlaces de anuncios.
Guardar esos parámetros junto con el primer mensaje del cliente, para saber el origen de cada venta.
Microsoft Ads permite importar campañas de Google Ads directamente.
Google Analytics, píxel de Meta y Microsoft Clarity
Qué es
Herramientas de medición del sitio web. Clarity, además, graba sesiones anónimas y dibuja mapas de calor.
Para qué
Entender qué hacen los visitantes de ecoplagas.cr y qué anuncio los trajo.
Cómo se conectó
Su código se carga recién con el primer gesto del visitante (tocar, desplazar) o a los 6 segundos. Eso mantiene el sitio en 100 de velocidad sin perder los clics a WhatsApp, que quedan en espera y salen igual.
Para replicarlo
Crear la propiedad en cada herramienta y copiar su código.
En vez de cargarlos en la cabecera, definir primero funciones vacías (gtag, fbq) que guardan los eventos en una lista.
Cargar los códigos reales con el primer scroll, touchstart o keydown, o a los 6 segundos. Los eventos guardados salen en ese momento.
Medir el resultado con PageSpeed Insights antes y después.
Inteligencia artificial y voz
Anthropic (Claude)
Qué es
La empresa que desarrolla Claude, un modelo de lenguaje: un programa que lee, entiende y escribe texto.
Para qué
Los vendedores virtuales Melissa y Adrián, la revisión de la nota del técnico antes de que la vea el cliente, la calificación del simulador de campo y mejorar comentarios dictados. Además, Claude Code escribió la mayor parte del código del sistema.
Cómo se conectó
Las funciones le mandan a la API de Anthropic las instrucciones y la conversación, y reciben la respuesta. Se usan modelos distintos según la tarea: uno rápido y barato para lo frecuente y uno más capaz donde un error cuesta caro.
Para replicarlo
Crear la cuenta en console.anthropic.com, cargar saldo y generar una llave.
Guardarla como secreto de la función: firebase functions:secrets:set ANTHROPIC_API_KEY.
Llamar a POST https://api.anthropic.com/v1/messages con las cabeceras x-api-key y anthropic-version, el modelo, las instrucciones y el mensaje.
Pedir la respuesta en un formato fijo (por ejemplo JSON con campos definidos) y validarla antes de usarla. Si la respuesta no cumple, no se aplica.
Usar un modelo pequeño para tareas frecuentes y uno grande donde equivocarse sale caro. Se cobra por cantidad de texto procesado.
ElevenLabs
Qué es
Un servicio que genera voz humana a partir de texto y que puede clonar una voz real con pocas muestras.
Para qué
Narración de los 20 videos de capacitación y del video semanal de la reunión de técnicos.
Locución de anuncios con la voz clonada de Pablo.
Los cuatro clientes difíciles del simulador de campo, cada uno con su voz y personalidad.
Cómo se conectó
Para los videos, un programa le manda cada párrafo del guion y recibe el audio, que luego se une a las imágenes. Para el simulador se usan sus agentes conversacionales: la función crea o actualiza el personaje y le entrega al navegador un enlace firmado de un solo uso, para que el técnico hable sin que la llave salga de la nube. Al terminar, la función baja la transcripción para calificarla.
Para replicarlo
Crear la cuenta en elevenlabs.io y generar una llave en el perfil. Los planes se cobran por cantidad de caracteres convertidos a voz.
Elegir una voz de la biblioteca y copiar su identificador (voice_id).
Voz a partir de texto: POST https://api.elevenlabs.io/v1/text-to-speech/{voice_id} con el texto; devuelve el audio.
Para clonar una voz se suben muestras limpias de la persona, con su consentimiento.
Para conversaciones en vivo, crear un agente conversacional. La función pide un enlace firmado y se lo pasa al navegador; la llave nunca sale del servidor.
Google Gemini
Qué es
El modelo de inteligencia artificial de Google, que también genera y edita imágenes.
Para qué
Imágenes para publicidad, artes del catálogo de la tienda y material de capacitación.
Cómo se conectó
Por su API, con instrucciones escritas y, cuando hace falta, fotos reales de referencia.
Para replicarlo
Generar una llave en aistudio.google.com.
Llamar al modelo de imágenes con una descripción escrita y, si se quiere mantener un estilo o un producto real, adjuntar fotos de referencia.
Revisar cada imagen antes de publicarla: la IA puede inventar textos o detalles del producto.
Mapas y ubicación
Google Maps
Qué es
Los mapas de Google, usados por programa.
Para qué
Mapas en pantalla y tiempos reales de trayecto entre citas del ruteador.
Cómo se conectó
Con una llave restringida a solo dos servicios (mapas y rutas) y solo a nuestros dominios, para que nadie la pueda aprovechar si la copia.
Para replicarlo
En Google Cloud, activar solo las APIs que se van a usar (Maps JavaScript y Directions, por ejemplo).
Crear la llave y restringirla de dos formas: por API (solo esas) y por sitio web (solo los dominios propios).
Poner una alerta de presupuesto. Google da un uso gratuito mensual y cobra después.
OSRM
Qué es
Un motor gratuito que calcula rutas en carretera sobre los mapas de OpenStreetMap.
Para qué
Los minutos de manejo que salen en la imagen de cada cita enviada a Ventas.
Cómo se conectó
Consulta directa al servidor público, con un límite de 8 segundos. Si no responde, la imagen sale igual, sin tiempos.
Para replicarlo
Pedir una ruta: https://router.project-osrm.org/route/v1/driving/{lng},{lat};{lng},{lat}. Ojo: primero longitud y después latitud.
La respuesta trae duración en segundos y distancia en metros.
Poner siempre un límite de tiempo y un plan B si no responde.
El servidor público es para uso liviano. Con volumen alto, montar un OSRM propio con el mapa del país, que es gratuito.
OpenStreetMap, Nominatim y Esri
Qué es
OpenStreetMap es el mapa libre del mundo, hecho por voluntarios. Nominatim es su buscador de direcciones. Esri (ArcGIS) ofrece mosaicos de mapa de calles.
Para qué
Traducir coordenadas a una dirección legible, ubicar las 334 gasolineras usadas en las alertas de GPS, y dibujar el mapa de fondo en las imágenes que se generan en el servidor.
Cómo se conectó
Las gasolineras se descargaron una vez y viven en un archivo propio. Los mapas se dibujan con Leaflet, una librería gratuita, tanto en pantalla como en el servidor.
Para replicarlo
Nominatim: https://nominatim.openstreetmap.org/reverse?lat=..&lon=..&format=json. Su política pide máximo una consulta por segundo e identificarse con un User-Agent propio.
Lugares de un tipo (gasolineras, farmacias): bajarlos una sola vez con Overpass Turbo (por ejemplo amenity=fuel en el país) y guardarlos en un archivo propio.
Mapas en pantalla: Leaflet con los mosaicos de OpenStreetMap, citando la fuente.
Esri: crear una cuenta en location.arcgis.com y una llave. Las llaves tienen fecha de vencimiento: anotarla.
DB-IP y mapa de distritos
Qué es
DB-IP es una base gratuita que relaciona direcciones de internet (IP) con lugares aproximados. El mapa de distritos son los límites oficiales de los distritos de Costa Rica en formato digital.
Para qué
Saber de qué distrito aproximado nos visita alguien en ecoplagas.cr, y en qué distrito está un cliente a partir de su ubicación.
Cómo se conectó
Un programa cruza cada rango de IP con el polígono de su distrito y guarda el resultado en un archivo. No hay consulta externa en cada visita: todo se resuelve en nuestra función.
Para replicarlo
Bajar el archivo gratuito "IP to City Lite" de db-ip.com. Su licencia (CC BY 4.0) pide citar la fuente.
Conseguir los límites de distritos del país en formato GeoJSON (hay repositorios públicos para Costa Rica).
Con un programa, ubicar cada coordenada dentro de su polígono y guardar una tabla de rango de IP a distrito.
Repetirlo cada pocos meses: las IP cambian de dueño.
GPS de los vehículos
Qué es
Rastreadores instalados en cada vehículo de la flota.
Para qué
GPS 360, alertas de velocidad y de paradas fuera de ruta, puntualidad y reporte de velocidad por empleado.
Cómo se conectó
Un receptor guarda los puntos de cada vehículo cada 2 minutos en Firestore. Desde ahí, un disparador revisa cada lote nuevo y una tarea cada 5 minutos busca rutas cerradas.
Para replicarlo
Al contratar el GPS, preguntar si ofrece API o si puede reenviar las posiciones a una dirección propia. Sin eso, los datos quedan encerrados en su plataforma.
Guardar cada punto con vehículo, hora, coordenadas y velocidad.
Definir con datos qué es una parada (tiempo y radio) y filtrar los saltos imposibles comparando distancia y tiempo entre puntos.
Pagos
Onvo
Qué es
Una pasarela de pagos costarricense para cobrar con tarjeta en línea.
Para qué
Que el cliente pague su servicio desde el cotizador de ecoplagas.cr.
Cómo se conectó
Con enlaces de pago por paquete, que el cotizador muestra según lo que el cliente eligió.
Para replicarlo
Crear la cuenta de comercio en onvopay.com y completar la verificación.
Crear un enlace de pago por producto o paquete desde su panel.
Para cobros con monto variable o confirmación automática, usar su API y sus avisos por webhook.
Herramientas gratuitas que usa el sistema
Además de los servicios, el sistema usa librerías de código abierto: programas gratuitos que cualquiera puede usar.
Librería
Para qué
Bootstrap
Botones, ventanas y tablas con apariencia uniforme en todas las páginas.
Leaflet
Mapas de rutas, GPS, zonas y clientes.
Chart.js
Gráficos de los reportes.
jsPDF y html2canvas
Convertir una pantalla en PDF o en imagen (certificados, pantallazos del botón del bicho).
QRCode
Códigos QR de certificados y estaciones de cebo.
browser-image-compression
Achicar fotos en el teléfono antes de subirlas.
Puppeteer y Chromium
Un navegador sin pantalla dentro de la nube que dibuja imágenes de citas, rutas y alertas para mandarlas por WhatsApp.
FFmpeg
Armar y cortar videos por instrucciones: anuncios, capacitaciones y reuniones.
Font Awesome
Íconos del sistema.
14
Lógicas que funcionaron, en pseudocódigo
Esta sección es para quien quiera replicar los módulos, o para pasársela a una IA que programe. Cada bloque explica el problema, la lógica en pseudocódigo (instrucciones en español con forma de programa, sin depender de un lenguaje) y lo que se aprendió. No incluye llaves, identificadores ni datos privados. Los números (umbrales, horarios, porcentajes) son los que funcionaron en EcoPlagas: conviene validarlos con los datos de cada negocio.
Principios que se repiten en todos los módulos
Antes de los módulos, estas reglas de diseño aparecen una y otra vez. Una IA que quiera replicar el sistema debería aplicarlas por defecto.
PRINCIPIO 1 El servidor decide, la pantalla solo muestra
toda regla que importa (quién recibe un lead, si un mensaje sale,
quién puede cambiar un monto) vive en una función o en las reglas
de la base de datos, nunca solo en la página
PRINCIPIO 2 Nada se borra
cita cancelada -> se mueve a "Ruta 20" (bolsa de canceladas)
cada cambio -> se agrega al historial de la ficha
historial -> solo puede crecer (la regla exige que la lista
nueva contenga entera a la vieja)
PRINCIPIO 3 Releer antes de actuar
al despertar una tarea, volver a leer la ficha:
pudo cambiar entre que se programó y que corrió
PRINCIPIO 4 Idempotencia: hacer dos veces = hacer una vez
antes de enviar algo, buscar el registro "ya enviado" con el mismo id
si existe -> no repetir
PRINCIPIO 5 Transacción cuando dos personas pueden chocar
leer + decidir + escribir dentro de una misma transacción
PRINCIPIO 6 Día operacional
el día va de 5:00 a. m. a 4:59 a. m. del día siguiente,
para que el turno nocturno no quede partido en dos fechas
PRINCIPIO 7 Configuración en la base de datos, no en el código
umbrales, interruptores y horarios en un documento "config"
-> se apagan o ajustan sin publicar código
PRINCIPIO 8 Medir antes de decidir, y rellenar el pasado
si la regla cambia, correr un "backfill" que la aplica
a los días anteriores como si hubiera existido
Modelo de datos
La base de datos es documental: colecciones de fichas. Estas son las principales y sus campos clave.
citas/{id}
fecha_dia: "AAAA-MM-DD" // día operacional, para filtrar rápido
inicio, fin
cliente: { nombre, telefono_normalizado, gps }
servicios: [ { servicioId, cantidad, precio } ]
monto // protegido: solo servidor o permiso especial
ruta: "Ruta 1".."Ruta 19" | "Ruta 20" (canceladas)
vendedores: [ username ]
reporte: { condicion, plagas, fotos, comentario_tecnico }
extras/historial: [ { campo, antes, despues, quien, cuando } ]
ruteador/{fecha}
rutas: { "Ruta 1": { tecnicos: [..], vehiculo, convocatoria } }
conversaciones/{telefono} // WhatsApp; Messenger usa "fb_<id>"
assignedTo, source, sourceAt, ultimoMensajeAt, ultimoDireccion
etapa, esCliente, optOut
mensajes/{id} { telefono, direccion, texto, media, enviadoPor, at }
empleados/{username} { permisos: {clave: true}, departamento, estado }
empleados_uid_lookup/{uid} -> username // así las reglas saben quién es
bodegas/{id} { tipo: principal | personal | vehiculo, grupo }
stock_bodegas/{id} { bodegaId, productoId, stockActual }
movimientos/{id} { tipo: transferencia | gasto, origen, destino, cantidad }
flota_puntos/{id} { vehiculo, at, lat, lng, velocidad }
Lo que se aprendió
Guardar el teléfono siempre normalizado (solo dígitos, con código de país) en un campo aparte. Es la llave para unir citas, chats y clientes.
Cuando un campo tiene un nombre ambiguo, documentarlo: una vez el reporte de reposición daba cero en todo porque se consultó cantidad en vez de stockActual.
Entrada y salida de WhatsApp
Todo mensaje entra por un webhook y sale por una sola función que aplica las reglas en orden.
AL RECIBIR webhook de Meta:
si firma(cuerpo, secretoApp) != cabecera.firma: rechazar
para cada mensaje:
si existe mensajes/{mensaje.id}: saltar // Meta reintenta
guardar mensaje; bajar foto/audio a almacenamiento
conv = conversaciones/{telefono} o crear con source = origen()
conv.ultimoMensajeAt = ahora; conv.ultimoDireccion = "entrante"
si conv no tiene dueño: repartir(conv) // ver reparto
FUNCIÓN enviar(usuario, telefono, contenido):
exigir sesión y ficha de empleado
conv = leer conversación
si conv.optOut y contenido es plantilla: error "pidió no recibir"
si contienePromesa(contenido): error 422 "promesa prohibida"
si pasaron más de 24 h desde el último mensaje del cliente
y contenido no es plantilla: error "necesita plantilla"
respuesta = Meta.enviar(telefono, contenido)
TRANSACCIÓN:
tomarPorTurno(conv, usuario) // antes de marcar "saliente"
conv.ultimoDireccion = "saliente"; conv.ultimoMensajeAt = ahora
guardar mensaje saliente con enviadoPor = usuario
Lo que se aprendió
Toda regla que se evalúa después de un envío tiene que ignorar ese mismo envío. La regla de lead frío dejó de funcionar una semana porque medía la inactividad con un campo que el envío recién había puesto en "ahora".
Reparto de leads por equidad
Cada cliente nuevo va a un vendedor, de forma pareja entre los que están trabajando en ese momento.
FUNCIÓN repartir(conv):
disponibles = vendedores donde
turnoAbierto y no enPausa
y minutosConActividad(últimos 20 min) >= 3 // clic, tecla o scroll
si vacío: dejar en "sin asignar"; return
firma = unir(ordenar(disponibles.username), "|")
saldo = config.saldo
si saldo.firma != firma: // entró o salió alguien
saldo = { firma, conteos: {}, ultimos: {} }
elegido = mínimo de disponibles por:
1. saldo.conteos[u] (menos recibidos)
2. saldo.ultimos[u] (más tiempo sin recibir)
3. username (alfabético, sin azar)
TRANSACCIÓN:
conv.assignedTo = elegido
saldo.conteos[elegido] += 1; saldo.ultimos[elegido] = ahora
Lo que se aprendió
La versión anterior repartía por "leads recibidos entre minutos activos en 24 horas" y falló: quien trabajó sin compañeros en turno acumulaba horas en su contra, y un clic cada 20 minutos bastaba para parecer activo. Reiniciar el saldo cuando cambia el grupo lo resolvió.
Sin azar en el desempate, la cola que se muestra en pantalla coincide con lo que de verdad pasa, y nadie desconfía del reparto.
Reparto por nivelación semanal
Modo alternativo: le toca a quien lleva menos leads en la semana, respetando turnos.
FUNCIÓN repartirNivelacion(conv):
si hora fuera de 8:00 a 22:00: no asignar
elegibles = vendedores con interruptor encendido
y latido de presencia en los últimos 3 min
y rolEnTurno(semana, ahora)
// roles por semana: Diurno 8-17 L-S, Temprano 8-17,
// Nocturno 12-22 L-V, Domingo 8-17 L-V y D
elegido = mínimo por (leadsDesdeLunes, tiempoSinRecibir)
asignar; recalcular tras cada entrega
Lo que se aprendió
El conteo de la semana tiene que ser exactamente el mismo que muestra el reporte de conversión. Si el reparto y el reporte cuentan distinto, nadie confía en ninguno.
Reasignación por turno y lead frío
Dos reglas para que un cliente que escribe no quede esperando a un vendedor ausente.
FUNCIÓN tomarPorTurno(conv, autor): // dentro de enviar()
si conv.ultimoDireccion != "entrante": return // el cliente no esperaba
si dueñoEnTurno(conv.assignedTo): return
si conv.esCliente o autor no es de Ventas o es bot: return
registrar { de: conv.assignedTo, a: autor, cuando }
conv.assignedTo = autor // el primero que responde gana
FUNCIÓN reasignarLeadFrio(conv, solicitante): // al mandar plantilla
si conv.esCliente o tieneCitas(conv): no
ultimos = últimos 5 mensajes del chat
sin contar salientes del solicitante en los últimos 10 min
si ningún mensaje en 3 días: conv.assignedTo = solicitante
si no: explicar el motivo en pantalla
Lo que se aprendió
Cuando una regla dice que no, decir por qué en pantalla. Ahorra los reportes de "no funcionó".
Bloqueo de promesas prohibidas
Evita que un vendedor o un bot prometa lo que la ley obliga a cumplir.
FUNCIÓN normalizar(texto):
minúsculas; quitar tildes
números escritos -> dígitos ("cien" -> "100")
letras repetidas -> una ("nuuuunca" -> "nunca")
quitar enlaces propios permitidos (página de garantía)
FUNCIÓN contienePromesa(texto):
t = normalizar(texto)
si t == alguna RESPUESTA_APROBADA (idéntica): false
para cada frase en PROHIBIDAS:
// "garantizado", "erradicar", "100 %", "para siempre",
// "nunca más", "libre de plagas", "control total" ...
si aparece en t con distancia de edición <= 1 o 2: true
false
se aplica en: enviar(), crear plantilla, respuestas del bot
Lo que se aprendió
La misma lista vive en la página (para avisar mientras se escribe) y en el servidor (para bloquear). El archivo del servidor es una copia exacta, nunca editada a mano.
Reloj de llamada obligatoria
Se midió que el 65 % de las ventas de Google cierra en las 3 horas siguientes a la primera respuesta.
EN enviar(), si es la primera respuesta humana a un lead de Google:
conv.llamadaInicio = ahora
conv.llamadaVence = ahora + 3 h
conv.llamadaResponsable = autor
EN PANTALLA (embudo):
si no hay llamada registrada:
antes de vencer -> reloj con el tiempo que queda
vencida -> aviso rojo y alerta al abrir el chat
registrar llamada = resultado + duración + pantallazo obligatorio
Lo que se aprendió
Los campos del reloj solo los escribe el servidor: las reglas impiden que un vendedor los mueva desde su pantalla.
Dónde cabe una cita con menos manejo
El vendedor pega la ubicación y el sistema ordena los huecos libres por el manejo extra que agregan.
// Modelo de manejo calibrado con 2.387 trayectos reales
FUNCIÓN minutos(a, b): 6,2 + 1,83 * kmEnLineaRecta(a, b)
FUNCIÓN huecos(cliente, duracion, dias):
candidatos = []
para cada día, cada ruta, cada hueco libre >= duracion:
ant = cita anterior (o la central)
sig = cita siguiente (o la central)
desvio = minutos(ant, cliente) + minutos(cliente, sig)
- minutos(ant, sig)
si el manejo no cabe en el hueco: descartar
si choca con citas, bloqueos del ruteador o propuestas del bot:
descartar
candidatos.agregar({ dia, ruta, hora, desvio })
ordenar candidatos por desvio
Lo que se aprendió
Ordenar por "cita más cercana" se equivoca: una cita vecina puede estar en una ruta que después se va al otro lado. Lo que importa es el desvío entre la anterior y la siguiente.
El análisis de 2.491 citas mostró que el orden dentro del día casi no tenía exceso (5,5 %). El ahorro real estaba en elegir otro día u otra ruta (hasta 14,5 % menos manejo).
Envío automático de la cita a Ventas
Al crear una cita, el grupo de Ventas recibe una imagen con la cita y el mapa de la ruta.
DISPARADOR al crear citas/{id}:
esperar 3 minutos // el vendedor termina de editar
cita = releer citas/{id}
si falta día, monto <= 0, sin vendedor, Ruta 20, ficticia
o cancelada: salir
si existe envios_ventas/{id}: salir // idempotencia
html = plantillaDeCita(cita, rutaDelDia(cita))
tiempos = OSRM(tramos de la ruta) con límite de 8 s, o vacío
imagen = navegadorSinPantalla.capturar(html + mapa + tiempos)
cola_whatsapp.agregar({ grupo: Ventas, imagen, texto })
envios_ventas/{id} = { por: "automatico", at: ahora }
Lo que se aprendió
La decisión se tomó con datos: los vendedores apretaban el botón un minuto después de crear la cita y solo 1 de 458 citas se editó en los primeros 30 minutos.
Un interruptor en la configuración permite volver al envío manual sin publicar código.
GPS: paradas, alertas y velocidad
Convierte miles de puntos por vehículo en paradas, recorridos y alertas útiles.
FUNCIÓN paradas(puntos):
parada = estar >= 5 min dentro de un radio de 60 m
unir paradas a menos de 150 m; descartar tramos de menos de 200 m
clasificar cada parada:
a <= 500 m de una cita de su ruta -> "cita"
a <= 500 m de la central -> "central"
a <= 100 m de una gasolinera y
dura <= 25 min -> "gasolinera" (no alerta)
cita del día movida o cancelada -> "cita movida" (no alerta)
otra y dura >= 20 min -> "fuera de ruta"
FUNCIÓN limpiarSaltos(puntos):
descartar un punto si sube más de 8 km/h por segundo
sobre sus dos vecinos, o si marca más de 90 y la distancia
y el tiempo hacia los vecinos no lo respaldan
ALERTA de velocidad (con cada lote de puntos):
racha = puntos > 90 km/h confirmados (>= 2)
juntar rachas a menos de 10 min
alertar si dura >= 20 s sobre 90, o si el pico >= 100
ATRIBUCIÓN de un punto a personas:
técnicos de la ruta que tenía ese vehículo en el ruteador,
según la convocatoria más reciente (tope 16 h)
Lo que se aprendió
Antes de alertar, estudiar el histórico: con 6 semanas de datos se vio cuántas alertas por día saldrían y qué lugares se repetían. Las gasolineras se descargaron de OpenStreetMap para no acusar a nadie por echar combustible.
Los aparatos GPS mienten a veces: hubo un 159 km/h que duró un instante. Sin el filtro de saltos, el ranking de velocidad castigaba a la persona equivocada.
Inventario: movimientos y reposición
Cada producto que se mueve resta de un lado y suma del otro, y la reposición sale del consumo real.
FUNCIÓN mover(producto, origen, destino, cantidad, tipo):
TRANSACCIÓN:
leer stock origen y destino
si stock origen < cantidad: error
origen.stockActual -= cantidad
si tipo == "transferencia": destino.stockActual += cantidad
registrar movimiento // "gasto" no tiene destino
FUNCIÓN pedidoSugerido(producto):
consumo28 = suma de movimientos tipo "gasto" en 28 días
+ faltantes de auditoría // fuga real
// las transferencias NO cuentan: no gastan nada
semanal = consumo28 / 4
pico = semana más cargada de las últimas 4
colchon = mín(pico / semanal - 1, 0,9) // más colchón si va a saltos
meta = semanal * 2 * (1 + colchon) // 14 días de cobertura
saldo = stockActual en TODAS las bodegas
pedir = máx(0, meta - saldo)
FUNCIÓN faltaEnBodega(bodega, producto):
meta = máx(0, kit[bodega.grupo][producto] + bodega.ajuste[producto])
faltante = máx(0, meta - stockActual)
Lo que se aprendió
Un colchón plano no sirve: 16 de 48 productos tenían semanas con más del doble de su promedio. El colchón por variabilidad compra más solo de lo que se usa a saltos.
De la publicidad a la venta
Para saber qué anuncio vendió, hay que seguir al cliente desde el clic hasta la cita pagada.
EN EL SITIO WEB:
al entrar, guardar gclid / utm del enlace
al tocar WhatsApp: generar código corto "W-xxxx"
guardar { codigo, gclid, utm, pagina } en la base
abrir WhatsApp con el código dentro del mensaje
AL LLEGAR EL PRIMER MENSAJE:
si trae un código W: conv.source = origen guardado de ese código
CADA HORA:
gasto[día][plataforma] = API de Meta + envío del script de Google
recalcular hoy (marcado "parcial") y los 3 días anteriores
CADA 6 HORAS:
para cada cita con monto creada desde una conversación de anuncio:
ventas_atribuidas.agregar(cita, conversación)
Meta: evento Purchase con monto y teléfono cifrado (SHA-256)
Google: fila en el CSV de conversiones (gclid, fecha, monto)
ROAS[plataforma] = ventas atribuidas / gasto
Lo que se aprendió
Recalcular los días anteriores es necesario: las plataformas ajustan su gasto durante uno o dos días después.
Vendedor virtual con IA
Un bot que conversa, cotiza y agenda, con frenos para que no se salga de control.
ESTADOS de cada conversación (ejemplo):
saludo -> entender plaga y lugar -> pedir ubicación
-> cotizar con tarifario -> proponer espacios -> confirmar
-> crear cita -> seguimiento
en cualquier paso: pasar a humano si lo pide, si se enoja
o si el caso no está en el tarifario
AL RECIBIR mensaje de un lead del bot:
si config.activo == false: salir
si gastoDelDía >= tope: apagar y avisar
estado = leer estado de la conversación
respuesta = IA(instrucciones + tarifario + ejemplos reales
+ historial + estado) -> salida en formato fijo
validar salida: si no cumple o trae promesa prohibida: a humano
si pide proponer: espacios = mismo motor que usan los vendedores
si pide agendar: validarEspacio() de nuevo; luego crearCita()
enviar por la misma función que usan las personas
CADA 15 MIN: seguimientos a quien dejó de responder
MODOS de encendido:
sombra -> genera respuestas y las guarda, sin enviar
fuera_horario -> solo atiende cuando no hay vendedores
porcentaje -> atiende una parte de los leads nuevos
Lo que se aprendió
Arrancar en modo sombra permite comparar lo que el bot habría dicho contra lo que dijo el vendedor, sin riesgo.
El bot usa las mismas funciones que las personas (enviar, proponer espacios, crear cita). Así hereda todas las reglas y no hay dos versiones de la verdad.
Las fichas de los bots existen como empleados, pero las reglas impiden asignarles leads a mano.
Revisión con IA de lo que ve el cliente
La nota del técnico pasa por una IA antes de publicarse, sin dejar que la IA invente.
DISPARADOR al crear reporte público:
original = reporte.comentario_tecnico
r = IA(instrucciones estrictas, original) -> {
problemas: [ { frase, motivo } ],
corregido: texto
}
si r.problemas vacío: publicado = original // letra por letra
si no:
si largo(r.corregido) > largo(original)
o contienePromesa(r.corregido): no publicar, marcar error
si no: publicado = r.corregido
el comentario interno nunca se publica
Lo que se aprendió
Pedirle a la IA una salida con estructura fija y verificarla con reglas normales. Si la verificación falla, es preferible no publicar a publicar algo inventado.
Formulario público con token
El cliente llena su garantía desde un enlace, sin cuenta, y solo ve su propio caso.
AL GENERAR:
token = aleatorio largo
garantias/{token} = { citaId, estado: "solicitud_enviada" }
enlace = sitio/garantia?t=token
REGLAS de la base para garantias/{token}:
leer: cualquiera que conozca el token (el id es el secreto)
actualizar sin sesión: solo si
estado actual == "solicitud_enviada"
y estado nuevo == "pendiente_revision"
y solo cambian { motivo, fotos, estado }
todo lo demás: solo empleados con permiso
Lo que se aprendió
Las fotos se comprimen en el teléfono antes de subir: los clientes suelen tener señal débil.
Reglas de seguridad y cómo probarlas
Las reglas son el último portero. Estos patrones cubren la mayoría de casos.
esEmpleado() = existe empleados_uid_lookup/{uid de la sesión}
permiso(clave) = ficha del empleado tiene permisos[clave] == true
match citas/{id}:
leer, crear: esEmpleado()
borrar: nunca
actualizar: esEmpleado()
y (no cambia monto o permiso("anular_monto") o esGerencia())
y campos de servidor sin tocar
match citas/{id}/extras/historial:
actualizar: nueva lista contiene entera a la vieja
PRUEBAS con el emulador, tres grupos:
1. lo que el equipo hace a diario -> debe pasar
2. intentos de abuso -> debe fallar
3. casos borde (gerencia, sin ficha)
Lo que se aprendió
Una regla que compila puede estar abierta. Un comodín recursivo dentro de citas también calzaba el documento de la cita y anulaba todas las protecciones. Solo el emulador lo detectó.
Los permisos especiales que mueven dinero no se heredan de ningún otro: se marcan persona por persona.
Capacitación con vigencia
Cursos obligatorios que vencen y un panel que dice quién está al día.
aprobado(intento) = puntaje >= 90 %
ultimoAprobado(persona, curso) = fecha del último intento aprobado
vigente(persona, curso) = hoy - ultimoAprobado <= 365 días
al armar el cuestionario:
preguntas de situación ("estás en la ruta y pasa esto...")
repartir la respuesta correcta entre A, B, C y D
panel de gerencia:
para cada aplicador activo y cada curso obligatorio:
vigente | vence en N días | vencido | nunca
Liquidación de un colaborador
Copia exacta del método de la hoja de cálculo que usaba Gerencia, con los salarios calculados por el sistema.
brutos = salario bruto de cada uno de los últimos 6 meses (de la planilla)
promedio = suma(brutos) / meses marcados
diario = promedio / 30
comisiones = ventas * porcentaje
horasExtra = horas * (diario / 8) * 1,5
diasPendientes = dias * diario
aguinaldo = suma(brutos desde el 1 de diciembre) / 12
vacaciones = diasVacaciones * diario
preaviso, cesantia = dias * diario // 0 si es renuncia
total = suma de todo + bono
// cada valor se puede corregir a mano antes de imprimir
Lo que se aprendió
Replicar primero el método que el negocio ya usa y confía. Mejorarlo después, con su aprobación.
Instrucción para usar esta guía con una IA
Para que otra IA (Claude, por ejemplo) arranque un proyecto propio con esta guía, se le puede pasar la versión en texto plano junto con esta instrucción, completando lo que está entre corchetes:
Vas a construir un sistema parecido al descrito en esta guía,
para [NEGOCIO] en [PAÍS]. Antes de escribir código:
1. Leé las secciones "Los ladrillos", "Cómo está armado" y
"Lógicas que funcionaron".
2. Haceme preguntas sobre mi operación hasta poder escribir el
modelo de datos (colecciones y campos) adaptado a mi negocio.
3. Proponé el orden de construcción, empezando por el módulo que
resuelve el problema más caro, y decime qué número vamos a medir.
Reglas al construir:
- Aplicá los principios de la guía: el servidor decide, nada se
borra, releer antes de actuar, idempotencia, transacciones,
configuración en la base de datos.
- Las llaves de servicios externos solo en secretos de la nube.
- Escribí pruebas de las reglas de seguridad con el emulador
antes de publicarlas.
- Usá la guía como referencia de lógica, no copies números
(umbrales, horarios, porcentajes) sin validarlos con mis datos.
15
Seguridad
Ser empleado es tener ficha. Además de iniciar sesión, el sistema revisa que exista la ficha de empleado antes de dejar leer o escribir datos.
Las citas no se borran. Se cancelan, y su historial solo puede crecer. El monto de una cita solo lo cambian Gerencia o quien tenga el permiso de anular.
Las reglas se prueban contra un emulador antes de publicarlas, porque una regla puede compilar bien y aun así dejar la puerta abierta. Así se detectó un caso real antes de que llegara a producción.
Las llaves de los proveedores solo existen en la nube, nunca en las páginas.
Todo texto que escribe un usuario se limpia antes de mostrarse, para que nadie pueda meter código en la pantalla de otro.
16
Proyectos hermanos
ecoplagas.cr
El sitio público. 100 de 100 en las cuatro categorías de Google Lighthouse (velocidad, accesibilidad, buenas prácticas, SEO). Primero de 15 en una comparación con fumigadoras de Costa Rica en octubre de 2026.
Cómo se construyó
Tipografía alojada en el mismo sitio y herramientas de medición que cargan con el primer gesto del visitante, para que la página aparezca rápido.
Cotizador: cinco preguntas y el precio publicado al instante, con enlace de pago y un mensaje de WhatsApp ya armado.
Medición propia por visita: tiempo activo, secciones leídas y distrito aproximado por IP.
Tienda en línea
22 productos de uso doméstico del inventario, con carrito y pago.
Cómo se construyó
Una sola página en Firebase con el catálogo en un archivo. Los productos usan el mismo identificador que en el inventario del sistema.
Apps Android
App de EcoPlagas, Archivero (gestor de archivos y buscador de duplicados), Compresor (ZIP, 7Z y RAR con clave) y Despierta (mantiene la pantalla encendida).
Cómo se construyó
Aplicaciones nativas escritas en Kotlin, el lenguaje oficial de Android, con Jetpack Compose para las pantallas, firmadas con llave propia.
Técnicos a Domicilio
Sitio y plataforma de la empresa hermana de reparaciones a domicilio.
Cómo se construyó
Su propio proyecto de Firebase, separado del de EcoPlagas, con la misma forma de trabajo.