diff --git a/pkg/models/portal_user.go b/pkg/models/portal_user.go index f9cd35b..0d38766 100644 --- a/pkg/models/portal_user.go +++ b/pkg/models/portal_user.go @@ -28,6 +28,7 @@ type PortalUser struct { Telefono string `json:"telefono" gorm:"column:telefono"` Indicativo string `json:"indicativo" gorm:"column:indicativo"` // ej: +57, +1 Pais string `json:"pais" gorm:"column:pais"` + Ciudad string `json:"ciudad" gorm:"column:ciudad"` Documento string `json:"documento" gorm:"column:documento"` // RUT, NIT, CC, etc. Empresa string `json:"empresa" gorm:"column:empresa"` // empresa que representa SitioWeb string `json:"sitio_web" gorm:"column:sitio_web"` // sitio web del partner/cliente @@ -93,6 +94,7 @@ func UpdatePortalUser(u *PortalUser) error { "telefono": u.Telefono, "indicativo": u.Indicativo, "pais": u.Pais, + "ciudad": u.Ciudad, "documento": u.Documento, "empresa": u.Empresa, "sitio_web": u.SitioWeb, diff --git a/resources/views/layouts/portal.html b/resources/views/layouts/portal.html index ec7baed..b4baa7e 100644 --- a/resources/views/layouts/portal.html +++ b/resources/views/layouts/portal.html @@ -210,6 +210,12 @@ class="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#8eb02f] focus:ring-2 focus:ring-[#8eb02f]/20"> +
Sin documentos disponibles.
+ + + + +Se usa para enviar notificaciones al usuario vía Telegram.
Sitio web del partner o cliente.
Ciudad
+ +Sitio Web
@@ -353,7 +384,7 @@ function portalUsuariosApp() { editId: null, deleteId: null, error: '', accesoUserId: null, accesoClienteId: '', showCredModal: false, credUserId: null, credNombre: '', credEmail: '', credPassword: '', credSending: false, credMsg: '', credOk: false, - form: { nombre:'', email:'', password:'', rol:'cliente', cliente_id:'', activo:true, notas:'', telegram_chat_id:'', sitio_web:'' }, + form: { nombre:'', email:'', password:'', rol:'cliente', cliente_id:'', activo:true, notas:'', telegram_chat_id:'', sitio_web:'', telefono:'', indicativo:'', pais:'', ciudad:'' }, async init() { await this.load(); }, @@ -382,12 +413,12 @@ function portalUsuariosApp() { openCreate() { this.editId=null; this.error=''; - this.form={nombre:'',email:'',password:'',rol:'cliente',cliente_id:'',activo:true,notas:'',telegram_chat_id:'',sitio_web:''}; + this.form={nombre:'',email:'',password:'',rol:'cliente',cliente_id:'',activo:true,notas:'',telegram_chat_id:'',sitio_web:'',telefono:'',indicativo:'',pais:'',ciudad:''}; this.showModal=true; }, openEdit(u) { this.editId=u.ID; this.error=''; - this.form={nombre:u.nombre,email:u.email,password:'',rol:u.rol,cliente_id:u.cliente_id||'',activo:u.activo,notas:u.notas||'',telegram_chat_id:u.telegram_chat_id||'',sitio_web:u.sitio_web||''}; + this.form={nombre:u.nombre,email:u.email,password:'',rol:u.rol,cliente_id:u.cliente_id||'',activo:u.activo,notas:u.notas||'',telegram_chat_id:u.telegram_chat_id||'',sitio_web:u.sitio_web||'',telefono:u.telefono||'',indicativo:u.indicativo||'',pais:u.pais||'',ciudad:u.ciudad||''}; this.showModal=true; }, async save() { diff --git a/rest/controllers/portal_controller.go b/rest/controllers/portal_controller.go index 4308b1e..ebfd04c 100644 --- a/rest/controllers/portal_controller.go +++ b/rest/controllers/portal_controller.go @@ -177,12 +177,14 @@ func PortalGetProyectoData(c *fiber.Ctx) error { tickets, _ := models.GetTicketsByPortalUser(u.ID) facturas, _ := models.GetFacturasByCliente(proy.ClienteID, true) facturasNoLeidas := models.CountUnreadFacturaNotifs(u.ID) + documentos, _ := models.GetDocumentosByProyecto(proy.ID) return c.JSON(fiber.Map{ "proyecto": proy, "fases": fases, "avances": avances, "entregables": entregables, + "documentos": documentos, "tickets": tickets, "facturas": facturas, "facturasNoLeidas": facturasNoLeidas, @@ -352,6 +354,46 @@ func PortalDownloadFactura(c *fiber.Ctx) error { return c.SendFile(clean) } +// PortalDownloadDocumento descarga un documento del proyecto (contrato, orden de servicio, etc.). +func PortalDownloadDocumento(c *fiber.Ctx) error { + u := middlewares.PortalUserFromLocals(c) + if u == nil { + return c.Status(401).JSON(fiber.Map{"error": "no autenticado"}) + } + docID, _ := c.ParamsInt("id") + doc, err := models.GetProyectoDocumentoByID(uint(docID)) + if err != nil || doc.Archivo == "" { + return c.Status(404).JSON(fiber.Map{"error": "No encontrado"}) + } + // Verificar que el proyecto pertenece al cliente del portal user + proy, err := models.GetProyectoByID(doc.ProyectoID) + if err != nil { + return c.Status(404).JSON(fiber.Map{"error": "Proyecto no encontrado"}) + } + fullUser, _ := models.GetPortalUserByID(u.ID) + clienteIDs := models.GetClienteIDsForPortalUser(fullUser) + hasAccess := false + for _, cid := range clienteIDs { + if cid == proy.ClienteID { + hasAccess = true + break + } + } + if !hasAccess { + return c.Status(403).JSON(fiber.Map{"error": "Sin acceso"}) + } + clean := doc.Archivo + if !strings.HasPrefix(clean, "uploads/") { + return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"}) + } + nombre := doc.OriginalName + if nombre == "" { + nombre = doc.Nombre + } + c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, nombre)) + return c.SendFile(clean) +} + // ─── Notificaciones del portal user ────────────────────────────────────────── func PortalGetMisNotifs(c *fiber.Ctx) error { @@ -429,6 +471,7 @@ func PortalGetMiPerfil(c *fiber.Ctx) error { "telefono": full.Telefono, "indicativo": full.Indicativo, "pais": full.Pais, + "ciudad": full.Ciudad, "documento": full.Documento, "empresa": full.Empresa, "sitio_web": full.SitioWeb, @@ -449,6 +492,7 @@ func PortalUpdateMiPerfil(c *fiber.Ctx) error { Telefono string `json:"telefono"` Indicativo string `json:"indicativo"` Pais string `json:"pais"` + Ciudad string `json:"ciudad"` Documento string `json:"documento"` Empresa string `json:"empresa"` SitioWeb string `json:"sitio_web"` @@ -479,6 +523,7 @@ func PortalUpdateMiPerfil(c *fiber.Ctx) error { full.Telefono = req.Telefono full.Indicativo = req.Indicativo full.Pais = req.Pais + full.Ciudad = req.Ciudad full.Documento = req.Documento full.Empresa = req.Empresa full.SitioWeb = req.SitioWeb diff --git a/rest/controllers/portal_usuario_controller.go b/rest/controllers/portal_usuario_controller.go index 6f06f5d..36602b2 100644 --- a/rest/controllers/portal_usuario_controller.go +++ b/rest/controllers/portal_usuario_controller.go @@ -52,6 +52,10 @@ func CreatePortalUsuario(c *fiber.Ctx) error { Notas string `json:"notas"` TelegramChatID string `json:"telegram_chat_id"` SitioWeb string `json:"sitio_web"` + Telefono string `json:"telefono"` + Indicativo string `json:"indicativo"` + Pais string `json:"pais"` + Ciudad string `json:"ciudad"` } var req Req if err := c.BodyParser(&req); err != nil { @@ -88,6 +92,10 @@ func CreatePortalUsuario(c *fiber.Ctx) error { Notas: req.Notas, TelegramChatID: req.TelegramChatID, SitioWeb: req.SitioWeb, + Telefono: req.Telefono, + Indicativo: req.Indicativo, + Pais: req.Pais, + Ciudad: req.Ciudad, } if err := models.CreatePortalUser(u); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) @@ -112,6 +120,10 @@ func UpdatePortalUsuario(c *fiber.Ctx) error { Notas string `json:"notas"` TelegramChatID string `json:"telegram_chat_id"` SitioWeb string `json:"sitio_web"` + Telefono string `json:"telefono"` + Indicativo string `json:"indicativo"` + Pais string `json:"pais"` + Ciudad string `json:"ciudad"` } var req Req if err := c.BodyParser(&req); err != nil { @@ -137,6 +149,10 @@ func UpdatePortalUsuario(c *fiber.Ctx) error { Notas: req.Notas, TelegramChatID: req.TelegramChatID, SitioWeb: req.SitioWeb, + Telefono: req.Telefono, + Indicativo: req.Indicativo, + Pais: req.Pais, + Ciudad: req.Ciudad, } u.ID = uint(id) if err := models.UpdatePortalUser(u); err != nil { diff --git a/rest/routes/portal.go b/rest/routes/portal.go index ac8fd1f..75ce866 100644 --- a/rest/routes/portal.go +++ b/rest/routes/portal.go @@ -30,6 +30,7 @@ func PortalRoutes(app fiber.Router) { // Descargas portal.Get("/entregables/:id/download", controllers.PortalDownloadEntregable) portal.Get("/facturas/:id/download", controllers.PortalDownloadFactura) + portal.Get("/documentos/:id/download", controllers.PortalDownloadDocumento) // Notificaciones in-app del portal user portal.Get("/mis-notifs", controllers.PortalGetMisNotifs)