Files
soft_usite/main.go
T
Lizandro GuarnizoandClaude Sonnet 5 997e3cc790 feat: log de auditoría técnica por agente
Nueva tabla umind_eventos_log + tab "Auditoría" en el panel: errores y
eventos que hasta ahora solo se podían ver pidiendo los logs del servidor
(fallos al llamar al AI, respuestas sin texto usable, tools/webhooks que
fallan, tokens de correo que no se pueden refrescar, firmas de WhatsApp
inválidas, errores procesando mensajes de Telegram/WhatsApp) quedan
registrados por agente, visibles directo en el panel con el JSON crudo
expandible para diagnosticar sin acceso al servidor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:58:59 -05:00

226 lines
7.6 KiB
Go
Executable File

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{},
// API Keys de /api/v2 (token + IP obligatoria + scopes)
&models.ApiKey{},
}
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.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)
app.Http.Route404()
log.Fatal(app.Http.Server.ServeWithGraceFullShutdown())
}
}