Initial commit: Hybrid polling, interactive log modal and 3-second hard reset

This commit is contained in:
cheveguerra
2026-07-24 15:31:56 +00:00
commit 7808501bf4
16 changed files with 34739 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
# Análisis Comparativo: Arquitecturas Alternativas para el Monitoreo de TotalConnect
Este documento analiza dos enfoques técnicos alternativos para resolver el mismo problema de obtención de estatus de alarmas, comparándolos con nuestra implementación actual en **Python (REST API)**.
---
## 📋 Cuadro Comparativo de Alternativas
| Característica | Enfoque 1: Python + REST API (Actual) | Enfoque 2: Node.js + Puppeteer (Scraping) | Enfoque 3: Go/Rust + REST/SOAP (Compilado) |
| :--- | :--- | :--- | :--- |
| **Consumo de Memoria** | Medio (~40-60 MB RAM) | Muy Alto (~150-350 MB RAM por navegador) | Mínimo (<15 MB RAM) |
| **Tiempo de Respuesta** | Medio (~1-2 minutos por el ciclo secuencial) | Lento (~2-3 minutos por carga de interfaz) | Ultra Rápido (Segundos, por concurrencia nativa) |
| **Robustez ante Cambios** | Alta (El API cambia muy rara vez) | Muy Baja (Cualquier cambio de HTML lo rompe) | Alta (El API cambia muy rara vez) |
| **Complejidad del Código** | Baja/Media (Usa librerías existentes) | Media (Fácil de entender, difícil de depurar) | Alta (Requiere programar clientes HTTP y parsing a mano) |
| **Facilidad de Despliegue**| Media (Requiere Python y venv) | Compleja (Requiere Node.js y dependencias de Chrome/X11) | Ultra Simple (Un único archivo binario compilado) |
---
## 🔍 Enfoque Alternativo A: Node.js + Puppeteer/Playwright (Web Scraping)
Consiste en programar un script en Node.js que levante un navegador "invisible" (Headless Chrome), entre a la página de inicio de sesión de Total Connect 2.0, ingrese el usuario y contraseña simulando clics humanos, navegue a la pantalla del teclado virtual y extraiga el texto y las clases CSS de las sucursales directamente del DOM del sitio web.
### 👍 Pros:
* **Fidelidad al Usuario:** No depende de endpoints REST no documentados o secretos. Si funciona en la página web oficial, funcionará en el script.
* **Manejo de Estados Visuales:** Obtiene exactamente lo que el usuario ve (los mismos íconos y textos que ya están renderizados en la web de Honeywell).
### 👎 Contras:
* **Footprint de Recursos Masivo:** Ejecutar Chrome sin interfaz en un servidor Linux consume una cantidad enorme de CPU y memoria RAM. No es viable en servidores pequeños o con recursos limitados.
* **Fragilidad del DOM:** Si Resideo actualiza el diseño de su página web (cambia un `id`, una clase de CSS o cambia la estructura de las etiquetas HTML), el scraper se rompe instantáneamente y requiere mantenimiento de emergencia.
* **Dificultad con MFA:** Si la página de Honeywell decide exigir Verificación de Dos Pasos (MFA) o CAPTCHA en horarios de alta carga, simular el login a través de código es sumamente difícil de automatizar.
---
## 🔍 Enfoque Alternativo B: Go (Golang) o Rust + Clientes REST Nativos
Consiste en desarrollar un servicio binario en Go o Rust que se conecte directamente a los endpoints REST de Honeywell (haciendo las mismas llamadas OAuth2 que hace nuestro script en Python), compile un binario único optimizado y exponga un servidor HTTP ultraligero.
### 👍 Pros:
* **Rendimiento Inigualable:** Un binario compilado en Go o Rust se ejecuta casi instantáneamente, consume menos de 15 MB de memoria RAM y no tiene dependencias en el servidor de producción (no necesita intérpretes de lenguaje, `venv` ni `node_modules`).
* **Concurrencia Nativa (Velocidad de Carga):** Al tener soporte nativo de multiprocesamiento ligero (como las `goroutines` en Go), el script puede consultar las 100+ sucursales de forma **simultánea** en paralelo (en lugar de una por una en un ciclo secuencial). Esto reduciría el tiempo de actualización de 2 minutos a **menos de 5 segundos**.
### 👎 Contras:
* **Curva de Aprendizaje y Desarrollo Lento:** No existen wrappers oficiales o librerías maduras de la comunidad para TotalConnect en Go o Rust. Habría que programar desde cero el flujo de autenticación OAuth2, la renovación del token de sesión y el mapeo de todas las complejas estructuras JSON y SOAP de Honeywell.
* **Mantenibilidad:** Cualquier cambio o ajuste requiere volver a compilar el binario para la arquitectura del servidor destino.
+45
View File
@@ -0,0 +1,45 @@
# Explicación de Modos de Polling (Rápido vs. Full)
Este documento describe la arquitectura de consultas del monitor de TotalConnect 2.0, especificando el funcionamiento del **Polling Rápido (Ligero)** y del **Polling Completo (Profundo)**, así como las circunstancias en las que se ejecuta cada uno.
---
## 1. Polling Rápido (Ligero / Delta)
El Polling Rápido es un mecanismo altamente optimizado diseñado para detectar cambios de estado en tiempo real con el mínimo consumo de ancho de banda y rendimiento.
* **Tecnología Utilizada:**
* **SOAP:** Método `GetLiveEvents` (usando el SessionID GUID).
* **REST (Respaldo):** Endpoint `/sessiondetails` (ejecutado únicamente si SOAP está desactivado o falla).
* **Funcionamiento:**
* Solicita al servidor de Honeywell la lista de eventos ocurridos a partir de un ID de evento de referencia (`LastEventIdReceived`).
* En modo de respaldo REST, solicita una lista compacta de las sucursales y sus estados básicos en una sola petición.
* **No realiza consultas directas a los paneles físicos** de las sucursales, lo que permite que la respuesta tarde menos de 1 segundo para cientos de ubicaciones.
* **Cuándo se ejecuta:**
* **De forma automática y continua:** Cada **45 segundos** en el hilo de segundo plano del servidor (`background_poller_thread`).
---
## 2. Polling Completo (Profundo / Full)
El Polling Completo obtiene el estado exhaustivo de una o todas las sucursales, interactuando directamente con el panel físico a través de la nube de Resideo.
* **Tecnología Utilizada:** Endpoint REST `/partitions/fullStatus` de cada sucursal.
* **Funcionamiento:**
* Consulta el estado en tiempo real de cada partición (P1-P6) y zona (Z1-Z285).
* Extrae detalles críticos como fallos de zonas (puertas/ventanas abiertas), alarmas de fuego/gas/intrusión activas y horas localized de disparo de alarma.
* Actualiza la persistencia local en `last_state.json` y regenera el reporte visual `status.html`.
* **Cuándo se ejecuta:**
### A. Para sucursales individuales (Optimización Delta):
Se ejecuta **únicamente para las sucursales específicas que cambiaron**, durante el ciclo automático de 45 segundos, si el Polling Rápido detecta alguna de las siguientes circunstancias:
1. **Actividad reportada por SOAP:** Un evento nuevo en el delta de SOAP indica que hubo cambios en esa sucursal.
2. **Cambio de estado general:** El estatus de armado básico en la respuesta REST de respaldo cambió respecto al valor en caché (ej: Desarmado ➔ Armado Stay).
3. **Alarma activa:** La sucursal entra en un estado de alarma disparada (`ALARMING`, `ALARMING_FIRE_SMOKE`, etc.).
4. **Validación de restablecimiento:** La sucursal tenía particiones disparadas en el ciclo anterior (necesario para verificar si ya se desactivó/silenció la alarma).
5. **Nueva sucursal:** Se detecta una sucursal en la cuenta que no existía previamente en la caché.
### B. Para todas las sucursales de la cuenta (Fuerza Bruta):
Se ejecuta una consulta completa para todas las sucursales de la cuenta bajo las siguientes circunstancias:
1. **Refresco manual:** El usuario hace clic en el botón **"Actualizar"** en el panel web (lo que envía una solicitud POST a `/refresh` con `full_refresh=True`).
2. **Arranque inicial sin caché:** El servidor inicia por primera vez y no existe el archivo `last_state.json`, lo que obliga a crear la base de datos de estatus desde cero.
+81
View File
@@ -0,0 +1,81 @@
# Guía de Empaquetado y Portabilidad: TotalConnect 2.0
Para hacer que este proyecto sea lo más portable posible y evitar configurar entornos virtuales (`venv`), instalar `pip` o gestionar dependencias manualmente en cada servidor, existen dos enfoques recomendados:
---
## 🚀 Método 1: Empaquetar como un Ejecutable Único (PyInstaller)
Este método compila tu script de Python y todas sus dependencias (incluyendo el intérprete de Python, las librerías `total-connect-client`, `zeep`, etc.) dentro de un **único archivo ejecutable binario para Linux**.
### 👍 Ventajas:
* Genera un solo archivo (ej. `totalconnect`).
* **Cero dependencias:** No necesitas tener instalado Python, `pip`, ni crear entornos virtuales en el servidor destino. Solo copias el archivo y lo corres.
* Súper ligero (~15-20 MB).
### 🛠️ Cómo empaquetarlo (Ejecutar en la VM Linux `192.168.100.50`):
1. **Instalar PyInstaller en el entorno virtual actual:**
```bash
cd /mnt/data2/Software/Bot-WhatsApp/TotalConnect
./venv/bin/pip install pyinstaller
```
2. **Compilar el script en un único archivo:**
```bash
./venv/bin/pyinstaller --onefile --name totalconnect index.py
```
*(Este comando creará una carpeta `dist/` que contendrá el ejecutable final llamado `totalconnect`).*
3. **Cómo usar el ejecutable generado:**
Puedes mover el archivo `dist/totalconnect` a cualquier parte (por ejemplo, a `/usr/local/bin/` para que sea un comando global) y correrlo directamente:
* **Modo Servidor:** `./totalconnect --server 8080`
* **Modo On-Demand:** `./totalconnect`
*Nota: Recuerda que `credentials.json` y `locations_cache.json` deben estar en la misma carpeta desde donde ejecutes el binario, ya que busca esos archivos en su directorio relativo.*
---
## 🐳 Método 2: Contenedorizar con Docker
Este método empaqueta todo el entorno (sistema operativo base, Python, librerías y código) dentro de una imagen de Docker.
### 👍 Ventajas:
* **Portabilidad absoluta:** Funciona de forma idéntica en cualquier sistema (Windows, macOS, Linux, AWS, Proxmox) que tenga instalado Docker.
* **Aislamiento total:** No interfiere en lo absoluto con las librerías del sistema operativo.
* Gestión de reinicios integrada (Docker se encarga de mantenerlo vivo).
### 🛠️ Archivos necesarios en la carpeta `/TotalConnect`:
1. **Crear un archivo `Dockerfile`:**
```dockerfile
FROM python:3.11-slim
WORKDIR /app
# Instalar dependencias
RUN pip install --no-cache-dir total-connect-client
# Copiar código
COPY index.py /app/index.py
# Exponer puerto del servidor
EXPOSE 8080
# Comando para arrancar el servidor por defecto
CMD ["python", "index.py", "--server", "8080"]
```
2. **Construir la imagen de Docker:**
```bash
docker build -t totalconnect-monitor .
```
3. **Ejecutar el contenedor (montando las credenciales para poder cambiarlas desde fuera):**
```bash
docker run -d \
--name tc-monitor \
--restart unless-stopped \
-p 8080:8080 \
-v $(pwd)/credentials.json:/app/credentials.json \
-v $(pwd)/locations_cache.json:/app/locations_cache.json \
totalconnect-monitor
```
+76
View File
@@ -0,0 +1,76 @@
# Guía de Ubicación, Actualización y Despliegue del Proyecto
Esta guía documenta la estructura de directorios del proyecto, el flujo de sincronización y los comandos necesarios para iniciar, reiniciar y monitorear el servicio en la máquina virtual Linux por SSH.
---
## 1. Ubicaciones del Código
El proyecto reside en tres entornos clave:
1. **Copia de Desarrollo (Windows Local):**
`E:\Software\TotalConnect\`
*Uso:* Compilación del ejecutable standalone `totalconnect.exe` para Windows usando PyInstaller.
2. **Copia Compartida (Samba Share):**
`Z:\data2\Software\Bot-WhatsApp\TotalConnect\`
*Uso:* Compartido en la red que la VM Linux monta de forma nativa. Cualquier cambio guardado en `Z:\` se refleja de forma instantánea en la VM.
3. **Entorno de Producción (Linux VM - `192.168.100.50`):**
`/mnt/data2/Software/Bot-WhatsApp/TotalConnect/`
*Uso:* Directorio donde se ejecuta el demonio de polling de forma continua.
---
## 2. Flujo de Actualización de Cambios
Cada vez que se modifica el código (ej: en `index.py`), se debe seguir el siguiente orden para sincronizar los entornos:
1. Modificar el archivo principal en la red compartida:
`Z:\data2\Software\Bot-WhatsApp\TotalConnect\index.py`
2. Copiar los cambios a la ruta de desarrollo local en Windows:
```powershell
Copy-Item "Z:\data2\Software\Bot-WhatsApp\TotalConnect\index.py" "E:\Software\TotalConnect\index.py" -Force
```
3. Si el cambio requiere actualizar el ejecutable de Windows `totalconnect.exe`, ejecutar en Windows:
```powershell
cd E:\Software\TotalConnect\
python -m PyInstaller --clean totalconnect.spec
Copy-Item "E:\Software\TotalConnect\dist\totalconnect.exe" "E:\Software\TotalConnect\totalconnect.exe" -Force
```
4. Reiniciar el demonio en la VM (ver comandos en sección 3) para que lea las modificaciones de `index.py`.
---
## 3. Ejecución del Servicio en la VM por SSH
El demonio de polling corre en la máquina virtual Linux en segundo plano. Para interactuar con él, conéctate por SSH:
```powershell
ssh -i "C:\Users\JAGU\.gemini\antigravity-ide\brain\d979a063-dd63-47a8-8ec1-785abc40b475\scratch\id_ed25519_win" -o StrictHostKeyChecking=no root@192.168.100.50
```
### Comandos de Control en la VM:
* **Detener el demonio activo:**
```bash
pkill -f 'python3 index.py'
```
* **Iniciar el demonio en segundo plano (Recomendado):**
```bash
cd /mnt/data2/Software/Bot-WhatsApp/TotalConnect
nohup ./venv/bin/python3 index.py >> server.log 2>&1 &
```
* **Reiniciar el demonio (Detener e Iniciar en una línea):**
```bash
pkill -f 'python3 index.py'; sleep 2; cd /mnt/data2/Software/Bot-WhatsApp/TotalConnect && nohup ./venv/bin/python3 index.py >> server.log 2>&1 &
```
### Comandos de Monitoreo:
* **Comprobar si el proceso de Python está activo:**
```bash
ps aux | grep 'python3 index.py' | grep -v grep
```
* **Visualizar los logs de actividad en tiempo real:**
```bash
tail -f /mnt/data2/Software/Bot-WhatsApp/TotalConnect/server.log
```
+70
View File
@@ -0,0 +1,70 @@
# Plan de Implementación y Walkthrough Propuesto: Integración de Eventos en Tiempo Real
Este documento detalla el plan de diseño técnico y la arquitectura de los cambios que se implementarán en el proyecto TotalConnect (`index.py`) para integrar el monitoreo por eventos en tiempo real.
---
## 🎯 Objetivos de la Implementación
1. **Monitoreo Automático en Segundo Plano:** El servidor Python consultará a Honeywell mediante `GetEvents` cada 4560 segundos sin requerir la intervención del usuario.
2. **Actualización Ultra-Rápida (Delta Update):** En lugar de consultar las 285 sucursales (1-2 minutos), el sistema identificará únicamente las sucursales que registraron eventos nuevos y actualizará el reporte en **1 a 3 segundos**.
3. **Cero Retraso al Abrir la Página (`GET /`):** La carga inicial seguirá leyendo `last_state.json` de inmediato (< 5 milisegundos).
4. **Control Manual y Circuit Breaker (Botonera & Kill-Switch):**
* Botón en la cabecera del dashboard para **Pausar / Reanudar Polling**.
* Pausa automática inteligente si Honeywell responde errores de autenticación (401) o límite de peticiones (429).
5. **DOM Patching Silencioso en la Interfaz (JS):** Actualización instantánea en el navegador únicamente de los nodos/tarjetas que cambiaron de estado, evitando parpadeos o animaciones molestas.
6. **Manejo Resiliente Multicuenta y Revocados:** Las cuentas fuera de línea se mantendrán clasificadas con prioridad 3 al final del listado con fondo gris.
---
## 🏗️ Cambios Arquitectónicos por Componente
### 1. Backend (`index.py`)
#### A. Hilo Demonio de Polling en Segundo Plano (`BackgroundPollerThread`)
* Se agregará un `threading.Thread(daemon=True)` que se ejecuta automáticamente al arrancar el servidor.
* Mantiene variables globales en memoria:
* `polling_enabled` (`True` / `False`): Estado global del motor de consulta.
* `last_event_ids`: Diccionario `{ "Ccontrol2020": 850123, "Ccontrol2019": 412099 }` con el último ID de evento procesado por cuenta.
* `consecutive_errors`: Contador de fallos consecutivos por cuenta.
#### B. Flujo del Poller (`poll_events_cycle()`):
1. **Verificación de Pausa:** Si `polling_enabled` es `False`, el hilo espera 15 segundos y re-evalúa.
2. **Petición Delta `GetEvents`:**
Para cada cuenta activa en `credentials.json`, envía una llamada ligera `GetEvents` pasando `LastEventIdReceived`.
3. **Evaluación de Resultados:**
* **Sin cambios:** Honeywell responde `[]`. No se hace nada más.
* **Con cambios:** Extrae los `LocationID` afectados. Ejecuta `fullStatus` únicamente para esos IDs.
4. **Actualización de Estado:** Consolida la información en `last_state.json` y vuelve a generar `status.html`.
5. **Manejo de Errores (Circuit Breaker):** Si una cuenta recibe 2 errores `401` o `429` seguidos, el servidor pausa el polling automático para esa cuenta y notifica a la UI.
#### C. Endpoints HTTP Nuevos en `DashboardHandler`
* `GET /polling-status`: Devuelve el estado actual (`enabled`, `last_run`, `active_errors`).
* `POST /toggle-polling`: Permite a la interfaz web activar o pausar manualmente el polling.
---
### 2. Frontend (`status.html` / Template en `index.py`)
#### A. Botón de Control de Polling en Cabecera
Junto al botón "Actualizar" y al selector de cuentas, se agregará un nuevo botón dinámico:
```html
<button id="toggle-polling-btn" class="btn-polling-active" onclick="togglePolling()">
<span class="polling-indicator"></span> Polling: Activo
</button>
```
#### B. Polling Local JS y DOM Patching
Un script JS en el cliente ejecutará una consulta ultrarrápida a `GET /polling-status` cada 15 segundos:
* Si la respuesta indica un cambio en el timestamp del último estado, realiza un `fetch('/')` silencioso.
* Compara los estados de las sucursales y actualiza el HTML **únicamente de las tarjetas que cambiaron**, manteniendo la posición y el foco del usuario.
* **Auto-Check por Visibilidad (`visibilitychange`):** Al volver a la pestaña tras estar inactivo, el navegador refresca los datos al instante.
---
## 📝 Plan de Verificación
1. **Prueba de Polling en Segundo Plano:** Iniciar el servidor, alterar el estado de una sucursal de prueba y verificar en consola que la consulta toma < 2 segundos.
2. **Prueba de Kill-Switch:** Presionar el botón "Pausar Polling" en la interfaz y verificar que cesan las llamadas HTTP a Honeywell.
3. **Prueba de Circuit Breaker:** Simular una credencial inválida y verificar que el servidor desactiva automáticamente el polling tras 2 intentos fallidos.
4. **Prueba de Rendimiento:** Confirmar que la carga inicial `GET /` sigue respondiendo en < 5ms.
+84
View File
@@ -0,0 +1,84 @@
# Walkthrough - Resumen de Mejoras y Correcciones (TotalConnect Client)
Este documento resume los cambios y optimizaciones realizados para robustecer el panel de monitoreo centralizado de alarmas.
---
## 🛠️ Cambios Realizados
### 1. Actualización en Tiempo Real del Progreso de Carga
* **Problema:** El modal de carga se quedaba en un mensaje estático ("Conectando a los paneles...") y no actualizaba qué sucursal estaba procesando.
* **Solución:**
* Se configuró la variable global `current_loading_status` para actualizarse en cada paso de `run_update()` (conexión inicial, selección de cuenta y consulta específica de sucursal).
* Se agregaron cabeceras anti-caché en la respuesta HTTP (`Cache-Control: no-cache, no-store, must-revalidate`).
* Se implementó un sondeo mediante `fetch` periódico (intervalo de 1.5s) que actualiza el texto de forma dinámica sin retrasos.
### 2. Lista de Exclusiones / Excepciones (`exclude`)
* **Problema:** El usuario deseaba omitir ciertas sucursales de prueba o fuera de servicio de la visualización global.
* **Solución:**
* Se agregó soporte para una clave `"exclude": [...]` en el archivo de configuración `credentials.json`.
* El script valida de forma inteligente si el ID numérico de la sucursal o su nombre coincide parcialmente con alguno de los elementos a excluir, omitiendo la consulta y el reporte de manera automática.
### 3. Apertura Automática del Navegador
* **Problema:** Iniciar el ejecutable requería que el usuario abriera manualmente la página en `http://localhost:8080/`.
* **Solución:**
* Se integró el uso del módulo nativo `webbrowser` para abrir la pestaña al arrancar en modo servidor.
* **Headless-safety:** El script detecta si el host no posee entorno gráfico (ausencia de `DISPLAY` en sistemas Linux/SSH) y desactiva esta apertura de forma segura para evitar cuelgues o warnings.
* Se puede personalizar a través de `"open_browser": true/false` en el JSON o usando las banderas `--no-browser` / `-nb` en la CLI.
### 4. Filtrado de Historial de Alarma (Alarm Memory)
* **Problema:** Algunas sucursales desarmadas o armadas normalmente mostraban alertas de alarma activas debido a memorias no limpiadas físicamente en el teclado Honeywell.
* **Solución:**
* Se rediseñó el bucle de validación de zonas. Ahora, una zona **sólo se marca como `ALARMA`** si la partición correspondiente de la sucursal está activamente disparada (`is_triggered`).
* Si la sucursal está normal, las zonas cerradas no generan falsas alertas y las abiertas se muestran propiamente como `Abierta/Fallo` (color ámbar).
### 5. Tarjetas Deshabilitadas para Cuentas con Token Revocado
* **Problema:** Cuando una cuenta de Total Connect pierde credenciales o se revoca, sus sucursales se cargaban de la caché local pero se visualizaban igual que las sucursales normales de cuentas activas.
* **Solución:**
* Se añadió la clase CSS `.branch-card-disabled` a las tarjetas cargadas desde la caché local con estatus `REVOKED`.
* Esto les otorga un fondo gris medio (`#e2e8f0`), una opacidad del 65% y bloquea los efectos visuales de interacción en hover (sombra y escala), permitiendo distinguir a simple vista qué datos son históricos fuera de línea.
### 6. Diseño Responsivo y Text Wrapping
* **Problema:** En zonas activas largas, las horas de disparo quedaban recortadas con puntos suspensivos en la tarjeta.
* **Solución:**
* Se eliminó el límite fijo de ancho de `.zone-desc` y se desactivó `white-space: nowrap`.
* Se aumentó el ancho mínimo de las columnas en el grid responsivo a `290px`. Las descripciones largas ahora realizan un salto de línea natural posicionando la hora debajo de forma sumamente estética.
### 7. Checkboxes de Selección de Cuentas en la UI
* **Problema:** El usuario deseaba poder elegir qué cuentas actualizar para evitar demoras innecesarias (por ejemplo, evitar actualizar cuentas revocadas).
* **Solución:**
* Se implementaron checkboxes en la cabecera que listan dinámicamente las cuentas de `credentials.json`.
* Al hacer clic en "Actualizar", la UI envía mediante el cuerpo del POST (JSON) únicamente las etiquetas de las cuentas seleccionadas.
### 8. Persistencia y Mezcla de Estado (`last_state.json`)
* **Problema:** Si se actualizaba únicamente una cuenta, el reporte en el frontend sobrescribía el archivo HTML y hacía desaparecer las demás sucursales.
* **Solución:**
* Se programó la escritura de `last_state.json` al finalizar cualquier consulta exitosa para guardar el último estado de todas las sucursales.
* Cuando se realiza una actualización parcial (donde no se seleccionaron todas las cuentas), el backend carga los estados anteriores desde `last_state.json`, los mezcla con los nuevos resultados y genera la vista unificada.
### 9. Priorización de Cuentas Revocadas al Final del Listado
* **Problema:** El usuario deseaba ver las cuentas inactivas/revocadas al final de la página para que no interfieran visualmente con las activas.
* **Solución:**
* Se definió un cuarto nivel de clasificación de prioridad:
* `0`: Alarmas Activas (cabeza de página)
* `1`: Sucursales Desarmadas
* `2`: Sucursales Armadas (Stay, Away, etc.)
* `3`: Sucursales Revocadas / Desconectadas (con fondo gris)
* Todas las tarjetas dentro de cada categoría mantienen su estricto orden secuencial numérico.
### 10. Polling en Segundo Plano & Controles (Kill-Switch y Circuit Breaker)
* **Problema:** El usuario requería monitoreo continuo sin bloqueos de API de Honeywell y con la capacidad de detener el polling manualmente o si Honeywell responde errores de autenticación/rate limit.
* **Solución:**
* **Motor Poller Daemon (`background_poller_thread`):** Se ejecuta cada 45s en segundo plano en el servidor Python.
* **Circuit Breaker:** Si se registran 2 fallos de autenticación seguidos (`401` / `429`), el backend desactiva el polling automático previniendo bloqueos de IP de Honeywell.
* **Endpoints Controladores:** Agregados `GET /polling-status` y `POST /toggle-polling`.
* **Botonera en UI:** Se agregó el botón interactivo `[ ● Polling: Activo ]` / `[ ○ Polling: Pausado ]` en el encabezado.
* **Auto-Focus (`visibilitychange`):** El navegador recarga el estatus automáticamente al regresar a la pestaña tras periodos de inactividad.
---
## 🔬 Verificación y Pruebas
* **Compilación y Empaquetado:** Generación exitosa de `totalconnect.exe` en Windows `E:\Software\TotalConnect\dist\totalconnect.exe`.
* **Sincronización:** Actualizado y desplegado en segundo plano en la VM de Linux accesible a través del puerto `8080`.
* **Validación de Funcionalidad:** Todas las rutas del dashboard, filtros por JSON, exclusiones y endpoints de polling operan correctamente.