Qué pasa cuando un cliente supera el tope de consumo de su plan: el servicio sigue funcionando y el excedente se factura en el próximo ciclo. Es la decisión de diseño y no cambia — cortarle el asistente a un negocio en medio de una conversación con un cliente suyo es peor que la factura. Lo que sí estaba mal es cómo se avisaba. El control de "a este ya le avisé" vivía en memoria, con un ponytail: que asumía reinicios raros. Con despliegue automático en cada push, ese flag se borra varias veces por día: el mismo cliente pasado de tope recibía el aviso de nuevo en el siguiente mensaje, y otra vez, y otra. Ahora se marca en la base, con un UPDATE condicionado que además lo hace atómico — dos mensajes que crucen el tope a la vez, o dos réplicas del proceso, avisan una sola vez. Y el aviso iba sólo al Telegram del staff. El dueño del negocio, que es el que va a recibir la factura con el excedente, no se enteraba por ningún lado: tenía que entrar al panel a mirar. Ahora le llega un correo que dice cuánto lleva consumido, cuánto incluye su plan y —lo más importante— que su asistente sigue funcionando con normalidad. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60 lines
2.0 KiB
Go
60 lines
2.0 KiB
Go
package services
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// El período del aviso sale del mes calendario. Si se calculara mal, el aviso
|
|
// se repetiría todos los meses el día 1 o no se repetiría nunca más.
|
|
func TestElPeriodoDelCicloEsElMesCalendario(t *testing.T) {
|
|
desde, hasta := cicloActual()
|
|
|
|
if desde.Day() != 1 || desde.Hour() != 0 || desde.Minute() != 0 {
|
|
t.Errorf("el ciclo tiene que arrancar el día 1 a las 00:00, arrancó %v", desde)
|
|
}
|
|
if !hasta.After(desde) {
|
|
t.Errorf("el fin del ciclo (%v) tiene que ser posterior al inicio (%v)", hasta, desde)
|
|
}
|
|
if hasta.Sub(desde) < 28*24*time.Hour || hasta.Sub(desde) > 31*24*time.Hour {
|
|
t.Errorf("el ciclo dura %v, fuera del rango de un mes", hasta.Sub(desde))
|
|
}
|
|
|
|
// El período que se guarda para no repetir el aviso.
|
|
periodo := desde.Format("2006-01")
|
|
if len(periodo) != 7 || !strings.Contains(periodo, "-") {
|
|
t.Errorf("el período tiene que ser YYYY-MM, es %q", periodo)
|
|
}
|
|
// Tiene que entrar en la columna, que es size:7.
|
|
if len(periodo) > 7 {
|
|
t.Errorf("el período %q no entra en la columna de 7 caracteres", periodo)
|
|
}
|
|
}
|
|
|
|
// El aviso se manda una sola vez por mes. Antes el control estaba en memoria y
|
|
// cada despliegue lo borraba — con varios despliegues por día, el mismo cliente
|
|
// recibía el aviso una y otra vez.
|
|
func TestElControlDelAvisoNoViveEnMemoria(t *testing.T) {
|
|
fuente, err := leerFuente("umind_uso_service.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(fuente, "avisosTope") {
|
|
t.Error("quedó el flag en memoria: un reinicio vuelve a disparar el aviso")
|
|
}
|
|
if !strings.Contains(fuente, "MarcarAvisoTope") {
|
|
t.Error("el aviso tiene que marcarse en la base para sobrevivir a un reinicio")
|
|
}
|
|
// Y el cliente tiene que enterarse, no sólo el staff.
|
|
if !strings.Contains(fuente, "avisarTopeAlCliente") {
|
|
t.Error("falta el aviso al cliente: enterarse en la factura es la peor forma")
|
|
}
|
|
}
|
|
|
|
func leerFuente(nombre string) (string, error) {
|
|
b, err := os.ReadFile(nombre)
|
|
return string(b), err
|
|
}
|