package models import ( "github.com/sujit-baniya/fiber-boilerplate/app" "gorm.io/gorm" ) // PlantillaDocumento es la fuente de verdad de las plantillas base usadas por // la automatización con IA: cotización, contrato, acta de proyecto y cuenta de cobro. // Editar una plantilla aquí actualiza automáticamente todos los canales (Telegram, // chat propio, Claude directo) que generan ese tipo de documento. type PlantillaDocumento struct { gorm.Model Tipo string `json:"tipo" gorm:"column:tipo;size:30;not null;index"` // cotizacion | contrato | acta | cuenta_cobro Nombre string `json:"nombre" gorm:"column:nombre;size:150;not null"` ContenidoHTML string `json:"contenido_html" gorm:"column:contenido_html;type:text"` // Go text/template Version int `json:"version" gorm:"column:version;default:1"` Activa bool `json:"activa" gorm:"column:activa;default:true"` Notas string `json:"notas" gorm:"column:notas;type:text"` // TenantID separa las plantillas de cada cliente de las globales del // staff. Nulo = global (el comportamiento de siempre). Mismo patrón que // AiConfig — el que ya tiene su lección aprendida y su test que la cuida. TenantID *uint `json:"tenant_id" gorm:"column:tenant_id;index"` } func (PlantillaDocumento) TableName() string { return "plantillas_documento" } func GetAllPlantillasDocumento(limit, offset int, search, tipo string) ([]PlantillaDocumento, int64, error) { var items []PlantillaDocumento var total int64 db := app.Http.Database.DB.Model(&PlantillaDocumento{}) if search != "" { db = db.Where("nombre ILIKE ?", "%"+search+"%") } if tipo != "" { db = db.Where("tipo = ?", tipo) } if err := db.Count(&total).Error; err != nil { return nil, 0, err } if err := db.Order("tipo ASC, version DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil { return nil, 0, err } return items, total, nil } func GetPlantillaDocumentoByID(id uint) (*PlantillaDocumento, error) { var item PlantillaDocumento if err := app.Http.Database.DB.First(&item, id).Error; err != nil { return nil, err } return &item, nil } // GetPlantillaDocumentoActiva retorna la plantilla activa más reciente para un tipo dado. // Es la que usan los endpoints de generación (cotizaciones, contratos, etc). // GetPlantillaDocumentoActiva devuelve la plantilla GLOBAL del staff. Filtra // tenant_id IS NULL a propósito: sin eso, la plantilla que un cliente escribe // para su propio contrato podría salir en un documento de la empresa. func GetPlantillaDocumentoActiva(tipo string) (*PlantillaDocumento, error) { var item PlantillaDocumento if err := app.Http.Database.DB. Where("tipo = ? AND activa = ? AND tenant_id IS NULL", tipo, true). Order("version DESC"). First(&item).Error; err != nil { return nil, err } return &item, nil } // GetPlantillaDocumentoDeTenant busca la plantilla propia del espacio y, si no // tiene una para ese tipo, cae a la global. Así un cliente nuevo puede emitir // una cotización desde el primer día y personalizarla cuando quiera. func GetPlantillaDocumentoDeTenant(tenantID uint, tipo string) (*PlantillaDocumento, error) { var item PlantillaDocumento err := app.Http.Database.DB. Where("tipo = ? AND activa = ? AND tenant_id = ?", tipo, true, tenantID). Order("version DESC").First(&item).Error if err == nil { return &item, nil } return GetPlantillaDocumentoActiva(tipo) } // GetPlantillasDeTenant lista las propias del espacio, sin las globales. func GetPlantillasDeTenant(tenantID uint) ([]PlantillaDocumento, error) { var items []PlantillaDocumento err := app.Http.Database.DB.Where("tenant_id = ?", tenantID). Order("tipo ASC, version DESC").Limit(100).Find(&items).Error return items, err } func CreatePlantillaDocumento(p PlantillaDocumento) error { return app.Http.Database.DB.Create(&p).Error } func UpdatePlantillaDocumento(id uint, updates map[string]interface{}) error { return app.Http.Database.DB.Model(&PlantillaDocumento{}).Where("id = ?", id).Updates(updates).Error } func DeletePlantillaDocumento(id uint) error { return app.Http.Database.DB.Delete(&PlantillaDocumento{}, id).Error } // TienePlantillasDocumento dice si el agente puede emitir documentos. Sin // plantilla la tool no se le ofrece al modelo: prometerle una capacidad que // después falla es peor que no tenerla. func TienePlantillasDocumento(agenteID uint) bool { agente, err := GetUmindAgenteByID(agenteID) if err != nil { return false } var n int64 app.Http.Database.DB.Model(&PlantillaDocumento{}). Where("activa = ? AND deleted_at IS NULL AND (tenant_id = ? OR tenant_id IS NULL)", true, agente.TenantID). Count(&n) return n > 0 } // SiguienteVersionPlantilla numera la nueva versión del cliente. Se guarda // versión nueva en vez de pisar la vieja: si la nueva sale mal, la anterior // sigue ahí. func SiguienteVersionPlantilla(tenantID uint, tipo string) int { var max int app.Http.Database.DB.Model(&PlantillaDocumento{}). Where("tenant_id = ? AND tipo = ?", tenantID, tipo). Select("COALESCE(MAX(version), 0)").Scan(&max) return max + 1 }