package main import ( "flag" "log" "os" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/pyroscope-io/pyroscope/pkg/agent/profiler" "github.com/sujit-baniya/fiber-boilerplate/app" "github.com/sujit-baniya/fiber-boilerplate/config" "github.com/sujit-baniya/fiber-boilerplate/migrations" "github.com/sujit-baniya/fiber-boilerplate/pkg/auth" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "github.com/sujit-baniya/fiber-boilerplate/pkg/services" "github.com/sujit-baniya/fiber-boilerplate/rest/routes" "github.com/sujit-baniya/fiber-boilerplate/utils" ) func main() { configFile := flag.String("config", "config.yml", "User Config file from user") migrate := flag.Bool("migrate", false, "Update db structure") flag.Parse() // Cargar configuración app.Load(*configFile) // Configurar Pyroscope profiler if app.Http.Profiler.Enabled { _, _ = profiler.Start(profiler.Config{ ApplicationName: app.Http.Server.Name, ServerAddress: app.Http.Profiler.Server, }) } // Actualizar versión del servidor app.Http.Server.Version = app.Version // Los archivos de /uploads dejan de ser públicos: solo se sirven a usuarios // con sesión iniciada en el panel. Los clientes del portal siguen bajando sus // documentos por los endpoints dedicados, que validan que les pertenezcan. config.UploadsGuard = func(c *fiber.Ctx) bool { return auth.IsLoggedIn(c) } // Habilitar CORS app.Http.Server.App.Use(cors.New(cors.Config{ AllowOrigins: "*", AllowMethods: "GET,POST,PUT,DELETE,OPTIONS", AllowHeaders: "Content-Type, Authorization", })) if *migrate { // Ejecutar migraciones migrations.Migrate() } else { // Crear/actualizar tablas de Renovaciones e idempotente) migrations.MigrateRenovaciones() // Crear/actualizar tablas de Portal, Telegram y Proyectos migrations.MigratePortal() // Crear tabla de Landing Generator migrations.MigrateLanding() // Crear tablas de integraciones y pasarelas si no existen. // 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, en silencio (fue la // causa real de que plantillas_documento/tarifas desaparecieran de // producción — un modelo antes en la lista falló y todo lo posterior no se // llegó a crear). Migrando uno por uno, un modelo roto no bloquea al resto. modelosBase := []interface{}{ // Tablas base del sistema (columnas nuevas) &models.Users{}, &models.Roles{}, &models.Submodules{}, &models.QueryHistory{}, &models.OssApi{}, &models.QrVcard{}, &models.VcfVcard{}, &models.HostingerConfig{}, &models.CloudflareConfig{}, &models.BoldConfig{}, &models.BoldWebhookLog{}, &models.BoldCallbackLog{}, &models.DlocalPaymentLog{}, // Documentación por SaaS &models.SaasProducto{}, &models.DocCategoria{}, &models.DocPagina{}, // Integraciones SaaS (dispatcher de pagos) &models.SaasApiConfig{}, &models.SaasDispatchLog{}, // Notificaciones &models.NotifEventoConfig{}, &models.SistemaNotificacion{}, &models.ServidorAlertaUmbral{}, // Shield &models.ShieldConfig{}, // Partner &models.PartnerRecurso{}, &models.PartnerComunicado{}, // Portal: recuperación de contraseña &models.PortalPasswordResetToken{}, // Integración VCard API Admin &models.VcardApiConfig{}, // Integración Coolify (multi-instancia) &models.CoolifyConfig{}, // AI Config: campos del agente Telegram &models.AiConfig{}, // Agente Telegram: historial y chats autorizados &models.TelegramAgentHistory{}, &models.TelegramAgentAuth{}, // Servidores: nuevos campos de agente + tabla join de integraciones &models.Servidor{}, &models.ConxDb{}, &models.ProvServidor{}, // SMTP &models.SmtpConfig{}, // WebSMS (LabsMobile) &models.WebSmsConfig{}, &models.WebSmsLog{}, &models.WebSmsWebhookLog{}, // Contabilidad &models.Cuenta{}, &models.Entidad{}, &models.Transaccion{}, &models.CuentaCobro{}, &models.CuentaPagar{}, &models.ConsolidadoMensual{}, // 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{}, // Soporte: webhook de correo entrante &models.SoporteWebhookConfig{}, // 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.UmindAgente{}, &models.UmindDocumento{}, &models.UmindChunk{}, &models.UmindMensaje{}, &models.UmindHerramienta{}, &models.UmindCanal{}, &models.UmindConexion{}, &models.UmindEventoLog{}, &models.UmindPlan{}, &models.UmindUso{}, // API Keys de /api/v2 (token + IP obligatoria + scopes) &models.ApiKey{}, // Integraciones: OCR y transcripción de audio (servicios propios) &models.OcrConfig{}, &models.WhisperAsrConfig{}, } for _, m := range modelosBase { if err := app.Http.Database.DB.AutoMigrate(m); err != nil { log.Printf("[MIGRATE] Error migrando %T: %v", m, err) } } // Seed automático (idempotente) de módulos del sistema migrations.SeedRenovaciones() migrations.SeedIntegraciones() migrations.SeedPasarelas() migrations.SeedSaas() migrations.SeedServidores() migrations.SeedAdministracion() migrations.SeedPlantillasBase() migrations.SeedTelegram() migrations.SeedPortalClientes() migrations.SeedNotifDefaults() migrations.SeedShield() migrations.SeedPartnerRecursos() models.SeedContabilidad() // SeedBalanceData carga transacciones contables reales de 2026. Ya no se // ejecuta en cada arranque: si el contador editaba o borraba una de esas // filas, el siguiente reinicio la recreaba y el balance quedaba duplicado. // Para recargarla puntualmente: SEED_BALANCE=1 ./apiv2 if os.Getenv("SEED_BALANCE") == "1" { log.Println("[SEED] SEED_BALANCE=1 — cargando datos de balance") models.SeedBalanceData() } migrations.SeedContabilidadMenu() migrations.SeedWebSms() migrations.SeedUrlMonitor() migrations.SeedTareas() migrations.SeedPagosExternos() migrations.SeedAutomatizacionIA() migrations.SeedUmind() migrations.SeedDocumentacion() // Va antes de MigrarUmindAgentes: libera las columnas huérfanas que // dejó el refactor multi-agente. Sin esto no se puede insertar nada en // uMind (ver el comentario de la función). migrations.LiberarColumnasHuerfanasUmind() migrations.IndicesUnicosMessageID() migrations.MigrarUmindAgentes() migrations.SeedApiKeys() if n, err := models.RepararEstadosTareaInvalidos(); err != nil { log.Printf("[FIX] Error reparando estados de tareas: %v", err) } else if n > 0 { log.Printf("[FIX] %d tarea(s) con estado inválido reparadas (ahora visibles en el Kanban)", n) } // Cargar config SMTP desde BD (sobreescribe valores del .env/config.yml si hay registro activo) if smtpCfg, err := models.GetSmtpConfig(); err == nil { app.Http.Mail.Host = smtpCfg.Host app.Http.Mail.Port = smtpCfg.Port app.Http.Mail.Username = smtpCfg.Username app.Http.Mail.Password = utils.Decrypt(smtpCfg.Password, app.Http.Server.Key) app.Http.Mail.Encryption = smtpCfg.Encryption app.Http.Mail.FromAddress = smtpCfg.FromAddress app.Http.Mail.FromName = smtpCfg.FromName app.Http.Mail.SetupMailer() log.Println("[SMTP] Configuración cargada desde base de datos") } else { log.Println("[SMTP] Sin config en BD, usando valores del entorno") } // Iniciar cron de vencimientos services.IniciarCron() defer services.DetenerCron() // Cargar rutas routes.LoadRoutes(app.Http.Server.App) // Con las rutas ya montadas se puede avisar de los ítems del menú que // apuntan a una URL inexistente — el síntoma es un 404 que parece un // problema de permisos. rutasGET := map[string]bool{} for _, capa := range app.Http.Server.Stack() { for _, r := range capa { if r.Method == "GET" { rutasGET[r.Path] = true } } } migrations.VerificarUrlsDeSubmodulos(rutasGET) app.Http.Route404() log.Fatal(app.Http.Server.ServeWithGraceFullShutdown()) } }