El sistema contesta a quien envió el correo con la plantilla que corresponda. Por defecto (NOTIFICACION_MODO=prueba) la respuesta va solo al equipo con el asunto "[PRUEBA → remitente]"; en modo real va al remitente con copia oculta al equipo. El correo técnico (tablas de SAP, JSON) pasa a ir solo al equipo. - respuesta_proveedor.py: contextos de las plantillas, envío y protecciones (remitente desconocido o sin dominio, propio buzón, no-reply, mensajes automáticos, respuestas a notificaciones, límite de 3 por remitente/24 h). - observaciones.py: motivos de rechazo con título, filas y cómo corregir (borrador de textos); también para correos sin comprobante válido. - Un error de SAP/HANA o un fallo inesperado no se le comunica al proveedor y ya no tumba el ciclo; "guía no encontrada" exige que HANA responda. - outlook_utilities: es_mensaje_automatico y copia oculta en send_email. - seguimiento: notificacion_destinatario (con migración) y contar_respuestas_correo. - config_validator anuncia el modo activo; documentación y pruebas. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
3.2 KiB
3.2 KiB
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). Cómo obtener las credenciales: configurar_msal.md.
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 y es_automatico (calculado con los encabezados del mensaje). 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, CC y copia oculta (bcc_recipients); 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_attachmentssolo guarda adjuntos tipofileAttachmentcon 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_SSLse respeta enget_access_token,get_emailsydownload_attachments, perosend_emailtieneverify_ssl = Falsefijo 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_tokenretornaNone(sin lanzar error) si MSAL devuelve un resultado vacío.es_mensaje_automatico(headers)marca como automáticos los mensajes conAuto-Submitteddistinto deno,X-Auto-Response-Suppress,X-Autoreply,Precedence(bulk,junk,list,auto_reply) oContent-Typede informe de entrega (rebotes). El sistema no responde a esos mensajes.