Escribí una segunda implementación de "llamar al proveedor" en vez de usar la que ya existe (callAI, la del bot de Telegram). Esa copia no hablaba Anthropic —se lo mandaba todo al endpoint de OpenAI— y, si la config tenía la URL base vacía (lo normal: la vista dice que es opcional), armaba una URL relativa que ni siquiera es una petición válida. Cualquiera de las dos cosas terminaba en el 502. Ahora usa callAI, que despacha por proveedor y completa la URL base. El error del proveedor queda en el log del servidor y el mensaje llega entero a la vista; si la respuesta no trae mensaje, la vista muestra el status para distinguir un fallo de la app de uno del proxy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
211 lines
7.1 KiB
Go
211 lines
7.1 KiB
Go
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})
|
|
}
|
|
|
|
// ─── 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})
|
|
}
|