Saltar a contenido

SDD — ReenvioZoho (Python)

Ficha técnica

Campo Valor
Repositorio / carpeta python/ReenvioZoho
Plataforma Python 3.14 + Selenium (Chrome)
Servidor VM-ROBOTS (Windows Server), cuenta robots_IT
Disparo Programador de tareas, tarea PY_main
Frecuencia Cada 15 min, lun-vie 08:35–17:30 (36 ejecuciones/día)
Proceso de negocio PDD

1. Arquitectura general

El robot tiene dos mitades. La primera solo lee por la API oficial de SalesIQ y arma un plan de reparto. La segunda ejecuta la asignación desde el navegador, porque la API oficial no permite asignar chats perdidos. Si la primera mitad falla, el navegador ni se abre.

flowchart LR
    TS[Programador de tareas<br/>PY_main] --> M[main.py]
    M --> L[bloqueo.py<br/>candado]
    M --> R[registro.py]
    R --> SQL[(SQL Server<br/>automatizaciones)]
    M --> P[planificador.py]
    P --> S[salesiq.py]
    S -->|OAuth, solo lectura| API[API oficial SalesIQ v2]
    M --> N[navegador.py]
    N -->|lanza| CH[Chrome<br/>perfil propio]
    N -->|debuggerAddress| CH
    CH -->|PUT assign + CSRF| WEB[API web interna SalesIQ]
    M --> T[telegram.py] -->|solo fallos| TG[Telegram]

2. Componentes

Archivo / módulo Responsabilidad
main.py Orquestador: candado, registro, reintentos, decide éxito/falla y si se notifica
config.py Lee el .env, valida que no falten variables y configura el logging
salesiq.py Cliente de la API oficial: token OAuth, operadoras y chats perdidos
planificador.py Filtros de negocio, memoria de chats asignados y reparto round-robin
navegador.py Lanza Chrome, conecta Selenium, inicia sesión y ejecuta el JS de asignación
registro.py Registra la ejecución y sus métricas en SQL Server (reutilizable)
bloqueo.py Candado de instancia única y limpieza de Chrome huérfanos (reutilizable)
telegram.py Envío de avisos; nunca lanza excepción
.env Credenciales y parámetros. No se versiona; ver .env.example

3. Flujo detallado

  1. Candado. main.py toma C:\automatizaciones\locks\python-reenviozoho.lock. Si otra corrida sigue viva, esta se sale con código 0 sin registrar nada. Si el PID del candado ya no existe, lo reclama.
  2. Registro. Abre un renglón en ejecuciones con resultado = 'en_curso'. Con --simular no se registra.
  3. Validación. config.validar() confirma credenciales, departamento y lista de operadoras. Si falta algo: código 2 y Telegram.
  4. Paso 1 — planificación (planificar()):
    1. Token OAuth con el refresh token.
    2. Operadoras del portal; se quedan las de ALLOWED_OPERATORS con status_code = 1 y is_enabled = true.
    3. Chats status=missed del DEPARTMENT_ID.
    4. Filtros: últimas MAX_HORAS_ANTIGUEDAD (72) horas, fuera los que están en la memoria de asignados, fuera los que ya tienen owner/attender humano.
    5. Orden del más antiguo al más reciente y reparto round-robin. Devuelve un objeto Plan.
  5. Si no hay plan (SIN_CHATS, SIN_OPERADORES, DRY_RUN o error), termina sin abrir el navegador.
  6. Limpieza. matar_huerfanos() cierra los Chrome que quedaron vivos con el perfil del robot.
  7. Paso 2 — navegador (Navegador):
    1. Lanza chrome.exe con --remote-debugging-port (puerto libre al azar) y el perfil del robot.
    2. Espera a que el puerto responda (/json/version, hasta 40 s).
    3. Conecta Selenium por debuggerAddress.
    4. Abre el portal; si no hay sesión, hace login con ZOHO_USER / ZOHO_PASSWORD.
  8. Asignación. Lee el token CSRF con get_cookie() y ejecuta el JS, que hace un PUT síncrono a /web/v2/{screen}/conversations/assign por cada operadora. Hasta 3 intentos si el estado es reintentable. Los ids asignados se guardan en la memoria después de cada intento, aunque sea parcial.
  9. Cierre. Escribe las tres métricas, cierra el renglón como exito o falla y, solo si es falla, avisa por Telegram. Al salir del with, cierra Chrome por CDP y limpia por perfil.

4. Sistemas y dependencias

Sistema Uso Acceso Notas
API oficial SalesIQ v2 Operadoras y chats perdidos API (OAuth) Solo lectura
API web interna SalesIQ Asignar chats Web (cookie de sesión + CSRF) Endpoint no documentado; puede cambiar sin aviso
Zoho Accounts Token OAuth y login del portal API / web DC .com
Google Chrome + ChromeDriver Sesión del portal Selenium ChromeDriver lo resuelve Selenium Manager según la versión de Chrome
SQL Server automatizaciones Ejecuciones y métricas BD (autenticación de Windows) localhost\SQLEXPRESS, driver ODBC detectado solo
Telegram Bot API Avisos de falla API —

5. Credenciales y configuración

No se escriben valores aquí; solo dónde están.

Nombre Tipo Dónde se guarda
ZOHO_CLIENT_ID, ZOHO_CLIENT_SECRET, ZOHO_REFRESH_TOKEN OAuth self-client .env del servidor / Bitwarden
ZOHO_USER, ZOHO_PASSWORD Usuario y contraseña del portal .env del servidor / Bitwarden
TELEGRAM_TOKEN, TELEGRAM_CHAT_ID Token de bot .env del servidor / Bitwarden
SQL Server Autenticación de Windows Cuenta robots_IT, sin credencial aparte

El refresh token debe tener los scopes SalesIQ.conversations.READ, SalesIQ.conversations.UPDATE y SalesIQ.operators.READ. Se genera en api-console.zoho.com.

Parámetros de negocio en el .env: SALESIQ_SCREEN_NAME, SALESIQ_DEPARTMENT_ID, ALLOWED_OPERATORS (correos separados por coma), MAX_HORAS_ANTIGUEDAD, DRY_RUN.

6. Estados y datos persistentes

Recurso Ruta Para qué
Memoria de asignados C:\Robot\logs\salesiq_asignados.json Ids ya repartidos; se purgan a los 7 días
Perfil de Chrome C:\automatizaciones\chrome_profile_Zoho Conserva la sesión del portal entre corridas
Candado C:\automatizaciones\locks\python-reenviozoho.lock PID e inicio de la corrida activa
Log del robot C:\Robot\logs\salesiq_robot.log Bitácora completa en UTF-8
Log de ChromeDriver C:\Robot\logs\chromedriver_verbose.log Diagnóstico de arranque del navegador
Tablas SQL ejecuciones, metricas Historial y cifras por corrida

7. Manejo de errores y reintentos

Estado final Resultado en SQL ¿Reintenta? ¿Telegram?
OK_ASIGNADO exito — No
SIN_CHATS exito — No
SIN_OPERADORES exito — No
DRY_RUN no se registra — No
ERROR_PARCIAL falla Sí, hasta 3 Sí
ERROR_TOTAL falla Sí, hasta 3 Sí
ERROR_SESION falla No Sí
ERROR_API (paso 1) falla No Sí
Configuración inválida falla (código 2) No Sí
Excepción no manejada falla (código 1) No Sí

En la API, los 401/403 y demás 4xx no se reintentan; los 5xx y errores de red sí son reintentables. Ante un error HTTP, el cuerpo de la respuesta de Zoho queda en el log, que es donde explica el motivo.

8. Monitoreo y notificaciones

  • SQL Server: cada corrida en ejecuciones (automatizacion_id = 'python-reenviozoho', origen scheduler o manual) con tres métricas: chats_encontrados (los que entraron al plan, ya filtrados), chats_asignados y chats_no_asignados. Un renglón que se queda en en_curso significa que el proceso murió sin cerrar.
  • Metabase: vistas para gerente (chats y horas ahorradas al mes), jefe (ejecuciones del día contra las 36 esperadas, fallas silenciosas por chats_no_asignados alto) y desarrollador (detalle de las últimas corridas).
  • Telegram: solo fallos. Las corridas sin chats o exitosas no avisan.

9. Restricciones conocidas y trampas

La API oficial no sirve para asignar chats perdidos

/conversations/{id}/transfer responde 400 Chat is not connected (código 2046): solo opera sobre chats en curso. Por eso existe la mitad del navegador. Se probó el 2026-09-16.

Paginación inconsistente entre endpoints

/operators solo acepta limit (con index da 500, con page da 400, sin parámetros recorta a 20 registros). /conversations sí acepta limit + index. salesiq.listar() prueba estrategias en orden y avisa en el log si el resultado parece un tope de la API. Cuando sin paginación devolvía 20, operadoras reales quedaban fuera del reparto.

El token CSRF cambió de nombre

La cookie pasó de CT_CSRF_TOKEN a LS_CSRF_TOKEN. Se lee con get_cookie() probando ambos nombres. Si vuelve a cambiar, el error lo dice: lista los nombres de todas las cookies presentes.

Chrome lo lanza el robot, no ChromeDriver

En el servidor, Chrome se relanza a sí mismo al arrancar (actualizaciones pendientes, desescalación de privilegios) y ChromeDriver pierde el proceso: session not created: Chrome instance exited. Por eso se lanza con subprocess y Selenium se engancha por puerto (debuggerAddress). En este modo driver.quit() no cierra Chrome; lo cierra Navegador.__exit__ por CDP y por perfil.

Un perfil de Chrome por robot

Chrome solo permite una instancia por perfil (ProcessSingleton). Si otro proceso, o una ventana abierta a mano, tiene el perfil tomado, el Chrome del robot se cierra al instante. No compartir el perfil con robots de Rocketbot.

psutil es obligatorio en la práctica

Sin psutil instalado en el mismo Python 3.14 que ejecuta la tarea, matar_huerfanos() solo avisa y no limpia, y los Chrome huérfanos se acumulan hasta bloquear el perfil.

Marcador de limpieza

main.py llama a matar_huerfanos() con el nombre de la carpeta del perfil, que se busca como texto en la línea de comandos de cada Chrome. Si el nombre es genérico (por ejemplo chrome_profile), puede coincidir con perfiles de Rocketbot y cerrarles el navegador. Con chrome_profile_Zoho el riesgo es bajo; lo más seguro es pasar la ruta completa.

Sin operadoras disponibles no hay ninguna señal

SIN_OPERADORES cuenta como éxito, no avisa y deja las tres métricas en 0, porque el plan se corta antes de consultar los chats. Si nadie está disponible por horas, los chats se acumulan y ni Telegram ni Metabase lo muestran.

Programador de tareas

La tarea debe correr con la misma cuenta que creó el perfil, sin "Ejecutar con los privilegios más altos" y solo con la sesión iniciada: el navegador es visible y necesita escritorio.

Actualizaciones de Chrome

Selenium Manager descarga el ChromeDriver que corresponde a cada versión de Chrome. Los robots de Rocketbot usan un ChromeDriver fijo y pueden romperse con la misma actualización.

10. Operación (runbook)

Telegram: session not created / Chrome instance exited
  1. Revisa si hay Chrome vivos con el perfil del robot: Get-CimInstance Win32_Process -Filter "name='chrome.exe'" | Where-Object CommandLine -like "*chrome_profile_Zoho*"
  2. Ciérralos (Stop-Process -Id <PID>) y confirma que psutil está instalado en el Python 3.14.
  3. Abre chrome://settings/help: si hay una actualización a medias, deja que termine y reinicia Chrome.
  4. Revisa el final de C:\Robot\logs\chromedriver_verbose.log.
Falla de inicio de sesión (ERROR_SESION)
  1. Confirma que ZOHO_USER y ZOHO_PASSWORD tienen valor en el .env del servidor.
  2. Corre a mano (python main.py --produccion --origen manual) y observa la ventana: verificación en dos pasos, captcha o una pantalla intermedia de Zoho detienen el login.
  3. Si el log dice que no encontró el token CSRF, revisa la lista de cookies que imprime.
Hay chats perdidos sin asignar y no llegó ningún aviso
  1. Busca en el log el bloque Estado de los operadores configurados.
  2. Si ninguna aparece con OK, nadie estaba disponible (SIN_OPERADORES): es el caso que no avisa.
  3. Si una aparece como NO ENCONTRADO EN EL PORTAL, revisa su correo en ALLOWED_OPERATORS.
Faltan ejecuciones contra las 36 esperadas
  1. Revisa el historial de la tarea PY_main en el Programador de tareas.
  2. Busca renglones en en_curso en ejecuciones: el proceso murió sin cerrar.
  3. Si el log dice Ya hay una corrida activa, revisa el candado en C:\automatizaciones\locks.
Error HTTP de la API de SalesIQ
  1. El log trae el código y el cuerpo de la respuesta de Zoho.
  2. 401/403: refresh token revocado o sin scopes; regenerarlo en api-console.zoho.com.
  3. 500 con Operation Failed (código 1000): suele ser un parámetro de paginación no aceptado.

11. Historial de cambios

Versión Fecha Cambio Autor
1.0 2026-09-16 Migración desde Rocketbot a Python puro Angel
1.1 2026-09-17 Registro en SQL Server con métricas; Telegram solo en fallos Angel
1.2 2026-09-22 Candado de instancia única y limpieza de Chrome huérfanos Angel
1.3 2026-10-01 Chrome lanzado por el robot y conexión por debuggerAddress Angel