Files
descarga_factura_transporte/docs/main.md
T
GeorgeandClaude Sonnet 5.5 6030320822 se actualiza la documentación: sapb1_manager, nombres de funciones y cifras
- Nueva página docs/sapb1_manager.md: sesión, funciones, clasificación de
  errores (SapNoDisponible frente a rechazo de SAP), búsqueda de facturas
  existentes y cómo se ubica una guía.
- procesar_pdfs.md y procesar_xmls.md usaban los nombres anteriores a la
  división de pipeline.py (_procesar_pdfs, _ruta_carpeta...) y números de
  línea que envejecen; ahora apuntan a documentos.py.
- inventario.md: árbol de dependencias con los módulos nuevos.
- pipeline.md: _procesar_factura, importes_coinciden, hana_disponible y
  buscar_factura_existente; validar_precios.md, hana_manager.md, main.md y
  procesar_xmls.md con las notas que faltaban; README sin la cifra de pruebas
  vieja.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 14:56:34 -05:00

4.1 KiB
Raw Blame History

main.py — Mapa del flujo

Estructura general

main.py
├── Constantes: HORARIOS (7 bloques que cubren las 24 horas)
├── 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()

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; rama inalcanzable mientras los bloques cubran el día)
│           └── 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
00:00 05:00 60s rápido
05:00 06:30 1800s full
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
21:30 24:00 3600s full

Los bloques cubren las 24 horas sin huecos ni solapes (lo comprueba tests/test_horarios.py). El modo (rápido/full) solo se registra en el log: run_pipeline se ejecuta igual en ambos. Antes de la versión 1.2.0 los tramos 21:30–00:00 y 05:00–06:30 no tenían bloque y el demonio dormía hasta las 06:30.

Entry point

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 (los módulos del pipeline ya usan logger con nivel explícito;
         los print() restantes se registran como INFO).
  2. logger.info(...) banner de inicio: fecha, versión del proyecto (pyproject.toml) y logger_manager.LOG_FILE
  3. main()
     └── Tras load_dotenv(), config_validator.verificar_configuracion():
         termina con código 1 si falta alguna variable obligatoria del .env
         (ver config_validator.OBLIGATORIAS) y advierte si la verificación SSL
         está desactivada o no hay ruta de red.