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 if err := app.Http.Database.DB.Migrator().AutoMigrate( &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{}, ); err != nil { log.Fatalf("Error during main migration: %v", 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() log.Println("Migration Completed...") } // 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) } var roles []models.Roles if err := db.Find(&roles).Error; err != nil { log.Printf("[SEED] Error obteniendo roles: %v", err) return } for _, rol := range roles { if err := db.Model(&rol).Association("Submodules").Append(&insertados); err != nil { log.Printf("[SEED] Error asignando submódulo Statuspage al rol '%s': %v", rol.Name, err) } else { log.Printf("[SEED] Submódulo Statuspage asignado al rol '%s'", rol.Name) } } 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: `
Hola {{.ClienteNombre}},
Hemos recibido y confirmado tu pago. Tu servicio ha sido renovado correctamente.
| {{.Nombre}} | {{.Moneda}} {{.Precio}} | Vence: {{.FechaVenc}} |
Si tienes alguna pregunta, responde a este correo.
`, }, { nombre: "Bienvenida (base)", asunto: "Bienvenido — tu servicio está activo", tipo: "bienvenida", cuerpo: `Hola {{.ClienteNombre}},
Tu servicio ha sido activado. A continuación el detalle:
| {{.Nombre}} | {{.Moneda}} {{.Precio}} | Vence: {{.FechaVenc}} |
Gracias por elegirnos.
`, }, } 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 a todos los roles existentes var roles []models.Roles if err := db.Find(&roles).Error; err != nil { log.Printf("[SEED] Error obteniendo roles: %v", err) return } for _, rol := range roles { if err := db.Model(&rol).Association("Submodules").Append(&insertados); err != nil { log.Printf("[SEED] Error asignando submódulos al rol '%s': %v", rol.Name, err) } else { log.Printf("[SEED] Submódulos de Renovaciones asignados al rol '%s'", rol.Name) } } log.Println("[SEED] Seed de Renovaciones 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 var roles []models.Roles if err := db.Find(&roles).Error; err != nil { log.Printf("[SEED] Error obteniendo roles: %v", err) return } for _, rol := range roles { if err := db.Model(&rol).Association("Submodules").Append(&insertados); err != nil { log.Printf("[SEED] Error asignando submódulos al rol '%s': %v", rol.Name, err) } else { log.Printf("[SEED] Submódulos de Integraciones asignados al rol '%s'", rol.Name) } } 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) } var roles []models.Roles if err := db.Find(&roles).Error; err != nil { log.Printf("[SEED] Error obteniendo roles: %v", err) return } for _, rol := range roles { if err := db.Model(&rol).Association("Submodules").Append(&insertados); err != nil { log.Printf("[SEED] Error asignando submódulos al rol '%s': %v", rol.Name, err) } else { log.Printf("[SEED] Submódulo Pasarelas asignado al rol '%s'", rol.Name) } } 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 var roles []models.Roles if err := db.Find(&roles).Error; err != nil { log.Printf("[SEED] Error obteniendo roles: %v", err) return } for _, rol := range roles { if err := db.Model(&rol).Association("Submodules").Append(&insertados); err != nil { log.Printf("[SEED] Error asignando submódulos al rol '%s': %v", rol.Name, err) } else { log.Printf("[SEED] Submódulos de Servidores asignados al rol '%s'", rol.Name) } } 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) } var roles []models.Roles if err := db.Find(&roles).Error; err != nil { log.Printf("[SEED] Error obteniendo roles: %v", err) return } for _, rol := range roles { if err := db.Model(&rol).Association("Submodules").Append(&insertados); err != nil { log.Printf("[SEED] Error asignando submódulos al rol '%s': %v", rol.Name, err) } else { log.Printf("[SEED] Submódulos de Administración asignados al rol '%s'", rol.Name) } } 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) } var roles []models.Roles if err := db.Find(&roles).Error; err != nil { log.Printf("[SEED] Error obteniendo roles: %v", err) return } for _, rol := range roles { if err := db.Model(&rol).Association("Submodules").Append(&insertados); err != nil { log.Printf("[SEED] Error asignando submódulos SaaS al rol '%s': %v", rol.Name, err) } else { log.Printf("[SEED] Submódulos SaaS asignados al rol '%s'", rol.Name) } } 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) } var roles []models.Roles if err := db.Find(&roles).Error; err != nil { return } for _, rol := range roles { db.Model(&rol).Association("Submodules").Append(&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) } var roles []models.Roles if err := db.Find(&roles).Error; err != nil { return } for _, rol := range roles { db.Model(&rol).Association("Submodules").Append(&insertados) } log.Println("[SEED] Seed de Portal de Clientes completado.") } // SeedNotifDefaults es un stub idempotente para configuraciones de notificaciones por defecto. func SeedNotifDefaults() { log.Println("[SEED] SeedNotifDefaults: sin entradas por defecto definidas aún.") } // 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 roles []models.Roles if err := db.Find(&roles).Error; err != nil { return } for _, rol := range roles { db.Model(&rol).Association("Submodules").Append(&sub) } log.Println("[SEED] Seed de Partner Recursos completado.") }