Filtro (opcional, apagado por defecto): antes de abrir ticket, la IA lee el correo y decide si es soporte o ruido —newsletters, notificaciones de plataformas, facturas de proveedores, spam—. Falla abierto: si la IA no está configurada o se cae, el ticket se abre igual. Ignorar a un cliente es peor que tener un ticket de más. Hay un campo de contexto del negocio para los casos raros de cada uno. Los correos masivos se descartan por sus propios encabezados (List-Unsubscribe, Precedence, Auto-Submitted) sin gastar una llamada de IA. Y el motivo por el que el mismo correo se leía y se contestaba de nuevo: la deduplicación era solo un SELECT previo, que no sirve si el flag de leído no llegó a guardarse o si dos instancias leen el buzón a la vez. Ahora hay un índice único parcial sobre message_id: la base rechaza el segundo, y ese rechazo se trata como "ya estaba", no como error — sin ticket repetido y sin segunda auto-respuesta. Si algún correo no se pudo marcar como leído, "Revisar buzón ahora" lo dice en vez de dejarlo solo en el log: es la señal de que van a volver a leerse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60 lines
1.7 KiB
Go
60 lines
1.7 KiB
Go
package services
|
|
|
|
import (
|
|
"net/mail"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Los correos masivos se descartan por sus propios encabezados: este test pasa
|
|
// sin base de datos ni IA, que es justamente la prueba de que no las usa.
|
|
func TestClasificarCorreoAutomaticoNoGastaIA(t *testing.T) {
|
|
cl := ClasificarCorreoSoporte(CorreoSoporte{
|
|
From: "news@marketing.com", Subject: "20% off", Texto: "Oferta", Automatico: true,
|
|
}, "")
|
|
if cl.EsSoporte {
|
|
t.Error("un correo masivo no debería contar como soporte")
|
|
}
|
|
if cl.Categoria != "newsletter" {
|
|
t.Errorf("Categoria = %q", cl.Categoria)
|
|
}
|
|
}
|
|
|
|
func TestEsCorreoAutomatico(t *testing.T) {
|
|
casos := []struct {
|
|
nombre string
|
|
cabeceras string
|
|
want bool
|
|
}{
|
|
{"newsletter", "List-Unsubscribe: <mailto:baja@x.com>\r\n", true},
|
|
{"lista", "List-Id: <avisos.x.com>\r\n", true},
|
|
{"bulk", "Precedence: bulk\r\n", true},
|
|
{"auto-generado", "Auto-Submitted: auto-generated\r\n", true},
|
|
{"auto-submitted no", "Auto-Submitted: no\r\n", false},
|
|
{"persona", "", false},
|
|
}
|
|
for _, c := range casos {
|
|
msg, err := mail.ReadMessage(strings.NewReader(
|
|
"From: a@b.com\r\nSubject: x\r\n" + c.cabeceras + "\r\ncuerpo\r\n"))
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", c.nombre, err)
|
|
}
|
|
if got := esCorreoAutomatico(msg.Header); got != c.want {
|
|
t.Errorf("%s: esCorreoAutomatico = %v, want %v", c.nombre, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSoloJSON(t *testing.T) {
|
|
casos := map[string]string{
|
|
"```json\n{\"es_soporte\":true}\n```": `{"es_soporte":true}`,
|
|
"Claro:\n{\"es_soporte\":false}\nEspero...": `{"es_soporte":false}`,
|
|
`{"es_soporte":true}`: `{"es_soporte":true}`,
|
|
}
|
|
for in, want := range casos {
|
|
if got := soloJSON(in); got != want {
|
|
t.Errorf("soloJSON(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|