This commit is contained in:
Lizandro Guarnizo
2026-05-16 15:26:48 -05:00
parent f5eef70e09
commit 1b22cc2fe7
9 changed files with 118 additions and 21 deletions
+5
View File
@@ -11,6 +11,7 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
func FacturasIndex(c *fiber.Ctx) error {
@@ -185,6 +186,10 @@ func UploadFacturaPDF(c *fiber.Ctx) error {
if err := models.UpdateFacturaArchivo(uint(id), savePath, file.Filename); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
// Notificar al cliente
if f, err := models.GetFacturaByID(uint(id)); err == nil {
go services.DispatchFacturaSubida(f)
}
return c.JSON(fiber.Map{"ok": true, "archivo": savePath})
}
+10 -1
View File
@@ -8,13 +8,14 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
"github.com/sujit-baniya/fiber-boilerplate/rest/middlewares"
)
// ─── Auth ─────────────────────────────────────────────────────────────────────
func PortalLoginPage(c *fiber.Ctx) error {
if auth.IsPortalLoggedIn(c) {
if user, err := auth.PortalUser(c); err == nil && user != nil {
return c.Redirect("/portal/dashboard")
}
return c.Render("portal/login", fiber.Map{
@@ -220,6 +221,7 @@ func PortalCrearTicket(c *fiber.Ctx) error {
if err := models.CreateProyectoTicket(t); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
go services.DispatchTicketNuevo(t, u, proy.Nombre)
return c.Status(201).JSON(t)
}
@@ -254,6 +256,13 @@ func PortalResponderTicket(c *fiber.Ctx) error {
if err := models.CreateTicketMensaje(msg); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
go func() {
proyNombre := ""
if proy, err := models.GetProyectoByID(ticket.ProyectoID); err == nil {
proyNombre = proy.Nombre
}
services.DispatchTicketRespuestaCliente(ticket, req.Contenido, proyNombre)
}()
return c.Status(201).JSON(msg)
}
+61 -8
View File
@@ -8,6 +8,7 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
func PortalUsuariosIndex(c *fiber.Ctx) error {
@@ -29,12 +30,14 @@ func LoadPortalUsuarios(c *fiber.Ctx) error {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
clientes, _, _ := models.GetAllClientes(200, 0, "")
portalRoles, _ := models.GetPortalRoles()
return c.JSON(fiber.Map{
"items": items,
"total": total,
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
"page": page,
"clientes": clientes,
"items": items,
"total": total,
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
"page": page,
"clientes": clientes,
"portalRoles": portalRoles,
})
}
@@ -44,6 +47,7 @@ func CreatePortalUsuario(c *fiber.Ctx) error {
Email string `json:"email"`
Password string `json:"password"`
ClienteID *uint `json:"cliente_id"`
RoleID *uint `json:"role_id"`
Rol string `json:"rol"`
Notas string `json:"notas"`
TelegramChatID string `json:"telegram_chat_id"`
@@ -62,12 +66,23 @@ func CreatePortalUsuario(c *fiber.Ctx) error {
if req.Rol == "" {
req.Rol = "cliente"
}
// Derivar Rol del tipo de rol seleccionado
rol := req.Rol
if req.RoleID != nil {
var role models.Roles
if err := app.Http.Database.DB.First(&role, *req.RoleID).Error; err == nil {
if role.EsPortalPartner {
rol = "partner"
}
}
}
u := &models.PortalUser{
Nombre: req.Nombre,
Email: strings.ToLower(strings.TrimSpace(req.Email)),
Password: hashed,
ClienteID: req.ClienteID,
Rol: req.Rol,
RoleID: req.RoleID,
Rol: rol,
Activo: true,
Notas: req.Notas,
TelegramChatID: req.TelegramChatID,
@@ -89,6 +104,7 @@ func UpdatePortalUsuario(c *fiber.Ctx) error {
Email string `json:"email"`
Password string `json:"password"`
ClienteID *uint `json:"cliente_id"`
RoleID *uint `json:"role_id"`
Rol string `json:"rol"`
Activo bool `json:"activo"`
Notas string `json:"notas"`
@@ -98,11 +114,22 @@ func UpdatePortalUsuario(c *fiber.Ctx) error {
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
// Derivar Rol del tipo de rol seleccionado
rol := req.Rol
if req.RoleID != nil {
var role models.Roles
if err := app.Http.Database.DB.First(&role, *req.RoleID).Error; err == nil {
if role.EsPortalPartner {
rol = "partner"
}
}
}
u := &models.PortalUser{
Nombre: req.Nombre,
Email: strings.ToLower(strings.TrimSpace(req.Email)),
ClienteID: req.ClienteID,
Rol: req.Rol,
RoleID: req.RoleID,
Rol: rol,
Activo: req.Activo,
Notas: req.Notas,
TelegramChatID: req.TelegramChatID,
@@ -113,7 +140,7 @@ func UpdatePortalUsuario(c *fiber.Ctx) error {
}
// Actualizar password solo si se envió
if strings.TrimSpace(req.Password) != "" {
hashed, err := app.Http.Hash.Create(req.Password)
hashed, err := app.Http.Hash.Create(req.Password)
if err == nil {
_ = models.UpdatePortalUserPassword(uint(id), hashed)
}
@@ -155,3 +182,29 @@ func RemovePortalAcceso(c *fiber.Ctx) error {
}
return c.JSON(fiber.Map{"ok": true})
}
// SendPortalCredentials envía las credenciales de acceso al portal al correo del usuario.
func SendPortalCredentials(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
if err != nil {
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
}
u, err := models.GetPortalUserByID(uint(id))
if err != nil {
return c.Status(404).JSON(fiber.Map{"error": "Usuario no encontrado"})
}
type Req struct {
Password string `json:"password"`
}
var req Req
_ = c.BodyParser(&req)
password := req.Password
if password == "" {
password = "(la contraseña que configuraste)"
}
services.SendPortalCredentialsEmail(u.Email, u.Nombre, password)
return c.JSON(fiber.Map{"ok": true, "message": "Correo enviado a " + u.Email})
}
+3 -1
View File
@@ -7,11 +7,13 @@ import (
)
// PortalAuth protege las rutas del portal de clientes.
// Si no hay sesión activa, redirige a /portal/login.
// Si no hay sesión válida, destruye la sesión obsoleta y redirige a /portal/login.
func PortalAuth() fiber.Handler {
return func(c *fiber.Ctx) error {
user, err := auth.PortalUser(c)
if err != nil || user == nil {
// Limpiar sesión inválida/obsoleta para romper posibles redirect loops
_ = auth.DestroyPortalSession(c)
return c.Redirect("/portal/login")
}
c.Locals("portalUser", user)
+1
View File
@@ -254,6 +254,7 @@ func UserRoutes(app fiber.Router) {
protected.Delete("/portal-usuarios/:id", controllers.DeletePortalUsuario)
protected.Post("/portal-usuarios/:id/acceso", controllers.AddPortalAcceso)
protected.Delete("/portal-usuarios/:id/acceso/:clienteID", controllers.RemovePortalAcceso)
protected.Post("/portal-usuarios/:id/send-credentials", controllers.SendPortalCredentials)
protected.Get("/facturas", middlewares.MenuMiddleware, controllers.FacturasIndex)
protected.Get("/loadfacturas", controllers.LoadFacturas)