package services import ( "fmt" "log" "sync" "time" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // avisosTope recuerda a qué tenants ya se les avisó en el ciclo actual, para // no mandar un aviso por cada mensaje una vez pasado el tope. Se pierde al // reiniciar (y entonces se vuelve a avisar una vez): aceptable para un aviso, // y mucho más simple que una columna de estado en el tenant. // // ponytail: en memoria y por proceso. Si algún día corren varias réplicas, // cada una avisa una vez — mover el flag a la BD si eso molesta. var avisosTope struct { sync.Mutex ultimo map[uint]string // tenantID → "2026-08" del último aviso } // RegistrarUso persiste el consumo y avisa si el tenant superó el tope de su // plan. El aviso NO corta el servicio: el agente sigue respondiendo. func RegistrarUso(agenteID uint, tipo string, cantidad float64, unidad string) { if agenteID == 0 || cantidad <= 0 { return } models.RegistrarUsoUmind(agenteID, tipo, cantidad, unidad) agente, err := models.GetUmindAgenteByID(agenteID) if err != nil { return } plan := models.GetPlanDeTenant(agente.TenantID) if plan == nil || plan.TopeConsumoMensual <= 0 { return } desde, hasta := cicloActual() resumen, err := models.GetResumenUso(agente.TenantID, desde, hasta) if err != nil { return } total := 0.0 for _, r := range resumen { total += r.Costo } if total < plan.TopeConsumoMensual { return } avisarTopeConsumo(agente.TenantID, plan, total, desde) } // cicloActual devuelve el mes calendario en curso. Se usa el mes natural (no // la fecha de alta del contrato) porque es lo que el cliente entiende al // mirar "consumo de agosto" en su panel. func cicloActual() (time.Time, time.Time) { ahora := time.Now() desde := time.Date(ahora.Year(), ahora.Month(), 1, 0, 0, 0, 0, ahora.Location()) return desde, desde.AddDate(0, 1, 0) } func avisarTopeConsumo(tenantID uint, plan *models.UmindPlan, total float64, desde time.Time) { periodo := desde.Format("2006-01") avisosTope.Lock() if avisosTope.ultimo == nil { avisosTope.ultimo = map[uint]string{} } yaAvisado := avisosTope.ultimo[tenantID] == periodo avisosTope.ultimo[tenantID] = periodo avisosTope.Unlock() if yaAvisado { return } tenant, err := models.GetUmindTenantByID(tenantID) nombre := fmt.Sprintf("tenant %d", tenantID) if err == nil { nombre = tenant.Nombre } log.Printf("[UMIND_USO] %s superó el tope de consumo del plan %s: %.2f %s de %.2f", nombre, plan.Nombre, total, plan.Moneda, plan.TopeConsumoMensual) sendTelegramAdmin(fmt.Sprintf( "📊 Tope de consumo superado\nCliente: %s\nPlan: %s\nConsumo del mes: %s %.2f (tope %s %.2f)\n\nEl servicio sigue activo — el excedente se cobra en el próximo ciclo.", escapeTelegramHTML(nombre), escapeTelegramHTML(plan.Nombre), plan.Moneda, total, plan.Moneda, plan.TopeConsumoMensual)) }