se agrega README y se completa y corrige la documentación

README en la raíz con instalación, uso, estructura e índice de docs. Nuevos
docs/hana_manager.md y docs/outlook_utilities.md. Se renombran los .mkd a .md
y se corrigen construir_draft y validar_precios, que describían lógica ya
inexistente (CUADRO_VALOR_REFERENCIAL, use_sap_prices). Se actualizan los
docstrings obsoletos y las cifras de líneas del inventario.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-20 22:03:38 -05:00
1 parent 74be942a3e
commit b1b6f70983
9 files changed
+249 -60

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
# outlook_utilities.py — Microsoft Graph (Outlook)
Cliente mínimo de Microsoft Graph para leer la bandeja de entrada, bajar adjuntos y enviar las notificaciones. Autenticación con MSAL (client credentials, permisos de aplicación).
## Estructura
```
outlook_utilities.py
├── CONFIG (.env): CLIENT_ID, CLIENT_SECRET, TENANT_ID, VERIFY_SSL (false por defecto)
├── get_access_token() → str
├── get_emails(user_email, token, fecha_inicio, fecha_fin) → list[dict]
├── download_attachments(user_email, message_id, token, download_path) → list[str]
└── send_email(access_token, from_email, to_recipients, subject, html_body,
attachment_paths=None, cc_recipients=None) → bool
```
## Funciones
| Función | Endpoint Graph | Qué hace |
|---------|----------------|----------|
| `get_access_token` | `login.microsoftonline.com/{TENANT_ID}` (scope `.default`) | Obtiene el token de aplicación. Lanza `Exception` si MSAL devuelve error. |
| `get_emails` | `GET /users/{user}/mailFolders/Inbox/messages` | Correos de la bandeja de entrada del rango, con paginación (`$top=50`, sigue `@odata.nextLink`). Devuelve `id`, `fecha`, `sender`, `subject`, `has_attach`. Lanza `Exception` si el status no es 200. |
| `download_attachments` | `GET /users/{user}/messages/{id}/attachments` | Guarda en `download_path` los adjuntos tipo archivo y devuelve sus nombres. Crea la carpeta si no existe. |
| `send_email` | `POST /users/{from}/sendMail` | Envía HTML con adjuntos y CC; guarda copia en Enviados. Devuelve `True` si Graph responde 202; `False` (con `ERROR` en el log) en cualquier otro caso. No lanza excepciones. |
## Zona horaria
`get_emails` asume UTC-5 (Perú, sin horario de verano): el rango `fecha_inicio`–`fecha_fin` se convierte a UTC sumando 5 h (el fin incluye todo el día), y el campo `fecha` devuelto resta 5 h a `receivedDateTime`.
## Adjuntos de `send_email`
Los adjuntos se leen de las rutas indicadas. Una ruta inexistente se omite con un `WARNING`. El `contentType` se deduce de la extensión (`.pdf`, `.xml`, `.json`; cualquier otra, `application/octet-stream`).
## Notas
- `download_attachments` solo guarda adjuntos tipo `fileAttachment` con contenido; los adjuntos de tipo mensaje o referencia se ignoran.
- Dos correos con un adjunto del mismo nombre se sobrescriben entre sí si comparten `download_path` (el pipeline usa una única carpeta, `DOWNLOAD_FOLDER`).
- `VERIFY_SSL` se respeta en `get_access_token`, `get_emails` y `download_attachments`, pero `send_email` tiene `verify_ssl = False` fijo y lo ignora.
- El módulo ejecuta `warnings.filterwarnings("ignore")`, lo que silencia todas las advertencias de Python del proceso (incluidas las de certificado SSL).
- `get_access_token` retorna `None` (sin lanzar error) si MSAL devuelve un resultado vacío.