Es la base del cobro por uso: hasta ahora no había ninguna medición de consumo en todo el repo. - UmindUso registra cada evento facturable con el costo YA calculado al precio vigente del plan. Congelarlo evita que subir un precio revalúe consumo pasado, que haría indefendible una factura ante un reclamo. - callAI devuelve los tokens que reportó el proveedor (campo usage, igual en todos los OpenAI-compatibles; input+output en Anthropic). Se mide cada ronda de tool-calling, no solo la última: todas gastan tokens. - ExtraerTextoOCR y TranscribirAudioSelfHosted reciben agenteID; 0 = no medir, que es lo que pasan los botones "Probar" del panel de staff. - Aviso al superar el tope del plan, una vez por mes y sin cortar el servicio. El flag de "ya avisé" es en memoria a propósito. - GET /app/umind/uso con filtros de fecha: resumen por tipo + detalle. - Test del cálculo de costo por tipo, incluida fracción de 1k tokens y tenant sin plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
222 lines
6.2 KiB
Go
222 lines
6.2 KiB
Go
package controllers
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
|
|
)
|
|
|
|
// ─── Vistas ──────────────────────────────────────────────────────────────────
|
|
|
|
func WebSmsConfigPage(c *fiber.Ctx) error {
|
|
cfg, _ := models.GetWebSmsConfig()
|
|
return c.Render("websms_config", fiber.Map{
|
|
"user": c.Locals("user"),
|
|
"modules": c.Locals("userModules"),
|
|
"cfg": cfg,
|
|
}, "layouts/main")
|
|
}
|
|
|
|
// ─── API ─────────────────────────────────────────────────────────────────────
|
|
|
|
func GetWebSmsConfig(c *fiber.Ctx) error {
|
|
cfg, err := models.GetWebSmsConfig()
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{"data": nil})
|
|
}
|
|
return c.JSON(fiber.Map{"data": cfg})
|
|
}
|
|
|
|
func SaveWebSmsConfig(c *fiber.Ctx) error {
|
|
type body struct {
|
|
ID uint `json:"id"`
|
|
Username string `json:"username"`
|
|
ApiToken string `json:"api_token"`
|
|
Sender string `json:"sender"`
|
|
Notas string `json:"notas"`
|
|
}
|
|
var b body
|
|
if err := c.BodyParser(&b); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
|
|
}
|
|
if b.Username == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "username es requerido"})
|
|
}
|
|
if b.ApiToken == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "api_token es requerido"})
|
|
}
|
|
|
|
cfg := models.WebSmsConfig{
|
|
Username: b.Username,
|
|
ApiToken: b.ApiToken,
|
|
Sender: b.Sender,
|
|
Notas: b.Notas,
|
|
}
|
|
cfg.ID = b.ID
|
|
|
|
if err := models.SaveWebSmsConfig(cfg); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"message": "Configuración WebSMS guardada"})
|
|
}
|
|
|
|
func TestWebSms(c *fiber.Ctx) error {
|
|
type body struct {
|
|
Para string `json:"para"`
|
|
Mensaje string `json:"mensaje"`
|
|
}
|
|
var b body
|
|
if err := c.BodyParser(&b); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
|
|
}
|
|
if b.Para == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "número de destino requerido"})
|
|
}
|
|
if b.Mensaje == "" {
|
|
b.Mensaje = "[TEST] Notificación desde U-site vía WebSMS"
|
|
}
|
|
|
|
cfg, err := models.GetWebSmsConfig()
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "WebSMS no configurado"})
|
|
}
|
|
|
|
resp, err := services.SendWebSms(cfg, b.Para, b.Mensaje)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
models.CreateWebSmsLog(&models.WebSmsLog{
|
|
Para: b.Para,
|
|
Mensaje: b.Mensaje,
|
|
Status: resp.Code,
|
|
MsgID: resp.MsgID(),
|
|
})
|
|
|
|
return c.JSON(fiber.Map{"ok": true, "response": resp})
|
|
}
|
|
|
|
func GetWebSmsLogs(c *fiber.Ctx) error {
|
|
logs, err := models.GetWebSmsLogs(50)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(logs)
|
|
}
|
|
|
|
func GetWebSmsWebhookLogs(c *fiber.Ctx) error {
|
|
logs, err := models.GetWebSmsWebhookLogs(50)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(logs)
|
|
}
|
|
|
|
// ─── Webhooks entrantes ─────────────────────────────────────────────────────
|
|
|
|
func WebSmsDeliveryWebhook(c *fiber.Ctx) error {
|
|
var payload services.WebSmsAckPayload
|
|
if err := c.BodyParser(&payload); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
|
|
}
|
|
raw := c.Body()
|
|
models.CreateWebSmsWebhookLog(&models.WebSmsWebhookLog{
|
|
Tipo: "delivery",
|
|
MsgID: payload.ID,
|
|
Para: payload.Msisdn,
|
|
Status: payload.Status,
|
|
Raw: string(raw),
|
|
})
|
|
return c.SendStatus(200)
|
|
}
|
|
|
|
func WebSmsClickWebhook(c *fiber.Ctx) error {
|
|
var payload services.WebSmsClickPayload
|
|
if err := c.BodyParser(&payload); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
|
|
}
|
|
raw := c.Body()
|
|
models.CreateWebSmsWebhookLog(&models.WebSmsWebhookLog{
|
|
Tipo: "click",
|
|
MsgID: payload.ID,
|
|
Para: payload.Msisdn,
|
|
Status: "clicked",
|
|
Raw: string(raw),
|
|
})
|
|
return c.SendStatus(200)
|
|
}
|
|
|
|
// ─── API pública ────────────────────────────────────────────────────────────
|
|
|
|
// ApiSendSms envía un SMS. Requiere Authorization: Bearer <secret_key>.
|
|
// POST /api/sms/send { "numero": "573001234567", "mensaje": "Hola" }
|
|
func ApiSendSms(c *fiber.Ctx) error {
|
|
bearer := strings.TrimPrefix(c.Get("Authorization"), "Bearer ")
|
|
if bearer == "" {
|
|
return c.Status(401).JSON(fiber.Map{"error": "token requerido"})
|
|
}
|
|
|
|
cfg, err := models.GetWebSmsConfig()
|
|
if err != nil {
|
|
return c.Status(503).JSON(fiber.Map{"error": "WebSMS no configurado"})
|
|
}
|
|
if cfg.SecretKey == "" || cfg.SecretKey != bearer {
|
|
return c.Status(401).JSON(fiber.Map{"error": "token inválido"})
|
|
}
|
|
|
|
type body struct {
|
|
Numero string `json:"numero"`
|
|
Mensaje string `json:"mensaje"`
|
|
}
|
|
var b body
|
|
if err := c.BodyParser(&b); err != nil || b.Numero == "" || b.Mensaje == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "numero y mensaje son requeridos"})
|
|
}
|
|
|
|
resp, err := services.SendWebSms(cfg, b.Numero, b.Mensaje)
|
|
errStr := ""
|
|
status := "ok"
|
|
if err != nil {
|
|
errStr = err.Error()
|
|
status = "error"
|
|
} else if resp != nil {
|
|
status = resp.Code
|
|
}
|
|
|
|
models.CreateWebSmsLog(&models.WebSmsLog{
|
|
Para: b.Numero,
|
|
Mensaje: b.Mensaje,
|
|
Status: status,
|
|
MsgID: func() string {
|
|
if resp != nil {
|
|
return resp.MsgID()
|
|
}
|
|
return ""
|
|
}(),
|
|
Error: errStr,
|
|
})
|
|
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": errStr})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true, "id": resp.MsgID()})
|
|
}
|
|
|
|
func WebSmsIncomingWebhook(c *fiber.Ctx) error {
|
|
var payload services.WebSmsIncomingPayload
|
|
if err := c.BodyParser(&payload); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "body inválido"})
|
|
}
|
|
raw := c.Body()
|
|
models.CreateWebSmsWebhookLog(&models.WebSmsWebhookLog{
|
|
Tipo: "incoming",
|
|
MsgID: payload.ID,
|
|
Para: payload.Msisdn,
|
|
Status: "received",
|
|
Raw: string(raw),
|
|
})
|
|
return c.SendStatus(200)
|
|
}
|