guias-beetrack-Ricardo-Fuentes 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. guias_beetrack_ricardo_fuentes-0.0.1/.env.example +36 -0
  2. guias_beetrack_ricardo_fuentes-0.0.1/.gitignore +191 -0
  3. guias_beetrack_ricardo_fuentes-0.0.1/.vscode/settings.json +3 -0
  4. guias_beetrack_ricardo_fuentes-0.0.1/LICENSE +21 -0
  5. guias_beetrack_ricardo_fuentes-0.0.1/PKG-INFO +423 -0
  6. guias_beetrack_ricardo_fuentes-0.0.1/README.md +382 -0
  7. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/__init__.py +4 -0
  8. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/__main__.py +5 -0
  9. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/dynamics/__init__.py +0 -0
  10. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/dynamics/app_config.py +49 -0
  11. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/dynamics/clases_guias_dynamics.py +140 -0
  12. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/dynamics/config_env.py +27 -0
  13. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/dynamics/guias.py +301 -0
  14. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/dynamics/guias_dynamics.py +125 -0
  15. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/dynamics/servicio_dynamics.py +144 -0
  16. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/dynamics/token_dynamics.py +118 -0
  17. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/ejemplos.json +32 -0
  18. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/main.py +121 -0
  19. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/tests/__init__.py +0 -0
  20. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/tests/test_token_dynamics.py +77 -0
  21. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/utils/correlation.py +5 -0
  22. guias_beetrack_ricardo_fuentes-0.0.1/guias_beetrack/utils/logger.py +99 -0
  23. guias_beetrack_ricardo_fuentes-0.0.1/pyproject.toml +42 -0
  24. guias_beetrack_ricardo_fuentes-0.0.1/requirements.txt +14 -0
@@ -0,0 +1,36 @@
1
+ # Dynamics 365 - Autenticación OAuth2
2
+ DYNAMICS_URL_TOKEN=https://login.microsoftonline.com/<TENANT_ID>/oauth2/token
3
+ DYNAMICS_URL_ACCESO=https://<ENVIRONMENT>.operations.dynamics.com/
4
+ DYNAMICS_ID_CLIENTE=<CLIENT_ID>
5
+ DYNAMICS_CLAVE_CLIENTE=<CLIENT_SECRET>
6
+ DYNAMICS_TIPO_CREDENCIAL=client_credentials
7
+
8
+ # Beetrack / DispatchTrack
9
+ BEETRACK_URL_ACCESO=https://<INSTANCIA>.dispatchtrack.com/api/external/v1/dispatches
10
+ BEETRACK_TOKEN_ACCESO=<API_TOKEN>
11
+
12
+ # SalesIds excluidos del envío a Beetrack (separados por coma)
13
+ BEETRACK_SALES_IDS_EXCLUIDOS=OV-000000,OV-000001
14
+
15
+ # SalesIds excluidos de la consulta en Dynamics (separados por coma)
16
+ DYNAMICS_SALES_IDS_EXCLUIDOS=OV-000000,OV-000001
17
+
18
+ # Bodega de origen (pickup)
19
+ PICKUP_ADDRESS_NAME=Bodega Zona 4
20
+ PICKUP_LATITUDE=14.621669674128709
21
+ PICKUP_LONGITUDE=-90.51804696621112
22
+
23
+ # Ventana de entrega (horas de offset desde DeliveryCustomerDateTime)
24
+ DELIVERY_MIN_OFFSET_HOURS=7
25
+ DELIVERY_MAX_OFFSET_HOURS=6
26
+
27
+ # Parámetros de despacho
28
+ DISPATCH_PRIORITY=1
29
+ DISPATCH_MODE=3
30
+ DISPATCH_PLACE=Servicio Express
31
+
32
+ # Validación de correo del cliente (separados por coma)
33
+ DOMINIOS_EMAIL_VALIDOS=@centrodistribuidor.com,@servir.com.gt
34
+
35
+ # Grupos de ventas válidos para correo (separados por coma)
36
+ GRUPOS_VENTAS_VALIDOS=CVTA-0006,CVTA-0029,CVTA-0034,CVTA-0076,CVTA-0077,CVTA-0088,CVTA-0091,CVTA-0094,CVTA-0095,CVTA-0102,CVTA-0109,CVTA-0106,CVTA-0103,CVTA-0104,CVTA-0107,CVTA-0005
@@ -0,0 +1,191 @@
1
+ # Created by https://www.toptal.com/developers/gitignore/api/python,venv
2
+ # Edit at https://www.toptal.com/developers/gitignore?templates=python,venv
3
+
4
+ ### Python ###
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ build/
16
+ develop-eggs/
17
+ dist/
18
+ downloads/
19
+ eggs/
20
+ .eggs/
21
+ lib/
22
+ lib64/
23
+ parts/
24
+ sdist/
25
+ var/
26
+ wheels/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # PyInstaller
34
+ # Usually these files are written by a python script from a template
35
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
36
+ *.manifest
37
+ *.spec
38
+
39
+ # Installer logs
40
+ pip-log.txt
41
+ pip-delete-this-directory.txt
42
+
43
+ # Unit test / coverage reports
44
+ htmlcov/
45
+ .tox/
46
+ .nox/
47
+ .coverage
48
+ .coverage.*
49
+ .cache
50
+ nosetests.xml
51
+ coverage.xml
52
+ *.cover
53
+ *.py,cover
54
+ .hypothesis/
55
+ .pytest_cache/
56
+ cover/
57
+
58
+ # Translations
59
+ *.mo
60
+ *.pot
61
+
62
+ # Django stuff:
63
+ *.log
64
+ local_settings.py
65
+ db.sqlite3
66
+ db.sqlite3-journal
67
+
68
+ # Flask stuff:
69
+ instance/
70
+ .webassets-cache
71
+
72
+ # Scrapy stuff:
73
+ .scrapy
74
+
75
+ # Sphinx documentation
76
+ docs/_build/
77
+
78
+ # PyBuilder
79
+ .pybuilder/
80
+ target/
81
+
82
+ # Jupyter Notebook
83
+ .ipynb_checkpoints
84
+
85
+ # IPython
86
+ profile_default/
87
+ ipython_config.py
88
+
89
+ # pyenv
90
+ # For a library or package, you might want to ignore these files since the code is
91
+ # intended to run in multiple environments; otherwise, check them in:
92
+ # .python-version
93
+
94
+ # pipenv
95
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
97
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
98
+ # install all needed dependencies.
99
+ #Pipfile.lock
100
+
101
+ # poetry
102
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
104
+ # commonly ignored for libraries.
105
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106
+ #poetry.lock
107
+
108
+ # pdm
109
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
110
+ #pdm.lock
111
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
112
+ # in version control.
113
+ # https://pdm.fming.dev/#use-with-ide
114
+ .pdm.toml
115
+
116
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
117
+ __pypackages__/
118
+
119
+ # Celery stuff
120
+ celerybeat-schedule
121
+ celerybeat.pid
122
+
123
+ # SageMath parsed files
124
+ *.sage.py
125
+
126
+ # Environments
127
+ .env
128
+ .venv
129
+ env/
130
+ venv/
131
+ ENV/
132
+ env.bak/
133
+ venv.bak/
134
+
135
+ # Spyder project settings
136
+ .spyderproject
137
+ .spyproject
138
+
139
+ # Rope project settings
140
+ .ropeproject
141
+
142
+ # mkdocs documentation
143
+ /site
144
+
145
+ # mypy
146
+ .mypy_cache/
147
+ .dmypy.json
148
+ dmypy.json
149
+
150
+ # Pyre type checker
151
+ .pyre/
152
+
153
+ # pytype static type analyzer
154
+ .pytype/
155
+
156
+ # Cython debug symbols
157
+ cython_debug/
158
+
159
+ # PyCharm
160
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
161
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
162
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
163
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
164
+ #.idea/
165
+
166
+ ### Python Patch ###
167
+ # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
168
+ poetry.toml
169
+
170
+ # ruff
171
+ .ruff_cache/
172
+
173
+ # LSP config files
174
+ pyrightconfig.json
175
+
176
+ ### venv ###
177
+ # Virtualenv
178
+ # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
179
+ [Bb]in
180
+ [Ii]nclude
181
+ [Ll]ib
182
+ [Ll]ib64
183
+ [Ll]ocal
184
+ [Ss]cripts
185
+ pyvenv.cfg
186
+ pip-selfcheck.json
187
+
188
+ # End of https://www.toptal.com/developers/gitignore/api/python,venv
189
+
190
+ tareas_auto
191
+ logs
@@ -0,0 +1,3 @@
1
+ {
2
+ "snyk.advanced.autoSelectOrganization": true
3
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ricardo Fuentes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,423 @@
1
+ Metadata-Version: 2.4
2
+ Name: guias_beetrack_Ricardo_Fuentes
3
+ Version: 0.0.1
4
+ Summary: Sincronizacion Dynamics 365 -> Beetrack
5
+ Project-URL: Homepage, https://github.com/pypa/sampleproject
6
+ Project-URL: Issues, https://github.com/pypa/sampleproject/issues
7
+ Author-email: Ricardo Fuentes <ricardof_EXT@centrodistribuidor.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Ricardo Fuentes
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: OS Independent
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.9
34
+ Classifier: Programming Language :: Python :: 3.10
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Requires-Python: >=3.9
37
+ Requires-Dist: apscheduler>=3.11.2
38
+ Requires-Dist: httpx>=0.28.1
39
+ Requires-Dist: python-dotenv>=1.2.1
40
+ Description-Content-Type: text/markdown
41
+
42
+ # Sincronización Dynamics 365 → Beetrack
43
+
44
+ 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.
45
+
46
+ ---
47
+
48
+ ## Índice
49
+
50
+ 1. [¿Qué hace el sistema?](#qué-hace-el-sistema)
51
+ 2. [Estructura del proyecto](#estructura-del-proyecto)
52
+ 3. [Flujo de ejecución paso a paso](#flujo-de-ejecución-paso-a-paso)
53
+ 4. [Módulos](#módulos)
54
+ 5. [Configuración (.env)](#configuración-env)
55
+ 6. [Instalación y ejecución](#instalación-y-ejecución)
56
+ 7. [Logs](#logs)
57
+ 8. [Manejo de errores](#manejo-de-errores)
58
+
59
+ ---
60
+
61
+ ## ¿Qué hace el sistema?
62
+
63
+ ```
64
+ Dynamics 365 ──────────────────────────────────► Beetrack
65
+ (pedidos con DocState = "Pending") (guías de despacho)
66
+ │ │
67
+ └──── al confirmar envío ────────────────────────── ┘
68
+ DocState → "Recived"
69
+ ```
70
+
71
+ El scheduler corre cada **5 minutos**. Si no hay pedidos pendientes, termina silenciosamente. Si los hay, ejecuta el pipeline completo.
72
+
73
+ ---
74
+
75
+ ## Estructura del proyecto
76
+
77
+ ```
78
+ tareas_atomaticas/
79
+
80
+ ├── .env # Variables de entorno (credenciales, configuración)
81
+ ├── .env.example # Plantilla de variables (sin valores reales)
82
+ ├── requirements.txt # Dependencias Python
83
+ ├── pending_dynamics_updates.json # Guías enviadas a Beetrack pero Dynamics no confirmó (auto-generado)
84
+
85
+ └── guias_beetrack/
86
+
87
+ ├── main.py # Punto de entrada, scheduler APScheduler
88
+
89
+ ├── dynamics/ # Lógica de negocio e integración
90
+ │ ├── app_config.py # Centraliza todas las constantes configurables
91
+ │ ├── config_env.py # Lectura de variables de entorno (.env)
92
+ │ ├── token_dynamics.py # Gestión del token OAuth2 de Dynamics (con caché)
93
+ │ ├── clases_guias_dynamics.py # Modelos de datos (dataclasses)
94
+ │ ├── guias_dynamics.py # Consultas OData a Dynamics 365
95
+ │ ├── guias.py # Orquestación: filtros, armado y envío de guías
96
+ │ └── servicio_dynamics.py # Capa HTTP (GET/PATCH Dynamics, POST Beetrack) con retry
97
+
98
+ ├── utils/
99
+ │ ├── logger.py # Logger con salida a consola y archivos rotativos
100
+ │ └── correlation.py # ID de correlación por ciclo (ContextVar)
101
+
102
+ └── tests/
103
+ └── test_token_dynamics.py
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Flujo de ejecución paso a paso
109
+
110
+ Cada 5 minutos `main.py` ejecuta el siguiente pipeline:
111
+
112
+ ```
113
+ ┌─────────────────────────────────────────────────────────────────────┐
114
+ │ INICIO DE CICLO (run_id: a3f7c1d2) │
115
+ ├─────────────────────────────────────────────────────────────────────┤
116
+ │ │
117
+ │ 0. Revisar pendientes │
118
+ │ └─ ¿Hay guías en pending_dynamics_updates.json? │
119
+ │ → Sí: avisar en log (se procesarán en el paso 5) │
120
+ │ → No: continuar │
121
+ │ │
122
+ │ 1. Consultar pedidos [Dynamics: CNDBeeTrackSalesTablesEntity] │
123
+ │ └─ Filtros: DocState=Pending, fecha=hoy, SalesStatus=Invoiced │
124
+ │ IdRoute≠CARGO EXPRESO, grupos IG6/CD6/BC/CAB/CDI │
125
+ │ │
126
+ │ 2. Datos primarios (3 consultas en paralelo) │
127
+ │ ├─ Facturas [CUSTINVOICEJOURCNDsEntity] → por SalesId │
128
+ │ ├─ Clientes [CustomersV3] → por InvoiceAccount │
129
+ │ └─ Productos [CNDBeeTrackSalesLinesEntity] → por SalesId │
130
+ │ │
131
+ │ 3. Datos secundarios (3 consultas en paralelo) │
132
+ │ ├─ Ubicaciones [CDSPostalAddressHistoryV2] → lat/lng │
133
+ │ ├─ Direcciones [LogisticsPostalAddressBiEntities] → dirección │
134
+ │ └─ Dimensiones [WHSPHYSDIMUOMCNDsEntity] → peso/alto/ancho │
135
+ │ │
136
+ │ 4. Armar guías │
137
+ │ └─ Combina todos los datos en objetos GuiaResponse │
138
+ │ │
139
+ │ 5. Enviar guías a Beetrack (máx. 10 concurrentes) │
140
+ │ Por cada guía: │
141
+ │ ┌─ ¿Está en pendientes? │
142
+ │ │ → Sí: saltar POST, solo reintentar PATCH a Dynamics │
143
+ │ │ → No: POST a Beetrack → si OK: PATCH a Dynamics │
144
+ │ └─ Si PATCH falla: guardar en pending_dynamics_updates.json │
145
+ │ │
146
+ │ FIN DE CICLO │
147
+ └─────────────────────────────────────────────────────────────────────┘
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Módulos
153
+
154
+ ### `main.py`
155
+ 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.
156
+
157
+ ---
158
+
159
+ ### `dynamics/app_config.py`
160
+ Centraliza **todas las constantes configurables** del negocio. Los valores se leen del `.env` con defaults. Nunca se hardcodean en la lógica.
161
+
162
+ | Constante | Default | Descripción |
163
+ |-----------|---------|-------------|
164
+ | `BEETRACK_SALES_IDS_EXCLUIDOS` | `OV-904710,OV-904736` | Guías que nunca se envían a Beetrack |
165
+ | `DYNAMICS_SALES_IDS_EXCLUIDOS` | `OV-912698,OV-912941` | Pedidos excluidos de la consulta OData |
166
+ | `PICKUP_ADDRESS_NAME` | `Bodega Zona 4` | Nombre de la bodega de origen |
167
+ | `PICKUP_LATITUDE/LONGITUDE` | `14.62…, -90.51…` | Coordenadas de la bodega |
168
+ | `DELIVERY_MIN_OFFSET_HOURS` | `7` | Horas antes del `DeliveryCustomerDateTime` para ventana mínima |
169
+ | `DELIVERY_MAX_OFFSET_HOURS` | `6` | Horas antes del `DeliveryCustomerDateTime` para ventana máxima |
170
+ | `DISPATCH_PRIORITY` | `1` | Prioridad de despacho en Beetrack |
171
+ | `DISPATCH_MODE` | `3` | Modo de despacho en Beetrack |
172
+ | `DISPATCH_PLACE` | `Servicio Express` | Nombre del servicio en Beetrack |
173
+ | `DOMINIOS_EMAIL_VALIDOS` | `@centrodistribuidor.com,…` | Dominios que habilitan envío de correo al cliente |
174
+ | `GRUPOS_VENTAS_VALIDOS` | `CVTA-0006,…` | Grupos de venta que habilitan correo |
175
+ | `BEETRACK_MAX_CONCURRENT` | `10` | Máximo de guías enviándose en paralelo |
176
+ | `PENDING_DYNAMICS_UPDATES_FILE` | `pending_dynamics_updates.json` | Archivo de guías pendientes de confirmación |
177
+
178
+ ---
179
+
180
+ ### `dynamics/token_dynamics.py`
181
+ Gestiona el token **OAuth2** para Dynamics 365.
182
+
183
+ - El token se guarda en memoria con su tiempo de expiración.
184
+ - Cada consulta llama a `validacion_token()`: si el token es válido lo reutiliza, si expiró solicita uno nuevo.
185
+ - Esto evita hacer una llamada de autenticación en cada request HTTP.
186
+
187
+ ```
188
+ validacion_token()
189
+ └─ ¿Token en memoria vigente?
190
+ → Sí: devuelve el token cacheado
191
+ → No: POST a Azure AD → nuevo token → guardar en memoria → devolver
192
+ ```
193
+
194
+ ---
195
+
196
+ ### `dynamics/guias_dynamics.py`
197
+ Consultas OData a Dynamics 365. Cada método es estático y async.
198
+
199
+ | Método | Entidad Dynamics | Para qué |
200
+ |--------|-----------------|----------|
201
+ | `obtener_pedidos` | `CNDBeeTrackSalesTablesEntity` | Pedidos del día con filtros de estado/grupo |
202
+ | `obtener_info_factura` | `CUSTINVOICEJOURCNDsEntity` | InvoiceId y dirección postal por SalesId |
203
+ | `obtener_info_cliente` | `CustomersV3` | Teléfono, email, ubicación por cuenta |
204
+ | `obtener_info_location_id` | `CDSPostalAddressHistoryV2` | Coordenadas GPS por LocationId |
205
+ | `obtener_info_direccion` | `LogisticsPostalAddressBiEntities` | Dirección en texto por SourceKey |
206
+ | `obtener_info_productos` | `CNDBeeTrackSalesLinesEntity` | Líneas de producto por SalesId |
207
+ | `obtener_info_dimensiones` | `WHSPHYSDIMUOMCNDsEntity` | Alto, peso, ancho, profundidad por ItemId |
208
+ | `actualizar_estado_guia` | `SALESTABLECNDsEntity` | PATCH: cambia DocState a "Recived" |
209
+
210
+ ---
211
+
212
+ ### `dynamics/guias.py`
213
+ Módulo principal de orquestación. Tiene tres clases y una función:
214
+
215
+ **`FiltrosGuiasBeetrack`** — Construye los strings de filtro OData a partir de los datos recuperados. El helper interno `_armar_filtro()` evita repetición de código.
216
+
217
+ **`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.
218
+
219
+ **`ArmarGuiasBeetrack`** — Combina todos los datos en objetos `GuiaResponse` y los envía a Beetrack con control de concurrencia y seguridad transaccional.
220
+
221
+ **`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.
222
+
223
+ ---
224
+
225
+ ### `dynamics/servicio_dynamics.py`
226
+ Capa HTTP pura. Tres funciones async:
227
+
228
+ - `get_consultar_dynamics()` — GET a OData con token Bearer. Elimina metadatos `@odata.etag`.
229
+ - `patch_actualizar_dynamics()` — PATCH para actualizar estado en Dynamics.
230
+ - `post_beetrack()` — POST a la API de Beetrack con X-AUTH-TOKEN.
231
+
232
+ 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).
233
+
234
+ ---
235
+
236
+ ### `dynamics/clases_guias_dynamics.py`
237
+ Modelos de datos (Python `dataclasses`):
238
+
239
+ | Clase | Descripción |
240
+ |-------|-------------|
241
+ | `InformacionConsulta` | Respuesta genérica: `consulta: bool`, `mensaje: str`, `data` |
242
+ | `PickupAddress` | Dirección de recogida (bodega de origen) |
243
+ | `Dimension` | Par nombre/valor para dimensiones de producto |
244
+ | `ProductoBeetrack` | Producto con descripción, cantidad, código y dimensiones |
245
+ | `GuiaResponse` | Guía completa lista para enviar a Beetrack |
246
+
247
+ `GuiaResponse` se construye en etapas mediante setters:
248
+ 1. `set_info_facturas()` — datos del pedido y ventana de entrega
249
+ 2. `set_info_cliente()` — contacto, dirección, validación de correo/grupo
250
+ 3. `set_info_localizacion()` — coordenadas GPS
251
+ 4. `set_dir_facturas()` — InvoiceId y dirección postal
252
+ 5. `set_info_productos()` — lista de productos
253
+
254
+ ---
255
+
256
+ ### `utils/logger.py`
257
+ Logger con salida dual:
258
+
259
+ | Destino | Nivel | Retención | Formato |
260
+ |---------|-------|-----------|---------|
261
+ | **Consola** | INFO y superior | — | `HH:MM:SS \| NIVEL \| [run_id] \| mensaje` |
262
+ | `logs/app_DD-MM-YYYY.log` | INFO y WARNING | 7 días | Formato completo con archivo:línea |
263
+ | `logs/errors_DD-MM-YYYY.log` | ERROR y CRITICAL | 30 días | Formato completo con archivo:línea |
264
+
265
+ Cada línea incluye el `run_id` del ciclo activo, lo que permite filtrar todos los eventos de una ejecución específica.
266
+
267
+ ---
268
+
269
+ ### `utils/correlation.py`
270
+ 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.
271
+
272
+ ---
273
+
274
+ ## Configuración (.env)
275
+
276
+ Copiar `.env.example` a `.env` y completar los valores:
277
+
278
+ ```bash
279
+ cp .env.example .env
280
+ ```
281
+
282
+ ### Variables obligatorias
283
+
284
+ ```env
285
+ # Autenticación Dynamics 365 (Azure AD)
286
+ DYNAMICS_URL_TOKEN=https://login.microsoftonline.com/<TENANT_ID>/oauth2/token
287
+ DYNAMICS_URL_ACCESO=https://<ENVIRONMENT>.operations.dynamics.com/
288
+ DYNAMICS_ID_CLIENTE=<CLIENT_ID>
289
+ DYNAMICS_CLAVE_CLIENTE=<CLIENT_SECRET>
290
+ DYNAMICS_TIPO_CREDENCIAL=client_credentials
291
+
292
+ # Beetrack / DispatchTrack
293
+ BEETRACK_URL_ACCESO=https://<INSTANCIA>.dispatchtrack.com/api/external/v1/dispatches
294
+ BEETRACK_TOKEN_ACCESO=<API_TOKEN>
295
+ ```
296
+
297
+ ### Variables opcionales (tienen defaults en app_config.py)
298
+
299
+ ```env
300
+ BEETRACK_SALES_IDS_EXCLUIDOS=OV-000000,OV-000001
301
+ DYNAMICS_SALES_IDS_EXCLUIDOS=OV-000000,OV-000001
302
+ PICKUP_ADDRESS_NAME=Bodega Zona 4
303
+ PICKUP_LATITUDE=14.621669674128709
304
+ PICKUP_LONGITUDE=-90.51804696621112
305
+ DELIVERY_MIN_OFFSET_HOURS=7
306
+ DELIVERY_MAX_OFFSET_HOURS=6
307
+ DISPATCH_PRIORITY=1
308
+ DISPATCH_MODE=3
309
+ DISPATCH_PLACE=Servicio Express
310
+ DOMINIOS_EMAIL_VALIDOS=@centrodistribuidor.com,@servir.com.gt
311
+ GRUPOS_VENTAS_VALIDOS=CVTA-0006,CVTA-0029,...
312
+ BEETRACK_MAX_CONCURRENT=10
313
+ PENDING_DYNAMICS_UPDATES_FILE=pending_dynamics_updates.json
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Instalación y ejecución
319
+
320
+ ### Instalación desde PyPI
321
+
322
+ ```bash
323
+ pip install guias-beetrack-ricardo-fuentes
324
+ guias-beetrack
325
+ ```
326
+
327
+ ### 1. Crear entorno virtual e instalar dependencias
328
+
329
+ ```bash
330
+ python -m venv tareas_auto
331
+ tareas_auto\Scripts\activate # Windows
332
+ pip install -r requirements.txt
333
+ ```
334
+
335
+ ### 2. Configurar variables de entorno
336
+
337
+ ```bash
338
+ copy .env.example .env
339
+ # Editar .env con las credenciales reales
340
+ ```
341
+
342
+ ### 3. Ejecutar
343
+
344
+ ```bash
345
+ # Desde la raiz del proyecto
346
+ python -m guias_beetrack
347
+ ```
348
+
349
+ El proceso corre indefinidamente. Para detenerlo: `Ctrl+C`.
350
+
351
+ ### Ejecutar solo una vez (sin scheduler)
352
+
353
+ Descomentar en `main.py`:
354
+ ```python
355
+ if __name__ == "__main__":
356
+ main()
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Logs
362
+
363
+ Al ejecutarse se verá en consola:
364
+
365
+ ```
366
+ 10:05:00 | INFO | [- ] =======================================================
367
+ 10:05:00 | INFO | [- ] INICIO DE CICLO Dynamics → Beetrack
368
+ 10:05:00 | INFO | [- ] =======================================================
369
+ 10:05:00 | INFO | [a3f7c1d2] [1/5] Consultando pedidos en Dynamics...
370
+ 10:05:01 | INFO | [a3f7c1d2] [1/5] Pedidos encontrados: 8
371
+ 10:05:01 | INFO | [a3f7c1d2] [2/5] Obteniendo facturas, clientes y productos (paralelo)...
372
+ 10:05:02 | INFO | [a3f7c1d2] [2/5] Facturas: 8 | Clientes: 5 | Productos para 8 pedidos
373
+ 10:05:02 | INFO | [a3f7c1d2] [3/5] Obteniendo ubicaciones, direcciones y dimensiones (paralelo)...
374
+ 10:05:03 | INFO | [a3f7c1d2] [3/5] Ubicaciones: 5 | Direcciones: 8 | Dimensiones: 12
375
+ 10:05:03 | INFO | [a3f7c1d2] [4/5] Armando guías...
376
+ 10:05:03 | INFO | [a3f7c1d2] [4/5] 8 guías armadas.
377
+ 10:05:03 | INFO | [a3f7c1d2] [5/5] Enviando 8 guías a Beetrack...
378
+ 10:05:03 | INFO | [a3f7c1d2] OV-905123 [1/8] → Enviando a Beetrack...
379
+ 10:05:04 | INFO | [a3f7c1d2] OV-905123 [1/8] → Beetrack OK. Actualizando estado en Dynamics...
380
+ 10:05:04 | INFO | [a3f7c1d2] OV-905123 [1/8] → Completada
381
+ 10:05:05 | INFO | [a3f7c1d2] Envío finalizado: 8/8 guías completadas.
382
+ 10:05:05 | INFO | [a3f7c1d2] =======================================================
383
+ 10:05:05 | INFO | [a3f7c1d2] FIN DE CICLO
384
+ ```
385
+
386
+ Los archivos de log se guardan en `guias_beetrack/logs/`.
387
+
388
+ ---
389
+
390
+ ## Manejo de errores
391
+
392
+ ### Errores de red (timeout, conexión caída)
393
+ `servicio_dynamics.py` reintenta automáticamente hasta 3 veces con espera exponencial:
394
+ - Intento 1 falla → espera 1s → intento 2
395
+ - Intento 2 falla → espera 2s → intento 3
396
+ - Intento 3 falla → propaga el error
397
+
398
+ ### Error al enviar a Beetrack
399
+ La guía falla silenciosamente para ese ciclo. Las demás guías no se ven afectadas. Se registra en el log de errores.
400
+
401
+ ### Error al actualizar Dynamics (después de enviar a Beetrack)
402
+ Este es el caso crítico: la guía **ya llegó a Beetrack** pero Dynamics aún la ve como pendiente.
403
+
404
+ 1. Se guarda el `SalesId` en `pending_dynamics_updates.json`.
405
+ 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**.
406
+ 3. Solo reintenta el PATCH a Dynamics.
407
+ 4. Si el PATCH tiene éxito, se elimina del archivo.
408
+ 5. Si sigue fallando, permanece en el archivo para el ciclo siguiente.
409
+
410
+ ```
411
+ pending_dynamics_updates.json
412
+ {
413
+ "OV-905124": {
414
+ "data_area_id": "CND",
415
+ "rec_id_1": "5637145328",
416
+ "estado": "{\"DocState\": \"Recived\"}"
417
+ }
418
+ }
419
+ ```
420
+
421
+ ### Errores por guía vs errores globales
422
+ - Errores **dentro del envío** (Beetrack o Dynamics por guía): aislados, no afectan al resto.
423
+ - 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.