diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md
new file mode 100644
index 0000000..0d0aad8
--- /dev/null
+++ b/.agents/AGENTS.md
@@ -0,0 +1,12 @@
+# Reglas del Proyecto - Colaborando
+
+1. **Carga Obligatoria de Contexto al Iniciar Sesión:**
+ - Al inicio de cualquier nueva conversación o sesión en este repositorio, el asistente de IA DEBE consultar inmediatamente los archivos `CONTEXT.md` y `.antigravityrules`.
+ - `CONTEXT.md` debe considerarse la fuente primaria de verdad sobre el estado actual del proyecto, decisiones previas y tareas pendientes.
+
+2. **Verificación de Sincronización (Git vs CONTEXT.md):**
+ - Al leer `CONTEXT.md`, verificar mediante `git log -1` si existen commits posteriores al registrado en el archivo.
+ - Si `CONTEXT.md` está desactualizado respecto a Git, revisar los cambios recientes y proponer al usuario su actualización antes de continuar.
+
+3. **Permisos y Modificaciones:**
+ - Respetar estrictamente las reglas de `.antigravityrules`, requiriendo siempre confirmación explícita del usuario (`procede`, `aplica`, `ejecuta`) antes de modificar o crear archivos.
diff --git a/.antigravityrules b/.antigravityrules
old mode 100755
new mode 100644
index 1f00ffd..d6ba404
--- a/.antigravityrules
+++ b/.antigravityrules
@@ -1,15 +1,11 @@
# Antigravity AI Rules
-1. **Permiso explícito obligatorio para realizar modificaciones**:
+1. **Permiso explícito obligatorio para realizar consultas**:
- El asistente de IA tiene permitido leer cualquier archivo y ejecutar comandos de lectura/diagnóstico sin necesidad de confirmación previa en el chat.
- El asistente **NUNCA** debe crear, modificar o eliminar ningún archivo (tanto dentro como fuera del espacio de trabajo) ni ejecutar comandos que modifiquen el sistema sin la autorización previa y explícita del usuario en el chat.
- Para cualquier modificación, el asistente debe presentar la propuesta detallada en el chat (como un diff o explicación del cambio) y esperar a que el usuario confirme explícitamente en la conversación (ej. "procede", "aplica", "ejecuta") antes de proceder.
-2. **Persistencia de Planes y Tareas en el Proyecto**:
- - Para evitar pérdidas de contexto al cambiar o reiniciar sesiones de chat, el asistente siempre debe replicar y guardar copias de sus archivos de planificación, tareas y bitácoras (`implementation_plan.md`, `task.md`, `walkthrough.md`) en la carpeta `doc/` ubicada en la raíz del proyecto.
- - Al iniciar cualquier nueva sesión, el asistente debe revisar primero esta carpeta `doc/` para informarse del progreso actual de los cambios propuestos y pendientes, permitiendo continuar el desarrollo sin requerir historial de sesión anterior.
-
-3. **Análisis Técnico Objetivo y Crítico**:
+2. **Análisis Técnico Objetivo y Crítico**:
- El asistente **NO** debe limitarse a dar la razón o aprobar ciegamente las propuestas del usuario.
- Ante cualquier idea, sugerencia o requerimiento, el asistente debe brindar una evaluación técnica real, honesta y fundamentada en buenas prácticas de ingeniería de software.
- - **Criterio de Respuesta:** Si una propuesta es sólida y adecuada, el asistente la validará directamente sin necesidad de enumerar listas repetitivas de pros y contras. Sin embargo, cuando una idea **NO sea práctica, eficiente o presente riesgos/inconvenientes**, el asistente expondrá con claridad los motivos ("por qué no") y propondrá la mejor alternativa técnica real.
\ No newline at end of file
+ - **Criterio de Respuesta:** Si una propuesta es sólida y adecuada, el asistente la validará directamente sin necesidad de enumerar listas repetitivas de pros y contras. Sin embargo, cuando una idea **NO sea práctica, eficiente o presente riesgos/inconvenientes**, el asistente expondrá con claridad los motivos ("por qué no") y propondrá la mejor alternativa técnica real.
diff --git a/.gitignore b/.gitignore
old mode 100755
new mode 100644
diff --git a/CONTEXT.md b/CONTEXT.md
new file mode 100644
index 0000000..525aa89
--- /dev/null
+++ b/CONTEXT.md
@@ -0,0 +1,37 @@
+# Estado del Proyecto - TotalConnect 2.0
+
+**Última actualización:** 2026-08-02
+**Versión actual:** v06.08.01
+
+---
+
+## 📌 Resumen del Estado Actual
+
+El proyecto **TotalConnect 2.0** es un panel web centralizado de alto rendimiento para el monitoreo de alarmas Honeywell / Resideo Total Connect 2.0.
+
+### 🛠️ Características Principales e Infraestructura
+1. **Motor Híbrido Dinámico de Monitoreo (`index.py`)**:
+ - **Grupo 0**: Prioridad absoluta para sucursales con alarmas activas (particiones, zonas y estado ALARMING) o eventos SOAP pendientes (`needs_full_update = True`).
+ - **Esquema 2:1 (SOAP + REST)**: 2 consultas ligeras vía SOAP por 1 consulta completa por REST para evitar el *rate-limiting* de Honeywell (máximo 35 consultas REST por ciclo).
+ - **Resiliencia de Sesión**: Purgado automático de tokens SOAP obsoletos y auto-recuperación ante errores de red (`ConnectionResetError`).
+2. **Dashboard Web e Interfaz en Tiempo Real**:
+ - **Badge de Escaneo en Vivo**: Muestra en tiempo real la sucursal actual en proceso de carga/sondeo (`Cargando sucursal: 6001`).
+ - **Visor de Bitácora Interactiva**: Modal visual en vivo para consultar las últimas 150 líneas del archivo de log (`activity.log`).
+ - **Hard Reset**: Mecanismo de purga de caché y reinicio por clic prolongado (3s) en el botón de actualización.
+ - **Kill-Switch / Polling Toggle**: Control de inicio/pausa de sondeo en segundo plano con *Circuit Breaker* ante errores `401`/`429`.
+3. **Portabilidad y Parámetros**:
+ - Eliminación de rutas absolutas en duro (`Z:\`, `E:\`), operando de manera 100% relativa a la ruta de ejecución actual.
+ - Binario autónomo `totalconnect.exe` para Windows y soporte para servicio Systemd en Linux.
+
+---
+
+## 🏷️ Historial de Tags / Versiones
+
+* **`v06.08.01`**: Prioridad en Grupo 0 para alarmas y eventos SOAP, indicador de escaneo en vivo en UI, eliminación de rutas absolutas en duro.
+
+---
+
+## 📋 Tareas Pendientes / Próximos Pasos
+
+- [ ] Monitorear estabilidad del motor híbrido en producción tras despliegue de `v06.08.01`.
+- [ ] Validar rendimiento en entornos con elevado número de cuentas Total Connect.
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
diff --git a/TotalConnect.20260730.2256.rar b/TotalConnect.20260730.2256.rar
new file mode 100644
index 0000000..3ee584c
Binary files /dev/null and b/TotalConnect.20260730.2256.rar differ
diff --git a/check_fullstatus_json.py b/check_fullstatus_json.py
deleted file mode 100755
index e4701ab..0000000
--- a/check_fullstatus_json.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import json
-from total_connect_client.client import TotalConnectClient
-from total_connect_client.const import make_http_endpoint
-
-creds = json.load(open('credentials.json', encoding='utf-8'))['accounts'][0]
-client = TotalConnectClient(creds['username'], creds['password'], load_details=False)
-client.authenticate()
-
-location_id = 1417027
-endpoint = make_http_endpoint(f"api/v3/locations/{location_id}/partitions/fullStatus")
-res = client.http_request(endpoint=endpoint, method="GET")
-
-print("Keys at root of fullStatus response:")
-print(list(res.keys()))
-
-print("\nValue of ArmingState at root:")
-print(res.get("ArmingState"))
-
-print("\nValue of PanelStatus -> ArmingState:")
-panel_status = res.get("PanelStatus", {})
-print(panel_status.get("ArmingState"))
-
-print("\nPartitions data:")
-for p in panel_status.get("Partitions", []):
- print(f" PartitionID={p.get('PartitionID')}, ArmingState={p.get('ArmingState')}")
diff --git a/doc/alternative_implementations_comparison.md b/doc/alternative_implementations_comparison.md
old mode 100755
new mode 100644
diff --git a/doc/polling_modes_explanation.md b/doc/polling_modes_explanation.md
old mode 100755
new mode 100644
diff --git a/doc/portability_and_packaging.md b/doc/portability_and_packaging.md
old mode 100755
new mode 100644
diff --git a/doc/project_deployment_guide.md b/doc/project_deployment_guide.md
old mode 100755
new mode 100644
diff --git a/doc/proposed_events_integration_plan.md b/doc/proposed_events_integration_plan.md
old mode 100755
new mode 100644
diff --git a/doc/walkthrough.md b/doc/walkthrough.md
old mode 100755
new mode 100644
diff --git a/index.js b/index.js
deleted file mode 100755
index da0c3c6..0000000
--- a/index.js
+++ /dev/null
@@ -1,517 +0,0 @@
-import fs from 'fs';
-import path from 'path';
-import axios from 'axios';
-import xml2js from 'xml2js';
-import { fileURLToPath } from 'url';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-const CREDENTIALS_FILE = path.join(__dirname, 'credentials.json');
-const HTML_OUTPUT_FILE = path.join(__dirname, 'status.html');
-const API_URL = 'https://rs.alarmnet.com/TC2API/TC2.asmx';
-const APPLICATION_ID = 14588; // App ID estándar de Total Connect 2.0
-const APPLICATION_VERSION = '3.0.0';
-
-// Parser de XML configurado para remover prefijos de namespaces (como soap:)
-const xmlParser = new xml2js.Parser({
- explicitArray: false,
- ignoreAttrs: true,
- tagNameProcessors: [xml2js.processors.stripPrefix]
-});
-
-// Función para parsear XML a objeto JS de manera segura
-async function parseXml(xmlString) {
- return await xmlParser.parseStringPromise(xmlString);
-}
-
-// Inicializar archivo de credenciales de ejemplo si no existe
-function initCredentialsFile() {
- if (!fs.existsSync(CREDENTIALS_FILE)) {
- const template = [
- {
- label: "Cuenta Principal",
- username: "tu_usuario_1",
- password: "tu_password_1"
- },
- {
- label: "Cuenta Secundaria",
- username: "tu_usuario_2",
- password: "tu_password_2"
- }
- ];
- fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(template, null, 2), 'utf8');
- console.log(`⚠️ Se ha creado un archivo de plantilla en: ${CREDENTIALS_FILE}`);
- console.log("Por favor edítalo con tus credenciales reales de TotalConnect 2.0 antes de ejecutar.");
- process.exit(1);
- }
-}
-
-// Traducir código de estado de alarma
-function parseArmingState(stateCode) {
- const code = parseInt(stateCode);
- switch (code) {
- // Códigos normales de desarmado
- case 102:
- case 10200:
- case 10201: // Listo para armar
- case 10203: // Alarma en memoria / aviso
- return {
- label: "Desarmado",
- class: "state-disarmed",
- icon: "🟢",
- badge: "Listo"
- };
- case 10202: // No listo para armar (p.ej. sensor abierto)
- return {
- label: "Desarmado (No Listo / Zona Abierta)",
- class: "state-fault",
- icon: "🟡",
- badge: "Revisar"
- };
- // Códigos de armado
- case 101:
- case 10100: // Armado fuera (Away)
- return {
- label: "Armado Fuera",
- class: "state-armed-away",
- icon: "🔴",
- badge: "Armado (Away)"
- };
- case 103:
- case 10300: // Armado casa (Stay)
- return {
- label: "Armado En Casa",
- class: "state-armed-stay",
- icon: "🟠",
- badge: "Armado (Stay)"
- };
- case 104:
- case 10400: // Armado noche (Night)
- return {
- label: "Armado Noche",
- class: "state-armed-night",
- icon: "🔵",
- badge: "Armado (Night)"
- };
- default:
- return {
- label: `Estado Desconocido (${stateCode})`,
- class: "state-unknown",
- icon: "⚪",
- badge: "Desconocido"
- };
- }
-}
-
-// 1. Realizar Login y obtener SessionID y lista de Sucursales (Locations)
-async function loginAndGetLocations(username, password) {
- const soapEnvelope = `
-
-
-
- ${username}
- ${password}
- ${APPLICATION_ID}
- ${APPLICATION_VERSION}
- en-US
-
-
-`;
-
- const response = await axios.post(API_URL, soapEnvelope, {
- headers: {
- 'Content-Type': 'text/xml; charset=utf-8',
- 'SOAPAction': 'https://services.alarmnet.com/TC2/LoginAndGetSessionDetails'
- }
- });
-
- console.log("DEBUG RESPONSE:", response.data);
- const parsed = await parseXml(response.data);
- const result = parsed.Envelope.Body.LoginAndGetSessionDetailsResponse.LoginAndGetSessionDetailsResult;
-
- if (result.ResultCode !== '0') {
- throw new Error(`Fallo en autenticación: Código de error ${result.ResultCode} (${result.ResultData || 'Credenciales inválidas'})`);
- }
-
- const sessionId = result.SessionID;
-
- // Normalizar la lista de locaciones (si es solo una, xml2js la deja como objeto en vez de array)
- let locations = [];
- const rawLocations = result.Locations?.LocationInfoBasic;
- if (rawLocations) {
- locations = Array.isArray(rawLocations) ? rawLocations : [rawLocations];
- }
-
- return { sessionId, locations };
-}
-
-// 2. Obtener el estatus de un panel específico (GetPanelMetaDataAndFullStatusEx_V2)
-async function getPanelStatus(sessionId, locationId) {
- const soapEnvelope = `
-
-
-
- ${sessionId}
- ${locationId}
- 0
- 0
-
- 1
-
-
-
-`;
-
- const response = await axios.post(API_URL, soapEnvelope, {
- headers: {
- 'Content-Type': 'text/xml; charset=utf-8',
- 'SOAPAction': 'https://services.alarmnet.com/TC2/GetPanelMetaDataAndFullStatusEx_V2'
- }
- });
-
- const parsed = await parseXml(response.data);
- const result = parsed.Envelope.Body.GetPanelMetaDataAndFullStatusEx_V2Response.GetPanelMetaDataAndFullStatusEx_V2Result;
-
- if (result.ResultCode !== '0') {
- throw new Error(`Fallo al consultar estatus del panel ${locationId}: Código ${result.ResultCode}`);
- }
-
- return result.ArmingState;
-}
-
-// 3. Generar el reporte HTML estático con diseño premium
-function generateHtmlReport(data) {
- const timeZone = 'America/Mexico_City';
- const timestamp = new Date().toLocaleString('es-MX', {
- timeZone,
- weekday: 'long',
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- hour12: true
- });
-
- // Construir filas de la tabla de sucursales
- let rowsHtml = '';
- data.forEach(account => {
- account.locations.forEach(loc => {
- const state = parseArmingState(loc.status);
- rowsHtml += `
-