Files
soft_usite/rest/routes/portal.go
T
Lizandro GuarnizoandClaude Sonnet 5 62c644b815 feat(portal): descarga del cronograma del proyecto en Excel
El cliente ya veía el roadmap en pantalla pero no tenía cómo pasárselo a
alguien que no entra al portal.

El .xlsx se genera con archive/zip + encoding/xml de la stdlib en vez de
sumar una dependencia de Excel para una sola pantalla. Un CSV renombrado
no servía: en Excel en español el separador y los acentos se rompen, y
esto es un archivo que el cliente le entrega a un tercero.

Detalles que hacen que Excel lo acepte y se lea bien: inline strings (sin
tabla sharedStrings, así ningún índice puede apuntar a la cadena
equivocada), encabezado en negrita y congelado, anchos de columna según
el contenido, y nombre de hoja saneado (Excel rechaza el archivo si pasa
de 31 caracteres o trae : \ / ? * [ ]).

El endpoint repite el chequeo de acceso de PortalProyecto: sin eso,
cualquiera con sesión de portal se bajaba el cronograma de otro cliente
adivinando el slug.

Verificado abriendo el archivo generado con openpyxl: 6 partes, CRC OK,
XML bien formado en todas, acentos y CJK intactos, negrita y panel
congelado donde corresponde.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 22:12:45 -05:00

75 lines
3.9 KiB
Go

package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/rest/controllers"
"github.com/sujit-baniya/fiber-boilerplate/rest/middlewares"
)
func PortalRoutes(app fiber.Router) {
// ─── Rutas públicas ────────────────────────────────────────────────────────
app.Get("/portal/login", controllers.PortalLoginPage)
app.Post("/portal/login", controllers.PortalLoginPost)
app.Get("/portal/logout", controllers.PortalLogout)
app.Get("/portal/forgot-password", controllers.PortalForgotPasswordPage)
app.Post("/portal/forgot-password", controllers.PortalForgotPasswordPost)
app.Get("/portal/reset-password", controllers.PortalResetPasswordPage)
app.Post("/portal/reset-password", controllers.PortalResetPasswordPost)
// ─── Rutas protegidas ──────────────────────────────────────────────────────
portal := app.Group("/portal").Use(middlewares.PortalAuth())
portal.Get("/dashboard", controllers.PortalDashboard)
portal.Get("/proyecto/:slug", controllers.PortalProyecto)
portal.Get("/proyecto/:slug/cronograma.xlsx", controllers.PortalDescargarCronograma)
// API JSON para Alpine.js
portal.Get("/api/proyectos", controllers.PortalGetProyectos)
portal.Get("/api/proyecto/:slug", controllers.PortalGetProyectoData)
// Tickets
portal.Post("/tickets", controllers.PortalCrearTicket)
portal.Post("/tickets/:id/mensaje", controllers.PortalResponderTicket)
portal.Post("/tickets/:id/leido", controllers.PortalMarcarTicketLeido)
// 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)
portal.Put("/mis-notifs/:id/leida", controllers.PortalMarcarNotifLeida)
portal.Post("/mis-notifs/marcar-todas", controllers.PortalMarcarTodasLeidas)
portal.Post("/mis-notifs/marcar-facturas", controllers.PortalMarcarFacturasLeidas)
// Partner: recursos y comunicados (solo usuarios con rol partner)
portal.Get("/partner-recursos", middlewares.PortalPartnerOnly(), controllers.PortalPartnerRecursos)
portal.Get("/partner-recursos/:id/download", middlewares.PortalPartnerOnly(), controllers.PortalDownloadPartnerRecurso)
portal.Get("/partner-comunicados/:id/download", middlewares.PortalPartnerOnly(), controllers.DownloadPartnerComunicadoArchivo)
// Mi perfil
portal.Get("/mi-perfil", controllers.PortalGetMiPerfil)
portal.Put("/mi-perfil", controllers.PortalUpdateMiPerfil)
portal.Put("/mi-perfil/password", controllers.PortalCambiarPassword)
portal.Post("/mi-perfil/rut-file", controllers.PortalSubirRutDocumento)
// Telegram: vinculación guiada
portal.Post("/mi-perfil/telegram-init", controllers.PortalTelegramInit)
portal.Get("/mi-perfil/telegram-status", controllers.PortalTelegramStatus)
portal.Post("/mi-perfil/telegram-validar", controllers.PortalTelegramValidar)
// ─── uMind Studio para el cliente ──────────────────────────────────────────
// Los MISMOS handlers que usa el staff, pero con UmindScopePortal: el
// cliente solo alcanza los tenants de sus Cliente. Sin middleware de
// escritura extra — administrar lo suyo es justamente el punto.
RegistrarRutasUmind(portal, middlewares.UmindScopePortal, nil)
// El SPA (mismo build que /orchestrator) con fallback para vue-router.
studioFallback := func(c *fiber.Ctx) error {
return c.SendFile("./public/orchestrator/index.html")
}
portal.Get("/studio", studioFallback)
portal.Get("/studio/*", studioFallback)
}