feat(plantillas): subir el documento y que la IA lo devuelva como plantilla editable

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>
This commit is contained in:
Lizandro Guarnizo
2026-08-17 18:39:11 -05:00
co-authored by Claude Opus 5
parent 8c3ab79e88
commit e681f41639
6 changed files with 410 additions and 0 deletions
@@ -1,12 +1,14 @@
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) ────
@@ -103,6 +105,48 @@ func DeletePlantillaDocumento(c *fiber.Ctx) 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 {