From f5eef70e0972baaea0c781a5d1dc197378ad47c9 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 16 May 2026 15:17:49 -0500 Subject: [PATCH] up --- pkg/services/notif_dispatch.go | 73 ++++++++++++++++++++++++-- resources/views/layouts/portal.html | 75 +++++++++++++++++++++++++-- rest/controllers/portal_controller.go | 39 ++++++++++++++ rest/routes/portal.go | 5 ++ 4 files changed, 184 insertions(+), 8 deletions(-) diff --git a/pkg/services/notif_dispatch.go b/pkg/services/notif_dispatch.go index 933ed6b..29c75b0 100644 --- a/pkg/services/notif_dispatch.go +++ b/pkg/services/notif_dispatch.go @@ -40,11 +40,37 @@ func DispatchTicketRespuestaAdmin(ticket *models.ProyectoTicket, contenido strin log.Printf("[Notif] PortalUser %d no encontrado: %v", ticket.PortalUserID, err) return } - if portalUser.Email == "" { + + cfg := models.GetNotifConfig("ticket_respuesta_admin", "portal_user") + if cfg == nil { return } - // TODO: enviar email al portal user cuando mail_service implemente SendTicketRespuestaPortalUser - _ = fmt.Sprintf("/portal/dashboard") + + portalURL := "/portal/dashboard" + 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: portalURL, + Icono: "πŸ’¬", + }) + } + if cfg.CanalEmail && portalUser.Email != "" { + go SendTicketRespuestaPortalUser(portalUser.Email, portalUser.Nombre, proyectoNombre, ticket.Titulo, contenido, portalURL) + } + if cfg.CanalTelegram && portalUser.TelegramChatID != "" { + ts := NewTelegramService() + msg := fmt.Sprintf("πŸ’¬ El equipo respondiΓ³ tu ticket\nProyecto: %s\nTicket: %s\n\n%s", + proyectoNombre, ticket.Titulo, contenido) + if err := ts.SendMessageWithToken(portalUser.TelegramChatID, msg, getAdminBotToken()); err != nil { + log.Printf("[Notif] Error telegram portal_user %d: %v", portalUser.ID, err) + } + } } // ─── DispatchFacturaSubida ──────────────────────────────────────────────────── @@ -53,12 +79,49 @@ 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 } - log.Printf("[Notif] DispatchFacturaSubida factura=%d portal_users=%d (pendiente de implementar)", - factura.ID, len(portalUsers)) + + clienteNombre := factura.Cliente.Empresa + if clienteNombre == "" { + clienteNombre = factura.Cliente.Nombre + } + + for _, u := range portalUsers { + u := u + portalURL := "/portal/dashboard" + 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: portalURL, + 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 != "" { + ts := NewTelegramService() + msg := fmt.Sprintf("🧾 Nueva factura disponible\nNΓΊmero: %s\nMonto: %s %.2f", + factura.Numero, factura.Moneda, factura.Monto) + if err := ts.SendMessageWithToken(u.TelegramChatID, msg, getAdminBotToken()); 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 ───────────────────────────────────────────────────────────────── diff --git a/resources/views/layouts/portal.html b/resources/views/layouts/portal.html index 7a6e2db..ca0a426 100644 --- a/resources/views/layouts/portal.html +++ b/resources/views/layouts/portal.html @@ -20,15 +20,84 @@ -
+
U Portal de Clientes - -
+ +
+ +
+ + +
+
+ Notificaciones + +
+
+ + +
+
+
+ diff --git a/rest/controllers/portal_controller.go b/rest/controllers/portal_controller.go index 82251f7..7457847 100644 --- a/rest/controllers/portal_controller.go +++ b/rest/controllers/portal_controller.go @@ -324,3 +324,42 @@ func PortalDownloadFactura(c *fiber.Ctx) error { c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, f.OriginalName)) return c.SendFile(clean) } + +// ─── Notificaciones del portal user ────────────────────────────────────────── + +func PortalGetMisNotifs(c *fiber.Ctx) error { + u := middlewares.PortalUserFromLocals(c) + if u == nil { + return c.Status(401).JSON(fiber.Map{"error": "no autenticado"}) + } + soloNoLeidas := c.Query("no_leidas") == "1" + items, err := models.GetSistemaNotifs("portal_user", u.ID, soloNoLeidas) + if err != nil { + return c.Status(500).JSON(fiber.Map{"error": err.Error()}) + } + count := models.CountUnreadNotifs("portal_user", u.ID) + return c.JSON(fiber.Map{"items": items, "unread": count}) +} + +func PortalMarcarNotifLeida(c *fiber.Ctx) error { + u := middlewares.PortalUserFromLocals(c) + if u == nil { + return c.Status(401).JSON(fiber.Map{"error": "no autenticado"}) + } + id, _ := c.ParamsInt("id") + if err := models.MarcarNotifLeida(uint(id)); err != nil { + return c.Status(500).JSON(fiber.Map{"error": err.Error()}) + } + return c.JSON(fiber.Map{"ok": true}) +} + +func PortalMarcarTodasLeidas(c *fiber.Ctx) error { + u := middlewares.PortalUserFromLocals(c) + if u == nil { + return c.Status(401).JSON(fiber.Map{"error": "no autenticado"}) + } + if err := models.MarcarTodasLeidas("portal_user", u.ID); err != nil { + return c.Status(500).JSON(fiber.Map{"error": err.Error()}) + } + return c.JSON(fiber.Map{"ok": true}) +} diff --git a/rest/routes/portal.go b/rest/routes/portal.go index 81829dc..4eb3732 100644 --- a/rest/routes/portal.go +++ b/rest/routes/portal.go @@ -29,4 +29,9 @@ func PortalRoutes(app fiber.Router) { // Descargas portal.Get("/entregables/:id/download", controllers.PortalDownloadEntregable) portal.Get("/facturas/:id/download", controllers.PortalDownloadFactura) + + // Notificaciones in-app del portal user + portal.Get("/mis-notifs", controllers.PortalGetMisNotifs) + portal.Put("/mis-notifs/:id/leida", controllers.PortalMarcarNotifLeida) + portal.Post("/mis-notifs/marcar-todas", controllers.PortalMarcarTodasLeidas) }