feat(api-v2): endpoint de generación de texto para la integración de VCard

El sistema de vCard necesitaba usar la IA configurada en /app/ai-config y el
único camino era /api/v1/ia/ollama-generate, que autentica con cookie de
sesión: incómodo para una llamada entre servidores y la razón de los 401.

Ahora hay POST /api/v2/vcard/ia, bajo el mismo scope y la misma API key
(Bearer + IP) que ya usa esa integración para los QR y los VCF. Manda prompt,
recibe texto.

La clave del proveedor no sale del servidor. La alternativa —devolverle la
configuración con la api_key adentro, como hace el Landing Generator— reparte
la credencial de OpenAI/Anthropic a cada aplicación que la quiera usar.

El módulo va fijo en "ia": si lo eligiera quien llama, una llave con scope
vcard podría usar la configuración del agente o la del Landing Generator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-17 20:44:35 -05:00
co-authored by Claude Opus 5
parent 5afbd9e375
commit 4135850e5b
4 changed files with 45 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
// GenerarTextoIA corre una generación con la configuración de IA que el staff
// eligió en /app/ai-config para el módulo "ia" (el que la pantalla llama
// "IA / vCard").
//
// La clave del proveedor no sale de acá: la integración manda su prompt con su
// propia API key de /api/v2 y recibe el texto. Es la diferencia con devolverle
// la configuración —incluida la clave de OpenAI o Anthropic— a otra aplicación.
//
// POST /api/v2/vcard/ia {"prompt": "...", "sistema": "..."}
func GenerarTextoIA(c *fiber.Ctx) error {
var body struct {
Prompt string `json:"prompt"`
Sistema string `json:"sistema"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
}
if body.Prompt == "" {
return c.Status(400).JSON(fiber.Map{"error": "prompt requerido"})
}
if body.Sistema == "" {
body.Sistema = "Respondé de forma clara y breve."
}
// El módulo va fijo: si lo eligiera quien llama, una llave con scope vcard
// podría usar la configuración del agente o la del Landing Generator.
texto, err := services.CompletarTextoIA("ia", body.Sistema, body.Prompt)
if err != nil {
return c.Status(502).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"texto": texto})
}
@@ -441,6 +441,7 @@ func AdminApiSpec(c *fiber.Ctx) error {
{"method": "POST", "path": "/api/v2/vcard/qr", "desc": "QR de contacto — devuelve imagen image/webp, NO JSON"},
{"method": "POST", "path": "/api/v2/vcard/qr-url", "desc": "QR de una URL → {\"url\": \"...\"}"},
{"method": "POST", "path": "/api/v2/vcard/vcf", "desc": "Generar archivo .vcf → {\"success\": true, \"url\": \"...\"}"},
{"method": "POST", "path": "/api/v2/vcard/ia", "desc": "Generar texto con la IA configurada en /app/ai-config (módulo 'ia') → {\"texto\": \"...\"}"},
{"method": "POST", "path": "/api/v2/vcard/dlocal/planes", "desc": "Crear plan de suscripción"},
{"method": "GET", "path": "/api/v2/vcard/dlocal/planes", "desc": "Listar planes"},
{"method": "GET", "path": "/api/v2/vcard/dlocal/planes/:planID", "desc": "Ver un plan"},
+4
View File
@@ -394,6 +394,10 @@ func AdminApiRoutes(api fiber.Router) {
vcard.Post("/qr-url", apiControllers.CreateUrlQr)
vcard.Post("/vcf", apiControllers.CreateVcf)
// Generación de texto con la configuración de IA del panel. La integración
// manda el prompt y recibe el texto: la clave del proveedor no sale de acá.
vcard.Post("/ia", apiControllers.GenerarTextoIA)
// dLocal y Rapyd bajo el mismo scope: son las operaciones de cobro que
// acompañan a la VCard, y quien integra una necesita la otra.
vcard.Post("/dlocal/planes", apiControllers.CreatePlan)
+1
View File
@@ -26,6 +26,7 @@ func TestRutasVcardRegistradas(t *testing.T) {
"POST /v2/vcard/qr",
"POST /v2/vcard/qr-url",
"POST /v2/vcard/vcf",
"POST /v2/vcard/ia",
"POST /v2/vcard/dlocal/planes",
"GET /v2/vcard/dlocal/planes",
"GET /v2/vcard/dlocal/planes/:planID",