package controllers import ( "io" "log" "math" "strconv" "text/template" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "github.com/sujit-baniya/fiber-boilerplate/pkg/services" ) // ─── Plantillas de documento (cotización, contrato, acta, cuenta de cobro) ──── func PlantillasDocumentoView(c *fiber.Ctx) error { return c.Render("automatizacion/plantillas_documento", fiber.Map{ "user": c.Locals("user"), "modules": c.Locals("userModules"), }, "layouts/main") } func TarifasView(c *fiber.Ctx) error { return c.Render("automatizacion/tarifas", fiber.Map{ "user": c.Locals("user"), "modules": c.Locals("userModules"), }, "layouts/main") } func GetPlantillasDocumento(c *fiber.Ctx) error { page, _ := strconv.Atoi(c.Query("page", "1")) limit, _ := strconv.Atoi(c.Query("limit", "20")) if page < 1 { page = 1 } offset := (page - 1) * limit records, total, err := models.GetAllPlantillasDocumento(limit, offset, c.Query("search"), c.Query("tipo")) if err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{ "registros": records, "total": total, "totalPages": int(math.Ceil(float64(total) / float64(limit))), "page": page, }) } func GetPlantillaDocumento(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"}) } item, err := models.GetPlantillaDocumentoByID(uint(id)) if err != nil { return c.Status(404).JSON(fiber.Map{"error": "No encontrada"}) } return c.JSON(item) } func CreatePlantillaDocumento(c *fiber.Ctx) error { var m models.PlantillaDocumento if err := c.BodyParser(&m); err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } if m.ContenidoHTML != "" { if _, err := template.New("validate").Parse(m.ContenidoHTML); err != nil { return c.Status(400).JSON(fiber.Map{"error": "Sintaxis inválida en el HTML: " + err.Error()}) } } if err := models.CreatePlantillaDocumento(m); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.Status(201).JSON(fiber.Map{"message": "Plantilla creada", "ok": true}) } func UpdatePlantillaDocumento(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"}) } var updates map[string]interface{} if err := c.BodyParser(&updates); err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } if html, ok := updates["contenido_html"].(string); ok && html != "" { if _, err := template.New("validate").Parse(html); err != nil { return c.Status(400).JSON(fiber.Map{"error": "Sintaxis inválida en el HTML: " + err.Error()}) } } if err := models.UpdatePlantillaDocumento(uint(id), updates); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"message": "Actualizado", "ok": true}) } func DeletePlantillaDocumento(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"}) } if err := models.DeletePlantillaDocumento(uint(id)); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"message": "Eliminado", "ok": true}) } // ImportarPlantillaDocumento recibe un documento (docx, html, txt o imagen), lo // lee y le pide a la IA que lo devuelva como plantilla HTML con las variables // del generador. NO guarda nada: el HTML vuelve al editor para revisarlo. // POST /app/api/plantillas-documento/importar (multipart: archivo, tipo) func ImportarPlantillaDocumento(c *fiber.Ctx) error { archivo, err := c.FormFile("archivo") if err != nil { return c.Status(400).JSON(fiber.Map{"error": "Subí un archivo en el campo 'archivo'"}) } if archivo.Size > 10<<20 { return c.Status(400).JSON(fiber.Map{"error": "El archivo supera los 10 MB"}) } f, err := archivo.Open() if err != nil { return c.Status(400).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(400).JSON(fiber.Map{"error": "No se pudo leer el archivo"}) } texto, err := services.ExtraerTextoDePlantilla(archivo.Filename, datos) if err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } tipo := c.FormValue("tipo", "cotizacion") html, err := services.ConvertirEnPlantilla(tipo, texto) if err != nil { // Al log también: el mensaje del proveedor es lo único que dice por qué // falló, y desde el navegador se ve recortado. log.Printf("[PLANTILLAS] importar (%s, %s): %v", tipo, archivo.Filename, err) return c.Status(422).JSON(fiber.Map{"error": err.Error()}) } // Si la IA devolvió algo que no compila, es mejor decirlo acá que al guardar. if _, err := template.New("validate").Parse(html); err != nil { return c.Status(200).JSON(fiber.Map{ "contenido_html": html, "aviso": "La IA devolvió HTML con sintaxis de plantilla inválida (" + err.Error() + "). Revisalo antes de guardar.", }) } return c.JSON(fiber.Map{"contenido_html": html}) } // PrevisualizarPlantillaDocumento renderiza la plantilla con datos de ejemplo // para ver cómo queda antes de guardarla. // POST /app/api/plantillas-documento/previsualizar {tipo, contenido_html} func PrevisualizarPlantillaDocumento(c *fiber.Ctx) error { var req struct { Tipo string `json:"tipo"` ContenidoHTML string `json:"contenido_html"` } if err := c.BodyParser(&req); err != nil { return c.Status(400).JSON(fiber.Map{"error": "body inválido"}) } html, err := services.RenderizarPlantillaEjemplo(req.Tipo, req.ContenidoHTML) if err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"html": html}) } // ─── Tarifas ──────────────────────────────────────────────────────────────── func GetTarifas(c *fiber.Ctx) error { page, _ := strconv.Atoi(c.Query("page", "1")) limit, _ := strconv.Atoi(c.Query("limit", "50")) if page < 1 { page = 1 } offset := (page - 1) * limit records, total, err := models.GetAllTarifas(limit, offset, c.Query("search"), c.Query("categoria")) if err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{ "registros": records, "total": total, "totalPages": int(math.Ceil(float64(total) / float64(limit))), "page": page, }) } func CreateTarifa(c *fiber.Ctx) error { var m models.Tarifa if err := c.BodyParser(&m); err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } if err := models.CreateTarifa(m); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.Status(201).JSON(fiber.Map{"message": "Tarifa creada", "ok": true}) } func UpdateTarifa(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"}) } var updates map[string]interface{} if err := c.BodyParser(&updates); err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) } if err := models.UpdateTarifa(uint(id), updates); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"message": "Actualizado", "ok": true}) } func DeleteTarifa(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"}) } if err := models.DeleteTarifa(uint(id)); err != nil { return c.Status(500).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"message": "Eliminado", "ok": true}) }