Hasta ahora crear una plantilla era escribir HTML con variables de Go a mano. Ahora se sube el documento que ya existe (.docx, .html, .txt o una foto del papel), se lee —docx con stdlib, imágenes por OCR— y la IA lo devuelve armado como plantilla con las variables del generador puestas donde iban los datos. No se guarda solo: el HTML cae en el editor para revisarlo, y si la IA devolvió sintaxis de plantilla inválida se avisa antes de guardar. PDF queda afuera por ahora; el mensaje de error dice qué hacer en su lugar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
207 lines
6.9 KiB
Go
207 lines
6.9 KiB
Go
package controllers
|
|
|
|
import (
|
|
"io"
|
|
"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 {
|
|
return c.Status(502).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})
|
|
}
|
|
|
|
// ─── 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})
|
|
}
|