Skip to content

Commit 8d4dbd0

Browse files
merge: unify correos into email_services
2 parents 7cdf766 + 2843cf6 commit 8d4dbd0

47 files changed

Lines changed: 502 additions & 3019 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ El **script maestro (`run_master.py`)** es el corazón del sistema y reemplaza a
2828
5. **Riesgos** (`run_riesgos.py`): Gestión de riesgos empresariales
2929

3030
#### Tareas Continuas (ejecutadas en cada ciclo):
31-
6. **Correos** (`run_correos.py`): Sistema de envío de correos
32-
7. **Correo Tareas** (`run_correo_tareas.py`): Sistema de gestión de correos que interactúa con la base de datos de tareas
31+
6. **Email Services** (`run_email_services.py`): Servicio unificado de envío de correos (fusiona antiguos módulos `correos` y `correo_tareas`)
3332

3433
### 🆕 Cambios Arquitectónicos Recientes (Refactor 2025)
3534

@@ -105,10 +104,10 @@ Ejemplo de runner minimalista:
105104
```python
106105
import sys
107106
from common.utils import execute_task_with_standard_boilerplate
108-
from correos.correos_task import CorreosTask
107+
from email_services.email_task import EmailServicesTask
109108

110109
def main():
111-
task = CorreosTask()
110+
task = EmailServicesTask()
112111
code = execute_task_with_standard_boilerplate("CORREOS", task_obj=task)
113112
sys.exit(code)
114113

@@ -247,8 +246,7 @@ scripts-python/
247246
│ │ └── add_status_to_tareas_db.py # Migración estado tareas
248247
│ ├── run_agedys.py # Script para módulo AGEDYS
249248
│ ├── run_brass.py # Script principal para módulo BRASS
250-
│ ├── run_correo_tareas.py # Script para módulo correo tareas
251-
│ ├── run_correos.py # Script para módulo correos
249+
│ ├── run_email_services.py # Runner unificado de servicios de correo
252250
│ ├── run_expedientes.py # Script para módulo expedientes
253251
│ ├── run_master.py # Script maestro - daemon principal con modo verbose
254252
│ ├── run_no_conformidades.py # Script para no conformidades
@@ -277,14 +275,10 @@ scripts-python/
277275
│ │ ├── task_registry.py # Registro de tareas (TaskRegistry OO)
278276
│ │ ├── user_adapter.py # Adaptador de usuarios
279277
│ │ └── utils.py # Utilidades HTML, logging, fechas
280-
│ ├── correo_tareas/ # Módulo de gestión de correos que interactúa con la base de datos de tareas
278+
│ ├── email_services/ # Módulo unificado de correos (correos + tareas)
281279
│ │ ├── __init__.py
282-
│ │ ├── correo_tareas_manager.py # Gestor de correos para tareas empresariales
283-
│ │ └── correo_tareas_task.py # Tareas de correo
284-
│ ├── correos/ # Módulo de correos
285-
│ │ ├── __init__.py
286-
│ │ ├── correos_manager.py # Gestor de correos
287-
│ │ └── correos_task.py # Tareas de correos
280+
│ │ ├── email_manager.py # Lógica centralizada de envío
281+
│ │ └── email_task.py # Task continua unificada
288282
│ ├── expedientes/ # Módulo de expedientes
289283
│ │ ├── __init__.py
290284
│ │ ├── expedientes_manager.py # Gestor de expedientes
@@ -316,8 +310,7 @@ scripts-python/
316310
│ │ ├── __init__.py
317311
│ │ ├── agedys/ # Integración del sistema AGEDYS
318312
│ │ ├── brass/ # Integración del sistema brass
319-
│ │ ├── correo_tareas/ # Integración del sistema de correo tareas
320-
│ │ ├── correos/ # Integración del sistema de correos
313+
│ │ ├── email_services/ # Integración del servicio unificado de correos
321314
│ │ ├── database/ # Integración con bases de datos
322315
│ │ ├── expedientes/ # Integración del sistema de expedientes
323316
│ │ ├── no_conformidades/ # Integración no conformidades
@@ -327,7 +320,7 @@ scripts-python/
327320
│ ├── agedys/ # Tests específicos AGEDYS
328321
│ ├── brass/ # Tests específicos BRASS
329322
│ ├── common/ # Tests módulos comunes
330-
│ ├── correos/ # Tests del módulo de correos
323+
│ ├── email_services/ # Tests del servicio unificado de correos
331324
│ ├── expedientes/ # Tests del módulo de expedientes
332325
│ ├── no_conformidades/ # Tests no conformidades
333326
│ └── riesgos/ # Tests del módulo de riesgos
@@ -728,7 +721,7 @@ El sistema soporta dos configuraciones SMTP:
728721
| `src/common/task_registry.py` | 64% ||
729722
| `src/common/notifications.py` | 100% ||
730723
| `src/common/utils.py` | 49% ||
731-
| `src/correos/correos_manager.py` | 91% ||
724+
| `src/email_services/email_manager.py` | 91% ||
732725
| `src/expedientes/expedientes_manager.py` | 98% ||
733726
| `src/riesgos/riesgos_manager.py` | 90% ||
734727

@@ -1081,15 +1074,12 @@ python scripts/run_riesgos.py # Ejecución normal
10811074
python scripts/run_riesgos.py --force # Fuerza ejecución
10821075
python scripts/run_riesgos.py --dry-run # Modo simulación
10831076

1084-
# Correos - Sistema de envío de correos
1085-
python scripts/run_correos.py # Ejecución normal
1086-
python scripts/run_correos.py --force # Fuerza ejecución
1087-
python scripts/run_correos.py --dry-run # Modo simulación
1077+
# Email Services - Servicio unificado de correo (remplaza correos y correo_tareas)
10881078

1089-
# Tareas - Sistema de gestión de tareas
1090-
python scripts/run_correo_tareas.py # Ejecución normal
1091-
python scripts/run_correo_tareas.py --force # Fuerza ejecución
1092-
python scripts/run_correo_tareas.py --dry-run # Modo simulación
1079+
### Manejo de errores transitorios SMTP (Refactor 2025)
1080+
Los errores de conexión SMTP (p.ej. desconexión inesperada, `SMTPConnectError`, `ConnectionRefusedError`) ahora se consideran **transitorios** y no marcan el correo como fallido en la base de datos. El registro permanece pendiente para reintentos en futuros ciclos. Sólo errores definitivos (credenciales inválidas, destinatario rechazado, formato de mensaje inválido) marcan el correo como fallido. Esto incrementa la resiliencia ante caídas puntuales del servidor de correo.
1081+
python scripts/run_email_services.py # Ejecución normal
1082+
python scripts/run_email_services.py --force # (Reservado) Fuerza ejecución
10931083

10941084
# Tests
10951085
python scripts/run_tests.py

scripts/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Este directorio contiene los scripts de producción del sistema de monitoreo con
77
```
88
scripts/
99
├── run_master.py # 🎯 Script maestro - daemon principal del sistema
10-
├── run_correos.py # 📧 Gestión y envío de correos electrónicos
10+
├── run_email_services.py # 📧 Servicios unificados de envío de correos
1111
├── run_brass.py # 🔧 Procesamiento de datos BRASS
1212
├── run_expedientes.py # 📋 Gestión de expedientes y ofertas
1313
├── run_riesgos.py # ⚠️ Análisis y gestión de riesgos
@@ -31,18 +31,18 @@ El **Master Runner** es el corazón del sistema de monitoreo continuo. Ejecuta d
3131
- **Configuración flexible**: Variables de entorno para todos los parámetros
3232

3333
### Scripts Gestionados:
34-
1. **correos**`run_correos.py` (continuo)
34+
1. **email_services**`run_email_services.py` (continuo unificado)
3535
2. **riesgos**`run_riesgos.py` (diario)
3636
3. **brass**`run_brass.py` (diario)
3737
4. **expedientes**`run_expedientes.py` (diario)
3838
5. **no_conformidades**`run_no_conformidades.py` (diario)
3939

4040
## 📧 Scripts de Producción
4141

42-
### run_correos.py
43-
- **Función**: Gestión y envío de correos electrónicos
42+
### run_email_services.py
43+
- **Función**: Gestión unificada de servicios de correo (reemplaza correos y correo_tareas)
4444
- **Tipo**: Tarea continua (ejecutada en cada ciclo)
45-
- **Descripción**: Procesa colas de correo y envía notificaciones
45+
- **Descripción**: Procesa colas de correo de múltiples orígenes (correos, tareas) y envía notificaciones
4646

4747
### run_brass.py
4848
- **Función**: Procesamiento de datos BRASS
@@ -89,7 +89,7 @@ python run_master.py --dry-run
8989
### Ejecución Individual de Scripts:
9090
```bash
9191
# Ejecutar script específico
92-
python run_correos.py
92+
python run_email_services.py
9393
python run_brass.py
9494
python run_expedientes.py
9595
python run_riesgos.py

scripts/run_correos.py

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
1-
"""Runner estandarizado para notificaciones de tareas (correo_tareas)."""
1+
"""Runner estandarizado para la tarea unificada de servicios de correo (correos + tareas)."""
22
from __future__ import annotations
33

44
import sys
55
import argparse
6-
import logging
76
from pathlib import Path
87

8+
# Ajuste de sys.path para asegurar importaciones desde src/
99
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
1010
_SRC_DIR = _PROJECT_ROOT / "src"
1111
if str(_SRC_DIR) not in sys.path:
1212
sys.path.insert(0, str(_SRC_DIR))
1313
from common.utils import ensure_project_root_in_path # type: ignore
1414
ensure_project_root_in_path()
1515

16-
from correo_tareas.correo_tareas_task import CorreoTareasTask # type: ignore
16+
from email_services.email_task import EmailServicesTask # type: ignore
1717
from common.utils import execute_task_with_standard_boilerplate # type: ignore
1818

1919

2020
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
21-
parser = argparse.ArgumentParser(description="Ejecuta notificaciones de correo de tareas.")
21+
parser = argparse.ArgumentParser(
22+
description="Ejecuta el servicio unificado de envío de correos (correos + tareas)."
23+
)
2224
parser.add_argument(
23-
"--force", action="store_true", help="(Reservado) Fuerza ejecución incluso sin criterios.",
25+
"--force",
26+
action="store_true",
27+
help="(Reservado) Fuerza la ejecución incluso sin criterios adicionales.",
2428
)
2529
return parser.parse_args(argv)
2630

2731

2832
def main(argv: list[str] | None = None): # pragma: no cover
2933
_ = parse_args(argv)
30-
task = CorreoTareasTask()
34+
task = EmailServicesTask()
3135
exit_code = execute_task_with_standard_boilerplate(
32-
"CORREO_TAREAS", task_obj=task
36+
"EMAIL_SERVICES", task_obj=task
3337
)
3438
sys.exit(exit_code)
3539

scripts/run_master.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -674,16 +674,15 @@ def ejecutar_script(self, script_name: str) -> Dict[str, any]:
674674

675675
def ejecutar_tareas_diarias(self) -> Dict[str, any]:
676676
"""Ejecuta las tareas diarias consultando la lógica OO de cada TareaDiaria."""
677-
# Asegurar path
678677
src_path = Path(__file__).parent.parent / 'src'
679678
if str(src_path) not in sys.path:
680679
sys.path.insert(0, str(src_path))
681-
try:
682-
from common.task_registry import TaskRegistry # import diferido
683-
task_instances = TaskRegistry().get_daily_tasks()
684-
except Exception as e: # pragma: no cover - fallback
685-
self.logger_adapter.warning(f"No se pudo importar task_registry: {e}. Saltando tareas diarias.")
686-
return {}
680+
try:
681+
from common.task_registry import TaskRegistry # import diferido
682+
task_instances = TaskRegistry().get_daily_tasks()
683+
except Exception as e: # pragma: no cover - fallback
684+
self.logger_adapter.warning(f"No se pudo importar task_registry: {e}. Saltando tareas diarias.")
685+
return {}
687686
# Mapa filename->key en available_scripts
688687
file_to_key = {v: k for k, v in self.available_scripts.items()}
689688
if self.verbose_mode:

scripts_config.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"scripts": {
3-
"correos": {"file": "run_correos.py", "type": "continuous"},
4-
"tareas": {"file": "run_correo_tareas.py", "type": "continuous"},
3+
"email_services": {"file": "run_email_services.py", "type": "continuous"},
54
"brass": {"file": "run_brass.py", "type": "daily", "task_name": "BRASSDiario"},
65
"expedientes": {"file": "run_expedientes.py", "type": "daily", "task_name": "ExpedientesDiario"},
76
"no_conformidades": {"file": "run_no_conformidades.py", "type": "daily", "task_name": ["NoConformidadesCalidad", "NoConformidadesTecnica"]},

src/common/task_registry.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@
2323
from src.expedientes.expedientes_task import ExpedientesTask # type: ignore
2424
from src.no_conformidades.no_conformidades_task import NoConformidadesTask # type: ignore
2525
from src.agedys.agedys_task import AgedysTask # type: ignore
26-
from src.correos.correos_task import CorreosTask # type: ignore
27-
from src.correo_tareas.correo_tareas_task import CorreoTareasTask # type: ignore
26+
from src.email_services.email_task import EmailServicesTask # type: ignore
2827
except ModuleNotFoundError:
2928
from brass.brass_task import BrassTask # type: ignore
3029
from expedientes.expedientes_task import ExpedientesTask # type: ignore
3130
from no_conformidades.no_conformidades_task import NoConformidadesTask # type: ignore
3231
from agedys.agedys_task import AgedysTask # type: ignore
33-
from correos.correos_task import CorreosTask # type: ignore
34-
from correo_tareas.correo_tareas_task import CorreoTareasTask # type: ignore
32+
from email_services.email_task import EmailServicesTask # type: ignore
3533

3634
# Intento de importación ligera de RiesgosTask; si falla, se crea stub para tests unitarios
3735
try:
@@ -188,25 +186,40 @@ def __init__(self):
188186
)
189187

190188

191-
class CorreosTask(TareaContinua):
192-
"""Tarea continua para envío de correos pendientes"""
193-
194-
def __init__(self):
195-
super().__init__(
196-
name="Correos",
197-
script_filename="run_correos.py"
198-
)
189+
class EmailServicesRegistryTask(TareaContinua):
190+
"""Wrapper de registro para tarea unificada de servicios de correo.
199191
192+
Se mantiene un wrapper mínimo para no acoplar directamente TaskRegistry
193+
a la implementación concreta y facilitar futuras extensiones (p.ej. división).
194+
"""
200195

201-
class CorreoTareasTask(TareaContinua):
202-
"""Tarea continua para notificaciones de tareas"""
203-
204196
def __init__(self):
205197
super().__init__(
206-
name="CorreoTareas",
207-
script_filename="run_correo_tareas.py"
198+
name="EmailServices",
199+
script_filename="run_email_services.py"
208200
)
209201

202+
# Delegación: la lógica real vive en EmailServicesTask ejecutada vía runner.
203+
# Aquí podríamos, en el futuro, instanciar y ejecutar EmailServicesTask directamente
204+
# si migramos completamente al modelo TaskRegistry para continuas.
205+
def execute_specific_logic(self) -> bool: # pragma: no cover - mínima
206+
"""Ejecuta la lógica de email unificado.
207+
208+
Si la variable de entorno EMAIL_SERVICES_REGISTRY_MODE == 'direct', instancia y
209+
ejecuta el EmailServicesTask directamente (útil en modo simple / tests).
210+
En otro caso devuelve True (no-op) dejando que el runner continuo lo gestione.
211+
"""
212+
import os
213+
mode = os.getenv('EMAIL_SERVICES_REGISTRY_MODE', 'delegate').lower()
214+
if mode == 'direct':
215+
try:
216+
from email_services.email_task import EmailServicesTask # import local para evitar ciclos
217+
task = EmailServicesTask()
218+
return task.execute_specific_logic()
219+
except Exception:
220+
return False
221+
return True
222+
210223

211224
class TaskRegistry:
212225
"""Registro encapsulado de tareas.
@@ -235,8 +248,7 @@ def __init__(self,
235248
])
236249
if include_continuous:
237250
self._continuous_tasks.extend([
238-
CorreosTask(),
239-
CorreoTareasTask()
251+
EmailServicesRegistryTask()
240252
])
241253
if extra_daily:
242254
self._daily_tasks.extend(extra_daily)
@@ -289,5 +301,5 @@ def get_all_tasks() -> List: # pragma: no cover - compat
289301
'get_all_tasks',
290302
# Clases de tareas
291303
'RiesgosTask', 'BrassTask', 'ExpedientesTask', 'NoConformidadesTask', 'AgedysTask',
292-
'CorreosTask', 'CorreoTareasTask'
304+
'EmailServicesRegistryTask'
293305
]

src/correo_tareas/__init__.py

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)