package models import ( "crypto/rand" "encoding/hex" "fmt" "github.com/sujit-baniya/fiber-boilerplate/app" "gorm.io/gorm" ) // UmindCanal es un canal de mensajería adicional (Telegram, WhatsApp) que // alimenta al mismo agente del tenant que ya atiende el widget web. Los // secretos reales (bot token, access token de WhatsApp, etc.) viven cifrados // en CredencialesEnc (ver pkg/services/umind_secrets.go) — el modelo solo // persiste el string ya cifrado, no conoce la clave. // // WebhookSecret es un identificador público generado por nosotros, distinto // del secreto real del proveedor, usado SOLO para enrutar el webhook // entrante al canal correcto (va en la URL que se registra en // Telegram/Meta). Evita que el token real del proveedor termine en logs de // acceso o de un proxy intermedio. type UmindCanal struct { gorm.Model AgenteID uint `json:"agente_id" gorm:"column:agente_id;index"` Tipo string `json:"tipo" gorm:"column:tipo;size:20;not null"` // telegram | whatsapp Activo bool `json:"activo" gorm:"column:activo;default:true"` WebhookSecret string `json:"webhook_secret" gorm:"column:webhook_secret;uniqueIndex;size:40;not null"` CredencialesEnc string `json:"-" gorm:"column:credenciales_enc;type:text"` UltimoError string `json:"ultimo_error" gorm:"column:ultimo_error;type:text"` // Si están activos, los mensajes de voz/audio e imágenes que llegan por // este canal se transcriben (Whisper) o se les extrae el texto (OCR) // antes de pasarlos al agente, en vez de ignorarse. UsarWhisperAudio bool `json:"usar_whisper_audio" gorm:"column:usar_whisper_audio;default:false"` UsarOcrImagenes bool `json:"usar_ocr_imagenes" gorm:"column:usar_ocr_imagenes;default:false"` // Documentos adjuntos (PDF, Word, texto): se les extrae el contenido y se // le pasa al agente como si el cliente lo hubiera escrito. UsarArchivosDocs bool `json:"usar_archivos_docs" gorm:"column:usar_archivos_docs;default:false"` } func (UmindCanal) TableName() string { return "umind_canales" } func GenerarWebhookSecret() (string, error) { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { return "", fmt.Errorf("no se pudo generar el webhook_secret: %w", err) } return "umc_" + hex.EncodeToString(b), nil } func CreateUmindCanal(c *UmindCanal) error { if c.WebhookSecret == "" { secret, err := GenerarWebhookSecret() if err != nil { return err } c.WebhookSecret = secret } return app.Http.Database.DB.Create(c).Error } func GetUmindCanalesByAgente(agenteID uint) ([]UmindCanal, error) { var items []UmindCanal err := app.Http.Database.DB.Where("agente_id = ?", agenteID).Order("id DESC").Find(&items).Error return items, err } func GetUmindCanalByID(id uint) (*UmindCanal, error) { var c UmindCanal if err := app.Http.Database.DB.First(&c, id).Error; err != nil { return nil, err } return &c, nil } // GetUmindCanalByWebhookSecret resuelve el canal a partir del identificador // público que viene en la URL del webhook. Solo matchea si está activo. func GetUmindCanalByWebhookSecret(tipo, webhookSecret string) (*UmindCanal, error) { var c UmindCanal err := app.Http.Database.DB.Where("tipo = ? AND webhook_secret = ? AND activo = ?", tipo, webhookSecret, true).First(&c).Error if err != nil { return nil, err } return &c, nil } func UpdateUmindCanal(id uint, updates map[string]interface{}) error { return app.Http.Database.DB.Model(&UmindCanal{}).Where("id = ?", id).Updates(updates).Error } func DeleteUmindCanal(id uint) error { return app.Http.Database.DB.Delete(&UmindCanal{}, id).Error }