This commit is contained in:
Lizandro Guarnizo
2026-05-12 18:34:20 -05:00
parent ee31ac978f
commit 387485e65b
5 changed files with 212 additions and 76 deletions
+100 -61
View File
@@ -13,15 +13,20 @@ var cronScheduler *cron.Cron
func IniciarCron() {
cronScheduler = cron.New()
// Ejecutar todos los días a las 8:00 AM
_, err := cronScheduler.AddFunc("0 8 * * *", ProcesarVencimientos)
if err != nil {
log.Printf("[CRON] Error registrando tarea: %v", err)
// Vencimientos próximos — todos los días a las 8:00 AM
if _, err := cronScheduler.AddFunc("0 8 * * *", ProcesarVencimientosProximos); err != nil {
log.Printf("[CRON] Error registrando tarea vencimientos_proximo: %v", err)
return
}
// Contratos ya vencidos — todos los días a las 9:00 AM
if _, err := cronScheduler.AddFunc("0 9 * * *", ProcesarYaVencidos); err != nil {
log.Printf("[CRON] Error registrando tarea ya_vencido: %v", err)
return
}
cronScheduler.Start()
log.Println("[CRON] Scheduler iniciado — verificando vencimientos diariamente a las 8:00 AM")
log.Println("[CRON] Scheduler iniciado — vencimientos próximos 8AM, ya vencidos 9AM")
}
// DetenerCron para graceful shutdown
@@ -31,13 +36,19 @@ func DetenerCron() {
}
}
// ProcesarVencimientos es la función principal del cron
// ProcesarVencimientos mantiene compatibilidad para llamadas manuales
func ProcesarVencimientos() {
log.Println("[CRON] Iniciando procesamiento de vencimientos...")
ProcesarVencimientosProximos()
ProcesarYaVencidos()
}
reglas, err := models.GetReglasActivas()
// ProcesarVencimientosProximos procesa reglas de tipo "vencimiento_proximo"
func ProcesarVencimientosProximos() {
log.Println("[CRON] Procesando vencimientos próximos...")
reglas, err := models.GetReglasByTipoEvento("vencimiento_proximo")
if err != nil {
log.Printf("[CRON] Error obteniendo reglas: %v", err)
log.Printf("[CRON] Error obteniendo reglas vencimiento_proximo: %v", err)
return
}
@@ -47,58 +58,86 @@ func ProcesarVencimientos() {
log.Printf("[CRON] Error obteniendo contratos para regla %d: %v", regla.ID, err)
continue
}
if len(contratos) == 0 {
continue
}
// Filtrar por AplicaA
var filtrados []models.Contrato
for _, c := range contratos {
switch regla.AplicaA {
case "renovable":
for _, s := range c.Servicios {
if s.Tipo == "renovable" {
filtrados = append(filtrados, c)
break
}
}
case "unico":
for _, s := range c.Servicios {
if s.Tipo == "unico" {
filtrados = append(filtrados, c)
break
}
}
default:
filtrados = append(filtrados, c)
}
}
if len(filtrados) == 0 {
continue
}
// Agrupar contratos por cliente
porCliente := make(map[uint][]models.Contrato)
for _, c := range filtrados {
porCliente[c.ClienteID] = append(porCliente[c.ClienteID], c)
}
for clienteID, grupoContratos := range porCliente {
// Evitar duplicados: ya enviado hoy para esta regla + cliente
if models.YaEnviadoHoy(clienteID, regla.ID) {
log.Printf("[CRON] Ya enviado hoy a cliente %d para regla %d — saltando", clienteID, regla.ID)
continue
}
cliente := &grupoContratos[0].Cliente
if err := EnviarNotificacionGrupo(&regla, cliente, grupoContratos); err != nil {
log.Printf("[CRON] Error enviando a cliente %d: %v", clienteID, err)
} else {
log.Printf("[CRON] Enviado a cliente %d (%s) — %d contrato(s)",
clienteID, cliente.Email, len(grupoContratos))
}
}
procesarContratos(&regla, contratos)
}
log.Println("[CRON] Procesamiento de vencimientos finalizado")
log.Println("[CRON] Vencimientos próximos finalizado")
}
// ProcesarYaVencidos procesa reglas de tipo "ya_vencido"
func ProcesarYaVencidos() {
log.Println("[CRON] Procesando contratos ya vencidos...")
reglas, err := models.GetReglasByTipoEvento("ya_vencido")
if err != nil {
log.Printf("[CRON] Error obteniendo reglas ya_vencido: %v", err)
return
}
if len(reglas) == 0 {
return
}
contratos, err := models.GetContratosYaVencidos()
if err != nil {
log.Printf("[CRON] Error obteniendo contratos vencidos: %v", err)
return
}
for _, regla := range reglas {
procesarContratos(&regla, contratos)
}
log.Println("[CRON] Ya vencidos finalizado")
}
// procesarContratos aplica filtros y envía notificaciones para una regla y lista de contratos
func procesarContratos(regla *models.NotificacionRegla, contratos []models.Contrato) {
if len(contratos) == 0 {
return
}
// Filtrar por AplicaA
var filtrados []models.Contrato
for _, c := range contratos {
switch regla.AplicaA {
case "renovable":
for _, s := range c.Servicios {
if s.Tipo == "renovable" {
filtrados = append(filtrados, c)
break
}
}
case "unico":
for _, s := range c.Servicios {
if s.Tipo == "unico" {
filtrados = append(filtrados, c)
break
}
}
default:
filtrados = append(filtrados, c)
}
}
if len(filtrados) == 0 {
return
}
// Agrupar por cliente
porCliente := make(map[uint][]models.Contrato)
for _, c := range filtrados {
porCliente[c.ClienteID] = append(porCliente[c.ClienteID], c)
}
for clienteID, grupoContratos := range porCliente {
if models.YaEnviadoHoy(clienteID, regla.ID) {
log.Printf("[CRON] Ya enviado hoy a cliente %d para regla %d — saltando", clienteID, regla.ID)
continue
}
cliente := &grupoContratos[0].Cliente
if err := EnviarNotificacionGrupo(regla, cliente, grupoContratos); err != nil {
log.Printf("[CRON] Error enviando a cliente %d: %v", clienteID, err)
} else {
log.Printf("[CRON] Enviado a cliente %d (%s) — %d contrato(s)", clienteID, cliente.Email, len(grupoContratos))
}
}
}
+32 -3
View File
@@ -80,11 +80,13 @@ func EnviarCorreoPrueba(email string, p *models.PlantillaCorreo) error {
func EnviarCorreoManual(contrato *models.Contrato) error {
// Intentar obtener plantilla y pasarela desde la primera regla activa
var p *models.PlantillaCorreo
var reglaID uint
gateway := "bold"
reglas, err := models.GetReglasActivas()
if err == nil && len(reglas) > 0 {
regla := reglas[0]
reglaID = regla.ID
gateway = regla.PasarelaEnlace
p, err = models.GetPlantillaByID(regla.PlantillaID)
if err != nil {
@@ -107,7 +109,7 @@ func EnviarCorreoManual(contrato *models.Contrato) error {
// Generar o reutilizar el enlace de pago
enlacePago := ObtenerOCrearEnlacePago(contrato, gateway)
dias := int(contrato.FechaVencimiento.Sub(time.Now()).Hours() / 24)
dias := int(time.Until(contrato.FechaVencimiento).Hours() / 24)
var items []ItemServicio
for _, s := range contrato.Servicios {
items = append(items, ItemServicio{
@@ -131,7 +133,34 @@ func EnviarCorreoManual(contrato *models.Contrato) error {
if err != nil {
return err
}
return app.Http.Mail.Send(contrato.Cliente.Email, p.Asunto, html)
// Crear log previo (pendiente)
idsJSON, _ := json.Marshal([]uint{contrato.ID})
logEntry := models.NotificacionLog{
ClienteID: contrato.ClienteID,
ReglaID: reglaID,
ContratosIDs: string(idsJSON),
FechaEnvio: time.Now(),
Estado: "pendiente",
Asunto: p.Asunto,
PreviewHTML: html,
}
savedLog, _ := models.CreateNotificacionLog(logEntry)
sendErr := app.Http.Mail.Send(contrato.Cliente.Email, p.Asunto, html)
// Actualizar estado del log
if savedLog != nil {
if sendErr != nil {
savedLog.Estado = "fallido"
savedLog.ErrorMsg = sendErr.Error()
} else {
savedLog.Estado = "enviado"
}
models.UpdateNotificacionLog(*savedLog)
}
return sendErr
}
// EnviarNotificacionGrupo envía un correo agrupado para un cliente con múltiples contratos
@@ -160,7 +189,7 @@ func EnviarNotificacionGrupo(regla *models.NotificacionRegla, cliente *models.Cl
}
}
dias := int(fechaVenc.Sub(time.Now()).Hours() / 24)
dias := int(time.Until(fechaVenc).Hours() / 24)
// Generar o reutilizar el enlace de pago para el primer contrato del grupo
enlacePago := ""