Skip to main content

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

  1. ¿Qué hace el sistema?
  2. Estructura del proyecto
  3. Flujo de ejecución paso a paso
  4. Módulos
  5. Configuración (.env)
  6. Instalación y ejecución
  7. Logs
  8. 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:

  1. set_info_facturas() — datos del pedido y ventana de entrega
  2. set_info_cliente() — contacto, dirección, validación de correo/grupo
  3. set_info_localizacion() — coordenadas GPS
  4. set_dir_facturas() — InvoiceId y dirección postal
  5. set_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.

  1. Se guarda el SalesId en pending_dynamics_updates.json.
  2. 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.
  3. Solo reintenta el PATCH a Dynamics.
  4. Si el PATCH tiene éxito, se elimina del archivo.
  5. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

guias_beetrack_ricardo_fuentes-0.0.1.tar.gz (21.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file guias_beetrack_ricardo_fuentes-0.0.1.tar.gz.

File metadata

File hashes

Hashes for guias_beetrack_ricardo_fuentes-0.0.1.tar.gz
Algorithm Hash digest
SHA256 3444e4deab7cd856552b0af06a49dc7ac7b75ff912947549be6fd3477768abcf
MD5 9e2cdf2d858f1d6ef3ddfde96f45ba72
BLAKE2b-256 40b07fe215d25a2216f9bcefb33b855191b91b5c5d8c1565bf2c3dd4b60c9990

See more details on using hashes here.

File details

Details for the file guias_beetrack_ricardo_fuentes-0.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for guias_beetrack_ricardo_fuentes-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 129fde85aca392b73de58c591cb0602a17c8500b3e969f7e7985c772919d9bfd
MD5 462e675a74c7b7f15d2cd5b5a3781954
BLAKE2b-256 416d1771e6b0d9835e26212bc90778c9512e2ff91b7f784d9309d33dcc9bf1af

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page