package services import ( "log" "github.com/robfig/cron/v3" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) var cronScheduler *cron.Cron // IniciarCron arranca el scheduler de tareas. Llamar desde app.go o main.go. func IniciarCron() { cronScheduler = cron.New() // 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 — vencimientos próximos 8AM, ya vencidos 9AM") } // DetenerCron para graceful shutdown func DetenerCron() { if cronScheduler != nil { cronScheduler.Stop() } } // ProcesarVencimientos mantiene compatibilidad para llamadas manuales func ProcesarVencimientos() { ProcesarVencimientosProximos() ProcesarYaVencidos() } // 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 vencimiento_proximo: %v", err) return } for _, regla := range reglas { contratos, err := models.GetContratosProximosVencer(regla.DiasAntes) if err != nil { log.Printf("[CRON] Error obteniendo contratos para regla %d: %v", regla.ID, err) continue } procesarContratos(®la, contratos) } 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(®la, 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)) } } }