|
1 | 1 | # Sistema de Gestión de Tareas Empresariales |
2 | 2 |
|
| 3 | +[](https://github.com/ardelperal/scripts-python/actions/workflows/python-ci.yml) |
| 4 | +[](https://codecov.io/gh/ardelperal/scripts-python) |
| 5 | + |
3 | 6 | Sistema de **monitoreo continuo** para la gestión automatizada de tareas empresariales desarrollado en Python. El objetivo principal es ejecutar el script maestro `run_master.py` que funciona como un **daemon de producción** que monitorea y ejecuta automáticamente todos los módulos del sistema según horarios específicos. |
4 | 7 |
|
5 | 8 | ## 🎯 Objetivo Principal |
@@ -85,6 +88,47 @@ class MiTarea(TareaDiaria): |
85 | 88 | registry = TaskRegistry(extra_daily=[MiTarea()]) |
86 | 89 | ``` |
87 | 90 |
|
| 91 | +### Helper de Ejecución Unificada |
| 92 | + |
| 93 | +Para reducir boilerplate en los `run_*.py`, todas las tareas se ejecutan mediante `execute_task_with_standard_boilerplate` (`common.utils`). |
| 94 | + |
| 95 | +Características: |
| 96 | +* Logging estándar con fichero dedicado `logs/<tarea>.log`. |
| 97 | +* Banners `=== INICIO TAREA X ===` / `=== FIN TAREA X ===`. |
| 98 | +* Modos soportados: normal, `--force` (ignora planificación y NO marca completada) y `--dry-run` (sólo evalúa planificación). |
| 99 | +* Detección automática del método de lógica: `execute_specific_logic` > `execute_logic` > `execute`. |
| 100 | +* Invoca `initialize()` si existe antes de la lógica. |
| 101 | +* Marca completada sólo en tareas diarias exitosas (y no en `--force`). |
| 102 | + |
| 103 | +Ejemplo de runner minimalista: |
| 104 | + |
| 105 | +```python |
| 106 | +import sys |
| 107 | +from common.utils import execute_task_with_standard_boilerplate |
| 108 | +from correos.correos_task import CorreosTask |
| 109 | + |
| 110 | +def main(): |
| 111 | + task = CorreosTask() |
| 112 | + code = execute_task_with_standard_boilerplate("CORREOS", task_obj=task) |
| 113 | + sys.exit(code) |
| 114 | + |
| 115 | +if __name__ == "__main__": |
| 116 | + main() |
| 117 | +``` |
| 118 | + |
| 119 | +Para lógica puntual sin clase se puede usar `custom_logic=callable`, pero se recomienda migrar a clases `TareaDiaria` / `TareaContinua` para uniformidad y testabilidad. |
| 120 | + |
| 121 | +#### Añadir una nueva tarea |
| 122 | +1. Crear clase `TareaDiaria` o `TareaContinua` con `execute_specific_logic`. |
| 123 | +2. Registrar en `TaskRegistry` o pasar como `extra_*`. |
| 124 | +3. Crear `run_<tarea>.py` que sólo instancie y llame al helper. |
| 125 | +4. Añadir tests (mock de planificación y lógica). |
| 126 | + |
| 127 | +#### ensure_project_root_in_path |
| 128 | + |
| 129 | +Los runners llaman a `ensure_project_root_in_path()` (en `common.utils`) para insertar `src` en `sys.path` de forma idempotente, eliminando bloques repetidos de manipulación manual. |
| 130 | + |
| 131 | + |
88 | 132 | ### Acceso unificado a BD |
89 | 133 |
|
90 | 134 | ```python |
@@ -150,6 +194,8 @@ El sistema ajusta automáticamente los tiempos de espera entre ciclos según el |
150 | 194 | - [Testing](#testing) |
151 | 195 | - [Variables de Entorno Principales](#variables-de-entorno-principales) |
152 | 196 | - [Arquitectura](#arquitectura) |
| 197 | +- [Arquitectura](#arquitectura) |
| 198 | +- [Arquitectura de Tareas](#arquitectura-de-tareas) |
153 | 199 |
|
154 | 200 | ## Estructura del Proyecto |
155 | 201 |
|
@@ -1494,6 +1540,113 @@ docker-compose down -v |
1494 | 1540 |
|
1495 | 1541 | ## Arquitectura |
1496 | 1542 |
|
| 1543 | +### Arquitectura de Tareas |
| 1544 | + |
| 1545 | +Esta sección describe cómo se estructuran y colaboran los componentes que permiten ejecutar cada módulo de negocio de forma consistente, testeable y extensible. |
| 1546 | + |
| 1547 | +#### 1. Componentes Principales |
| 1548 | + |
| 1549 | +| Componente | Responsabilidad | Código típico | |
| 1550 | +|------------|-----------------|---------------| |
| 1551 | +| Script Runner (`scripts/run_x.py`) | Punto de entrada ejecutable: parsea argumentos CLI, inicializa logging y delega en la Task | `scripts/run_no_conformidades.py` | |
| 1552 | +| Task (`BaseTask`, `TareaDiaria`, `TareaContinua`) | Orquestación de la lógica: decide si ejecutar, encapsula medición, logging estructurado y control de errores | `src/no_conformidades/no_conformidades_task.py` | |
| 1553 | +| Manager | Lógica de dominio y acceso a datos (queries, composición de datos, generación de HTML) | `no_conformidades_manager.py` / `*_manager.py` | |
| 1554 | +| TaskRegistry | Registro central de instancias de tareas para el script maestro | `common/task_registry.py` | |
| 1555 | +| Master Runner (`run_master.py`) | Bucle continuo que consulta el `TaskRegistry` y lanza tareas según frecuencia / tipo | `scripts/run_master.py` | |
| 1556 | + |
| 1557 | +Separar estas capas reduce acoplamiento: los runners quedan triviales, las Tasks son testeables aislando sus métodos de decisión y ejecución con mocks, y los Managers concentran la lógica SQL / dominio reutilizable. |
| 1558 | + |
| 1559 | +#### 2. Flujo General (Runner Individual) |
| 1560 | + |
| 1561 | +``` |
| 1562 | +parse_args() |
| 1563 | +setup_logging() |
| 1564 | +with Task() as task: |
| 1565 | + if args.force_flags: |
| 1566 | + task.ejecutar_forzado(sub-selección) |
| 1567 | + elif task.debe_ejecutarse(): |
| 1568 | + task.ejecutar() |
| 1569 | + else: |
| 1570 | + log("skip") |
| 1571 | +``` |
| 1572 | + |
| 1573 | +La Task maneja internamente: |
| 1574 | +1. Registro de inicio (`event=task_start`). |
| 1575 | +2. Llamada a `execute_specific_logic()` (implementación concreta). |
| 1576 | +3. Marcado de completitud (`marcar_como_completada()`) sólo si la ejecución fue efectiva. |
| 1577 | +4. Registro de fin (`event=task_end`, `exit_code`). |
| 1578 | +5. Captura y log estructurado de excepciones sin comprometer el proceso principal. |
| 1579 | + |
| 1580 | +#### 3. Flujo General (Master Runner) |
| 1581 | + |
| 1582 | +1. Crea / reutiliza instancia de `TaskRegistry`. |
| 1583 | +2. Obtiene listas: `get_daily_tasks()` y `get_continuous_tasks()`. |
| 1584 | +3. Para cada tarea diaria: evalúa `debe_ejecutarse()` (frecuencia + horario + festivos) antes de lanzar. |
| 1585 | +4. Para cada tarea continua: se ejecuta en cada ciclo. |
| 1586 | +5. Aplica timeouts y registra resultados agregados para observabilidad. |
| 1587 | + |
| 1588 | +#### 4. Contrato Simplificado de una Task |
| 1589 | + |
| 1590 | +| Método | Propósito | |
| 1591 | +|--------|-----------| |
| 1592 | +| `debe_ejecutarse()` | Decide si corresponde ejecutar (diarias) | |
| 1593 | +| `execute_specific_logic()` | Lógica principal; devuelve bool éxito | |
| 1594 | +| `marcar_como_completada()` | Actualiza estado persistente (última ejecución) | |
| 1595 | + |
| 1596 | +Errores lanzados en `execute_specific_logic()` se capturan en el wrapper de `BaseTask` para asegurar logging uniforme y evitar caída del ciclo maestro. |
| 1597 | + |
| 1598 | +#### 5. Caso Específico: `NoConformidadesTask` |
| 1599 | + |
| 1600 | +La tarea combina dos sub-tareas independientes: Calidad y Técnica. Para maximizar testabilidad se dividió en métodos discretos: |
| 1601 | + |
| 1602 | +| Método | Rol | |
| 1603 | +|--------|-----| |
| 1604 | +| `debe_ejecutar_tarea_calidad()` | Evalúa si hay NC de calidad que justifiquen envío | |
| 1605 | +| `debe_ejecutar_tarea_tecnica()` | Evalúa si hay AR técnicas pendientes | |
| 1606 | +| `ejecutar_logica_calidad()` | Construye datos + HTML y registra envío (usa `NoConformidadesManagerPure`) | |
| 1607 | +| `ejecutar_logica_tecnica()` | Agrega datos técnicos por usuario mediante `get_technical_report_data_for_user()` | |
| 1608 | +| `execute_specific_logic()` | Orquesta decisiones, ejecuta subtareas y consolida resultado (éxito parcial permitido) | |
| 1609 | + |
| 1610 | +Características clave: |
| 1611 | +* Separación de decisión vs ejecución -> tests unitarios rápidos (mocks sobre cada rama). |
| 1612 | +* Agregación técnica: una sola llamada por técnico en vez de 3 queries separadas (eficiencia y menor riesgo de inconsistencia temporal). |
| 1613 | +* Tolerancia a fallos: excepción en una sub-tarea no detiene la otra; se reporta resultado combinado. |
| 1614 | +* Flags de forzado (`--force-calidad`, `--force-tecnica`, `--force-all`) saltan las evaluaciones de `debe_ejecutar_*`. |
| 1615 | + |
| 1616 | +Secuencia simplificada (técnica + calidad): |
| 1617 | + |
| 1618 | +``` |
| 1619 | +execute_specific_logic(): |
| 1620 | + resultados = [] |
| 1621 | + if forzar_calidad or debe_ejecutar_tarea_calidad(): |
| 1622 | + try: resultados.append(ejecutar_logica_calidad()) |
| 1623 | + except Exception: log(error) |
| 1624 | + if forzar_tecnica or debe_ejecutar_tarea_tecnica(): |
| 1625 | + try: resultados.append(ejecutar_logica_tecnica()) |
| 1626 | + except Exception: log(error) |
| 1627 | + return any(resultados) # éxito si al menos una rama hizo trabajo |
| 1628 | +``` |
| 1629 | + |
| 1630 | +#### 6. Beneficios de la Arquitectura de Tareas |
| 1631 | + |
| 1632 | +| Beneficio | Explicación | |
| 1633 | +|-----------|-------------| |
| 1634 | +| Testabilidad | Métodos pequeños permiten mocks específicos y alta cobertura | |
| 1635 | +| Observabilidad | Eventos start/end homogéneos y exit codes previsibles | |
| 1636 | +| Evolutividad | Añadir una nueva Task sólo requiere implementarla y registrarla | |
| 1637 | +| Aislamiento de fallos | Una Task con error no compromete el ciclo maestro | |
| 1638 | +| Reutilización | Managers compartidos entre múltiples Tasks o runners futuros | |
| 1639 | +| Rendimiento | Reducción de queries duplicadas y posibilidad futura de caching | |
| 1640 | + |
| 1641 | +#### 7. Próximos Mejoras Potenciales |
| 1642 | + |
| 1643 | +* Persistir métricas (duración, número de registros procesados) para dashboards. |
| 1644 | +* Sistema de descubrimiento dinámico de Tasks (entry points / plugin folder). |
| 1645 | +* Instrumentación opcional (trazas / spans) para tareas de larga duración. |
| 1646 | +* Caching de resultados intermedios entre subtareas (cuando comparten dataset base). |
| 1647 | + |
| 1648 | +--- |
| 1649 | + |
1497 | 1650 | ### Módulos Comunes (`src/common/`) |
1498 | 1651 |
|
1499 | 1652 | - **config.py**: Gestión centralizada de configuración |
|
0 commit comments