Cómo se construyó ecoplagas.online

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
Contenido
  1. Los ladrillos, explicados
  2. Cómo está armado
  3. Cómo se trabaja
  4. Ventas y CRM
  5. Agenda y rutas
  6. GPS de la flota
  7. Inventario
  8. Planilla y personal
  9. Ministerio de Salud y capacitación
  10. Clientes y calidad
  11. Números del negocio
  12. Inteligencia artificial
  13. Servicios externos y APIs
  14. Lógicas en pseudocódigo
  15. Seguridad
  16. Proyectos hermanos
01

Los ladrillos, explicados

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.

1. Pantallas Teléfonos y computadoras del equipo abren ecoplagas.online en el navegador. 288 páginas. 2. Base de datos (Firestore) Citas, clientes, leads, empleados, inventario, GPS. Con reglas de acceso. 3. Funciones en la nube 169 funciones, 37 con horario. Guardan las llaves de los proveedores. 4. Proveedores externos (por API) Meta: WhatsApp, Messenger, Facebook, Instagram, anuncios Google: Ads, Maps · TikTok Ads · Microsoft Ads Anthropic (Claude) · ElevenLabs (voces) · Gemini (imágenes) GPS de los vehículos · OSRM y OpenStreetMap (mapas y distancias)
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:

  1. 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".
  2. 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.
  3. Se escribe el cambio en la página o en la función correspondiente, imitando el estilo del código que ya existe.
  4. 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.
  5. Se sube a GitHub con una descripción en español. Un robot lo publica en ecoplagas.online en minutos.
  6. Se verifica en vivo, entrando al sitio con un navegador automático como lo haría un usuario.
  7. 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ó:

  1. 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.
  2. 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ó.
  3. 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.
  4. 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.
  5. Probar con casos reales y medir el número del paso 1 antes y después.
  6. 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_KEY
const llave = defineSecret("ANTHROPIC_API_KEY");

exports.resumirNota = onCall({ secrets: [llave] }, async (req) => {
  // 1. Solo usuarios con sesión
  if (!req.auth) throw new HttpsError("unauthenticated", "Inicie sesión");

  // 2. Llamar al servicio externo con la llave, que nunca sale del servidor
  const 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 necesita
  const 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
  1. Crear el proyecto en console.firebase.google.com con una cuenta de Google.
  2. 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.
  3. 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).
  4. Escribir las reglas de Firestore desde el día uno, cerradas por defecto, y probarlas con el emulador (firebase emulators:start).
  5. 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
  1. En console.cloud.google.com, dentro del mismo proyecto de Firebase, activar "Cloud Speech-to-Text API".
  2. 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.
  3. Para audios de más de un minuto, guardarlos primero en Storage y usar el modo longrunningrecognize con la dirección gs:// del archivo.
  4. 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
  1. En Google Cloud, menú Facturación, entrar a "Exportación de facturación".
  2. Activar "Exportación de costos de uso estándar" hacia un conjunto de datos de BigQuery del mismo proyecto.
  3. 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
  1. Crear un repositorio privado y subir la carpeta del proyecto.
  2. 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).
  3. 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.
  4. Agregar concurrency al flujo para que dos publicaciones seguidas hagan fila y la más vieja no pise a la nueva.
  5. 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
  1. 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.
  2. En developers.facebook.com, crear una app tipo Business y agregarle el producto WhatsApp.
  3. 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.
  4. 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.
  5. 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.
  6. Enviar con POST https://graph.facebook.com/v21.0/{id-del-número}/messages.
  7. 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
  1. En la misma app de Meta, agregar Messenger y conectar la página de Facebook.
  2. Pedir los permisos pages_messaging y pages_read_engagement y generar un token de página.
  3. 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.
  4. 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
  1. Rentar un servidor Linux pequeño que esté siempre encendido (un VPS) e instalar Node.js y Chrome.
  2. 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.
  3. Guardar la sesión en disco para no escanear de nuevo en cada reinicio.
  4. 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
  1. 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.
  2. Crear la cuenta, cargar saldo y activar los permisos geográficos de llamadas al país.
  3. Comprar un número o verificar uno propio como identificador de llamada.
  4. 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
  1. En la app de Meta, pedir los permisos ads_read (leer gasto) y ads_management (crear campañas).
  2. Generar el token desde un usuario del sistema con acceso a la cuenta publicitaria.
  3. Gasto: GET /act_{cuenta}/insights con el rango de fechas y el nivel (campaña, conjunto o anuncio).
  4. 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.
  5. 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
  1. Conectar la cuenta de Instagram (tipo empresa) a la página de Facebook.
  2. Pedir los permisos pages_manage_posts e instagram_content_publish.
  3. Facebook: POST /{página}/photos o /videos.
  4. 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
  1. 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.
  2. Ventas: en el sitio, guardar el parámetro gclid que trae cada clic y pasarlo con la conversación.
  3. Crear en Google Ads una acción de conversión de tipo "Importación".
  4. 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
  1. Poner parámetros utm_source y utm_campaign en todos los enlaces de anuncios.
  2. Guardar esos parámetros junto con el primer mensaje del cliente, para saber el origen de cada venta.
  3. 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
  1. Crear la propiedad en cada herramienta y copiar su código.
  2. En vez de cargarlos en la cabecera, definir primero funciones vacías (gtag, fbq) que guardan los eventos en una lista.
  3. Cargar los códigos reales con el primer scroll, touchstart o keydown, o a los 6 segundos. Los eventos guardados salen en ese momento.
  4. 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
  1. Crear la cuenta en console.anthropic.com, cargar saldo y generar una llave.
  2. Guardarla como secreto de la función: firebase functions:secrets:set ANTHROPIC_API_KEY.
  3. 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.
  4. 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.
  5. 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
  1. Crear la cuenta en elevenlabs.io y generar una llave en el perfil. Los planes se cobran por cantidad de caracteres convertidos a voz.
  2. Elegir una voz de la biblioteca y copiar su identificador (voice_id).
  3. Voz a partir de texto: POST https://api.elevenlabs.io/v1/text-to-speech/{voice_id} con el texto; devuelve el audio.
  4. Para clonar una voz se suben muestras limpias de la persona, con su consentimiento.
  5. 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
  1. Generar una llave en aistudio.google.com.
  2. 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.
  3. 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
  1. En Google Cloud, activar solo las APIs que se van a usar (Maps JavaScript y Directions, por ejemplo).
  2. Crear la llave y restringirla de dos formas: por API (solo esas) y por sitio web (solo los dominios propios).
  3. 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
  1. Pedir una ruta: https://router.project-osrm.org/route/v1/driving/{lng},{lat};{lng},{lat}. Ojo: primero longitud y después latitud.
  2. La respuesta trae duración en segundos y distancia en metros.
  3. Poner siempre un límite de tiempo y un plan B si no responde.
  4. 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
  1. 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.
  2. 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.
  3. Mapas en pantalla: Leaflet con los mosaicos de OpenStreetMap, citando la fuente.
  4. 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
  1. Bajar el archivo gratuito "IP to City Lite" de db-ip.com. Su licencia (CC BY 4.0) pide citar la fuente.
  2. Conseguir los límites de distritos del país en formato GeoJSON (hay repositorios públicos para Costa Rica).
  3. Con un programa, ubicar cada coordenada dentro de su polígono y guardar una tabla de rango de IP a distrito.
  4. 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
  1. 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.
  2. Guardar cada punto con vehículo, hora, coordenadas y velocidad.
  3. 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
  1. Crear la cuenta de comercio en onvopay.com y completar la verificación.
  2. Crear un enlace de pago por producto o paquete desde su panel.
  3. 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íaPara qué
BootstrapBotones, ventanas y tablas con apariencia uniforme en todas las páginas.
LeafletMapas de rutas, GPS, zonas y clientes.
Chart.jsGráficos de los reportes.
jsPDF y html2canvasConvertir una pantalla en PDF o en imagen (certificados, pantallazos del botón del bicho).
QRCodeCódigos QR de certificados y estaciones de cebo.
browser-image-compressionAchicar fotos en el teléfono antes de subirlas.
Puppeteer y ChromiumUn navegador sin pantalla dentro de la nube que dibuja imágenes de citas, rutas y alertas para mandarlas por WhatsApp.
FFmpegArmar 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.

Cifras tomadas del repositorio de ecoplagas.online al 31 de octubre de 2026. Más sobre el sistema en el caso EcoPlagas Online. Contacto: hola@latamcodelabs.com.