package controllers import ( "io" "strconv" "strings" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "github.com/sujit-baniya/fiber-boilerplate/pkg/services" ) // Las plantillas del cliente: sus cotizaciones y contratos con su membrete. // Reusan el mismo motor e importador con IA que las del staff — lo único que // cambia es que llevan tenant_id y no se ven entre clientes. // GetUmindPlantillasHandler — GET /umind/plantillas?tenant_id=N func GetUmindPlantillasHandler(c *fiber.Ctx) error { tenantID, _ := strconv.ParseUint(c.Query("tenant_id"), 10, 64) if err := accesoTenant(c, uint(tenantID)); err != nil { return err } items, err := models.GetPlantillasDeTenant(uint(tenantID)) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"items": items}) } // ImportarUmindPlantillaHandler convierte un Word/PDF del cliente en plantilla // con IA. No guarda: devuelve el HTML para que lo revise antes. // POST /umind/plantillas/importar (multipart: tenant_id, tipo, archivo) func ImportarUmindPlantillaHandler(c *fiber.Ctx) error { tenantID, _ := strconv.ParseUint(c.FormValue("tenant_id"), 10, 64) if err := accesoTenant(c, uint(tenantID)); err != nil { return err } tipo := strings.TrimSpace(c.FormValue("tipo")) if !tipoPlantillaValido(tipo) { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tipo inválido"}) } fh, err := c.FormFile("archivo") if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "subí el documento a convertir"}) } if fh.Size > 10<<20 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "el archivo supera los 10 MB"}) } f, err := fh.Open() if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "no se pudo leer el archivo"}) } defer f.Close() datos, err := io.ReadAll(io.LimitReader(f, 10<<20)) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "no se pudo leer el archivo"}) } texto, err := services.ExtraerTextoDePlantilla(fh.Filename, datos) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } html, err := services.ConvertirEnPlantilla(tipo, texto) if err != nil { return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"contenido_html": html}) } // CreateUmindPlantillaHandler guarda la plantilla del espacio. // POST /umind/plantillas func CreateUmindPlantillaHandler(c *fiber.Ctx) error { var req struct { TenantID uint `json:"tenant_id"` Tipo string `json:"tipo"` Nombre string `json:"nombre"` ContenidoHTML string `json:"contenido_html"` } if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"}) } if err := accesoTenant(c, req.TenantID); err != nil { return err } if !tipoPlantillaValido(req.Tipo) { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tipo inválido"}) } if strings.TrimSpace(req.ContenidoHTML) == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "la plantilla está vacía"}) } // Se valida que compile antes de guardar: una plantilla rota descubierta // al generar deja al cliente esperando un PDF que nunca llega. if err := services.ValidarPlantillaHTML(req.ContenidoHTML); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } tid := req.TenantID nombre := strings.TrimSpace(req.Nombre) if nombre == "" { nombre = req.Tipo } p := models.PlantillaDocumento{ Tipo: req.Tipo, Nombre: nombre, ContenidoHTML: req.ContenidoHTML, Version: models.SiguienteVersionPlantilla(tid, req.Tipo), Activa: true, TenantID: &tid, } if err := models.CreatePlantillaDocumento(p); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.Status(fiber.StatusCreated).JSON(fiber.Map{"ok": true}) } // DeleteUmindPlantillaHandler — DELETE /umind/plantillas/:id func DeleteUmindPlantillaHandler(c *fiber.Ctx) error { id, err := strconv.ParseUint(c.Params("id"), 10, 64) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "id inválido"}) } p, err := models.GetPlantillaDocumentoByID(uint(id)) if err != nil { return errSinAcceso(c) } // Una plantilla global del staff no se borra desde acá ni por error. if p.TenantID == nil { return errSinAcceso(c) } if err := accesoTenant(c, *p.TenantID); err != nil { return err } if err := models.DeletePlantillaDocumento(uint(id)); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"ok": true}) } func tipoPlantillaValido(t string) bool { switch t { case "cotizacion", "contrato", "acta", "cuenta_cobro": return true } return false }