SPA nueva en /orchestrator (Vue 3 + Vite, servida por el mismo binario Go bajo /orchestrator para que la cookie de sesión funcione sin tocar CORS), reemplaza al panel Alpine.js como punto de entrada del menú. Backend, todo aditivo sobre el motor de uMind ya existente: - UmindHerramienta: tools custom por tenant que llaman un webhook HTTP, integradas al loop de function-calling existente. Cliente HTTP con guardas SSRF (bloqueo de IPs privadas/loopback/link-local resuelto en el momento de conectar, no antes, para cerrar la ventana de DNS rebinding) que no existían en el proyecto. - UmindCanal: Telegram y WhatsApp Business Cloud API como canales adicionales del mismo agente que ya atiende el widget web, ambos reusando ProcessWidgetMessage. WhatsApp valida X-Hub-Signature-256. Credenciales cifradas en reposo con el mismo AES-GCM+APP_KEY que ya usa el proyecto para la contraseña SMTP (primer uso para secretos de uMind). - Se conecta middlewares.Limit() (rate limiter que existía pero no se usaba en ningún lado) al widget público y a los webhooks nuevos. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1367 lines
50 KiB
Go
Executable File
1367 lines
50 KiB
Go
Executable File
package migrations
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Session structure for managing sessions
|
|
type Session struct {
|
|
ID uint `gorm:"primaryKey"`
|
|
SessionID string `gorm:"unique;not null"`
|
|
Data string `gorm:"type:text"`
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
ExpireAt time.Time
|
|
}
|
|
|
|
// Migrate performs the database migrations
|
|
func Migrate() {
|
|
log.Println("Initiating migration...")
|
|
|
|
// Migrate the main application models.
|
|
// Un solo AutoMigrate(a, b, c...) se detiene en el primer modelo que falle:
|
|
// todo lo que viene después en la lista se queda sin tabla. Migrando uno por
|
|
// uno, un modelo roto no bloquea al resto (mismo fix que en main.go).
|
|
modelosPrincipales := []interface{}{
|
|
&models.Roles{},
|
|
&models.Users{},
|
|
&models.Modules{},
|
|
&models.Submodules{},
|
|
&models.Inicio{},
|
|
&models.TipoServidor{},
|
|
&models.TipoDb{},
|
|
&models.Servidor{},
|
|
&models.ConxDb{},
|
|
&models.ConxSsh{},
|
|
&models.ProvServidor{},
|
|
&models.QrVcard{},
|
|
&models.OssApi{},
|
|
&models.VcfVcard{},
|
|
&models.DlocalApi{},
|
|
// Módulo de Renovaciones
|
|
&models.Servicio{},
|
|
&models.Cliente{},
|
|
&models.PlantillaCorreo{},
|
|
&models.NotificacionRegla{},
|
|
&models.Contrato{},
|
|
&models.NotificacionLog{},
|
|
&models.SmtpConfig{},
|
|
// Integraciones externas
|
|
&models.HostingerConfig{},
|
|
&models.CloudflareConfig{},
|
|
// Query Runner
|
|
&models.QueryHistory{},
|
|
// Pasarelas de pago
|
|
&models.BoldConfig{},
|
|
&models.BoldWebhookLog{},
|
|
&models.BoldCallbackLog{},
|
|
&models.DlocalPaymentLog{},
|
|
&models.PaypalConfig{},
|
|
// Documentación por SaaS
|
|
&models.SaasProducto{},
|
|
&models.DocCategoria{},
|
|
&models.DocPagina{},
|
|
// Integraciones SaaS (dispatcher de pagos)
|
|
&models.SaasApiConfig{},
|
|
&models.SaasDispatchLog{},
|
|
&models.SaasWebhookInLog{},
|
|
// Documentos de clientes
|
|
&models.ClienteDocumento{},
|
|
// Telegram
|
|
&models.TelegramConfig{},
|
|
&models.TelegramLog{},
|
|
// Portal de Clientes
|
|
&models.PortalUser{},
|
|
&models.PortalAcceso{},
|
|
&models.Proyecto{},
|
|
&models.ProyectoFase{},
|
|
&models.ProyectoAvance{},
|
|
&models.ProyectoEntregable{},
|
|
&models.ProyectoDocumento{},
|
|
&models.ProyectoTicket{},
|
|
&models.TicketMensaje{},
|
|
&models.Factura{},
|
|
// Sistema de notificaciones por evento
|
|
&models.NotifEventoConfig{},
|
|
&models.SistemaNotificacion{},
|
|
&models.ServidorAlertaUmbral{},
|
|
// Submódulo Partner
|
|
&models.PartnerRecurso{},
|
|
&models.PartnerComunicado{},
|
|
// Integración VCard API Admin (Laravel)
|
|
&models.VcardApiConfig{},
|
|
// Integración Coolify
|
|
&models.CoolifyConfig{},
|
|
// Historial de métricas del agente
|
|
&models.ServidorMetricasHistory{},
|
|
// Monitor de disponibilidad de URLs
|
|
&models.UrlMonitor{},
|
|
&models.UrlMonitorLog{},
|
|
// Tablero de tareas
|
|
&models.Tarea{},
|
|
&models.TareaComentario{},
|
|
// Pagos externos: API de cobros para aplicaciones de terceros
|
|
&models.ServicioPagoExterno{},
|
|
&models.SolicitudPagoExterna{},
|
|
// Automatización de cotizaciones, contratos, actas y cuentas de cobro con IA
|
|
&models.PlantillaDocumento{},
|
|
&models.Tarifa{},
|
|
&models.DocumentoGenerado{},
|
|
&models.Arquitectura{},
|
|
// Vinculación de Telegram para staff interno
|
|
&models.TelegramStaffToken{},
|
|
// uMind: chat con IA embebible por tenant (widget web + RAG)
|
|
&models.UmindTenant{},
|
|
&models.UmindDocumento{},
|
|
&models.UmindChunk{},
|
|
&models.UmindMensaje{},
|
|
&models.UmindHerramienta{},
|
|
&models.UmindCanal{},
|
|
// API Keys de /api/v2 (token + IP obligatoria + scopes)
|
|
&models.ApiKey{},
|
|
}
|
|
for _, m := range modelosPrincipales {
|
|
if err := app.Http.Database.DB.Migrator().AutoMigrate(m); err != nil {
|
|
log.Printf("[MIGRATE] Error migrando %T: %v", m, err)
|
|
}
|
|
}
|
|
|
|
// Open (or create) a SQLite database for sessions
|
|
db, err := gorm.Open(sqlite.Open("session.db"), &gorm.Config{})
|
|
if err != nil {
|
|
log.Fatalf("Error connecting to the database: %v", err)
|
|
return
|
|
}
|
|
|
|
// Migrate the Session structure
|
|
if err := db.AutoMigrate(&Session{}); err != nil {
|
|
log.Fatalf("Error migrating session database: %v", err)
|
|
return
|
|
}
|
|
|
|
fmt.Println("Session database created and table migrated successfully.")
|
|
|
|
// Insertar módulo y submódulos de Renovaciones
|
|
SeedRenovaciones()
|
|
|
|
// Insertar módulo y submódulos de Integraciones externas
|
|
SeedIntegraciones()
|
|
|
|
// Insertar submódulo "Pasarelas de Pago" en el módulo Integraciones
|
|
SeedPasarelas()
|
|
|
|
// Insertar submódulos de SaaS (Productos + Integraciones) en el módulo Integraciones
|
|
SeedSaas()
|
|
|
|
// Insertar submódulo "Statuspage" en el módulo Integraciones
|
|
SeedStatuspage()
|
|
|
|
// Crear plantillas y reglas mínimas si no existen (bienvenida, pago_recibido)
|
|
SeedPlantillasBase()
|
|
|
|
// Agregar submódulo "Telegram" al módulo Integraciones
|
|
SeedTelegram()
|
|
|
|
// Crear módulo y submódulos de Portal de Clientes
|
|
SeedPortalClientes()
|
|
|
|
// Agregar submódulo "Partner Recursos" al módulo Portal de Clientes
|
|
SeedPartnerRecursos()
|
|
|
|
// Crear módulo y submódulos de Automatización IA (cotizaciones, contratos, asistente)
|
|
SeedAutomatizacionIA()
|
|
|
|
log.Println("Migration Completed...")
|
|
}
|
|
|
|
// asignarSubmodulosSiFaltan agrega submódulos a un rol solo si el rol todavía no
|
|
// tiene NINGUNO de ellos.
|
|
//
|
|
// Antes los seeds hacían Append incondicional en cada arranque: si un admin le
|
|
// quitaba un módulo a un rol desde el panel, el siguiente despliegue se lo
|
|
// devolvía y la gestión de permisos era, en la práctica, imposible de sostener.
|
|
func asignarSubmodulosSiFaltan(rol *models.Roles, subs []models.Submodules) {
|
|
if rol == nil || len(subs) == 0 {
|
|
return
|
|
}
|
|
db := app.Http.Database.DB
|
|
|
|
var ids []uint
|
|
for _, s := range subs {
|
|
if s.ID > 0 {
|
|
ids = append(ids, s.ID)
|
|
}
|
|
}
|
|
if len(ids) == 0 {
|
|
return
|
|
}
|
|
|
|
var yaAsignados int64
|
|
db.Table("roles_submodules").
|
|
Where("roles_id = ? AND submodules_id IN ?", rol.ID, ids).
|
|
Count(&yaAsignados)
|
|
if yaAsignados > 0 {
|
|
// El rol ya conoce este grupo de submódulos: lo que tenga hoy es una
|
|
// decisión del administrador y no se toca.
|
|
return
|
|
}
|
|
|
|
if err := db.Model(rol).Association("Submodules").Append(&subs); err != nil {
|
|
log.Printf("[SEED] Error asignando submódulos al rol %s: %v", rol.Name, err)
|
|
}
|
|
}
|
|
|
|
// SeedStatuspage agrega el submódulo "Statuspage" (Atlassian) al módulo "Integraciones"
|
|
// y lo asigna a todos los roles. Es idempotente.
|
|
func SeedStatuspage() {
|
|
db := app.Http.Database.DB
|
|
|
|
// Obtener el módulo "Integraciones" (debe existir luego de SeedIntegraciones)
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Integraciones").First(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Módulo 'Integraciones' no encontrado para SeedStatuspage: %v", err)
|
|
return
|
|
}
|
|
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Statuspage", "Monitoreo de estado de servicios vía Atlassian Statuspage", "/app/statuspage"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// Solo se asigna al rol Administrador: los demás roles pueden estar
|
|
// restringidos a propósito y no deben recibir módulos nuevos por su cuenta
|
|
// — hay que habilitarlos manualmente desde /app/roles si corresponde.
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err)
|
|
return
|
|
}
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
|
|
log.Println("[SEED] Seed de Statuspage completado.")
|
|
}
|
|
|
|
// SeedPlantillasBase crea una plantilla mínima y la regla de pago_recibido si no existen.
|
|
// Es idempotente — no sobreescribe registros existentes.
|
|
func SeedPlantillasBase() {
|
|
db := app.Http.Database.DB
|
|
|
|
plantillasBase := []struct {
|
|
nombre string
|
|
asunto string
|
|
tipo string
|
|
cuerpo string
|
|
}{
|
|
{
|
|
nombre: "Pago recibido (base)",
|
|
asunto: "Pago recibido — gracias por tu pago",
|
|
tipo: "pago",
|
|
cuerpo: `<!DOCTYPE html><html><body style="font-family:sans-serif;color:#333;max-width:600px;margin:auto;padding:24px">
|
|
<h2 style="color:#5a7d2b">Pago recibido ✔</h2>
|
|
<p>Hola <strong>{{.ClienteNombre}}</strong>,</p>
|
|
<p>Hemos recibido y confirmado tu pago. Tu servicio ha sido renovado correctamente.</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:16px 0">
|
|
{{range .Servicios}}
|
|
<tr style="border-bottom:1px solid #eee">
|
|
<td style="padding:8px">{{.Nombre}}</td>
|
|
<td style="padding:8px;text-align:right">{{.Moneda}} {{.Precio}}</td>
|
|
<td style="padding:8px;text-align:right;color:#888">Vence: {{.FechaVenc}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
<p style="font-size:13px;color:#888">Si tienes alguna pregunta, responde a este correo.</p>
|
|
</body></html>`,
|
|
},
|
|
{
|
|
nombre: "Bienvenida (base)",
|
|
asunto: "Bienvenido — tu servicio está activo",
|
|
tipo: "bienvenida",
|
|
cuerpo: `<!DOCTYPE html><html><body style="font-family:sans-serif;color:#333;max-width:600px;margin:auto;padding:24px">
|
|
<h2 style="color:#5a7d2b">¡Bienvenido!</h2>
|
|
<p>Hola <strong>{{.ClienteNombre}}</strong>,</p>
|
|
<p>Tu servicio ha sido activado. A continuación el detalle:</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:16px 0">
|
|
{{range .Servicios}}
|
|
<tr style="border-bottom:1px solid #eee">
|
|
<td style="padding:8px">{{.Nombre}}</td>
|
|
<td style="padding:8px;text-align:right">{{.Moneda}} {{.Precio}}</td>
|
|
<td style="padding:8px;text-align:right;color:#888">Vence: {{.FechaVenc}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
<p style="font-size:13px;color:#888">Gracias por elegirnos.</p>
|
|
</body></html>`,
|
|
},
|
|
}
|
|
|
|
for _, p := range plantillasBase {
|
|
var existing models.PlantillaCorreo
|
|
if err := db.Where("nombre = ?", p.nombre).First(&existing).Error; err != nil {
|
|
nueva := models.PlantillaCorreo{
|
|
Nombre: p.nombre,
|
|
Asunto: p.asunto,
|
|
Tipo: p.tipo,
|
|
CuerpoHTML: p.cuerpo,
|
|
}
|
|
if err := db.Create(&nueva).Error; err != nil {
|
|
log.Printf("[SEED] Error creando plantilla '%s': %v", p.nombre, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Plantilla '%s' creada (ID %d)", p.nombre, nueva.ID)
|
|
} else {
|
|
log.Printf("[SEED] Plantilla '%s' ya existe (ID %d)", existing.Nombre, existing.ID)
|
|
}
|
|
}
|
|
|
|
// Crear regla pago_recibido si no existe
|
|
var reglaExistente models.NotificacionRegla
|
|
if err := db.Where("tipo_evento = ?", "pago_recibido").First(®laExistente).Error; err != nil {
|
|
// Buscar la plantilla de pago
|
|
var plantilla models.PlantillaCorreo
|
|
if err2 := db.Where("tipo = ?", "pago").First(&plantilla).Error; err2 != nil {
|
|
log.Printf("[SEED] No se encontró plantilla de tipo 'pago' para crear regla pago_recibido")
|
|
return
|
|
}
|
|
regla := models.NotificacionRegla{
|
|
Nombre: "Confirmación de pago",
|
|
TipoEvento: "pago_recibido",
|
|
PlantillaID: plantilla.ID,
|
|
Activo: true,
|
|
AplicaA: "todos",
|
|
PasarelaEnlace: "ninguna",
|
|
}
|
|
if err := db.Create(®la).Error; err != nil {
|
|
log.Printf("[SEED] Error creando regla pago_recibido: %v", err)
|
|
} else {
|
|
log.Printf("[SEED] Regla 'pago_recibido' creada (ID %d) con plantilla ID %d", regla.ID, plantilla.ID)
|
|
}
|
|
} else {
|
|
log.Printf("[SEED] Regla 'pago_recibido' ya existe (ID %d)", reglaExistente.ID)
|
|
}
|
|
|
|
log.Println("[SEED] SeedPlantillasBase completado.")
|
|
}
|
|
|
|
// MigrateRenovaciones crea/actualiza las tablas del módulo de Renovaciones.
|
|
// Es idempotente: GORM AutoMigrate solo añade columnas/tablas nuevas, nunca las borra.
|
|
func MigrateRenovaciones() {
|
|
db := app.Http.Database.DB
|
|
if err := db.AutoMigrate(
|
|
&models.Servicio{},
|
|
&models.Cliente{},
|
|
&models.PlantillaCorreo{},
|
|
&models.NotificacionRegla{},
|
|
&models.Contrato{}, // incluye columnas enlace_pago y enlace_pago_link_id
|
|
&models.NotificacionLog{},
|
|
&models.SmtpConfig{},
|
|
); err != nil {
|
|
log.Printf("[MIGRATE] Error en MigrateRenovaciones: %v", err)
|
|
} else {
|
|
log.Println("[MIGRATE] Tablas de Renovaciones OK")
|
|
}
|
|
}
|
|
|
|
// SeedRenovaciones inserta el módulo "Renovaciones" y sus submódulos si no existen,
|
|
// y los asigna a todos los roles presentes en la BD.
|
|
func SeedRenovaciones() {
|
|
db := app.Http.Database.DB
|
|
|
|
// 1. Crear/obtener módulo
|
|
var modulo models.Modules
|
|
result := db.Where("title = ?", "Renovaciones").First(&modulo)
|
|
if result.Error != nil {
|
|
modulo = models.Modules{
|
|
Title: "Renovaciones",
|
|
Description: "Gestión de contratos, clientes y notificaciones de vencimiento",
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Error creando módulo Renovaciones: %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Módulo 'Renovaciones' creado con ID %d", modulo.ID)
|
|
} else {
|
|
log.Printf("[SEED] Módulo 'Renovaciones' ya existe (ID %d)", modulo.ID)
|
|
}
|
|
|
|
// 2. Definir submódulos
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Contratos", "Gestión de contratos y vencimientos", "/app/contratos"},
|
|
{"Clientes", "Clientes y datos de contacto", "/app/clientes"},
|
|
{"Servicios", "Catálogo de servicios", "/app/servicios"},
|
|
{"Plantillas Correo", "Editor de plantillas de correo", "/app/plantillas-correo"},
|
|
{"Reglas de Aviso", "Reglas de notificación automática", "/app/reglas-notificacion"},
|
|
{"Historial Notif.", "Historial de correos enviados", "/app/historial-notificaciones"},
|
|
{"Config. SMTP", "Configuración del servidor de correo", "/app/smtp-config"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
// Asegurar que el submódulo apunte al módulo correcto (Renovaciones)
|
|
if sub.ModuleId != modulo.ID {
|
|
if err := db.Model(&sub).Update("module_id", modulo.ID).Error; err != nil {
|
|
log.Printf("[SEED] Error actualizando module_id de '%s': %v", sub.Title, err)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' reasignado a módulo Renovaciones", sub.Title)
|
|
}
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// 3. Asignar solo al rol Administrador
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
log.Printf("[SEED] Submódulos de Renovaciones asignados al rol 'Administrador'")
|
|
}
|
|
|
|
log.Println("[SEED] Seed de Renovaciones completado.")
|
|
}
|
|
|
|
// SeedAutomatizacionIA inserta el módulo "Automatización IA" y sus submódulos
|
|
// (Asistente, Plantillas de Documento, Tarifas) si no existen, y los asigna al
|
|
// rol Administrador. Es idempotente.
|
|
func SeedAutomatizacionIA() {
|
|
db := app.Http.Database.DB
|
|
|
|
// 1. Crear/obtener módulo
|
|
var modulo models.Modules
|
|
result := db.Where("title = ?", "Automatización IA").First(&modulo)
|
|
if result.Error != nil {
|
|
modulo = models.Modules{
|
|
Title: "Automatización IA",
|
|
Description: "Cotizaciones, contratos, actas y cuentas de cobro generados por IA",
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Error creando módulo Automatización IA: %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Módulo 'Automatización IA' creado con ID %d", modulo.ID)
|
|
} else {
|
|
log.Printf("[SEED] Módulo 'Automatización IA' ya existe (ID %d)", modulo.ID)
|
|
}
|
|
|
|
// 2. Definir submódulos
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Asistente", "Chat propio: mismo motor y tools que el bot de Telegram", "/app/asistente"},
|
|
{"Chats autorizados", "Quién puede hablarle al bot de Telegram y ejecutar acciones", "/app/agente/chats-autorizados"},
|
|
{"Plantillas de Documento", "Fuente de verdad de cotización, contrato, acta y cuenta de cobro", "/app/automatizacion/plantillas"},
|
|
{"Tarifas", "Valor por hora, licencias, VMs y márgenes usados al cotizar", "/app/automatizacion/tarifas"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
if sub.ModuleId != modulo.ID {
|
|
if err := db.Model(&sub).Update("module_id", modulo.ID).Error; err != nil {
|
|
log.Printf("[SEED] Error actualizando module_id de '%s': %v", sub.Title, err)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' reasignado a módulo Automatización IA", sub.Title)
|
|
}
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// 3. Asignar solo al rol Administrador
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
log.Printf("[SEED] Submódulos de Automatización IA asignados al rol 'Administrador'")
|
|
}
|
|
|
|
log.Println("[SEED] Seed de Automatización IA completado.")
|
|
}
|
|
|
|
// SeedIntegraciones crea el módulo "Integraciones" con los submódulos
|
|
// de Hostinger y Cloudflare, y los asigna a todos los roles existentes.
|
|
// Es idempotente: si el registro ya existe (por URL) lo reutiliza sin duplicar.
|
|
func SeedIntegraciones() {
|
|
db := app.Http.Database.DB
|
|
|
|
// 1. Crear/obtener módulo
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Integraciones").First(&modulo).Error; err != nil {
|
|
modulo = models.Modules{
|
|
Title: "Integraciones",
|
|
Description: "Conexión con servicios externos: Hostinger, Cloudflare y más",
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Error creando módulo Integraciones: %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Módulo 'Integraciones' creado con ID %d", modulo.ID)
|
|
} else {
|
|
log.Printf("[SEED] Módulo 'Integraciones' ya existe (ID %d)", modulo.ID)
|
|
}
|
|
|
|
// 2. Definir submódulos
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Hostinger", "Panel de VPS, dominios, hosting y DNS de Hostinger", "/app/hostinger"},
|
|
{"Cloudflare", "Gestión de zonas, DNS, SSL y firewall en Cloudflare", "/app/cloudflare"},
|
|
{"VCard API", "Integración con el sistema VCard externo (Laravel + Sanctum): usuarios, membresías, vcards, pagos y más", "/app/vcard-api"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
if sub.ModuleId != modulo.ID {
|
|
if err := db.Model(&sub).Update("module_id", modulo.ID).Error; err != nil {
|
|
log.Printf("[SEED] Error actualizando module_id de '%s': %v", sub.Title, err)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' reasignado a módulo Integraciones", sub.Title)
|
|
}
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// 3. Asignar a todos los roles existentes
|
|
// Solo se asigna al rol Administrador: los demás roles pueden estar
|
|
// restringidos a propósito y no deben recibir módulos nuevos por su cuenta
|
|
// — hay que habilitarlos manualmente desde /app/roles si corresponde.
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err)
|
|
return
|
|
}
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
|
|
log.Println("[SEED] Seed de Integraciones completado.")
|
|
}
|
|
|
|
// SeedPasarelas agrega el submódulo "Pasarelas de Pago" al módulo "Integraciones"
|
|
// y lo asigna a todos los roles. Es idempotente.
|
|
func SeedPasarelas() {
|
|
db := app.Http.Database.DB
|
|
|
|
// Obtener el módulo "Integraciones" (debe existir luego de SeedIntegraciones)
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Integraciones").First(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Módulo 'Integraciones' no encontrado para SeedPasarelas: %v", err)
|
|
return
|
|
}
|
|
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Pasarelas de Pago", "Configuración de Bold, dLocal y otras pasarelas", "/app/pasarelas-pago"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// Solo se asigna al rol Administrador: los demás roles pueden estar
|
|
// restringidos a propósito y no deben recibir módulos nuevos por su cuenta
|
|
// — hay que habilitarlos manualmente desde /app/roles si corresponde.
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err)
|
|
return
|
|
}
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
|
|
log.Println("[SEED] Seed de Pasarelas completado.")
|
|
}
|
|
|
|
// SeedServidores crea el módulo "Servidores" con los submódulos de gestión
|
|
// de servidores, conexiones SSH/DB, tipos y el Query Runner. Es idempotente.
|
|
func SeedServidores() {
|
|
db := app.Http.Database.DB
|
|
|
|
// 1. Crear/obtener módulo
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Servidores").First(&modulo).Error; err != nil {
|
|
modulo = models.Modules{
|
|
Title: "Servidores",
|
|
Description: "Gestión de servidores, conexiones de base de datos y Query Runner",
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Error creando módulo Servidores: %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Módulo 'Servidores' creado con ID %d", modulo.ID)
|
|
} else {
|
|
log.Printf("[SEED] Módulo 'Servidores' ya existe (ID %d)", modulo.ID)
|
|
}
|
|
|
|
// 2. Definir submódulos
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Servidores", "Lista y gestión de servidores registrados", "/app/servidor"},
|
|
{"Conexión DB", "Conexiones de base de datos a servidores", "/app/conexion_db"},
|
|
{"Conexión SSH", "Conexiones SSH a servidores", "/app/conexion_ssh"},
|
|
{"Tipo DB", "Catálogo de tipos de base de datos", "/app/tipo_db"},
|
|
{"Tipo Servidor", "Catálogo de tipos de servidor", "/app/tipo_servidor"},
|
|
{"Proveedor Servidor", "Catálogo de proveedores de servidor", "/app/prov_servidor"},
|
|
{"Query Runner", "Editor SQL con historial y exportación", "/app/query-runner"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
if sub.ModuleId != modulo.ID {
|
|
db.Model(&sub).Update("module_id", modulo.ID)
|
|
log.Printf("[SEED] Submódulo '%s' reasignado a módulo Servidores", sub.Title)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// 3. Asignar a todos los roles existentes
|
|
// Solo se asigna al rol Administrador: los demás roles pueden estar
|
|
// restringidos a propósito y no deben recibir módulos nuevos por su cuenta
|
|
// — hay que habilitarlos manualmente desde /app/roles si corresponde.
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err)
|
|
return
|
|
}
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
|
|
log.Println("[SEED] Seed de Servidores completado.")
|
|
}
|
|
|
|
// SeedAdministracion crea el módulo "Administración" con sus submódulos
|
|
// (Roles, Módulos, Submódulos, Usuarios) y los asigna a todos los roles. Es idempotente.
|
|
func SeedAdministracion() {
|
|
db := app.Http.Database.DB
|
|
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Administración").First(&modulo).Error; err != nil {
|
|
modulo = models.Modules{
|
|
Title: "Administración",
|
|
Description: "Gestión de usuarios, roles, módulos y permisos del sistema",
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Error creando módulo Administración: %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Módulo 'Administración' creado con ID %d", modulo.ID)
|
|
} else {
|
|
log.Printf("[SEED] Módulo 'Administración' ya existe (ID %d)", modulo.ID)
|
|
}
|
|
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Roles", "Gestión de roles y permisos", "/app/roles"},
|
|
{"Módulos", "Gestión de módulos del menú lateral", "/app/modules"},
|
|
{"Submódulos", "Gestión de submódulos y URLs", "/app/submodules"},
|
|
{"Usuarios", "Gestión de usuarios del sistema", "/app/users"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
if sub.ModuleId != modulo.ID {
|
|
db.Model(&sub).Update("module_id", modulo.ID)
|
|
log.Printf("[SEED] Submódulo '%s' reasignado a módulo Administración", sub.Title)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// Solo se asigna al rol Administrador: los demás roles pueden estar
|
|
// restringidos a propósito y no deben recibir módulos nuevos por su cuenta
|
|
// — hay que habilitarlos manualmente desde /app/roles si corresponde.
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err)
|
|
return
|
|
}
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
|
|
log.Println("[SEED] Seed de Administración completado.")
|
|
}
|
|
|
|
// SeedSaas agrega los submódulos de gestión de productos SaaS e integraciones
|
|
// de pago al módulo "Integraciones" ya existente. Es idempotente.
|
|
func SeedSaas() {
|
|
db := app.Http.Database.DB
|
|
|
|
// Reutilizar el módulo "Integraciones" (creado por SeedIntegraciones)
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Integraciones").First(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Módulo 'Integraciones' no encontrado para SeedSaas: %v", err)
|
|
return
|
|
}
|
|
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Productos SaaS", "Gestión de productos SaaS y su documentación asociada", "/app/saas"},
|
|
{"Integraciones SaaS", "Callbacks de pago hacia APIs externas y logs de despacho", "/app/saas-api"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// Solo se asigna al rol Administrador: los demás roles pueden estar
|
|
// restringidos a propósito y no deben recibir módulos nuevos por su cuenta
|
|
// — hay que habilitarlos manualmente desde /app/roles si corresponde.
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err)
|
|
return
|
|
}
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
|
|
log.Println("[SEED] Seed de SaaS completado.")
|
|
}
|
|
|
|
// MigratePortal ejecuta AutoMigrate para los modelos del portal de clientes.
|
|
func MigratePortal() {
|
|
db := app.Http.Database.DB
|
|
if err := db.AutoMigrate(
|
|
&models.Proyecto{},
|
|
&models.ProyectoFase{},
|
|
&models.ProyectoAvance{},
|
|
&models.ProyectoEntregable{},
|
|
&models.ProyectoDocumento{},
|
|
&models.ProyectoTicket{},
|
|
&models.TicketMensaje{},
|
|
&models.PortalUser{},
|
|
&models.PortalAcceso{},
|
|
&models.TelegramPortalToken{},
|
|
&models.Factura{},
|
|
); err != nil {
|
|
log.Printf("[MIGRATE] Error en MigratePortal: %v", err)
|
|
} else {
|
|
log.Println("[MIGRATE] MigratePortal completado.")
|
|
}
|
|
}
|
|
|
|
// MigrateLanding crea/actualiza la tabla de sesiones del Landing Generator.
|
|
func MigrateLanding() {
|
|
db := app.Http.Database.DB
|
|
if err := db.AutoMigrate(&models.LandingSession{}, &models.AiConfig{}); err != nil {
|
|
log.Printf("[MIGRATE] Error en MigrateLanding: %v", err)
|
|
} else {
|
|
log.Println("[MIGRATE] Tablas landing_sessions y ai_configs OK")
|
|
}
|
|
}
|
|
|
|
// SeedTelegram agrega el submódulo de Telegram al módulo "Integraciones". Es idempotente.
|
|
func SeedTelegram() {
|
|
db := app.Http.Database.DB
|
|
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Integraciones").First(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Módulo 'Integraciones' no encontrado para SeedTelegram: %v", err)
|
|
return
|
|
}
|
|
|
|
url := "/app/telegram"
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: "Telegram",
|
|
Description: "Configuración de bots de Telegram para notificaciones",
|
|
Url: url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err2 := db.Create(&sub).Error; err2 != nil {
|
|
log.Printf("[SEED] Error creando submódulo Telegram: %v", err2)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Submódulo 'Telegram' creado (ID %d)", sub.ID)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo 'Telegram' ya existe (ID %d)", sub.ID)
|
|
}
|
|
|
|
// Solo se asigna al rol Administrador: los demás roles pueden estar
|
|
// restringidos a propósito y no deben recibir módulos nuevos por su cuenta
|
|
// — hay que habilitarlos manualmente desde /app/roles si corresponde.
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err)
|
|
return
|
|
}
|
|
asignarSubmodulosSiFaltan(&rolAdmin, []models.Submodules{sub})
|
|
log.Println("[SEED] Seed de Telegram completado.")
|
|
}
|
|
|
|
// SeedPortalClientes agrega el módulo "Portal de Clientes" con sus submódulos. Es idempotente.
|
|
func SeedPortalClientes() {
|
|
db := app.Http.Database.DB
|
|
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Portal de Clientes").First(&modulo).Error; err != nil {
|
|
modulo = models.Modules{
|
|
Title: "Portal de Clientes",
|
|
Description: "Gestión del portal de acceso para clientes",
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err2 := db.Create(&modulo).Error; err2 != nil {
|
|
log.Printf("[SEED] Error creando módulo 'Portal de Clientes': %v", err2)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Módulo 'Portal de Clientes' creado (ID %d)", modulo.ID)
|
|
} else {
|
|
log.Printf("[SEED] Módulo 'Portal de Clientes' ya existe (ID %d)", modulo.ID)
|
|
}
|
|
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Proyectos", "Gestión de proyectos y roadmap para clientes", "/app/proyectos"},
|
|
{"Facturas", "Gestión de facturas y cobros del portal", "/app/facturas"},
|
|
{"Portal Usuarios", "Gestión de usuarios del portal de clientes", "/app/portal-usuarios"},
|
|
}
|
|
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title,
|
|
Description: e.desc,
|
|
Url: e.url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err2 := db.Create(&sub).Error; err2 != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err2)
|
|
continue
|
|
}
|
|
log.Printf("[SEED] Submódulo '%s' creado (ID %d)", e.title, sub.ID)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo '%s' ya existe (ID %d)", sub.Title, sub.ID)
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
|
|
// Solo se asigna al rol Administrador: los demás roles pueden estar
|
|
// restringidos a propósito y no deben recibir módulos nuevos por su cuenta
|
|
// — hay que habilitarlos manualmente desde /app/roles si corresponde.
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err)
|
|
return
|
|
}
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
log.Println("[SEED] Seed de Portal de Clientes completado.")
|
|
}
|
|
|
|
// SeedNotifDefaults inserta configuraciones de notificación por defecto (idempotente).
|
|
// Solo crea registros que no existen; no sobreescribe cambios del admin.
|
|
func SeedNotifDefaults() {
|
|
db := app.Http.Database.DB
|
|
defaults := []models.NotifEventoConfig{
|
|
// Tickets
|
|
{Evento: "ticket_nuevo", Destinatario: "admin", CanalEmail: true, CanalTelegram: false, CanalSistema: true, Descripcion: "Admin recibe cuando el cliente abre un ticket"},
|
|
{Evento: "ticket_respuesta_cliente", Destinatario: "admin", CanalEmail: true, CanalTelegram: false, CanalSistema: true, Descripcion: "Admin recibe cuando el cliente responde un ticket"},
|
|
{Evento: "ticket_respuesta_admin", Destinatario: "portal_user", CanalEmail: true, CanalTelegram: false, CanalSistema: true, Descripcion: "Portal user recibe cuando el admin responde"},
|
|
{Evento: "factura_subida", Destinatario: "portal_user", CanalEmail: true, CanalTelegram: false, CanalSistema: true, Descripcion: "Portal user recibe cuando se sube una factura"},
|
|
// Servidores
|
|
{Evento: "servidor_caido", Destinatario: "admin", CanalEmail: false, CanalTelegram: true, CanalSistema: true, Descripcion: "Admin recibe cuando un servidor deja de reportar"},
|
|
{Evento: "servidor_vence_pronto", Destinatario: "admin", CanalEmail: true, CanalTelegram: true, CanalSistema: true, Descripcion: "Admin recibe cuando un VPS está próximo a vencer"},
|
|
{Evento: "servidor_recurso_alto", Destinatario: "admin", CanalEmail: false, CanalTelegram: true, CanalSistema: true, Descripcion: "Admin recibe cuando CPU/RAM/Disco supera el umbral"},
|
|
// Tareas
|
|
{Evento: "tarea_asignada", Destinatario: "admin", CanalEmail: true, CanalTelegram: true, CanalSistema: true, Descripcion: "Notifica cuando se asigna una tarea a un usuario"},
|
|
{Evento: "tarea_estado", Destinatario: "admin", CanalEmail: false, CanalTelegram: false, CanalSistema: true, Descripcion: "Notifica cuando cambia el estado de una tarea"},
|
|
{Evento: "tarea_comentario", Destinatario: "admin", CanalEmail: false, CanalTelegram: false, CanalSistema: true, Descripcion: "Notifica cuando hay un nuevo comentario en una tarea"},
|
|
}
|
|
created := 0
|
|
for _, d := range defaults {
|
|
var existing models.NotifEventoConfig
|
|
if err := db.Where("evento = ? AND destinatario = ?", d.Evento, d.Destinatario).First(&existing).Error; err != nil {
|
|
db.Create(&d)
|
|
created++
|
|
}
|
|
}
|
|
log.Printf("[SEED] SeedNotifDefaults: %d nuevas configuraciones creadas.", created)
|
|
|
|
// Corrección puntual (una sola vez): tarea_asignada/admin se sembró originalmente
|
|
// con canal_telegram=false, dejando la notificación muerta desde su creación.
|
|
// Si nadie tocó el registro desde entonces, se activa por defecto.
|
|
var taRow models.NotifEventoConfig
|
|
if err := db.Where("evento = ? AND destinatario = ?", "tarea_asignada", "admin").First(&taRow).Error; err == nil {
|
|
if !taRow.CanalTelegram && taRow.UpdatedAt.Equal(taRow.CreatedAt) {
|
|
db.Model(&taRow).Update("canal_telegram", true)
|
|
log.Println("[SEED] tarea_asignada/admin: canal_telegram activado (corrección de default)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// SeedShield es un stub idempotente para la configuración de Shield.
|
|
func SeedShield() {
|
|
log.Println("[SEED] SeedShield: sin entradas por defecto definidas aún.")
|
|
}
|
|
|
|
// SeedPartnerRecursos agrega el submódulo "Partner Recursos" al módulo "Portal de Clientes". Es idempotente.
|
|
func SeedPartnerRecursos() {
|
|
db := app.Http.Database.DB
|
|
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Portal de Clientes").First(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Módulo 'Portal de Clientes' no encontrado para SeedPartnerRecursos: %v", err)
|
|
return
|
|
}
|
|
|
|
url := "/app/partner-recursos"
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: "Partner Recursos",
|
|
Description: "Documentación y comunicados exclusivos para partners",
|
|
Url: url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err2 := db.Create(&sub).Error; err2 != nil {
|
|
log.Printf("[SEED] Error creando submódulo Partner Recursos: %v", err2)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Submódulo 'Partner Recursos' creado (ID %d)", sub.ID)
|
|
} else {
|
|
log.Printf("[SEED] Submódulo 'Partner Recursos' ya existe (ID %d)", sub.ID)
|
|
}
|
|
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rolAdmin, []models.Submodules{sub})
|
|
log.Printf("[SEED] Submódulo 'Partner Recursos' asignado al rol 'Administrador'")
|
|
}
|
|
log.Println("[SEED] Seed de Partner Recursos completado.")
|
|
}
|
|
|
|
// SeedContabilidadMenu crea el módulo "Contabilidad" y sus submódulos en el menú lateral.
|
|
func SeedContabilidadMenu() {
|
|
db := app.Http.Database.DB
|
|
var modulo models.Modules
|
|
result := db.Where("title = ?", "Contabilidad").First(&modulo)
|
|
if result.Error != nil {
|
|
modulo = models.Modules{
|
|
Title: "Contabilidad",
|
|
Description: "Gestión contable: ingresos, egresos, cuentas por cobrar/pagar",
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&modulo).Error; err != nil {
|
|
log.Printf("[SEED] Error creando módulo Contabilidad: %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Módulo 'Contabilidad' creado con ID %d", modulo.ID)
|
|
} else {
|
|
log.Printf("[SEED] Módulo 'Contabilidad' ya existe (ID %d)", modulo.ID)
|
|
}
|
|
entries := []struct{ title, desc, url string }{
|
|
{"Dashboard", "Resumen mensual", "/app/contabilidad"},
|
|
{"Transacciones", "Ingresos y egresos", "/app/contabilidad/transacciones"},
|
|
{"Categorías", "Plan de cuentas", "/app/contabilidad/cuentas"},
|
|
{"Entidades", "Clientes y proveedores", "/app/contabilidad/entidades"},
|
|
{"Cuentas por cobrar", "Pendientes de cobro", "/app/contabilidad/cuentas-cobro"},
|
|
{"Cuentas por pagar", "Pendientes de pago", "/app/contabilidad/cuentas-pagar"},
|
|
}
|
|
var insertados []models.Submodules
|
|
for _, e := range entries {
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", e.url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: e.title, Description: e.desc, Url: e.url,
|
|
ModuleId: modulo.ID, ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo '%s': %v", e.title, err)
|
|
continue
|
|
}
|
|
} else if sub.ModuleId != modulo.ID {
|
|
db.Model(&sub).Update("module_id", modulo.ID)
|
|
}
|
|
insertados = append(insertados, sub)
|
|
}
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rolAdmin, insertados)
|
|
log.Printf("[SEED] Submódulos de Contabilidad asignados al rol 'Administrador'")
|
|
}
|
|
log.Println("[SEED] Seed de Contabilidad completado.")
|
|
}
|
|
|
|
// SeedWebSms crea el submódulo de WebSMS bajo "Integraciones".
|
|
func SeedWebSms() {
|
|
db := app.Http.Database.DB
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Integraciones").First(&modulo).Error; err != nil {
|
|
log.Println("[SEED] Módulo 'Integraciones' no encontrado, se creará")
|
|
modulo = models.Modules{
|
|
Title: "Integraciones", Description: "Conexión con servicios externos",
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
db.Create(&modulo)
|
|
}
|
|
url := "/app/websms"
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: "WebSMS", Description: "Notificaciones SMS vía LabsMobile",
|
|
Url: url, ModuleId: modulo.ID, ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo WebSMS: %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Submódulo 'WebSMS' creado")
|
|
} else if sub.ModuleId != modulo.ID {
|
|
db.Model(&sub).Update("module_id", modulo.ID)
|
|
}
|
|
var rolAdmin models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rolAdmin, []models.Submodules{sub})
|
|
log.Printf("[SEED] Submódulo 'WebSMS' asignado al rol 'Administrador'")
|
|
}
|
|
log.Println("[SEED] Seed de WebSMS completado.")
|
|
}
|
|
|
|
// SeedUrlMonitor agrega el submódulo "Monitor de URLs" al módulo "Servidores". Es idempotente.
|
|
func SeedUrlMonitor() {
|
|
db := app.Http.Database.DB
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Servidores").First(&modulo).Error; err != nil {
|
|
log.Println("[SEED] Módulo 'Servidores' no encontrado, se omite SeedUrlMonitor")
|
|
return
|
|
}
|
|
url := "/app/url-monitor"
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: "Monitor de URLs",
|
|
Description: "Monitoreo de disponibilidad y latencia de sitios y APIs",
|
|
Url: url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo 'Monitor de URLs': %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Submódulo 'Monitor de URLs' creado")
|
|
} else if sub.ModuleId != modulo.ID {
|
|
db.Model(&sub).Update("module_id", modulo.ID)
|
|
}
|
|
var rol models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rol).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rol, []models.Submodules{sub})
|
|
log.Printf("[SEED] Submódulo 'Monitor de URLs' asignado al rol 'Administrador'")
|
|
}
|
|
log.Println("[SEED] Seed de Monitor de URLs completado.")
|
|
}
|
|
|
|
// SeedTareas agrega el submódulo "Tareas" al módulo "Administración" solo para el rol Administrador. Es idempotente.
|
|
func SeedTareas() {
|
|
db := app.Http.Database.DB
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Administración").First(&modulo).Error; err != nil {
|
|
log.Println("[SEED] Módulo 'Administración' no encontrado, se omite SeedTareas")
|
|
return
|
|
}
|
|
url := "/app/tareas"
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: "Tareas",
|
|
Description: "Tablero kanban de tareas internas",
|
|
Url: url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo 'Tareas': %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Submódulo 'Tareas' creado")
|
|
} else if sub.ModuleId != modulo.ID {
|
|
db.Model(&sub).Update("module_id", modulo.ID)
|
|
}
|
|
var rol models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rol).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rol, []models.Submodules{sub})
|
|
log.Printf("[SEED] Submódulo 'Tareas' asignado al rol 'Administrador'")
|
|
}
|
|
log.Println("[SEED] Seed de Tareas completado.")
|
|
}
|
|
|
|
// SeedPagosExternos registra el submódulo del panel de administración de
|
|
// servicios de pago externos (emisión de tokens de API para terceros).
|
|
func SeedPagosExternos() {
|
|
db := app.Http.Database.DB
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Administración").First(&modulo).Error; err != nil {
|
|
log.Println("[SEED] Módulo 'Administración' no encontrado, se omite SeedPagosExternos")
|
|
return
|
|
}
|
|
url := "/app/pagos-externos"
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: "Pagos externos",
|
|
Description: "Tokens de API para que apps de terceros pidan cobros por Bold/dLocal/PayPal",
|
|
Url: url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo 'Pagos externos': %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Submódulo 'Pagos externos' creado")
|
|
} else if sub.ModuleId != modulo.ID {
|
|
db.Model(&sub).Update("module_id", modulo.ID)
|
|
}
|
|
var rol models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rol).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rol, []models.Submodules{sub})
|
|
log.Printf("[SEED] Submódulo 'Pagos externos' asignado al rol 'Administrador'")
|
|
}
|
|
log.Println("[SEED] Seed de Pagos externos completado.")
|
|
}
|
|
|
|
// SeedUmind registra el submódulo del panel de administración de uMind
|
|
// (tenants, base de conocimiento y conversaciones del widget embebible).
|
|
func SeedUmind() {
|
|
db := app.Http.Database.DB
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Automatización IA").First(&modulo).Error; err != nil {
|
|
log.Println("[SEED] Módulo 'Automatización IA' no encontrado, se omite SeedUmind")
|
|
return
|
|
}
|
|
// El panel viejo (Alpine, /app/umind) sigue existiendo y respondiendo, pero
|
|
// el menú ahora apunta al orquestador (SPA Vue). Si ya existía el submódulo
|
|
// apuntando a la URL vieja, se migra en el mismo registro en vez de crear
|
|
// uno duplicado.
|
|
url := "/orchestrator"
|
|
urlVieja := "/app/umind"
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ? OR url = ?", url, urlVieja).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: "uMind",
|
|
Description: "Chat con IA embebible por sitio, con base de conocimiento propia (RAG)",
|
|
Url: url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo 'uMind': %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Submódulo 'uMind' creado")
|
|
} else {
|
|
updates := map[string]interface{}{}
|
|
if sub.ModuleId != modulo.ID {
|
|
updates["module_id"] = modulo.ID
|
|
}
|
|
if sub.Url != url {
|
|
updates["url"] = url
|
|
}
|
|
if len(updates) > 0 {
|
|
db.Model(&sub).Updates(updates)
|
|
}
|
|
}
|
|
var rol models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rol).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rol, []models.Submodules{sub})
|
|
log.Printf("[SEED] Submódulo 'uMind' asignado al rol 'Administrador'")
|
|
}
|
|
log.Println("[SEED] Seed de uMind completado.")
|
|
}
|
|
|
|
// SeedApiKeys registra el submódulo del panel de administración de API Keys
|
|
// (credenciales scoped para /api/v2, alternativa al ADMIN_API_KEY maestro).
|
|
func SeedApiKeys() {
|
|
db := app.Http.Database.DB
|
|
var modulo models.Modules
|
|
if err := db.Where("title = ?", "Administración").First(&modulo).Error; err != nil {
|
|
log.Println("[SEED] Módulo 'Administración' no encontrado, se omite SeedApiKeys")
|
|
return
|
|
}
|
|
url := "/app/api-keys"
|
|
var sub models.Submodules
|
|
if err := db.Where("url = ?", url).First(&sub).Error; err != nil {
|
|
sub = models.Submodules{
|
|
Title: "API Keys",
|
|
Description: "Credenciales scoped para /api/v2 (token + IP + alcance), alternativa a la llave maestra",
|
|
Url: url,
|
|
ModuleId: modulo.ID,
|
|
ModifiedAt: time.Now(),
|
|
}
|
|
if err := db.Create(&sub).Error; err != nil {
|
|
log.Printf("[SEED] Error creando submódulo 'API Keys': %v", err)
|
|
return
|
|
}
|
|
log.Printf("[SEED] Submódulo 'API Keys' creado")
|
|
} else if sub.ModuleId != modulo.ID {
|
|
db.Model(&sub).Update("module_id", modulo.ID)
|
|
}
|
|
var rol models.Roles
|
|
if err := db.Where("name = ?", "Administrador").First(&rol).Error; err != nil {
|
|
log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err)
|
|
} else {
|
|
asignarSubmodulosSiFaltan(&rol, []models.Submodules{sub})
|
|
log.Printf("[SEED] Submódulo 'API Keys' asignado al rol 'Administrador'")
|
|
}
|
|
log.Println("[SEED] Seed de API Keys completado.")
|
|
}
|