Saltar a contenido

SDD — PreciosMEM

Ficha técnica

Campo Valor
ID preciosmem
Plataforma Rocketbot: robot principal PreciosMEM y subrobot LoginPreciosMEM
Archivo desplegado [confirmar]
Disparo [confirmar: tarea programada y horario]
Frecuencia Varias veces al día, hasta completar la presentación [confirmar horario]

| Vigencia | Temporal, mientras dure la eliminación de impuestos al combustible en Guatemala | | Módulos de Rocketbot | gmail_ 1.13.17, Telegram 1.0.2, OpenCloudflare 1.0.4 | | Librerías Python | openpyxl, pyotp | | Proceso de negocio | PDD |

1. Arquitectura general

flowchart LR
    OD["Excel de precios (OneDrive corporativo)"] -->|descarga| R[PreciosMEM]
    J["mapeo_licencias.json"] --> R
    R -->|subrobot| L[LoginPreciosMEM]
    L -->|CUI + contraseña + TOTP| M[Portal MEM DGH]
    R -->|JS por estación| M
    R --> X["resultado_precios.xlsx"]
    X -->|adjunto| G[Gmail]
    R -->|avisos| T[Telegram]

2. Componentes

Paso Tipo Responsabilidad
Conexiones Módulos gmail_ y Telegram Configura el correo y el bot
dia_siguiente Python Fecha de mañana, usada para archivar
Descarga del Excel Navegador Abre el enlace de descarga de OneDrive y lo deja en la carpeta de descargas
Verificar precios Python precios_subidos = True si existe la hoja de mañana y no hay precios vacíos
preparar_cola() Python Cruza Excel y mapeo; genera cola, excluidas y estado_preparacion
LoginPreciosMEM Subrobot Ingreso al portal con 2FA
filtrar_cola() Python Quita de la cola las estaciones ya resueltas hoy
Formulario 1 JS Selecciona prefijo, número y resolución, y entra a Ingreso de Precios Diarios
Verificar licencia JS Confirma que el portal cargó la licencia correcta
Autoservicio JS Marca "No vende" en todos los productos
Formulario 2 JS Llena Servicio Completo y vacía Autoservicio
Preparar envío JS Responde el diálogo de confirmación e instala el espía de /exencion/guardar
Leer resultado JS Clasifica la respuesta de /exencion/guardar
guardar_resultado() Python Escribe el Excel de resultados

3. Flujo detallado

Robot principal

  1. Configura Gmail y Telegram, calcula dia_siguiente e inicializa estado_preparacion = False e intentos = 0.
  2. Descarga Precios_Estaciones.xlsx desde OneDrive a C:\Users\robots_IT\Downloads\ y cierra el navegador.
  3. Verifica los precios: busca la hoja Precios-<d>-<m>-<aaaa> de mañana y revisa que las columnas 3 a 5 (Plus, Premium, Diesel) estén llenas en todas las filas con estación.
  4. Ciclo de vueltas (while {intentos} < 2):
    1. Si precios_subidos: ejecuta preparar_cola().
    2. Si estado_preparacion es falso, sale del ciclo sin avisar. Esto cubre tanto "aún no hay precios" como un error de preparación; la siguiente ejecución programada lo reintenta.
    3. Ejecuta el subrobot LoginPreciosMEM.
    4. Entra a ESTACION DE SERVICIOS → EXENCION DTO.
    5. Ejecuta filtrar_cola().
    6. Recorre cola dentro de un try/catch; cada elemento queda en estaciones (ver "Por estación").
    7. Ejecuta filtrar_cola() otra vez.
    8. Si total_cola == 0, es la última ejecución del día: envía el correo con el Excel, lo archiva como Completados\precios-{dia_siguiente}.xlsx y sale del ciclo. Mientras quede cola, el Excel de resultados permanece en C:\rpa\PreciosMEM\ para que las siguientes ejecuciones omitan lo ya resuelto.
    9. Si no: avisa por Telegram con la cola pendiente, intentos + 1 y cierra el navegador.
  5. Borra el Excel descargado y cierra el navegador.

Por estación

  1. Formulario 1 → resultado_formulario. Espera 4 s.
  2. Verificar licencia → licencia_presente. Revisa la respuesta de la API para la resolución, que exista la tabla SERVICIO COMPLETO y que la licencia en pantalla coincida con el número.
  3. Si licencia_presente es OK:
    1. Marca "No vende" en Autoservicio.
    2. Formulario 2 → resultado_precios.
    3. Preparar envío → cerrar_alerta. Reemplaza window.confirm para aceptar solo si el mensaje trae la fecha de mañana (dd-mm-aaaa) y window.alert para no bloquear.
    4. Clic en PRESENTAR INFORMACION.
    5. Leer resultado → resultado_envio. Si es SIN_RESPUESTA_AUN, repite cada 2 s hasta 10 veces; si se agota, avisa "Nunca escuchó la respuesta" y detiene el robot.
    6. Si el portal responde que la licencia ya fue presentada en esa fecha, hace clic en RECTIFICAR INFORMACION y registra el resultado como guardado con éxito.
    7. guardar_resultado().
  4. Si licencia_presente es ERROR_PRESENTACION_NO_EXISTE, ejecuta guardar_resultado() con "Error MEM".
  5. Con cualquier otro resultado no registra nada; la estación sigue pendiente para la segunda vuelta o la siguiente ejecución.

Subrobot LoginPreciosMEM

  1. Inicializa intentos = 0 e intentos2 = 0.
  2. Abre https://comercializaciondgh.mem.gob.gt/login con OpenCloudflare y hace clic por imagen en el desafío de Cloudflare si aparece.
  3. Espera el botón Iniciar Sesión, escribe CUI y contraseña, y envía.
  4. Ciclo 2FA (while {intentos} < 3):

    1. Instala o reinicia el espía de respuestas HTTP (window.__respuestas).
    2. Calcula el código TOTP con pyotp desde {secreto}. Ajusta el reloj con el encabezado Date del servidor del MEM (desfase_hora) y, si quedan menos de 10 s en la ventana actual, espera a la siguiente.
    3. Escribe el código en el campo (input-38, o el primer input visible como respaldo).
    4. Ciclo de envío (while {intentos2} < 3): clic en Verificar y clasifica la respuesta de la API → resultado_login:

      Resultado Significado Acción
      LOGIN_OK 2xx Sale de ambos ciclos
      CODIGO_INVALIDO 422 Espera 3 s e intentos2 + 1
      ERROR_CREDENCIAL 401/403 intentos + 1 e intentos2 + 1
      ERROR_SERVIDOR 5xx intentos + 1 e intentos2 + 1
      SIN_RESPUESTA_AUN Sin respuesta intentos + 1 e intentos2 + 1
  5. El navegador queda abierto y con sesión para el robot principal. El comando close_browser del final está desactivado y no debe activarse.

4. Sistemas y dependencias

Sistema Uso Acceso Notas
Portal de comercialización DGH (MEM) Presentación de precios Web con OpenCloudflare Vue/Vuetify; la API se lee con espías de XHR/fetch
OneDrive corporativo Excel de precios Enlace con ?download=1 abierto en el navegador El archivo se descarga y se lee localmente
Gmail Envío del resultado Módulo gmail_
Telegram Avisos Módulo Telegram

5. Credenciales y configuración

Variable Uso Dónde se guarda
cui, password Usuario del portal del MEM Rocketbot cifrado
secreto URI TOTP del 2FA del MEM Rocketbot cifrado
user_gmail, password_gmail Cuenta que envía el correo Rocketbot cifrado
token_telegram, chat_id Bot y chat de Telegram Rocketbot cifrado

token es el código TOTP calculado en cada ejecución, no una credencial.

Formato de los archivos de entrada

Excel de precios: una hoja por día llamada Precios-<d>-<m>-<aaaa>, sin ceros a la izquierda (ej. Precios-7-10-2026). Encabezados obligatorios: Centro de Costo, Estación, Precio Plus, Precio Premium, Precio Diesel. La verificación inicial asume que los precios están en las columnas C, D y E.

mapeo_licencias.json: objeto cuya clave es la licencia (ej. ES-0030) y cuyo valor trae centro_costo, estacion, prefijo, numero y resolucion.

6. Estados y datos persistentes

Ruta Contenido
C:\Users\robots_IT\Downloads\Precios_Estaciones.xlsx Excel descargado; se borra al final
C:\rpa\PreciosMEM\mapeo_licencias.json Mapeo de licencias
C:\rpa\PreciosMEM\resultado_precios.xlsx Resultado del día. Solo se toma en cuenta si fue modificado hoy
C:\rpa\PreciosMEM\Completados\precios-<fecha>.xlsx Resultado archivado con la fecha presentada

El Excel de resultados tiene una fila por licencia (Fecha y hora, Licencia, Centro de Costo, Estación, Plus, Premium, Diesel, Resultado). Cada escritura reemplaza la fila de la licencia, así que refleja el último resultado. filtrar_cola() trata como resueltas las filas cuyo resultado empieza con "OK" o "Error MEM".

7. Manejo de errores y reintentos

Tipo Ejemplo Comportamiento
Negocio Precios incompletos en el Excel Termina sin avisar; la siguiente ejecución lo reintenta
Negocio Exclusiones (EX-01 a EX-06 del PDD) Se registran en el Excel de resultados y no se presentan
Negocio El MEM indica que la presentación no existe Se registra "Error MEM" y no se reintenta
Aplicación Código TOTP rechazado Hasta 3 envíos por intento de login, hasta 3 intentos
Aplicación Estación con resultado no controlado No se registra; queda para la segunda vuelta o la siguiente ejecución
Aplicación Estaciones pendientes tras la primera vuelta Telegram y segunda vuelta con login nuevo
Infraestructura /exencion/guardar no responde en 20 s Telegram y fin del robot
Infraestructura Excel de resultados abierto guardar_resultado() falla con PermissionError y lo deja en estado_log

8. Monitoreo y notificaciones

  • Correo con el Excel de resultados al completar.
  • Telegram si queda cola pendiente tras una vuelta o si el portal no responde al guardar.
  • Log de Rocketbot en la ruta base del robot.
  • [completar: registro en ejecuciones del orquestador]

9. Restricciones conocidas y trampas

Comandos alert desactivados

El flujo conserva comandos alert de depuración y de error, desactivados. No reactivarlos en producción: una alerta detiene el robot hasta que alguien la cierre.

Resultados como bytes

Los resultados de JS llegan como b'...'. Las comparaciones en Rocketbot usan esa forma ("{licencia_presente}" == "b'OK'"), y los scripts de Python los decodifican en UTF-8 con respaldo en cp1252. La comparación de ERROR_PRESENTACION_NO_EXISTE incluye el mensaje completo del portal con su acento en cp1252 (\xf3); si el MEM cambia el texto, esa rama deja de coincidir.

Espías y diálogos del portal

Los scripts reemplazan XMLHttpRequest.prototype.send, fetch, window.confirm y window.alert. Se instalan una vez por página y se limpian en cada estación. Si el portal recarga la página, se vuelven a instalar en el siguiente paso.

Rectificar

Cuando la licencia ya estaba presentada, se hace clic en Rectificar y el resultado se registra como éxito sin leer la respuesta del portal.

Excel de resultados

Si resultado_precios.xlsx está abierto en Excel, no se puede escribir ni filtrar. Solo cuenta si fue modificado hoy; un archivo de otro día se ignora y se reemplaza.

Selectores del portal

Formulario 1 busca etiquetas por texto ("Prefijo de la licencia", "Numero Licencia", "Resolucion") y usa la instancia de Vue (__vue__.selectItem) para el prefijo. El login usa el id autogenerado input-38, con respaldo al primer input visible.

Cierre del navegador en el subrobot

LoginPreciosMEM tiene un close_browser al final, desactivado. Si se activa, el robot principal se queda sin navegador después del login.

10. Operación (runbook)

El robot terminó sin presentar nada

Es lo esperado mientras el Excel no tenga los precios completos de mañana. Si ya deberían estar:

  1. Verificar que exista la hoja de mañana en el Excel con el nombre exacto Precios-<d>-<m>-<aaaa>.
  2. Verificar que no haya celdas vacías en Plus, Premium o Diesel.
  3. Si los precios están completos, revisar error_preparacion en el log: columna no encontrada, Excel bloqueado o cola vacía por exclusiones.
Llegó \"No se completó todo en la primera vuelva\"
  1. El mensaje trae la cola pendiente. El robot da una segunda vuelta automáticamente.
  2. Si persisten, revisar en el Excel de resultados el último resultado de esas licencias.
Llegó \"Nunca escuchó la respuesta\"
  1. El robot se detuvo. Revisar en el portal si la última estación quedó presentada.
  2. Volver a ejecutar: las estaciones ya resueltas hoy se omiten.
Agregar o corregir una estación

Editar mapeo_licencias.json con licencia, prefijo, número, resolución, centro de costo y estación. El centro de costo debe coincidir con el del Excel de precios.

11. Historial de cambios

Reconstruido a partir de las versiones guardadas en el .db.

Fecha Cambio
2026-09-30 Primera versión del robot
2026-10-01 Login separado en el subrobot LoginPreciosMEM con 2FA por TOTP
2026-10-01 a 2026-10-03 Ajustes del recorrido por estación y del registro de resultados
2026-10-05 Verificación de que los precios estén completos antes de iniciar, segunda vuelta, correo final y avisos por Telegram
2026-10-06 Se desactivan las alertas y el close_browser del login; el login incrementa intentos2 en resultados distintos de código inválido para no quedar en un ciclo sin fin