se agrega la rotación de log y se actualiza la documentación del proyecto
This commit is contained in:
1 parent
cf2c1ee147
commit
372e6fe7f8
13 files changed
+459
-97
No files matched your search
+118
@@ -0,0 +1,118 @@
|
||||
# Descarga Adjuntos Correos CPE
|
||||
|
||||
Daemon que descarga automáticamente adjuntos de correos electrónicos (facturas, boletas, notas de crédito) desde Microsoft Outlook, extrae metadatos de archivos XML/PDF, y los sincroniza a una carpeta compartida SMB.
|
||||
|
||||
## Arquitectura
|
||||
|
||||

|
||||
|
||||
## Requisitos
|
||||
|
||||
- Docker y Docker Compose
|
||||
- Python 3.12 (solo para ejecución local sin Docker)
|
||||
- Acceso a Microsoft Graph API (credenciales de Azure AD)
|
||||
- Servidor SMB/CIFS en la red local
|
||||
|
||||
## Instalación
|
||||
|
||||
### Con Docker (recomendado)
|
||||
|
||||
```bash
|
||||
# Clonar el repositorio
|
||||
git clone <url-del-repositorio>
|
||||
cd descarga_adjuntos_correos_cpe
|
||||
|
||||
# Crear los directorios necesarios
|
||||
mkdir -p logs data adjuntos resultado output_json
|
||||
|
||||
# Configurar archivos (ver configuracion.md)
|
||||
# Editar config.json y scheduler.json
|
||||
|
||||
# Construir y ejecutar
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Sin Docker
|
||||
|
||||
```bash
|
||||
# Crear entorno virtual
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux/Mac
|
||||
# venv\Scripts\activate # Windows
|
||||
|
||||
# Instalar dependencias
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Ejecutar
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Estructura del Proyecto
|
||||
|
||||
```
|
||||
├── main.py # Punto de entrada - daemon principal
|
||||
├── config.json # Configuracion de credenciales y rutas
|
||||
├── config_loader.py # Carga de config.json en dataclasses
|
||||
├── config_model.py # Definicion del esquema de configuracion
|
||||
├── scheduler.json # Horarios y dias de operacion
|
||||
├── outlook_utilities.py # Autenticacion MSAL y descarga de correos
|
||||
├── email_services.py # Filtrado de correos y descompresion ZIP
|
||||
├── document_processor.py # Extraccion de metadata de XML
|
||||
├── pdf_utilities.py # Extraccion de texto y regex de PDFs
|
||||
├── llm_processor.py # Fallback con LLM (Ollama) para PDFs
|
||||
├── file_manager.py # Renombrado, movimiento y limpieza de archivos
|
||||
├── database_utilities.py # SQLite para tracking de correos procesados
|
||||
├── smb_manager.py # Montaje y sincronizacion SMB
|
||||
├── logger_utilities.py # Sistema de logging con rotacion diaria
|
||||
├── Dockerfile # Imagen Docker
|
||||
├── docker-compose.yml # Orquestacion del servicio
|
||||
├── logs/ # Archivos de log diarios
|
||||
├── data/ # Base de datos SQLite
|
||||
├── adjuntos/ # Descarga temporal de adjuntos
|
||||
├── resultado/ # Archivos procesados y renombrados
|
||||
├── output_json/ # Exportacion JSON de resultados
|
||||
└── tests/ # Suite de pruebas
|
||||
```
|
||||
|
||||
## Comandos Utiles
|
||||
|
||||
```bash
|
||||
# Ver logs en tiempo real
|
||||
docker compose logs -f descarga-cpe
|
||||
|
||||
# Ver ultimas lineas de log
|
||||
docker compose logs --tail=50 descarga-cpe
|
||||
|
||||
# Detener el servicio
|
||||
docker compose stop descarga-cpe
|
||||
|
||||
# Reiniciar el servicio
|
||||
docker compose restart descarga-cpe
|
||||
|
||||
# Reconstruir tras cambios en el codigo
|
||||
docker compose build descarga-cpe && docker compose up -d descarga-cpe
|
||||
|
||||
# Verificar estado del contenedor
|
||||
docker compose ps descarga-cpe
|
||||
|
||||
# Ejecutar pruebas
|
||||
python -m pytest tests/ -v
|
||||
```
|
||||
|
||||
## Scheduler
|
||||
|
||||
El daemon opera segun los horarios definidos en `scheduler.json`:
|
||||
|
||||
| Horario | Intervalo | Modo |
|
||||
|---------|-----------|------|
|
||||
| 06:30 - 13:15 | 5 minutos | rapido |
|
||||
| 13:15 - 14:00 | 1 hora | full |
|
||||
| 14:00 - 17:30 | 5 minutos | rapido |
|
||||
| 17:30 - 21:30 | 1 hora | full |
|
||||
|
||||
Dias activos: Lunes a Viernes (0-4)
|
||||
|
||||
## Logs
|
||||
|
||||
Los archivos de log se guardan en `logs/` con formato `YYYY-MM-DD.log`. La rotacion es automatica al cambiar de dia. Los logs con mas de 30 dias se eliminan automaticamente al iniciar el daemon.
|
||||
@@ -0,0 +1,192 @@
|
||||
# Guia de Configuracion
|
||||
|
||||
## config.json
|
||||
|
||||
Archivo de configuracion principal con credenciales y rutas. **No commitear este archivo al repositorio.**
|
||||
|
||||
### Estructura completa
|
||||
|
||||
```json
|
||||
{
|
||||
"hana": {
|
||||
"server": "IP_DEL_SERVIDOR",
|
||||
"port": 30015,
|
||||
"user": "USUARIO_HANA",
|
||||
"password": "CONTRASENA_HANA"
|
||||
},
|
||||
"outlook": {
|
||||
"client_id": "CLIENT_ID_AZURE_AD",
|
||||
"client_secret": "CLIENT_SECRET_AZURE_AD",
|
||||
"tenant_id": "TENANT_ID_AZURE_AD",
|
||||
"download_folder": "./adjuntos",
|
||||
"verify_ssl": false,
|
||||
"ca_bundle": null
|
||||
},
|
||||
"smb": {
|
||||
"server": "IP_SERVIDOR_SMB",
|
||||
"username": "USUARIO_SMB",
|
||||
"password": "CONTRASENA_SMB",
|
||||
"share": "NOMBRE_DEL_SHARE",
|
||||
"mount_points": {
|
||||
"windows": "S:",
|
||||
"linux": "/mnt/FileSAP",
|
||||
"mac": "/Users/usuario/FileSAP",
|
||||
"docker": "/mnt/FileSAP"
|
||||
}
|
||||
},
|
||||
"mails": [
|
||||
{
|
||||
"email": "correo@empresa.com",
|
||||
"ruta_descarga": "./adjuntos/empresa",
|
||||
"ruta_final": "./resultado/empresa",
|
||||
"base_datos": "SBO_EMPRESA_XX",
|
||||
"usuario": "manager",
|
||||
"password": "CONTRASENA_SAP",
|
||||
"smb_folder": "/COMPRAS/EMPRESA/ADJUNTOS_CORREOS/2025-03"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Secciones
|
||||
|
||||
#### hana
|
||||
Conexion a SAP HANA Database para consultas de datos de negocio.
|
||||
|
||||
| Campo | Descripcion |
|
||||
|-------|-------------|
|
||||
| `server` | IP o hostname del servidor HANA |
|
||||
| `port` | Puerto (por defecto 30015) |
|
||||
| `user` | Usuario de conexion |
|
||||
| `password` | Contrasena |
|
||||
|
||||
#### outlook
|
||||
Credenciales de la aplicacion registrada en Azure AD para Microsoft Graph API.
|
||||
|
||||
| Campo | Descripcion |
|
||||
|-------|-------------|
|
||||
| `client_id` | ID de la aplicacion en Azure AD |
|
||||
| `client_secret` | Secreto de la aplicacion |
|
||||
| `tenant_id` | ID del tenant de Azure AD |
|
||||
| `download_folder` | Carpeta temporal para descarga de adjuntos |
|
||||
| `verify_ssl` | `true` para verificar certificados SSL, `false` para omitir |
|
||||
| `ca_bundle` | Ruta a bundle CA personalizado, `null` para usar el del sistema |
|
||||
|
||||
**Como obtener credenciales de Outlook:**
|
||||
|
||||
1. Ir a [Azure Portal](https://portal.azure.com) > Azure Active Directory > App registrations
|
||||
2. Crear una nueva aplicacion o usar una existente
|
||||
3. Configurar permisos de Microsoft Graph: `Mail.Read` (delegated)
|
||||
4. Copiar el Application (client) ID, Directory (tenant) ID, y crear un Client Secret
|
||||
|
||||
#### smb
|
||||
Configuracion del servidor de archivos compartidos SMB/CIFS.
|
||||
|
||||
| Campo | Descripcion |
|
||||
|-------|-------------|
|
||||
| `server` | IP o hostname del servidor SMB |
|
||||
| `username` | Usuario de autenticacion |
|
||||
| `password` | Contrasena |
|
||||
| `share` | Nombre del recurso compartido |
|
||||
| `mount_points` | Puntos de montaje segun el sistema operativo |
|
||||
|
||||
Los `mount_points` se usan automaticamente segun donde se ejecute:
|
||||
- `docker` se usa cuando corre en contenedor
|
||||
- `linux`, `mac`, `windows` para ejecucion local
|
||||
|
||||
#### mails
|
||||
Lista de buzones de correo a monitorear. Cada entrada es un buzón independiente.
|
||||
|
||||
| Campo | Descripcion |
|
||||
|-------|-------------|
|
||||
| `email` | Direccion del buzón de correo |
|
||||
| `ruta_descarga` | Carpeta temporal para descargar adjuntos de este buzón |
|
||||
| `ruta_final` | Carpeta donde se mueven los archivos procesados |
|
||||
| `base_datos` | Codigo de base de datos SAP Business One |
|
||||
| `usuario` | Usuario de SAP Business One |
|
||||
| `password` | Contrasena de SAP Business One |
|
||||
| `smb_folder` | Ruta destino en el share SMB para sincronizar |
|
||||
|
||||
Se pueden configurar multiples buzones en el array `mails`.
|
||||
|
||||
---
|
||||
|
||||
## scheduler.json
|
||||
|
||||
Define los horarios de operacion del daemon.
|
||||
|
||||
### Estructura
|
||||
|
||||
```json
|
||||
{
|
||||
"HORARIOS": [
|
||||
[6, 30, 13, 15, 300, "rapido"],
|
||||
[13, 15, 14, 0, 3600, "full"],
|
||||
[14, 0, 17, 30, 300, "rapido"],
|
||||
[17, 30, 21, 30, 3600, "full"]
|
||||
],
|
||||
"DIAS_ACTIVOS": [0, 1, 2, 3, 4]
|
||||
}
|
||||
```
|
||||
|
||||
### HORARIOS
|
||||
|
||||
Cada entrada es un array con 6 valores:
|
||||
|
||||
```
|
||||
[hora_inicio, minuto_inicio, hora_fin, minuto_fin, intervalo_segundos, modo]
|
||||
```
|
||||
|
||||
| Indice | Descripcion |
|
||||
|--------|-------------|
|
||||
| 0 | Hora de inicio (0-23) |
|
||||
| 1 | Minuto de inicio (0-59) |
|
||||
| 2 | Hora de fin (0-23) |
|
||||
| 3 | Minuto de fin (0-59) |
|
||||
| 4 | Intervalo entre ciclos en segundos |
|
||||
| 5 | Modo de operacion (`rapido` o `full`) |
|
||||
|
||||
**Ejemplo:** `[6, 30, 13, 15, 300, "rapido"]` significa:
|
||||
- Activo desde las 06:30 hasta las 13:15
|
||||
- Cada 300 segundos (5 minutos)
|
||||
- Modo rapido
|
||||
|
||||
### DIAS_ACTIVOS
|
||||
|
||||
Array con los dias de la semana en formato numerico:
|
||||
|
||||
| Valor | Dia |
|
||||
|-------|-----|
|
||||
| 0 | Lunes |
|
||||
| 1 | Martes |
|
||||
| 2 | Miercoles |
|
||||
| 3 | Jueves |
|
||||
| 4 | Viernes |
|
||||
| 5 | Sabado |
|
||||
| 6 | Domingo |
|
||||
|
||||
**Ejemplo:** `[0, 1, 2, 3, 4]` = Lunes a Viernes
|
||||
|
||||
### Modos de operacion
|
||||
|
||||
- **rapido**: Busca correos con frecuencia alta (cada 5 min). Indicado para horas pico.
|
||||
- **full**: Busca correos con frecuencia baja (cada 1 hora). Indicado para horas de baja actividad.
|
||||
|
||||
---
|
||||
|
||||
## Variables de Entorno
|
||||
|
||||
| Variable | Valor | Descripcion |
|
||||
|----------|-------|-------------|
|
||||
| `PYTHONUNBUFFERED` | `1` | Desactiva buffering de Python para logs en tiempo real |
|
||||
| `TZ` | `America/Lima` | Zona horaria del contenedor |
|
||||
|
||||
Estas se configuran en el `Dockerfile` y `docker-compose.yml`.
|
||||
|
||||
---
|
||||
|
||||
## Seguridad
|
||||
|
||||
- **Nunca commitear** `config.json` al repositorio (ya esta en `.gitignore`)
|
||||
- Usar volumenes Docker montados como solo lectura (`:ro`) para archivos sensibles
|
||||
- En produccion, considerar usar Docker secrets o variables de entorno en vez de archivos JSON
|
||||
@@ -0,0 +1,76 @@
|
||||
# Diagrama de Flujo - Descarga Adjuntos Correos CPE
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Start((<b>INICIO</b><br/>main.py)) --> Init[setup_logger + limpiar_logs_antiguos<br/>init_db SQLite]
|
||||
Init --> Loop{<b>while _running</b>}
|
||||
|
||||
Loop --> RotarLog[rotar_log - rotacion diaria]
|
||||
RotarLog --> CheckDay{Dia activo?<br/>weekdays 0-4}
|
||||
CheckDay -->|No| SleepDay[Esperar hasta<br/>proximo dia activo]
|
||||
SleepDay --> Loop
|
||||
|
||||
CheckDay -->|Si| CheckTime{Dentro de<br/>horario?}
|
||||
CheckTime -->|No| SleepTime[Esperar hasta<br/>06:30]
|
||||
SleepTime --> Loop
|
||||
|
||||
CheckTime -->|Si| Token[obtener_token<br/>MSAL - renueva cada 50min]
|
||||
Token --> LoopAccounts
|
||||
|
||||
subgraph LoopAccounts [Bucle por cada Cuenta de Correo - config.mails]
|
||||
direction TB
|
||||
Process[procesar_cuenta]
|
||||
Process --> GetEmails[get_emails<br/>Graph API]
|
||||
GetEmails --> FilterNew[filtrar_correos_nuevos<br/>SQLite - IDs ya procesados]
|
||||
FilterNew --> HasNew{Hay correos<br/>nuevos?}
|
||||
HasNew -->|No| Skip[Sin correos nuevos]
|
||||
HasNew -->|Si| EmailLoop
|
||||
|
||||
subgraph EmailLoop [Bucle por cada Correo Nuevo con Adjuntos]
|
||||
direction TB
|
||||
Download[download_attachments<br/>Graph API]
|
||||
Download --> Unzip[procesar_adjuntos_email<br/>Descomprimir ZIP + filtrar PDF/XML]
|
||||
Unzip --> ExtractMeta[procesar_adjuntos_enriquecidos<br/>obtener_datos_xml + extraer_texto_pdf]
|
||||
|
||||
ExtractMeta --> Classify{Clasificar<br/>metadatos}
|
||||
Classify -->|Completos| Rename[renombrar_archivos_por_metadata<br/>F001.xml -> RUC-01-F001.xml]
|
||||
Classify -->|Parciales| Evaluar[mover_a_evaluar<br/>revision manual]
|
||||
Classify -->|Vacios| Delete[limpiar_carpeta_descarga]
|
||||
|
||||
Rename --> MoveResult[mover_validos_a_resultado<br/>a /resultado/empresa]
|
||||
MoveResult --> Cleanup[limpiar_carpeta_descarga]
|
||||
end
|
||||
|
||||
Skip --> SMBSync
|
||||
EmailLoop --> SMBSync[sincronizar_a_smb<br/>rsync a carpeta compartida]
|
||||
end
|
||||
|
||||
LoopAccounts --> ExportJSON[guardar_json<br/>/output_json/timestamp.json]
|
||||
ExportJSON --> Sleep[Esperar intervalo<br/>segundos - 5min o 1hr]
|
||||
Sleep --> Loop
|
||||
|
||||
Loop -->|senal SIGTERM/SIGINT| Stop((<b>FIN</b>))
|
||||
|
||||
style Start fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Stop fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Loop fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
|
||||
style LoopAccounts fill:#e1f5fe,stroke:#01579b,stroke-dasharray: 5 5
|
||||
style EmailLoop fill:#fff3e0,stroke:#e65100
|
||||
style SMBSync fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
|
||||
style Classify fill:#fce4ec,stroke:#c62828
|
||||
```
|
||||
|
||||
## Flujo resumido
|
||||
|
||||
1. **Inicio**: Configura logger con rotacion diaria, limpia logs viejos (>30 dias), inicializa SQLite
|
||||
2. **Loop principal**: Verifica dia activo y horario antes de procesar
|
||||
3. **Token**: Obtiene/renueva token OAuth de Microsoft Graph (valido 60 min)
|
||||
4. **Por cada cuenta de correo**:
|
||||
- Consulta correos via Graph API
|
||||
- Filtra los ya procesados (SQLite)
|
||||
- Descarga adjuntos, descomprime ZIPs
|
||||
- Extrae metadata de XML y PDF (con fallback a LLM/Ollama)
|
||||
- Clasifica: completos (renombrar+mover), parciales (evaluar/), vacios (eliminar)
|
||||
5. **SMB**: Sincroniza archivos procesados a carpeta compartida via rsync
|
||||
6. **Exporta**: Guarda resultados en JSON con timestamp
|
||||
7. **Duerme**: Espera el intervalo configurado (5min rapido / 1hr full)
|
||||
@@ -1,53 +0,0 @@
|
||||
```mermaid
|
||||
graph TD
|
||||
Start((<b>INICIO</b><br/>main.py)) --> Init[Configurar Logger y<br/>Base de Datos SQLITE]
|
||||
Init --> Auth[Obtener Token de Acceso<br/>Outlook API]
|
||||
|
||||
subgraph MailConfigLoop [Bucle por cada Cuenta de Correo en config.json]
|
||||
direction TB
|
||||
GetEmails[Consultar correos del día<br/>outlook_utilities.get_emails]
|
||||
GetEmails --> FilterNew[Filtrar correos no procesados<br/>email_services.filtrar_correos_nuevos]
|
||||
|
||||
subgraph EmailLoop [Bucle por cada Correo Nuevo con Adjuntos]
|
||||
direction TB
|
||||
Download[Descargar adjuntos a /adjuntos/temp<br/>outlook_utilities.download_attachments]
|
||||
Download --> Unzip[Descomprimir ZIPs y filtrar archivos<br/>email_services.procesar_adjuntos_email]
|
||||
|
||||
subgraph DataExtraction [Extracción de Metadatos]
|
||||
direction LR
|
||||
ParseXML[Parsear XML<br/>document_processor.obtener_datos_xml]
|
||||
ParsePDF[Parsear PDF multilínea<br/>pdf_utilities.obtener_datos_pdf]
|
||||
end
|
||||
|
||||
Unzip --> DataExtraction
|
||||
|
||||
DataExtraction --> Grouping[Lógica de Agrupación y Renombrado<br/>file_manager.procesar_renombrado_agrupado]
|
||||
|
||||
Grouping --> Rename[Renombrado físico de archivos<br/>F001.xml -> RUC-01-F001.xml]
|
||||
|
||||
Rename --> MoveLocal[Mover archivos agrupados a /resultado/<br/>file_manager.mover_archivos_a_resultado]
|
||||
end
|
||||
|
||||
FilterNew --> EmailLoop
|
||||
|
||||
EmailLoop --> SMBSync[<b>Sincronización SMB</b><br/>smb_utils.sincronizar_a_smb]
|
||||
|
||||
subgraph SMBSteps [Proceso SMB]
|
||||
direction LR
|
||||
Mount[Montar Unidad según SO<br/>mount_smbfs / net use] --> Rsync[Ejecutar rsync -av<br/>--remove-source-files]
|
||||
end
|
||||
|
||||
SMBSync --- SMBSteps
|
||||
end
|
||||
|
||||
Auth --> MailConfigLoop
|
||||
|
||||
MailConfigLoop --> ExportJSON[Exportar resultados finales a JSON<br/>/output_json/YYYY-MM-DD.json]
|
||||
ExportJSON --> End((<b>FIN</b>))
|
||||
|
||||
style Start fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style End fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style MailConfigLoop fill:#e1f5fe,stroke:#01579b,stroke-dasharray: 5 5
|
||||
style EmailLoop fill:#fff3e0,stroke:#e65100
|
||||
style SMBSync fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
|
||||
```
|
||||
Reference in new issue
Block a user