Bee-Boys Solutions · InnovaTecNM 2026
⚙️
Manual
Técnico
BeeBox · Documentación Completa del Sistema
Stack: ESP32-S3 · Cloudflare Workers · KV · Pages · Vite
Firmware: Arduino C++ · WebServer · ArduinoJson v7
Versión del sistema: v2.2 · Mayo 2026
Asesor: Eric González Vallejo · Ing. Mecatrónica · TecNM Ciudad Hidalgo
Bee-Boys Solutions
Cajón Apícola Inteligente
Manual Técnico
Rev. 2.2 · 2026
Índice
1. Arquitectura del sistemaPág. 1
2. Módulo Worker API (Cloudflare)Pág. 2
3. Módulo Pages / Frontend (Vite)Pág. 2
4. Dashboard WebPág. 3
5. Firmware ESP32-S3Págs. 4–5
6. Seguridad del sistemaPág. 6
7. Puesta en marcha completaPág. 7
8. Errores conocidos y solucionesPág. 8
9. Preguntas frecuentes de juecesPágs. 9–10
1. Arquitectura del sistema
┌─────────────────────────────────────────────────────────┐
│ PLATAFORMA BEEBOX v2.2 │
├─────────────────────────────────────────────────────────┤
│ │
│ [Apicultor / Teléfono] │
│ │ HTTPS │
│ ▼ │
│ [beeboxmx.com] ←────────────────────────────────┐ │
│ Cloudflare Pages │ │
│ Vite + Vanilla JS │ │
│ │ HTTPS POST/GET │ │
│ ▼ │ │
│ [Cloudflare Worker API] → [KV Store] │ │
│ beebox-api.workers.dev device:{pin} │ │
│ │ HTTP POST /api/sync cada 3s │ │
│ ▼ │ │
│ [ESP32-S3 en el cajón] ─── WiFi AP ─────────────┘ │
│ 192.168.4.1 (panel local) │
│ │ GPIO │
│ ┌──────┴──────┐ │
│ [Relay 1] [Relay 2] │
│ Nebulizador Ventilador │
└─────────────────────────────────────────────────────────┘
Flujo de comando (cloud → dispositivo)
1. Apicultor presiona "Activar 5 min" en beeboxmx.com →
2. Dashboard POST /api/command al Worker con PIN →
3. Worker guarda {pendingCommand:{action:"nebulize",duration:300}} en KV →
4. ESP32 hace POST /api/sync cada 3s → recibe el comando en la respuesta →
5. ESP32 activa GPIO 13 (relay nebulizador, lógica inversa: LOW=ON) →
6. Tras 300s el ESP32 apaga el relay automáticamente →
7. Dashboard lee estado actualizado vía GET /api/status
⚡ Módulo 1
Endpoints
| Método | Ruta | Auth | Función |
| POST | /api/sync | X-Device-Pin | ESP32 reporta estado y recibe pendingCommand |
| GET | /api/status | X-Device-Pin | Dashboard lee estado del dispositivo desde KV |
| POST | /api/command | X-Device-Pin | Dashboard envía comando (guardado en KV, el ESP32 lo recoge en el próximo sync) |
| POST | /api/contact | Sin auth | Formulario de contacto de la landing page |
Estructura del estado en KV
// Clave: device:{pin} — ej: device:482916
{
"nebulizer": "off", // "on" | "off"
"treatment": "off", // "on" | "off"
"pump": "off", // "on" | "off"
"battery": 85, // 0–100
"temperature": 24.3, // °C
"humidity": 61.0, // %
"fanOnSec": 5, // segundos ON del ciclo
"fanOffSec": 5, // segundos OFF del ciclo
"timeLeft": -1, // segundos restantes del tratamiento
"lastSeen": "2026-05-17T10:00:00.000Z",
"pendingCommand": null, // {action,duration} | null
"history": [] // últimas 10 activaciones
}
Puesta en marcha del Worker
1
npm install en carpeta beebox-web
2
npx wrangler login → autenticar con cuenta Cloudflare
3
npx wrangler kv namespace create BEEBOX_STORE → copiar ID resultante a wrangler.toml
4
npx wrangler deploy → URL del Worker aparece al final
5
Verificar: https://beebox-api.SUBDOMINIO.workers.dev/api/status → debe responder {"error":"PIN requerido"}
🌐 Módulo 2
Variables de entorno (Cloudflare Dashboard)
| Variable | Valor producción |
| VITE_WORKER_URL | https://beebox-api.banarysource.workers.dev |
| VITE_SITE_URL | https://beeboxmx.com |
⚠️ Error frecuente
Si
VITE_WORKER_URL apunta a beeboxmx.com (la Pages misma), el dashboard no llega al Worker. Debe apuntar siempre al subdominio
.workers.dev.
Rutas del sitio
| Ruta | Archivo |
| / | src/index.html |
| /dashboard | src/dashboard.html |
| /presentacion.html | src/public/presentacion.html |
| /print/ | src/public/print/index.html |
💻 Módulo 3
Flujo de autenticación
Usuario ingresa PIN → handleLogin() valida formato (/^\d{6}$/) → PIN especial 311286 muestra easter egg → getStatus() llama Worker con header X-Device-Pin → si responde 200, guarda PIN en localStorage['beebox_pin'] → muestra dashboard → inicia polling cada 10s.
Modos de conexión (api.js)
| Modo | Condición | Endpoint | Indicador |
| cloud | Fetch Worker exitoso y lastSeen < 45s | https://beebox-api…/api/status | 🟢 Nube activa |
| local | Worker falla → fetch 192.168.4.1/status exitoso | http://192.168.4.1/status | 🟡 Conexión local (AP) |
| offline | Ambos fallan o lastSeen > 45s | — | 🔴 Sin conexión |
Command lock — UI optimista
Al enviar un comando, el dashboard actualiza la UI de inmediato (optimista) y bloquea el polling 9 segundos (LOCK_MS=9000) para evitar que el poll sobreescriba el estado antes de que el ESP32 confirme. Si el ESP32 responde antes, el lock se libera y muestra "✓ Confirmado".
Controles disponibles
| Acción | Comando enviado | Descripción |
| Activar N min / manual | {action:"nebulize", duration:N} | N=segundos, 0=manual sin límite |
| Detener tratamiento | {action:"nebulize_off"} | Apaga nebulizador y ventilador |
| Nebulizador individual | {action:"neb_on"} / neb_off | Sin ciclo de ventilador |
| Ventilador individual | {action:"fan_on"} / fan_off | Para pruebas independientes |
| Ciclo del ventilador | {action:"fan_cycle", onSec:N, offSec:N} | Guarda en NVS del ESP32 |
Countdown del tratamiento
El timer es cliente-side (localStorage: bb_neb_start + bb_neb_dur). Resiste recarga de página. El ESP32 también lleva su propio timer independiente (nebulizerEndMs). Si el ESP32 termina el tratamiento antes, el poll de 10s lo detecta y limpia el timer del cliente.
🔌 Módulo 4 — Firmware (1/2)
Hardware — ESP32-S3 Supermini
| Pin | Función | Lógica |
| GPIO 13 | Relay nebulizador | Inversa: LOW=ON |
| GPIO 12 | Relay ventilador | Inversa: LOW=ON |
| GPIO 5 | Reset de fábrica | INPUT_PULLUP → GND |
| GPIO 0 | BOOT (bootloader) | Solo para programar |
| GPIO 48 | LED RGB WS2812B | Requiere NeoPixel |
| Parámetro Arduino IDE | Valor |
| Board | ESP32S3 Dev Module |
| USB CDC On Boot | Enabled |
| Flash Size | 4MB (32Mb) |
| Partition Scheme | Default 4MB with spiffs |
| Upload Speed | 921600 |
Estados del firmware
| Estado | Condición | Comportamiento |
| SETUP | Flash sin config (primer boot / post-reset) | WIFI_AP · BeeBox-XXXX sin contraseña · DNS capta todo tráfico → 192.168.4.1 · Sirve portal de configuración branded |
| RUNNING | Config guardada en NVS (PIN + SSID + Worker) | WIFI_AP_STA · AP siempre activo + conecta a WiFi · POST /api/sync cada 3s · Servidor local en 192.168.4.1 con auth PIN |
AP SSID consistente
El SSID se calcula una sola vez en setup() usando ESP.getEfuseMac() antes de inicializar WiFi. Esto garantiza el mismo SSID en ambos modos. WiFi.macAddress() retorna MACs distintas según el modo WiFi (bug conocido del core ESP32).
uint64_t m = ESP.getEfuseMac();
snprintf(gApSSID, 20, "BeeBox-%02X%02X",
(uint8_t)((m >> 32) & 0xFF),
(uint8_t)((m >> 40) & 0xFF));
Ciclo del ventilador durante tratamiento
El ventilador cicla automáticamente durante el tratamiento activo. El ciclo (5s ON / 5s OFF por defecto) se controla en loop() usando fanLastToggleMs y es configurable vía comando fan_cycle. Los valores se persisten en NVS (fanOnMs / fanOffMs).
Persistencia NVS (flash interno)
| Clave | Valor | Modificado por |
| beebox/ssid | SSID de la red WiFi | Portal setup · /save-wifi |
| beebox/pass | Contraseña WiFi | Portal setup · /save-wifi |
| beebox/pin | PIN de 6 dígitos | Portal setup (no modificable desde dashboard) |
| beebox/worker | URL del Worker | Portal setup (sección avanzada) |
| beebox/fanOnMs | Tiempo ON del ventilador (ms) | Comando fan_cycle desde dashboard |
| beebox/fanOffMs | Tiempo OFF del ventilador (ms) | Comando fan_cycle desde dashboard |
🔌 Módulo 4 — Firmware (2/2)
Rutas del servidor local (running mode)
| Ruta | Método | Auth | Handler |
| / | GET | — | Sirve HTML_LOCAL (panel completo embebido) |
| /ok | GET | — | Pantalla de espera post-configuración con polling activo a /ping |
| /ping | GET | — | Responde {"ok":true} sin auth — usado por /ok para detectar reinicio |
| /status | GET | X-Pin ✓ | Devuelve stateJson() — estado real de todas las salidas y sensores |
| /command | POST | X-Pin ✓ | Ejecuta comando JSON → executeCommand() |
| /change-wifi | GET | Sesión ✓ | Scanner de redes WiFi con selección interactiva |
| /save-wifi | POST | Sesión ✓ | Guarda nueva red WiFi · reconnecta sin reinicio · 303 → /?wf=1 |
Loop principal — tareas por intervalo
| Tarea | Intervalo | Condición |
| Reinicio diferido (post-portal-save) | — | pendingRestart=true y millis()≥restartAt |
| WiFi.begin() diferido (post-save-wifi) | 150ms | pendingWifiBegin=true y millis()≥wifiBeginAt |
| Temporizador del tratamiento | Continuo | treatmentOn y millis()≥nebulizerEndMs |
| Ciclo ventilador | fanCycleOnMs / fanCycleOffMs | treatmentOn=true |
| Lectura de sensores | 10s | Siempre (running mode) |
| Sync con Worker | 3s | WiFi.status()==WL_CONNECTED |
| Reconexión WiFi | 20s | WiFi desconectado y SSID configurado |
| Reset de fábrica | Continuo | GPIO 5 LOW durante ≥10s |
stateJson() — estado en tiempo real
El endpoint /status llama stateJson() que lee directamente los globales en RAM del firmware: nebulizerOn, treatmentOn, fanOn, nebulizerEndMs. Estos globales nunca se borran cuando cae WiFi — el panel local siempre muestra el estado físico real.
// Respuesta típica de /status:
{"nebulizer":"on","treatment":"on","pump":"off",
"battery":82,"temperature":25.3,"humidity":68.0,
"fanOnSec":5,"fanOffSec":5,"timeLeft":247}
Captive portal (setup mode)
Endpoints para captive portal detection de iOS, Android y Windows: /hotspot-detect.html y /generate_204 responden 200 con meta-refresh a 192.168.4.1. /connecttest.txt, /redirect y onNotFound responden 302 → 192.168.4.1. Esto activa el popup automático del OS en iOS 14+ y Android 10+.
🔒 Módulo 5
Capas de seguridad
| Capa | Mecanismo | Alcance |
| Cloud | Header X-Device-Pin en todas las peticiones al Worker | Todas las rutas /api/* excepto /api/contact |
| Local — comandos | Header X-Pin validado en checkPin() | /status y /command |
| Local — páginas | Sesión sessionValidUntil renovada por checkPin() (5 min) | /change-wifi y /save-wifi |
| Transporte cloud | HTTPS obligatorio (Cloudflare) | Todas las peticiones al Worker y Pages |
| Persistencia PIN | NVS del ESP32 (flash cifrado internamente) | PIN nunca viaja en texto en URLs |
Auditoría de seguridad v2.2 — hallazgos y correcciones
| # | Severidad | Problema | Corrección aplicada |
| 1 | Critical | PIN expuesto en URL (/change-wifi?pin=XXXXXX) → queda en historial del navegador | Sesión local (sessionValidUntil) renovada por checkPin(). /change-wifi y /save-wifi validan sesión, no PIN en URL |
| 2 | Warning | VLA (Variable Length Array) en buildWifiList(): int idx[n] no es C++ estándar, posible stack overflow | Array fijo int idx[MAX_NETS=20]; sort sobre min(n,20) redes |
| 3 | Warning | delay(100) bloqueante en handleSaveWifi() bloquea server.handleClient() durante HTTP response | WiFi.disconnect() antes del 303; WiFi.begin() diferido 150ms en loop() via flag pendingWifiBegin |
| 4 | Warning | sendLocalJson() no incluía X-Pin en Access-Control-Allow-Headers (inconsistente con handleLocalOptions) | Corregido a "Content-Type, X-Pin" en sendLocalJson() |
| 5 | Info | Comentario duplicado "Configuración: NVS (flash)" en el firmware | Eliminado el bloque duplicado |
Flujo de sesión local
Usuario ingresa PIN en HTML_LOCAL → fetch('/status', {headers:{'X-Pin':pin}}) →
checkPin() valida → si OK: sessionValidUntil = millis() + 300000 →
Usuario abre /change-wifi → handleChangeWifi() verifica millis() < sessionValidUntil →
si sesión expiró (5 min sin actividad) → redirect a / para re-login.
✓ PIN nunca aparece en URLs ni en código fuente del HTML servido
El hidden field del formulario /change-wifi fue eliminado en la auditoría v2.2. El único lugar donde viaja el PIN es el header X-Pin en peticiones AJAX al servidor local.
Estado producción actual (Mayo 2026)
Worker: https://beebox-api.banarysource.workers.dev ✓ · Pages: https://beeboxmx.com ✓ · Firmware: v2.2 compilado y probado en ESP32-S3 Supermini ✓
Primera vez — sistema nuevo
| # | Paso | Comando / Acción | Verificación |
| 1 | Dependencias | npm install en beebox-web/ | Sin errores en consola |
| 2 | Auth Cloudflare | npx wrangler login | Navegador muestra "Logged in" |
| 3 | Crear KV | npx wrangler kv namespace create BEEBOX_STORE | Copiar ID devuelto a wrangler.toml |
| 4 | Deploy Worker | npx wrangler deploy | URL .workers.dev en consola |
| 5 | Var. entorno Pages | Cloudflare Dashboard → Pages → Settings → Env Vars | VITE_WORKER_URL = URL del Worker |
| 6 | Redeploy Pages | Cloudflare Dashboard → Pages → Deployments → Retry | Build exitoso, sin errores Vite |
| 7 | Instalar librería | Arduino IDE → Tools → Manage Libraries → ArduinoJson v7 | Librería aparece instalada |
| 8 | Config Arduino | Board: ESP32S3 Dev Module · USB CDC: Enabled · Flash: 4MB · Erase All Flash: Enabled | Board seleccionada correctamente |
| 9 | Primer flash | Compilar y subir (Ctrl+U) | Serial Monitor 115200: "[Setup] Red WiFi: BeeBox-XXXX" |
| 10 | Configurar ESP32 | Conectar celular a BeeBox-XXXX → portal → WiFi + PIN + Guardar | Dispositivo reinicia y conecta |
| 11 | Verificar cloud | beeboxmx.com → ingresar PIN | Indicador 🟢 Nube activa |
| 12 | Test nebulización | Botón "1 min" en dashboard | Serial: "[RELAY] Nebulizador ON 60s" |
Flasheos siguientes (sin borrar NVS)
Cambiar Erase All Flash Before Sketch Upload a Disabled. Esto preserva la configuración WiFi y PIN en NVS. Solo volver a Enabled si el ESP32 no arranca correctamente (core dump en bucle).
¿Cómo funciona el sistema de forma resumida?
BeeBox es una plataforma IoT de 4 capas: dispositivo ESP32-S3 con relays en el cajón, API serverless en Cloudflare Workers que almacena el estado en KV, dashboard web en Cloudflare Pages, y panel local en el propio ESP32 vía AP WiFi. El apicultor activa tratamientos desde cualquier dispositivo con internet; el ESP32 sincroniza cada 3 segundos y ejecuta los comandos de forma autónoma.
¿Qué pasa si no hay internet en el apiario?
El ESP32 siempre crea su propia red WiFi (BeeBox-XXXX) simultáneamente con la conexión a internet, usando el modo WIFI_AP_STA. El apicultor se conecta directamente a esa red y accede al panel de control local en 192.168.4.1. Las funciones son idénticas al panel cloud. El AP permanece activo incluso cuando hay conexión WiFi.
¿Cómo garantizan la seguridad del sistema?
Cuatro capas: (1) PIN de 6 dígitos almacenado en flash del ESP32, nunca en texto en URLs; (2) peticiones locales con header HTTP X-Pin validado por el servidor; (3) sesión local de 5 minutos que expira tras inactividad; (4) comunicación cloud vía HTTPS obligatorio en Cloudflare. En la auditoría de seguridad v2.2 identificamos y corregimos 5 vulnerabilidades incluyendo PIN expuesto en URLs.
¿Por qué nebulización piezoeléctrica y no resistiva o térmica?
Los nebulizadores piezoeléctricos generan aerosol frío (~4°C sobre temperatura ambiente) sin elemento calefactor. Esto es crítico para las abejas: el calor por encima de 35°C las estresa y puede dañar la cera. Además consumen 10–20 veces menos energía (1–2W vs 20–50W del resistivo), tienen vida útil más larga (3,000–5,000 horas) y el repuesto cuesta menos de $80 MXN.
¿Cuánto cuesta el sistema completo?
Componentes por unidad: ESP32-S3 Supermini ~$90 MXN, módulo relay doble ~$35 MXN, nebulizador piezoeléctrico ~$140 MXN, caja de control y cableado ~$80 MXN. Total componentes: ~$345 MXN (~$18 USD). Precio de venta: $1,200 MXN. La infraestructura cloud (Cloudflare) es gratuita hasta ~3,500 dispositivos activos simultáneos.
¿Pueden manejar múltiples cajones por apicultor?
Sí. La arquitectura escala horizontalmente: cada cajón tiene su propio ESP32 con un PIN único. El mismo Worker y KV store maneja n dispositivos sin cambios en el código. El roadmap v3.0 incluye un panel administrador para controlar múltiples cajones desde una cuenta, con activación grupal de tratamientos.
¿Qué tan precisos son los sensores de temperatura y humedad?
En la versión de demo actual usamos sensores simulados para mostrar el flujo completo de datos. El firmware tiene los hooks listos para integrar un sensor DHT22 real (±0.5°C de precisión en temperatura, ±2% en humedad) en GPIO 4. El hardware de control de relays y nebulización está completamente funcional y validado. Los sensores son el siguiente paso de desarrollo (roadmap v2.3, Q3 2026).
¿Por qué Cloudflare Workers y no AWS Lambda o un servidor propio?
Tres razones principales: (1) Latencia edge: Cloudflare Workers ejecuta en el edge point más cercano al usuario, sin cold starts (vs 100–500ms de AWS Lambda). (2) Costo: plan gratuito cubre 100,000 requests/día por Worker, suficiente para ~3,500 dispositivos sincronizando cada 3 segundos. (3) Simplicidad: sin gestión de servidores, escala automáticamente, KV integrado para almacenamiento de estado.
¿Cómo protegen los datos personales del apicultor?
El sistema no recopila datos personales. El único identificador es el PIN de 6 dígitos que el propio apicultor elige. Los datos almacenados en Cloudflare KV son exclusivamente estado del dispositivo (temperatura, humedad, historial de activaciones). No hay nombre, email, teléfono ni geolocalización. Cloudflare cumple GDPR y tiene certificaciones SOC 2 Type II.
¿Cuál es la vida útil del sistema y qué mantenimiento requiere?
El módulo ESP32-S3 no tiene partes móviles y su MTBF es >50,000 horas (~6 años continuo). El nebulizador piezoeléctrico es el único consumible: vida útil de 3,000–5,000 horas de operación (con tratamientos semanales de 10 minutos son más de 10 años). Mantenimiento: limpiar el transductor con agua destilada cada 3 meses. El firmware se actualiza vía USB con Arduino IDE.
¿Qué tratamiento aplican exactamente? ¿Está certificado?
El sistema nebuliza ácido oxálico dihidratado al 4.2% en solución acuosa tibia, el tratamiento orgánico estándar certificado para control de Varroa destructor en muchos países. Las partículas de 1–5 micras penetran entre los opérculos de cría cerrada. El ventilador cicla (5s ON / 5s OFF) para distribuir uniformemente el aerosol en todo el volumen del cajón. BeeBox no produce ni vende el compuesto; el apicultor lo prepara según protocolo estándar.
¿Cómo validan que el tratamiento fue efectivo?
El dashboard registra cada activación con fecha, hora, duración y origen (nube/local) en el historial. Los sensores de humedad permiten confirmar que el aerosol se dispersó (la humedad relativa sube durante la nebulización). La efectividad biológica del ácido oxálico sobre Varroa es ampliamente documentada en la literatura científica apícola con tasas de mortalidad parasitaria del 90–97%.
¿Qué diferencia a BeeBox de otras soluciones en el mercado?
BeeBox combina tres factores únicos para el mercado mexicano: (1) Costo accesible: componentes por $345 MXN, infraestructura cloud gratuita; las soluciones importadas cuestan $3,000–8,000 MXN. (2) Control remoto real: otras soluciones son timers físicos; BeeBox es la única con app web y control desde cualquier lugar. (3) Soporte local en español: desarrollado en México, documentado en español, equipo disponible para soporte.