Sincronizacion Dynamics 365 -> Beetrack
Project description
Sincronización Dynamics 365 → Beetrack
Servicio automático que consulta pedidos pendientes en Microsoft Dynamics 365, construye guías de despacho y las envía a Beetrack / DispatchTrack. Una vez confirmado el envío, actualiza el estado del pedido en Dynamics para evitar duplicados.
Índice
- ¿Qué hace el sistema?
- Estructura del proyecto
- Flujo de ejecución paso a paso
- Módulos
- Configuración (.env)
- Instalación y ejecución
- Logs
- Manejo de errores
¿Qué hace el sistema?
Dynamics 365 ──────────────────────────────────► Beetrack
(pedidos con DocState = "Pending") (guías de despacho)
│ │
└──── al confirmar envío ────────────────────────── ┘
DocState → "Recived"
El scheduler corre cada 5 minutos. Si no hay pedidos pendientes, termina silenciosamente. Si los hay, ejecuta el pipeline completo.
Estructura del proyecto
tareas_atomaticas/
│
├── .env # Variables de entorno (credenciales, configuración)
├── .env.example # Plantilla de variables (sin valores reales)
├── requirements.txt # Dependencias Python
├── pending_dynamics_updates.json # Guías enviadas a Beetrack pero Dynamics no confirmó (auto-generado)
│
└── guias_beetrack/
│
├── main.py # Punto de entrada, scheduler APScheduler
│
├── dynamics/ # Lógica de negocio e integración
│ ├── app_config.py # Centraliza todas las constantes configurables
│ ├── config_env.py # Lectura de variables de entorno (.env)
│ ├── token_dynamics.py # Gestión del token OAuth2 de Dynamics (con caché)
│ ├── clases_guias_dynamics.py # Modelos de datos (dataclasses)
│ ├── guias_dynamics.py # Consultas OData a Dynamics 365
│ ├── guias.py # Orquestación: filtros, armado y envío de guías
│ └── servicio_dynamics.py # Capa HTTP (GET/PATCH Dynamics, POST Beetrack) con retry
│
├── utils/
│ ├── logger.py # Logger con salida a consola y archivos rotativos
│ └── correlation.py # ID de correlación por ciclo (ContextVar)
│
└── tests/
└── test_token_dynamics.py
Flujo de ejecución paso a paso
Cada 5 minutos main.py ejecuta el siguiente pipeline:
┌─────────────────────────────────────────────────────────────────────┐
│ INICIO DE CICLO (run_id: a3f7c1d2) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 0. Revisar pendientes │
│ └─ ¿Hay guías en pending_dynamics_updates.json? │
│ → Sí: avisar en log (se procesarán en el paso 5) │
│ → No: continuar │
│ │
│ 1. Consultar pedidos [Dynamics: CNDBeeTrackSalesTablesEntity] │
│ └─ Filtros: DocState=Pending, fecha=hoy, SalesStatus=Invoiced │
│ IdRoute≠CARGO EXPRESO, grupos IG6/CD6/BC/CAB/CDI │
│ │
│ 2. Datos primarios (3 consultas en paralelo) │
│ ├─ Facturas [CUSTINVOICEJOURCNDsEntity] → por SalesId │
│ ├─ Clientes [CustomersV3] → por InvoiceAccount │
│ └─ Productos [CNDBeeTrackSalesLinesEntity] → por SalesId │
│ │
│ 3. Datos secundarios (3 consultas en paralelo) │
│ ├─ Ubicaciones [CDSPostalAddressHistoryV2] → lat/lng │
│ ├─ Direcciones [LogisticsPostalAddressBiEntities] → dirección │
│ └─ Dimensiones [WHSPHYSDIMUOMCNDsEntity] → peso/alto/ancho │
│ │
│ 4. Armar guías │
│ └─ Combina todos los datos en objetos GuiaResponse │
│ │
│ 5. Enviar guías a Beetrack (máx. 10 concurrentes) │
│ Por cada guía: │
│ ┌─ ¿Está en pendientes? │
│ │ → Sí: saltar POST, solo reintentar PATCH a Dynamics │
│ │ → No: POST a Beetrack → si OK: PATCH a Dynamics │
│ └─ Si PATCH falla: guardar en pending_dynamics_updates.json │
│ │
│ FIN DE CICLO │
└─────────────────────────────────────────────────────────────────────┘
Módulos
main.py
Punto de entrada. Contiene la función guias_beetrack() que orquesta el pipeline completo, y el bloque __main__ que inicia el BlockingScheduler con intervalo de 5 minutos.
dynamics/app_config.py
Centraliza todas las constantes configurables del negocio. Los valores se leen del .env con defaults. Nunca se hardcodean en la lógica.
| Constante | Default | Descripción |
|---|---|---|
BEETRACK_SALES_IDS_EXCLUIDOS |
OV-904710,OV-904736 |
Guías que nunca se envían a Beetrack |
DYNAMICS_SALES_IDS_EXCLUIDOS |
OV-912698,OV-912941 |
Pedidos excluidos de la consulta OData |
PICKUP_ADDRESS_NAME |
Bodega Zona 4 |
Nombre de la bodega de origen |
PICKUP_LATITUDE/LONGITUDE |
14.62…, -90.51… |
Coordenadas de la bodega |
DELIVERY_MIN_OFFSET_HOURS |
7 |
Horas antes del DeliveryCustomerDateTime para ventana mínima |
DELIVERY_MAX_OFFSET_HOURS |
6 |
Horas antes del DeliveryCustomerDateTime para ventana máxima |
DISPATCH_PRIORITY |
1 |
Prioridad de despacho en Beetrack |
DISPATCH_MODE |
3 |
Modo de despacho en Beetrack |
DISPATCH_PLACE |
Servicio Express |
Nombre del servicio en Beetrack |
DOMINIOS_EMAIL_VALIDOS |
@centrodistribuidor.com,… |
Dominios que habilitan envío de correo al cliente |
GRUPOS_VENTAS_VALIDOS |
CVTA-0006,… |
Grupos de venta que habilitan correo |
BEETRACK_MAX_CONCURRENT |
10 |
Máximo de guías enviándose en paralelo |
PENDING_DYNAMICS_UPDATES_FILE |
pending_dynamics_updates.json |
Archivo de guías pendientes de confirmación |
dynamics/token_dynamics.py
Gestiona el token OAuth2 para Dynamics 365.
- El token se guarda en memoria con su tiempo de expiración.
- Cada consulta llama a
validacion_token(): si el token es válido lo reutiliza, si expiró solicita uno nuevo. - Esto evita hacer una llamada de autenticación en cada request HTTP.
validacion_token()
└─ ¿Token en memoria vigente?
→ Sí: devuelve el token cacheado
→ No: POST a Azure AD → nuevo token → guardar en memoria → devolver
dynamics/guias_dynamics.py
Consultas OData a Dynamics 365. Cada método es estático y async.
| Método | Entidad Dynamics | Para qué |
|---|---|---|
obtener_pedidos |
CNDBeeTrackSalesTablesEntity |
Pedidos del día con filtros de estado/grupo |
obtener_info_factura |
CUSTINVOICEJOURCNDsEntity |
InvoiceId y dirección postal por SalesId |
obtener_info_cliente |
CustomersV3 |
Teléfono, email, ubicación por cuenta |
obtener_info_location_id |
CDSPostalAddressHistoryV2 |
Coordenadas GPS por LocationId |
obtener_info_direccion |
LogisticsPostalAddressBiEntities |
Dirección en texto por SourceKey |
obtener_info_productos |
CNDBeeTrackSalesLinesEntity |
Líneas de producto por SalesId |
obtener_info_dimensiones |
WHSPHYSDIMUOMCNDsEntity |
Alto, peso, ancho, profundidad por ItemId |
actualizar_estado_guia |
SALESTABLECNDsEntity |
PATCH: cambia DocState a "Recived" |
dynamics/guias.py
Módulo principal de orquestación. Tiene tres clases y una función:
FiltrosGuiasBeetrack — Construye los strings de filtro OData a partir de los datos recuperados. El helper interno _armar_filtro() evita repetición de código.
GuiasInfoDynamics — Wraps de las consultas OData. Recibe el filtro, llama a guias_dynamics.py, y devuelve un diccionario indexado por la clave correspondiente (SalesId, CustomerAccount, etc.) para búsqueda O(1) al armar las guías.
ArmarGuiasBeetrack — Combina todos los datos en objetos GuiaResponse y los envía a Beetrack con control de concurrencia y seguridad transaccional.
procesar_actualizaciones_pendientes() — Función que al inicio de cada ciclo informa si hay guías cuyo PATCH a Dynamics quedó pendiente del ciclo anterior.
dynamics/servicio_dynamics.py
Capa HTTP pura. Tres funciones async:
get_consultar_dynamics()— GET a OData con token Bearer. Elimina metadatos@odata.etag.patch_actualizar_dynamics()— PATCH para actualizar estado en Dynamics.post_beetrack()— POST a la API de Beetrack con X-AUTH-TOKEN.
Todas usan _con_reintentos(): ante errores de red (timeout, conexión) o HTTP transitorio (429, 500-504) reintenta hasta 3 veces con backoff exponencial (1s → 2s → 4s).
dynamics/clases_guias_dynamics.py
Modelos de datos (Python dataclasses):
| Clase | Descripción |
|---|---|
InformacionConsulta |
Respuesta genérica: consulta: bool, mensaje: str, data |
PickupAddress |
Dirección de recogida (bodega de origen) |
Dimension |
Par nombre/valor para dimensiones de producto |
ProductoBeetrack |
Producto con descripción, cantidad, código y dimensiones |
GuiaResponse |
Guía completa lista para enviar a Beetrack |
GuiaResponse se construye en etapas mediante setters:
set_info_facturas()— datos del pedido y ventana de entregaset_info_cliente()— contacto, dirección, validación de correo/gruposet_info_localizacion()— coordenadas GPSset_dir_facturas()— InvoiceId y dirección postalset_info_productos()— lista de productos
utils/logger.py
Logger con salida dual:
| Destino | Nivel | Retención | Formato |
|---|---|---|---|
| Consola | INFO y superior | — | HH:MM:SS | NIVEL | [run_id] | mensaje |
logs/app_DD-MM-YYYY.log |
INFO y WARNING | 7 días | Formato completo con archivo:línea |
logs/errors_DD-MM-YYYY.log |
ERROR y CRITICAL | 30 días | Formato completo con archivo:línea |
Cada línea incluye el run_id del ciclo activo, lo que permite filtrar todos los eventos de una ejecución específica.
utils/correlation.py
Define run_id, un ContextVar que se inicializa al comienzo de cada ciclo con un UUID corto (8 caracteres). Al usar asyncio, el valor se propaga automáticamente a todas las corrutinas del mismo asyncio.run(), sin necesidad de pasarlo como parámetro.
Configuración (.env)
Copiar .env.example a .env y completar los valores:
cp .env.example .env
Variables obligatorias
# Autenticación Dynamics 365 (Azure AD)
DYNAMICS_URL_TOKEN=https://login.microsoftonline.com/<TENANT_ID>/oauth2/token
DYNAMICS_URL_ACCESO=https://<ENVIRONMENT>.operations.dynamics.com/
DYNAMICS_ID_CLIENTE=<CLIENT_ID>
DYNAMICS_CLAVE_CLIENTE=<CLIENT_SECRET>
DYNAMICS_TIPO_CREDENCIAL=client_credentials
# Beetrack / DispatchTrack
BEETRACK_URL_ACCESO=https://<INSTANCIA>.dispatchtrack.com/api/external/v1/dispatches
BEETRACK_TOKEN_ACCESO=<API_TOKEN>
Variables opcionales (tienen defaults en app_config.py)
BEETRACK_SALES_IDS_EXCLUIDOS=OV-000000,OV-000001
DYNAMICS_SALES_IDS_EXCLUIDOS=OV-000000,OV-000001
PICKUP_ADDRESS_NAME=Bodega Zona 4
PICKUP_LATITUDE=14.621669674128709
PICKUP_LONGITUDE=-90.51804696621112
DELIVERY_MIN_OFFSET_HOURS=7
DELIVERY_MAX_OFFSET_HOURS=6
DISPATCH_PRIORITY=1
DISPATCH_MODE=3
DISPATCH_PLACE=Servicio Express
DOMINIOS_EMAIL_VALIDOS=@centrodistribuidor.com,@servir.com.gt
GRUPOS_VENTAS_VALIDOS=CVTA-0006,CVTA-0029,...
BEETRACK_MAX_CONCURRENT=10
PENDING_DYNAMICS_UPDATES_FILE=pending_dynamics_updates.json
Instalación y ejecución
Instalación desde PyPI
pip install guias-beetrack-ricardo-fuentes
guias-beetrack
1. Crear entorno virtual e instalar dependencias
python -m venv tareas_auto
tareas_auto\Scripts\activate # Windows
pip install -r requirements.txt
2. Configurar variables de entorno
copy .env.example .env
# Editar .env con las credenciales reales
3. Ejecutar
# Desde la raiz del proyecto
python -m guias_beetrack
El proceso corre indefinidamente. Para detenerlo: Ctrl+C.
Ejecutar solo una vez (sin scheduler)
Descomentar en main.py:
if __name__ == "__main__":
main()
Logs
Al ejecutarse se verá en consola:
10:05:00 | INFO | [- ] =======================================================
10:05:00 | INFO | [- ] INICIO DE CICLO Dynamics → Beetrack
10:05:00 | INFO | [- ] =======================================================
10:05:00 | INFO | [a3f7c1d2] [1/5] Consultando pedidos en Dynamics...
10:05:01 | INFO | [a3f7c1d2] [1/5] Pedidos encontrados: 8
10:05:01 | INFO | [a3f7c1d2] [2/5] Obteniendo facturas, clientes y productos (paralelo)...
10:05:02 | INFO | [a3f7c1d2] [2/5] Facturas: 8 | Clientes: 5 | Productos para 8 pedidos
10:05:02 | INFO | [a3f7c1d2] [3/5] Obteniendo ubicaciones, direcciones y dimensiones (paralelo)...
10:05:03 | INFO | [a3f7c1d2] [3/5] Ubicaciones: 5 | Direcciones: 8 | Dimensiones: 12
10:05:03 | INFO | [a3f7c1d2] [4/5] Armando guías...
10:05:03 | INFO | [a3f7c1d2] [4/5] 8 guías armadas.
10:05:03 | INFO | [a3f7c1d2] [5/5] Enviando 8 guías a Beetrack...
10:05:03 | INFO | [a3f7c1d2] OV-905123 [1/8] → Enviando a Beetrack...
10:05:04 | INFO | [a3f7c1d2] OV-905123 [1/8] → Beetrack OK. Actualizando estado en Dynamics...
10:05:04 | INFO | [a3f7c1d2] OV-905123 [1/8] → Completada
10:05:05 | INFO | [a3f7c1d2] Envío finalizado: 8/8 guías completadas.
10:05:05 | INFO | [a3f7c1d2] =======================================================
10:05:05 | INFO | [a3f7c1d2] FIN DE CICLO
Los archivos de log se guardan en guias_beetrack/logs/.
Manejo de errores
Errores de red (timeout, conexión caída)
servicio_dynamics.py reintenta automáticamente hasta 3 veces con espera exponencial:
- Intento 1 falla → espera 1s → intento 2
- Intento 2 falla → espera 2s → intento 3
- Intento 3 falla → propaga el error
Error al enviar a Beetrack
La guía falla silenciosamente para ese ciclo. Las demás guías no se ven afectadas. Se registra en el log de errores.
Error al actualizar Dynamics (después de enviar a Beetrack)
Este es el caso crítico: la guía ya llegó a Beetrack pero Dynamics aún la ve como pendiente.
- Se guarda el
SalesIdenpending_dynamics_updates.json. - En el siguiente ciclo, cuando esa guía aparece de nuevo en la consulta (porque Dynamics aún la ve como Pending), el sistema detecta que ya fue enviada y omite el POST a Beetrack.
- Solo reintenta el PATCH a Dynamics.
- Si el PATCH tiene éxito, se elimina del archivo.
- Si sigue fallando, permanece en el archivo para el ciclo siguiente.
pending_dynamics_updates.json
{
"OV-905124": {
"data_area_id": "CND",
"rec_id_1": "5637145328",
"estado": "{\"DocState\": \"Recived\"}"
}
}
Errores por guía vs errores globales
- Errores dentro del envío (Beetrack o Dynamics por guía): aislados, no afectan al resto.
- Errores antes del envío (fallo en consulta a Dynamics, token inválido): detienen el ciclo completo y se registran en el log de errores.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file guias_beetrack_ricardo_fuentes-0.0.1.tar.gz.
File metadata
- Download URL: guias_beetrack_ricardo_fuentes-0.0.1.tar.gz
- Upload date:
- Size: 21.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3444e4deab7cd856552b0af06a49dc7ac7b75ff912947549be6fd3477768abcf
|
|
| MD5 |
9e2cdf2d858f1d6ef3ddfde96f45ba72
|
|
| BLAKE2b-256 |
40b07fe215d25a2216f9bcefb33b855191b91b5c5d8c1565bf2c3dd4b60c9990
|
File details
Details for the file guias_beetrack_ricardo_fuentes-0.0.1-py3-none-any.whl.
File metadata
- Download URL: guias_beetrack_ricardo_fuentes-0.0.1-py3-none-any.whl
- Upload date:
- Size: 26.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
129fde85aca392b73de58c591cb0602a17c8500b3e969f7e7985c772919d9bfd
|
|
| MD5 |
462e675a74c7b7f15d2cd5b5a3781954
|
|
| BLAKE2b-256 |
416d1771e6b0d9835e26212bc90778c9512e2ff91b7f784d9309d33dcc9bf1af
|