Skip to content

Commit 57fcede

Browse files
Refactor: unified DB layer, TaskRegistry, simple mode fast-path, remove legacy adapter & update README
1 parent 2f251c0 commit 57fcede

34 files changed

Lines changed: 1658 additions & 1258 deletions

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ coverage.xml
5252
# Logs
5353
logs/
5454
*.log
55-
setup_local_environment.log
5655

5756
# Database files
5857
*.accdb

README.md

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,76 @@ El **script maestro (`run_master.py`)** es el corazón del sistema y reemplaza a
2828
6. **Correos** (`run_correos.py`): Sistema de envío de correos
2929
7. **Correo Tareas** (`run_correo_tareas.py`): Sistema de gestión de correos que interactúa con la base de datos de tareas
3030

31+
### 🆕 Cambios Arquitectónicos Recientes (Refactor 2025)
32+
33+
Refactor integral para simplificar arquitectura, mejorar testabilidad y eliminar código legacy.
34+
35+
Principales mejoras:
36+
1. Capa de datos unificada:
37+
- Eliminados `AccessAdapter` y `DemoDatabase`.
38+
- Nueva clase única `AccessDatabase` con soporte opcional de pool.
39+
- Introducido `AccessConnectionPool` (gestiona instancias reutilizables por cadena de conexión).
40+
2. Gestión de tareas:
41+
- Reemplazo de funciones globales por clase `TaskRegistry` (extensible, inyectable, test-friendly).
42+
- API: `get_daily_tasks()`, `get_continuous_tasks()`, `get_all_tasks()`, `summary()`, filtros y extensión por parámetros `extra_daily/extra_continuous`.
43+
- Backwards compatibility: funciones wrapper conservadas para código legado.
44+
3. Script maestro (`run_master.py`):
45+
- Consolidado antiguo `run_master_new.py` (eliminado).
46+
- Añadido modo `--simple` sobre `TaskRegistry` con resumen estructurado.
47+
- Fast-path en tests (`MASTER_DRY_SUBPROCESS=1`) evitando importaciones pesadas.
48+
4. Riesgos y No Conformidades: parametrización explícita de frecuencias vía variables de entorno para subtareas.
49+
5. Limpieza y cobertura:
50+
- Eliminado definitivamente archivo legacy `database_adapter.py` y su test.
51+
- Stub ligero de `RiesgosTask` para unit tests cuando el módulo completo no es necesario.
52+
6. Documentación actualizada: ejemplos de extensión de tareas, uso de pools y guía de migración.
53+
54+
Pendiente futuro (no implementado aún):
55+
- Sistema de plugins de tareas (descubrimiento dinámico).
56+
- Persistencia de métricas de ejecución (duración/estado) para observabilidad.
57+
- Reducción selectiva de coste de importación en módulos grandes (lazy loading adicional).
58+
59+
### Uso de TaskRegistry
60+
61+
```python
62+
from common.task_registry import TaskRegistry
63+
64+
registry = TaskRegistry()
65+
for task in registry.get_daily_tasks():
66+
if task.debe_ejecutarse():
67+
task.ejecutar()
68+
task.marcar_como_completada()
69+
```
70+
71+
Extender con tareas personalizadas:
72+
73+
```python
74+
from common.base_task import TareaDiaria
75+
from common.task_registry import TaskRegistry
76+
77+
class MiTarea(TareaDiaria):
78+
def __init__(self):
79+
super().__init__(name="MiTarea", script_filename="run_mi_tarea.py", task_names=["MiTareaDiaria"], frequency_days=1)
80+
def debe_ejecutarse(self):
81+
return True
82+
def marcar_como_completada(self):
83+
pass
84+
85+
registry = TaskRegistry(extra_daily=[MiTarea()])
86+
```
87+
88+
### Acceso unificado a BD
89+
90+
```python
91+
from common.database import AccessDatabase
92+
from common.access_connection_pool import get_tareas_connection_pool
93+
from common.config import config
94+
95+
conn_str = config.get_db_tareas_connection_string()
96+
pool = get_tareas_connection_pool(conn_str)
97+
db = AccessDatabase(conn_str, pool=pool)
98+
rows = db.execute_query("SELECT TOP 1 * FROM TbTareas")
99+
```
100+
31101
### 🚀 Modo Verbose del Script Maestro
32102

33103
El script maestro incluye un **modo verbose** para debugging y monitoreo detallado:
@@ -135,7 +205,6 @@ scripts-python/
135205
│ ├── run_correos.py # Script para módulo correos
136206
│ ├── run_expedientes.py # Script para módulo expedientes
137207
│ ├── run_master.py # Script maestro - daemon principal con modo verbose
138-
│ ├── run_master_new.py # Nueva versión del script maestro
139208
│ ├── run_no_conformidades.py # Script para no conformidades
140209
│ └── run_riesgos.py # Script para módulo de riesgos
141210
├── src/ # Código fuente
@@ -155,12 +224,11 @@ scripts-python/
155224
│ │ ├── base_email_manager.py # Gestor base para emails
156225
│ │ ├── base_task.py # Clase base para tareas
157226
│ │ ├── config.py # Configuración multi-entorno
158-
│ │ ├── database.py # Capa abstracción bases datos Access
159-
│ │ ├── database_adapter.py # Adaptador de bases de datos
227+
│ │ ├── database.py # Capa unificada Access (AccessDatabase + pools)
160228
│ │ ├── html_report_generator.py # Generador reportes HTML
161229
│ │ ├── logger.py # Sistema de logging
162230
│ │ ├── notifications.py # Sistema de notificaciones
163-
│ │ ├── task_registry.py # Registro de tareas
231+
│ │ ├── task_registry.py # Registro de tareas (TaskRegistry OO)
164232
│ │ ├── user_adapter.py # Adaptador de usuarios
165233
│ │ └── utils.py # Utilidades HTML, logging, fechas
166234
│ ├── correo_tareas/ # Módulo de gestión de correos que interactúa con la base de datos de tareas
@@ -611,7 +679,7 @@ El sistema soporta dos configuraciones SMTP:
611679
|--------|-----------|-------|
612680
| `src/common/config.py` | 88% ||
613681
| `src/common/database.py` | 55% ||
614-
| `src/common/database_adapter.py` | 95% ||
682+
| `src/common/task_registry.py` | 64% ||
615683
| `src/common/notifications.py` | 100% ||
616684
| `src/common/utils.py` | 49% ||
617685
| `src/correos/correos_manager.py` | 91% ||
@@ -1015,7 +1083,7 @@ python tools/setup_local_environment.py --empty-correos
10151083

10161084
4. **Actualización de Vínculos**: Actualiza automáticamente todas las tablas vinculadas para que apunten a las bases de datos locales
10171085

1018-
5. **Logging Detallado**: Genera un log completo del proceso en `setup_local_environment.log`
1086+
5. **Logging Detallado**: Genera un log completo del proceso en `logs/setup_local_environment.log` (directorio de logs central). Si usas stack Grafana/Loki, puedes desactivar este archivo estableciendo la variable de entorno `SETUP_LOCAL_FILE_LOG=0` y capturando stdout.
10191087

10201088
**📋 Casos de Uso Típicos:**
10211089

@@ -1430,7 +1498,7 @@ docker-compose down -v
14301498

14311499
- **config.py**: Gestión centralizada de configuración
14321500
- **database.py**: Abstracción para bases de datos Access con ODBC
1433-
- **database_adapter.py**: Adaptador de conexiones de base de datos
1501+
- (Eliminado) `database_adapter.py` sustituido por `AccessDatabase`
14341502
- **utils.py**: Utilidades compartidas (HTML, fechas, logging)
14351503

14361504
### Mejoras vs VBS Original

legacy/script-continuo.vbs.txt

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
Option Explicit
2+
3+
4+
Public Function EsNoche()
5+
Dim Hora
6+
Dim HoraCompleta
7+
HoraCompleta = Now()
8+
Hora = Hour(HoraCompleta)
9+
If Hora >= 20 And Hora <= 24 Then
10+
EsNoche = True
11+
ElseIf Hora >= 0 And Hora < 7 Then
12+
EsNoche = True
13+
Else
14+
EsNoche = False
15+
End If
16+
End Function
17+
Function EsLaborable(fecha)
18+
Dim fso, archivo, linea,rutaScript, dirPadre, rutaFestivos
19+
20+
21+
22+
23+
' Si es s�bado o domingo, no es laborable
24+
If Weekday(fecha, vbMonday) > 5 Then
25+
EsLaborable = False
26+
Exit Function
27+
End If
28+
29+
Set fso = CreateObject("Scripting.FileSystemObject")
30+
rutaScript = fso.GetParentFolderName(WScript.ScriptFullName)
31+
dirPadre = fso.GetParentFolderName(rutaScript) ' Un directorio atrás
32+
rutaFestivos = fso.BuildPath(dirPadre, "herramientas\\Festivos.txt")
33+
34+
35+
Set archivo = fso.OpenTextFile(rutaFestivos, 1)
36+
37+
Do While Not archivo.AtEndOfStream
38+
linea = archivo.ReadLine
39+
' Busca la fecha al principio de la línea (puede estar entre paréntesis)
40+
' Extrae solo la parte de la fecha
41+
If InStr(1, linea, fecha) <> 0 Then
42+
archivo.Close
43+
Set archivo = Nothing
44+
Set fso = Nothing
45+
EsLaborable = False
46+
Exit Function
47+
End If
48+
49+
Loop
50+
archivo.Close
51+
Set archivo = Nothing
52+
Set fso = Nothing
53+
54+
EsLaborable = True
55+
End Function
56+
Public Function getTiempo(m_EsNoche, EsLaborableDia)
57+
Dim minutosLaborablesDia
58+
Dim minutosNoLaborablesDia
59+
Dim minutosLaborablesNoche
60+
Dim minutosNoLaborablesNoche
61+
Dim minutos
62+
Dim segundos
63+
64+
minutosNoLaborablesDia = 60
65+
minutosLaborablesDia = 5
66+
minutosLaborablesNoche = 60
67+
minutosNoLaborablesNoche = 120
68+
69+
If EsLaborableDia Then
70+
If m_EsNoche Then
71+
minutos = minutosLaborablesNoche
72+
Else
73+
minutos = minutosLaborablesDia
74+
End If
75+
Else
76+
If m_EsNoche Then
77+
minutos = minutosNoLaborablesNoche
78+
Else
79+
minutos = minutosNoLaborablesDia
80+
End If
81+
End If
82+
segundos = minutos * 60
83+
getTiempo = minutos & "|" & segundos * 1000
84+
End Function
85+
86+
Public Function main()
87+
Dim objShell
88+
Dim minutos
89+
Dim flag
90+
Dim Tiempo
91+
Dim dato
92+
Dim DiaActual
93+
Dim DiaRealizacionTareas
94+
Dim TiempotareaNC
95+
Dim TiempoTareaBrass
96+
Dim TiempoTareaTareas
97+
Dim TiempoTareaRiesgos
98+
Dim EsLaborableHoy
99+
Dim TareasHechas
100+
Dim fso, scriptDir
101+
102+
TiempotareaNC = 30000
103+
TiempoTareaBrass = 30000
104+
TiempoTareaTareas = 120000
105+
TiempoTareaRiesgos = 60000
106+
Set objShell = WScript.CreateObject("WScript.Shell")
107+
DiaRealizacionTareas = Date
108+
109+
EsLaborableHoy = EsLaborable(Date)
110+
TareasHechas = False
111+
Set fso = CreateObject("Scripting.FileSystemObject")
112+
scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)
113+
Do While True
114+
objShell.CurrentDirectory =scriptDir
115+
DiaActual = Date
116+
117+
' Verificar si la fecha ha cambiado para recalcular EsLaborableHoy
118+
If DiaActual <> DiaRealizacionTareas Then
119+
EsLaborableHoy = EsLaborable(DiaActual) ' Recalcular solo si el día ha cambiado
120+
TareasHechas = False
121+
End If
122+
123+
flag = getTiempo(EsNoche(), EsLaborableHoy)
124+
dato = Split(flag, "|")
125+
minutos = dato(0)
126+
Tiempo = dato(1)
127+
128+
If EsLaborableHoy And TareasHechas = False Then
129+
If Hour(Now()) > 6 Then
130+
debug.WriteLine "1/7 Iniciando NoConformidades " & Now()
131+
objShell.Run "NoConformidades.vbs"
132+
WScript.Sleep TiempotareaNC
133+
134+
debug.WriteLine "2/7 Iniciando GestionRiesgos_bat " & Now()
135+
objShell.Run "GestionRiesgos.vbs"
136+
WScript.Sleep TiempoTareaRiesgos
137+
138+
debug.WriteLine "3/7 Iniciando BRASS.vbs " & Now()
139+
objShell.Run "BRASS.vbs"
140+
WScript.Sleep TiempoTareaBrass
141+
debug.WriteLine "4/7 Iniciando Expedientes.vbs " & Now()
142+
objShell.Run "Expedientes.vbs"
143+
WScript.Sleep TiempoTareaBrass
144+
debug.WriteLine "5/7 Iniciando AGEDYS " & Now()
145+
objShell.Run "AGEDYS.VBS"
146+
WScript.Sleep TiempoTareaTareas
147+
debug.WriteLine "Pasa Por tareas diarias"
148+
DiaRealizacionTareas = Date ' Actualizar la fecha de última ejecución de las tareas diarias
149+
TareasHechas=true
150+
End If
151+
Else
152+
debug.WriteLine "No Pasa Por tareas diarias"
153+
End If
154+
155+
debug.WriteLine "6/7 Iniciando Correos de Tareas " & Now()
156+
objShell.Run "EnviarCorreoTareas.vbs"
157+
debug.WriteLine "7/7 Iniciando Correos Resto " & Now()
158+
objShell.Run "EnviarCorreoNoEnviado.vbs"
159+
debug.WriteLine "Próximo ciclo en " & minutos & " minutos" & " " & DateAdd("n", minutos, Now())
160+
debug.WriteLine "--------------------------------------------------------------------------"
161+
WScript.Sleep Tiempo
162+
Loop
163+
End Function
164+
main

scripts/run_brass.py

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99

1010
# Añadir el directorio raíz del proyecto al path para importaciones
1111
project_root = Path(__file__).parent.parent
12-
sys.path.insert(0, str(project_root))
12+
src_dir = project_root / 'src'
13+
if str(project_root) not in sys.path:
14+
sys.path.insert(0, str(project_root))
15+
if str(src_dir) not in sys.path:
16+
sys.path.insert(0, str(src_dir))
1317

1418
from src.common.config import config
1519
from src.common.utils import setup_logging
16-
from src.brass.brass_manager import BrassManager
20+
from src.brass.brass_task import BrassTask
1721

1822
def main():
1923
"""Función principal"""
@@ -26,33 +30,31 @@ def main():
2630
args = parser.parse_args()
2731

2832
# Configurar logging
29-
setup_logging(config.log_level, config.log_file)
33+
# setup_logging espera primero el archivo y luego el nivel
34+
setup_logging(log_file=config.log_file, level=config.log_level)
3035

3136
try:
32-
# Crear instancia del gestor BRASS
33-
brass_manager = BrassManager()
34-
35-
if args.dry_run:
36-
print("Modo simulación - verificando si la tarea debe ejecutarse...")
37-
should_run = brass_manager.should_execute_task()
38-
print(f"¿Debe ejecutarse la tarea BRASS? {'Sí' if should_run else 'No'}")
39-
return 0
40-
41-
if args.force:
42-
print("Modo forzado - ejecutando tarea BRASS...")
43-
success = brass_manager.ejecutar_logica_especifica()
37+
with BrassTask() as task:
38+
if args.dry_run:
39+
print("Modo simulación - verificando si la tarea debe ejecutarse...")
40+
should_run = task.debe_ejecutarse()
41+
print(f"¿Debe ejecutarse la tarea BRASS? {'Sí' if should_run else 'No'}")
42+
return 0
43+
44+
if args.force:
45+
print("Modo forzado - ejecutando tarea BRASS...")
46+
success = task.execute_specific_logic()
47+
if success:
48+
task.marcar_como_completada()
49+
else:
50+
success = task.run()
51+
4452
if success:
45-
brass_manager.marcar_como_completada()
46-
else:
47-
# Ejecución normal con verificación de horarios
48-
success = brass_manager.run()
49-
50-
if success:
51-
print("Tarea BRASS ejecutada exitosamente")
52-
return 0
53-
else:
54-
print("Error en la ejecución de la tarea BRASS")
55-
return 1
53+
print("Tarea BRASS ejecutada exitosamente")
54+
return 0
55+
else:
56+
print("Error en la ejecución de la tarea BRASS")
57+
return 1
5658

5759
except Exception as e:
5860
print(f"Error crítico: {e}")

0 commit comments

Comments
 (0)