This commit is contained in:
Lizandro Guarnizo
2026-05-16 13:44:32 -05:00
parent 85595e639e
commit 76a0c28b16
16 changed files with 225 additions and 725 deletions
-5
View File
@@ -11,7 +11,6 @@ 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 {
@@ -186,10 +185,6 @@ 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})
}
+2 -52
View File
@@ -2,23 +2,19 @@ package controllers
import (
"fmt"
"log"
"net/url"
"strings"
"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 {
// Usar PortalUser (validación completa) en vez de IsPortalLoggedIn (solo key)
// para evitar loops cuando hay sesión obsoleta pero el usuario ya no existe en DB.
if user, err := auth.PortalUser(c); err == nil && user != nil {
if auth.IsPortalLoggedIn(c) {
return c.Redirect("/portal/dashboard")
}
return c.Render("portal/login", fiber.Map{
@@ -31,14 +27,11 @@ func PortalLoginPost(c *fiber.Ctx) error {
password := c.FormValue("password")
u, err := models.CheckPortalLogin(email, password)
if err != nil {
log.Printf("[PORTAL LOGIN] Fallo para %q: %v", email, err)
return c.Redirect("/portal/login?error=" + url.QueryEscape(err.Error()))
}
if err := auth.SetPortalSession(c, u.ID); err != nil {
log.Printf("[PORTAL LOGIN] Error guardando sesión para usuario %d: %v", u.ID, err)
return c.Redirect("/portal/login?error=Error+interno")
}
log.Printf("[PORTAL LOGIN] OK usuario %d (%s)", u.ID, email)
return c.Redirect("/portal/dashboard")
}
@@ -52,14 +45,12 @@ func PortalLogout(c *fiber.Ctx) error {
func PortalDashboard(c *fiber.Ctx) error {
u := middlewares.PortalUserFromLocals(c)
if u == nil {
log.Println("[PORTAL DASHBOARD] Usuario no en locals, redirigiendo a login")
return c.Redirect("/portal/login")
}
// Recargar con accesos
fullUser, err := models.GetPortalUserByID(u.ID)
if err != nil {
log.Printf("[PORTAL DASHBOARD] Error cargando usuario %d: %v", u.ID, err)
return c.Redirect("/portal/login")
}
@@ -87,7 +78,7 @@ func PortalDashboard(c *fiber.Ctx) error {
"portalUser": fullUser,
"proyectos": proyectos,
"grupos": grupos,
"isPartner": fullUser.Rol == "partner" || (fullUser.Role != nil && fullUser.Role.EsPortalPartner),
"isPartner": fullUser.Rol == "partner",
}, "layouts/portal")
}
@@ -229,7 +220,6 @@ 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)
}
@@ -264,7 +254,6 @@ func PortalResponderTicket(c *fiber.Ctx) error {
if err := models.CreateTicketMensaje(msg); err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
go services.DispatchTicketRespuestaCliente(ticket, req.Contenido, ticket.Proyecto.Nombre)
return c.Status(201).JSON(msg)
}
@@ -335,42 +324,3 @@ func PortalDownloadFactura(c *fiber.Ctx) error {
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, f.OriginalName))
return c.SendFile(clean)
}
// ─── Notificaciones in-app del portal user ────────────────────────────────────
func PortalGetNotifs(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})
}
+12 -60
View File
@@ -8,7 +8,6 @@ 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 {
@@ -30,14 +29,12 @@ 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,
"portalRoles": portalRoles,
"items": items,
"total": total,
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
"page": page,
"clientes": clientes,
})
}
@@ -47,7 +44,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"`
}
var req Req
@@ -61,23 +58,15 @@ func CreatePortalUsuario(c *fiber.Ctx) error {
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": "Error al hashear contraseña"})
}
// Derivar Rol del tipo de rol seleccionado
rol := "cliente"
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"
}
}
if req.Rol == "" {
req.Rol = "cliente"
}
u := &models.PortalUser{
Nombre: req.Nombre,
Email: strings.ToLower(strings.TrimSpace(req.Email)),
Password: hashed,
ClienteID: req.ClienteID,
RoleID: req.RoleID,
Rol: rol,
Rol: req.Rol,
Activo: true,
Notas: req.Notas,
}
@@ -98,7 +87,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"`
}
@@ -106,22 +95,11 @@ 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 := "cliente"
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,
RoleID: req.RoleID,
Rol: rol,
Rol: req.Rol,
Activo: req.Activo,
Notas: req.Notas,
}
@@ -131,7 +109,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)
}
@@ -173,29 +151,3 @@ 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})
}
+5 -31
View File
@@ -12,7 +12,6 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
// ─── Admin pages ──────────────────────────────────────────────────────────────
@@ -67,7 +66,7 @@ func LoadProyectos(c *fiber.Ctx) error {
func CreateProyecto(c *fiber.Ctx) error {
type Req struct {
ClienteID string `json:"cliente_id"`
ClienteID uint `json:"cliente_id"`
Nombre string `json:"nombre"`
Slug string `json:"slug"`
Descripcion string `json:"descripcion"`
@@ -78,8 +77,7 @@ func CreateProyecto(c *fiber.Ctx) error {
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
clienteID, _ := strconv.ParseUint(req.ClienteID, 10, 64)
if clienteID == 0 || strings.TrimSpace(req.Nombre) == "" {
if req.ClienteID == 0 || strings.TrimSpace(req.Nombre) == "" {
return c.Status(400).JSON(fiber.Map{"error": "cliente y nombre son requeridos"})
}
if req.Slug == "" {
@@ -92,7 +90,7 @@ func CreateProyecto(c *fiber.Ctx) error {
req.Estado = "activo"
}
p := &models.Proyecto{
ClienteID: uint(clienteID),
ClienteID: req.ClienteID,
Nombre: req.Nombre,
Slug: req.Slug,
Descripcion: req.Descripcion,
@@ -111,7 +109,7 @@ func UpdateProyecto(c *fiber.Ctx) error {
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
}
type Req struct {
ClienteID string `json:"cliente_id"`
ClienteID uint `json:"cliente_id"`
Nombre string `json:"nombre"`
Slug string `json:"slug"`
Descripcion string `json:"descripcion"`
@@ -123,9 +121,8 @@ func UpdateProyecto(c *fiber.Ctx) error {
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
updateClienteID, _ := strconv.ParseUint(req.ClienteID, 10, 64)
p := &models.Proyecto{
ClienteID: uint(updateClienteID),
ClienteID: req.ClienteID,
Nombre: req.Nombre,
Slug: req.Slug,
Descripcion: req.Descripcion,
@@ -459,11 +456,6 @@ func AdminResponderTicket(c *fiber.Ctx) error {
}
// Cambiar estado a en_progreso si estaba abierto
_ = models.UpdateTicketEstado(uint(ticketID), "en_progreso")
// Notificar al portal user
ticket, _ := models.GetTicketByID(uint(ticketID))
if ticket != nil {
go services.DispatchTicketRespuestaAdmin(ticket, req.Contenido, ticket.Proyecto.Nombre)
}
return c.Status(201).JSON(msg)
}
@@ -506,21 +498,3 @@ func mimeFromHeader(fh *multipart.FileHeader) string {
}
return ct
}
// ─── Tickets global (admin) ───────────────────────────────────────────────────
func TicketsAdminIndex(c *fiber.Ctx) error {
return c.Render("tickets_admin", fiber.Map{
"user": c.Locals("user"),
"modules": c.Locals("userModules"),
}, "layouts/main")
}
func GetAllTicketsAdmin(c *fiber.Ctx) error {
estado := c.Query("estado", "todos")
items, err := models.GetAllTickets(estado)
if err != nil {
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(items)
}