package services import ( "fmt" "log" "net/url" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // ─── DispatchTicketNuevo ────────────────────────────────────────────────────── // Notifica al admin cuando un portal user crea un ticket. func DispatchTicketNuevo(ticket *models.ProyectoTicket, portalUser *models.PortalUser, proyectoNombre string) { if ticket == nil || portalUser == nil { return } cfg := models.GetNotifConfig("ticket_nuevo", "admin") if cfg == nil { return } ticketPath := adminTicketPath(ticket.ID) ticketURL := absAppURL(ticketPath) titulo := fmt.Sprintf("Nuevo ticket: %s", ticket.Titulo) cuerpo := fmt.Sprintf("Cliente: %s\nProyecto: %s\n%s", ticket.AutorNombre, proyectoNombre, ticket.Descripcion) if cfg.CanalSistema { _ = models.CreateSistemaNotif(&models.SistemaNotificacion{ TipoUsuario: "admin", UsuarioID: 0, Titulo: titulo, Cuerpo: cuerpo, Url: ticketPath, Icono: "🎫", }) } if cfg.CanalEmail { if adminEmail := getAdminEmail(); adminEmail != "" { go SendTicketNuevoAdmin(adminEmail, proyectoNombre, ticket.AutorNombre, ticket.Titulo, ticket.Descripcion, ticketURL) } } if cfg.CanalTelegram { msg := fmt.Sprintf("🎫 Nuevo ticket\nProyecto: %s\nCliente: %s\nTítulo: %s\n\n%s\n\n🔗 %s", escapeTelegramHTML(proyectoNombre), escapeTelegramHTML(ticket.AutorNombre), escapeTelegramHTML(ticket.Titulo), escapeTelegramHTML(ticket.Descripcion), telegramHTMLLink(ticketURL, "Ver ticket")) sendTelegramAdmin(msg) } } // ─── DispatchTicketRespuestaCliente ────────────────────────────────────────── // Notifica al admin cuando el portal user responde un ticket. func DispatchTicketRespuestaCliente(ticket *models.ProyectoTicket, contenido string, proyectoNombre string) { if ticket == nil { return } cfg := models.GetNotifConfig("ticket_respuesta_cliente", "admin") if cfg == nil { return } ticketPath := adminTicketPath(ticket.ID) ticketURL := absAppURL(ticketPath) titulo := fmt.Sprintf("Respuesta de cliente en: %s", ticket.Titulo) cuerpo := fmt.Sprintf("Cliente: %s\nProyecto: %s\n%s", ticket.AutorNombre, proyectoNombre, contenido) if cfg.CanalSistema { _ = models.CreateSistemaNotif(&models.SistemaNotificacion{ TipoUsuario: "admin", UsuarioID: 0, Titulo: titulo, Cuerpo: cuerpo, Url: ticketPath, Icono: "💬", }) } if cfg.CanalEmail { if adminEmail := getAdminEmail(); adminEmail != "" { go SendTicketRespuestaAdmin(adminEmail, proyectoNombre, ticket.AutorNombre, ticket.Titulo, contenido, ticketURL) } } if cfg.CanalTelegram { msg := fmt.Sprintf("💬 Respuesta de cliente\nProyecto: %s\nCliente: %s\n\n%s\n\n🔗 %s", escapeTelegramHTML(proyectoNombre), escapeTelegramHTML(ticket.AutorNombre), escapeTelegramHTML(contenido), telegramHTMLLink(ticketURL, "Ver ticket")) sendTelegramAdmin(msg) } } // ─── DispatchTicketRespuestaAdmin ───────────────────────────────────────────── // Notifica al portal user cuando el admin responde un ticket. func DispatchTicketRespuestaAdmin(ticket *models.ProyectoTicket, contenido string, proyectoNombre string) { if ticket == nil { return } portalUser, err := models.GetPortalUserByID(ticket.PortalUserID) if err != nil || portalUser == nil { log.Printf("[Notif] PortalUser %d no encontrado: %v", ticket.PortalUserID, err) return } cfg := models.GetNotifConfig("ticket_respuesta_admin", "portal_user") if cfg == nil { return } proyectoSlug := "" if proy, err := models.GetProyectoByID(ticket.ProyectoID); err == nil { proyectoSlug = proy.Slug } portalPath := portalTicketPath(proyectoSlug, ticket.ID) portalURL := absAppURL(portalPath) titulo := fmt.Sprintf("Respuesta en tu ticket: %s", ticket.Titulo) cuerpo := fmt.Sprintf("El equipo respondió: %s", contenido) if cfg.CanalSistema { _ = models.CreateSistemaNotif(&models.SistemaNotificacion{ TipoUsuario: "portal_user", UsuarioID: portalUser.ID, Titulo: titulo, Cuerpo: cuerpo, Url: portalPath, Icono: "💬", }) } if cfg.CanalEmail && portalUser.Email != "" { go SendTicketRespuestaPortalUser(portalUser.Email, portalUser.Nombre, proyectoNombre, ticket.Titulo, contenido, portalURL) } if cfg.CanalTelegram && portalUser.TelegramChatID != "" { msg := fmt.Sprintf("💬 El equipo respondió tu ticket\nProyecto: %s\nTicket: %s\n\n%s\n\n🔗 %s", escapeTelegramHTML(proyectoNombre), escapeTelegramHTML(ticket.Titulo), escapeTelegramHTML(contenido), telegramHTMLLink(portalURL, "Ver en el portal")) if err := sendTelegramPortalUser(portalUser.TelegramChatID, msg); err != nil { log.Printf("[Notif] Error telegram portal_user %d: %v", portalUser.ID, err) } } } // ─── DispatchFacturaSubida ──────────────────────────────────────────────────── // Notifica a los portal_users del cliente cuando se sube una factura. func DispatchFacturaSubida(factura *models.Factura) { if factura == nil || factura.ClienteID == 0 { return } cfg := models.GetNotifConfig("factura_subida", "portal_user") if cfg == nil { return } portalUsers, err := models.GetPortalUsersByClienteID(factura.ClienteID) if err != nil || len(portalUsers) == 0 { return } clienteNombre := factura.Cliente.Empresa if clienteNombre == "" { clienteNombre = factura.Cliente.Nombre } portalPath := "/portal/dashboard" if factura.ProyectoID != nil && factura.Proyecto != nil && factura.Proyecto.Slug != "" { portalPath = fmt.Sprintf("/portal/proyecto/%s?tab=Facturas", url.PathEscape(factura.Proyecto.Slug)) } portalURL := absAppURL(portalPath) for _, u := range portalUsers { u := u titulo := fmt.Sprintf("Nueva factura disponible: %s", factura.Numero) cuerpo := fmt.Sprintf("Factura %s por %s %.2f", factura.Numero, factura.Moneda, factura.Monto) if cfg.CanalSistema { _ = models.CreateSistemaNotif(&models.SistemaNotificacion{ TipoUsuario: "portal_user", UsuarioID: u.ID, Titulo: titulo, Cuerpo: cuerpo, Url: portalPath, Icono: "🧾", }) } if cfg.CanalEmail && u.Email != "" { go SendFacturaSubidaPortalUser(u.Email, u.Nombre, clienteNombre, factura.Numero, factura.Monto, factura.Moneda, portalURL) } if cfg.CanalTelegram && u.TelegramChatID != "" { msg := fmt.Sprintf("🧾 Nueva factura disponible\nNúmero: %s\nMonto: %s %.2f\n\n🔗 %s", escapeTelegramHTML(factura.Numero), escapeTelegramHTML(factura.Moneda), factura.Monto, telegramHTMLLink(portalURL, "Ver en el portal")) if err := sendTelegramPortalUser(u.TelegramChatID, msg); err != nil { log.Printf("[Notif] Error telegram portal_user %d: %v", u.ID, err) } } } log.Printf("[Notif] DispatchFacturaSubida factura=%d clientes notificados=%d", factura.ID, len(portalUsers)) } // ─── helpers ───────────────────────────────────────────────────────────────── func getAdminEmail() string { cfg, err := models.GetSmtpConfig() if err != nil || cfg == nil { return "" } return cfg.FromAddress } func getAdminBotToken() string { configs, err := models.GetAllTelegramConfigs() if err != nil { return "" } for _, c := range configs { if c.Activo { return c.BotToken } } return "" } // sendTelegramPortalUser intenta enviar usando todos los bots activos hasta que uno funcione. // Esto evita fallos cuando el usuario se vinculó con un bot distinto al primer bot activo. func sendTelegramPortalUser(chatID string, mensaje string) error { configs, err := models.GetAllTelegramConfigs() if err != nil { return err } var lastErr error for _, cfg := range configs { if !cfg.Activo || cfg.BotToken == "" { continue } ts := &TelegramService{BotToken: cfg.BotToken} if err := ts.SendMessage(chatID, mensaje); err != nil { lastErr = err continue } return nil } if lastErr != nil { return lastErr } return fmt.Errorf("no hay bots de Telegram activos para enviar") } // NotificarReniceAction envía Telegram cuando el agente aplica un renice automático. func NotificarReniceAction(servidorNombre, accion string) { msg := fmt.Sprintf("🔧 Renice automático aplicado\nServidor: %s\n%s\n\nCPU sostenida por encima del umbral.", escapeTelegramHTML(servidorNombre), escapeTelegramHTML(accion)) sendTelegramAdmin(msg) } func sendTelegramAdmin(mensaje string) { configs, err := models.GetAllTelegramConfigs() if err != nil { log.Printf("[Notif] Error obteniendo configs telegram: %v", err) return } for _, cfg := range configs { if !cfg.Activo { continue } ts := &TelegramService{BotToken: cfg.BotToken} sendErr := ts.SendMessage(cfg.ChatID, mensaje) logEntry := &models.TelegramLog{ TelegramConfigID: cfg.ID, Titulo: "Notificación del sistema", Mensaje: mensaje, Estado: "ok", } if sendErr != nil { log.Printf("[Notif] Error enviando telegram admin (cfg %d): %v", cfg.ID, sendErr) logEntry.Estado = "failed" logEntry.ErrorMsg = sendErr.Error() } _ = models.CreateTelegramLog(logEntry) } }