Files
soft_usite/pkg/models/umind_agente.go
T
Lizandro GuarnizoandClaude Sonnet 5 4995c5fca5 fix(widget): deja de mostrar los errores del servidor como respuestas del bot
El widget hacía `data.respuesta || data.message` sin mirar el status, así
que un 403/404/500 se pintaba en una burbuja como si lo hubiera dicho el
agente. Imposible distinguir "el bot contestó raro" de "el widget está
siendo rechazado", que es justo el lazo en el que se puede quedar alguien
diagnosticando esto.

Además los rechazos del middleware ocurren ANTES del motor, así que no
dejaban rastro en ningún lado: ni en la auditoría del agente ni en el
navegador. De ahí el síntoma "el chat de prueba anda pero el widget no".

- El widget mira r.ok, manda el motivo real a la consola y al visitante le
  muestra un mensaje neutro.
- AuthUmindWidget distingue las tres causas que el chat de prueba NO tiene
  (agente inactivo, tenant inactivo, dominio no autorizado) y las registra
  en la auditoría del agente con el detalle accionable — incluyendo qué
  dominio llamó y cuáles están permitidos.
- GetUmindAgentePorSiteKey busca sin filtrar por activo, para poder decir
  "está apagado" en vez de "no existe".
- Test del allowlist de dominios: es fail-closed y www.ejemplo.com NO
  matchea ejemplo.com, la causa más probable de este síntoma.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 12:06:42 -05:00

85 lines
3.0 KiB
Go

package models
import (
"github.com/sujit-baniya/fiber-boilerplate/app"
"gorm.io/gorm"
)
// UmindAgente es un agente de IA independiente dentro de un tenant — un
// mismo negocio puede tener varios (ej. "Ventas", "Soporte"), cada uno con
// su propia base de conocimiento, tools, canales y conexión de correo. Lo
// único que sigue siendo del tenant (no del agente) son los dominios
// permitidos y el nombre del negocio que se muestra al visitante — eso es
// del sitio, no de un agente puntual.
type UmindAgente struct {
gorm.Model
TenantID uint `json:"tenant_id" gorm:"column:tenant_id;index;not null"`
Nombre string `json:"nombre" gorm:"column:nombre;size:150;not null"` // etiqueta interna, ej: "Ventas"
SiteKey string `json:"site_key" gorm:"column:site_key;uniqueIndex;size:40;not null"`
AiConfigID *uint `json:"ai_config_id" gorm:"column:ai_config_id"`
Tono string `json:"tono" gorm:"column:tono;type:text"`
MensajeBienvenida string `json:"mensaje_bienvenida" gorm:"column:mensaje_bienvenida;type:text"`
Color string `json:"color" gorm:"column:color;size:7;default:'#8eb02f'"`
Activo bool `json:"activo" gorm:"column:activo;default:true"`
}
func (UmindAgente) TableName() string { return "umind_agentes" }
func CreateUmindAgente(a *UmindAgente) error {
if a.SiteKey == "" {
key, err := GenerarSiteKey()
if err != nil {
return err
}
a.SiteKey = key
}
if a.Color == "" {
a.Color = "#8eb02f"
}
return app.Http.Database.DB.Create(a).Error
}
func GetUmindAgentesByTenant(tenantID uint) ([]UmindAgente, error) {
var items []UmindAgente
err := app.Http.Database.DB.Where("tenant_id = ?", tenantID).Order("id ASC").Find(&items).Error
return items, err
}
func GetUmindAgenteByID(id uint) (*UmindAgente, error) {
var a UmindAgente
if err := app.Http.Database.DB.First(&a, id).Error; err != nil {
return nil, err
}
return &a, nil
}
// GetUmindAgenteBySiteKey resuelve el agente a partir de la site_key pública
// que manda el widget. Solo hace match si el agente está activo.
func GetUmindAgenteBySiteKey(siteKey string) (*UmindAgente, error) {
var a UmindAgente
if err := app.Http.Database.DB.Where("site_key = ? AND activo = ?", siteKey, true).First(&a).Error; err != nil {
return nil, err
}
return &a, nil
}
func UpdateUmindAgente(id uint, updates map[string]interface{}) error {
return app.Http.Database.DB.Model(&UmindAgente{}).Where("id = ?", id).Updates(updates).Error
}
func DeleteUmindAgente(id uint) error {
return app.Http.Database.DB.Delete(&UmindAgente{}, id).Error
}
// GetUmindAgentePorSiteKey resuelve el agente por site_key SIN filtrar por
// activo, para que el llamador pueda distinguir "no existe" de "está
// apagado" y decirlo. GetUmindAgenteBySiteKey (que sí filtra) queda para
// quien solo necesita el camino feliz.
func GetUmindAgentePorSiteKey(siteKey string) (*UmindAgente, error) {
var a UmindAgente
if err := app.Http.Database.DB.Where("site_key = ?", siteKey).First(&a).Error; err != nil {
return nil, err
}
return &a, nil
}