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¶
- Configura Gmail y Telegram, calcula
dia_siguientee inicializaestado_preparacion = Falseeintentos = 0. - Descarga
Precios_Estaciones.xlsxdesde OneDrive aC:\Users\robots_IT\Downloads\y cierra el navegador. - 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. - Ciclo de vueltas (
while {intentos} < 2):- Si
precios_subidos: ejecutapreparar_cola(). - Si
estado_preparaciones 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. - Ejecuta el subrobot
LoginPreciosMEM. - Entra a ESTACION DE SERVICIOS → EXENCION DTO.
- Ejecuta
filtrar_cola(). - Recorre
coladentro de un try/catch; cada elemento queda enestaciones(ver "Por estación"). - Ejecuta
filtrar_cola()otra vez. - Si
total_cola == 0, es la última ejecución del día: envía el correo con el Excel, lo archiva comoCompletados\precios-{dia_siguiente}.xlsxy sale del ciclo. Mientras quede cola, el Excel de resultados permanece enC:\rpa\PreciosMEM\para que las siguientes ejecuciones omitan lo ya resuelto. - Si no: avisa por Telegram con la cola pendiente,
intentos + 1y cierra el navegador.
- Si
- Borra el Excel descargado y cierra el navegador.
Por estación¶
- Formulario 1 →
resultado_formulario. Espera 4 s. - 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. - Si
licencia_presenteesOK:- Marca "No vende" en Autoservicio.
- Formulario 2 →
resultado_precios. - Preparar envío →
cerrar_alerta. Reemplazawindow.confirmpara aceptar solo si el mensaje trae la fecha de mañana (dd-mm-aaaa) ywindow.alertpara no bloquear. - Clic en PRESENTAR INFORMACION.
- Leer resultado →
resultado_envio. Si esSIN_RESPUESTA_AUN, repite cada 2 s hasta 10 veces; si se agota, avisa "Nunca escuchó la respuesta" y detiene el robot. - 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.
guardar_resultado().
- Si
licencia_presenteesERROR_PRESENTACION_NO_EXISTE, ejecutaguardar_resultado()con "Error MEM". - Con cualquier otro resultado no registra nada; la estación sigue pendiente para la segunda vuelta o la siguiente ejecución.
Subrobot LoginPreciosMEM¶
- Inicializa
intentos = 0eintentos2 = 0. - Abre
https://comercializaciondgh.mem.gob.gt/logincon OpenCloudflare y hace clic por imagen en el desafío de Cloudflare si aparece. - Espera el botón Iniciar Sesión, escribe CUI y contraseña, y envía.
-
Ciclo 2FA (
while {intentos} < 3):- Instala o reinicia el espía de respuestas HTTP (
window.__respuestas). - Calcula el código TOTP con
pyotpdesde{secreto}. Ajusta el reloj con el encabezadoDatedel servidor del MEM (desfase_hora) y, si quedan menos de 10 s en la ventana actual, espera a la siguiente. - Escribe el código en el campo (
input-38, o el primer input visible como respaldo). -
Ciclo de envío (
while {intentos2} < 3): clic en Verificar y clasifica la respuesta de la API →resultado_login:Resultado Significado Acción LOGIN_OK2xx Sale de ambos ciclos CODIGO_INVALIDO422 Espera 3 s e intentos2 + 1ERROR_CREDENCIAL401/403 intentos + 1eintentos2 + 1ERROR_SERVIDOR5xx intentos + 1eintentos2 + 1SIN_RESPUESTA_AUNSin respuesta intentos + 1eintentos2 + 1
- Instala o reinicia el espía de respuestas HTTP (
-
El navegador queda abierto y con sesión para el robot principal. El comando
close_browserdel 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
ejecucionesdel 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:
- Verificar que exista la hoja de mañana en el Excel con el nombre exacto
Precios-<d>-<m>-<aaaa>. - Verificar que no haya celdas vacías en Plus, Premium o Diesel.
- Si los precios están completos, revisar
error_preparacionen el log: columna no encontrada, Excel bloqueado o cola vacía por exclusiones.
Llegó \"No se completó todo en la primera vuelva\"
- El mensaje trae la cola pendiente. El robot da una segunda vuelta automáticamente.
- Si persisten, revisar en el Excel de resultados el último resultado de esas licencias.
Llegó \"Nunca escuchó la respuesta\"
- El robot se detuvo. Revisar en el portal si la última estación quedó presentada.
- 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 |