package services import ( "fmt" "log" "time" "github.com/sujit-baniya/fiber-boilerplate/app" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // Un agente creado pero sin fuentes de conocimiento no le sirve a nadie: // responde de memoria, inventa datos, y el cliente concluye que el producto no // funciona. Es el punto donde más gente se cae, y se detecta solo — así que en // vez de esperar a que alguien lo note, se avisa. // // El aviso se manda UNA sola vez por agente (UmindEventoLog hace de registro, // sin tabla nueva): insistir sobre algo que el cliente ya decidió ignorar // convierte un recordatorio útil en spam. const origenActivacion = "activacion" // diasParaAvisar es la espera antes del aviso. Lo suficiente para no molestar // a quien está configurando el agente en ese momento. const diasParaAvisar = 3 // AvisarAgentesSinConocimiento recorre los agentes activos que llevan varios // días sin ninguna fuente cargada y le escribe al cliente dueño. func AvisarAgentesSinConocimiento() { db := app.Http.Database.DB corte := time.Now().AddDate(0, 0, -diasParaAvisar) var agentes []models.UmindAgente if err := db.Where("activo = ? AND created_at < ? AND deleted_at IS NULL", true, corte). Find(&agentes).Error; err != nil { log.Printf("[ACTIVACION] no se pudieron listar los agentes: %v", err) return } for _, a := range agentes { var docs int64 db.Model(&models.UmindDocumento{}). Where("agente_id = ? AND deleted_at IS NULL", a.ID).Count(&docs) if docs > 0 { continue } if yaSeAviso(a.ID) { continue } if enviarAvisoSinConocimiento(&a) { models.RegistrarEventoUmind(a.ID, "warn", origenActivacion, "Se avisó al cliente que el agente no tiene base de conocimiento", fmt.Sprintf("El agente lleva más de %d días activo sin ninguna fuente cargada.", diasParaAvisar)) } } } // yaSeAviso usa el log de auditoría como registro de envío: el evento que // deja enviarAvisoSinConocimiento es la marca de "ya se mandó". func yaSeAviso(agenteID uint) bool { var n int64 app.Http.Database.DB.Model(&models.UmindEventoLog{}). Where("agente_id = ? AND origen = ?", agenteID, origenActivacion).Count(&n) return n > 0 } // enviarAvisoSinConocimiento devuelve true solo si el correo salió, para no // marcar como avisado a alguien que nunca recibió nada. func enviarAvisoSinConocimiento(a *models.UmindAgente) bool { tenant, err := models.GetUmindTenantByID(a.TenantID) if err != nil || tenant.ClienteID == nil { return false // sin cliente asignado no hay a quién escribirle } destinatarios, err := models.GetPortalUsersByClienteID(*tenant.ClienteID) if err != nil || len(destinatarios) == 0 { return false } url := GetPublicURL() + "/portal/studio" asunto := fmt.Sprintf("Tu asistente %q todavía no tiene información cargada", a.Nombre) enviadoAlguno := false for _, u := range destinatarios { if u.Email == "" { continue } html := fmt.Sprintf(`
U

Falta un paso para que tu asistente responda bien

uMind Studio · U-site

Hola %s,

Tu asistente %s ya está creado, pero todavía no tiene ninguna fuente de información cargada.

Sin eso solo puede dar respuestas genéricas: no conoce tus precios, tus horarios ni tus servicios. Cargarle tu sitio web toma un minuto y es lo que hace la diferencia entre un chat que responde cualquier cosa y uno que de verdad atiende.

Cargar la información

¿Preferís que lo hagamos nosotros? Respondé este correo con la dirección de tu sitio y lo dejamos listo.

`, u.Nombre, a.Nombre, url) if err := app.Http.Mail.Send(u.Email, asunto, html); err != nil { log.Printf("[ACTIVACION] no se pudo avisar a %s por el agente %d: %v", u.Email, a.ID, err) continue } enviadoAlguno = true } return enviadoAlguno }