Un ticket que entraba por correo guardaba la dirección como texto suelto: no se podía cruzar con nada, el cliente no lo veía en su portal y no había forma de pedir "todos los tickets de Acme". Ahora al crearlo se resuelve el remitente — primero contra los usuarios del portal, si no contra el correo del cliente— y el ticket queda atado a quien escribió. El que no matchea queda como contacto externo, que también es información. Solo coincidencia exacta de dirección, nunca por dominio: con gmail.com de por medio, adivinar ata el ticket al cliente equivocado. Y la llamada de IA que ya clasificaba el correo ahora devuelve también prioridad y categoría en el mismo JSON: sin costo ni latencia extra. El campo prioridad existía desde siempre y nadie lo llenaba. La urgencia la define el problema descrito, no el tono del mensaje — está dicho en el prompt. En la lista de tickets se ven los tres datos nuevos: cliente (o "externo"), categoría y la prioridad que ya se mostraba. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.2 KiB
Go
72 lines
2.2 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClasificarNormalizaPrioridad(t *testing.T) {
|
|
// El correo automático corta antes de llamar a la IA, así que es el único
|
|
// camino que se puede probar sin base de datos — y ahí la prioridad tiene
|
|
// que salir puesta igual.
|
|
cl := ClasificarCorreoSoporte(CorreoSoporte{
|
|
From: "news@x.com", Subject: "promo", Automatico: true,
|
|
}, "")
|
|
if cl.Prioridad != "baja" {
|
|
t.Errorf("Prioridad = %q, want baja", cl.Prioridad)
|
|
}
|
|
}
|