# main.py — Mapa del flujo ## Estructura general ``` main.py ├── Constantes: HORARIOS (5 bloques horarios) ├── obtener_intervalo(dt) → (intervalo, modo) | None ├── segundos_hasta_proximo_inicio(dt) → int ├── non_negative_int(value) → int (validador argparse) └── main() → None (bucle infinito envuelto en try/except KeyboardInterrupt) ``` ## Proceso de `main()` ```mermaid flowchart TD A[Inicio main] --> B[Parsear args --dias] B --> C[Cargar .env → EMAIL, DOWNLOAD_FOLDER, OUTPUT_FOLDER] C --> Z[try:] Z --> D{Bucle infinito} D --> E[inicio_ciclo = datetime.now] E --> F[obtener_intervalo inicio_ciclo] F -- Sí, está en horario --> G[Extraer intervalo, modo] G --> H[run_pipeline]:::highlight H --> I[Calcular duración real] I --> J{Duración > intervalo?} J -- Sí --> K[⚠️ Warning] J -- No --> L[dormir = intervalo - duración] K --> L L --> M[💤 Próximo ciclo en dormir seg] M --> N[time.sleep dormir] N --> E F -- No, fuera de horario --> O[dormir = segundos_hasta_proximo_inicio] O --> P[Log próxima ejecución] P --> N D -. KeyboardInterrupt .-> Q[🛑 Demonio detenido por el usuario] classDef highlight fill:#fff3e0,stroke:#e65100,stroke-width:3px; ``` ### Paso a paso ``` main() │ ├── 1. Parsear args (--dias, default=2) │ ├── 2. Cargar .env → EMAIL, DOWNLOAD_FOLDER, OUTPUT_FOLDER │ ├── 3. try: │ └── LOOP INFINITO (while True): │ │ │ ├── 3.1. inicio_ciclo = datetime.now() │ │ │ ├── 3.2. resultado = obtener_intervalo(inicio_ciclo) │ │ │ │ ├── ¿Sí está en horario? │ │ │ │ │ ├── Extraer (intervalo, modo) │ │ ├── run_pipeline(dias, EMAIL, DOWNLOAD_FOLDER, OUTPUT_FOLDER) │ │ ├── Calcular duración real del ciclo │ │ ├── ⚠️ Si duración > intervalo → warning │ │ └── dormir = max(0, intervalo - duración) │ │ │ └── ¿No está en horario? │ │ │ ├── dormir = segundos_hasta_proximo_inicio() (06:30 del día siguiente) │ └── Log "Fuera del horario, próxima ejecución en..." │ │ │ └── 3.3. time.sleep(dormir) → vuelve a 3.1 │ └── except KeyboardInterrupt: 🛑 "Demonio detenido por el usuario" (sin traceback) ``` ## Bloques horarios definidos | Desde | Hasta | Intervalo | Modo | |---------|---------|-----------|----------| | 06:30 | 13:15 | 300s | rápido | | 13:15 | 14:00 | 1800s | full | | 14:00 | 18:30 | 300s | rápido | | 18:30 | 21:30 | 3600s | full | | 00:00 | 05:00 | 60s | rápido | Nota: entre las 21:30 y las 00:00, y entre las 05:00 y las 06:30, ningún bloque cubre esos minutos. En esos huecos `obtener_intervalo()` devuelve `None` y `segundos_hasta_proximo_inicio()` calcula el tiempo de espera hasta las 06:30 del día siguiente (no hasta el bloque de las 00:00), por lo que el demonio se queda dormido hasta las 06:30 aunque exista un bloque configurado a las 00:00. ## Entry point (línea 126-134) ``` if __name__ == "__main__": 1. logger_manager.setup_logging() └── Configura logging a archivo rotado (logs/descarga_factura_transporte.log, rotación diaria, 30 días de historial) + consola, y redirige print()/stderr al mismo log (no hace falta migrar cada print a logger.info). 2. print(...) banner de inicio, incluyendo logger_manager.LOG_FILE 3. main() ```