feat(soporte): el ticket de correo ya sabe de quién es, y llega con prioridad
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6afad025c2
commit
bfcc320b7c
@@ -10,7 +10,8 @@ import (
|
||||
// ClasificacionCorreo es el veredicto sobre un correo entrante.
|
||||
type ClasificacionCorreo struct {
|
||||
EsSoporte bool `json:"es_soporte"`
|
||||
Categoria string `json:"categoria"` // soporte | comercial | factura | newsletter | notificacion | spam | otro
|
||||
Categoria string `json:"categoria"` // error | facturacion | acceso | consulta | comercial | newsletter | spam | otro
|
||||
Prioridad string `json:"prioridad"` // baja | media | alta | urgente
|
||||
Motivo string `json:"motivo"`
|
||||
}
|
||||
|
||||
@@ -29,6 +30,7 @@ func ClasificarCorreoSoporte(correo CorreoSoporte, contextoNegocio string) Clasi
|
||||
return ClasificacionCorreo{
|
||||
EsSoporte: false,
|
||||
Categoria: "newsletter",
|
||||
Prioridad: "baja",
|
||||
Motivo: "el correo viene marcado como masivo o automático en sus encabezados",
|
||||
}
|
||||
}
|
||||
@@ -40,13 +42,19 @@ func ClasificarCorreoSoporte(correo CorreoSoporte, contextoNegocio string) Clasi
|
||||
|
||||
sistema := `Clasificás correos que llegan a la casilla de soporte de una empresa.
|
||||
Respondé SOLO con este JSON, sin explicaciones ni bloques de código:
|
||||
{"es_soporte": true|false, "categoria": "soporte|comercial|factura|newsletter|notificacion|spam|otro", "motivo": "una frase corta"}
|
||||
{"es_soporte": true|false, "categoria": "...", "prioridad": "...", "motivo": "una frase corta"}
|
||||
|
||||
es_soporte = true cuando una persona pide ayuda, reporta un problema, hace una
|
||||
consulta sobre un servicio contratado o responde una conversación de soporte.
|
||||
es_soporte = false para newsletters, promociones, notificaciones automáticas de
|
||||
plataformas, facturas de proveedores y spam.
|
||||
Ante la duda, es_soporte = true: es peor ignorar a un cliente que abrir un ticket de más.`
|
||||
Ante la duda, es_soporte = true: es peor ignorar a un cliente que abrir un ticket de más.
|
||||
|
||||
categoria: error | facturacion | acceso | consulta | comercial | newsletter | spam | otro
|
||||
prioridad: urgente si algo está caído, si hay plata o datos en riesgo, o si el
|
||||
cliente dice que está bloqueado; alta si no puede trabajar pero tiene cómo
|
||||
seguir; media para el resto; baja para consultas sin apuro y para lo que no es
|
||||
soporte. La urgencia la da el problema, no el tono del mensaje.`
|
||||
|
||||
if c := strings.TrimSpace(contextoNegocio); c != "" {
|
||||
sistema += "\n\nContexto del negocio:\n" + c
|
||||
@@ -57,17 +65,22 @@ Ante la duda, es_soporte = true: es peor ignorar a un cliente que abrir un ticke
|
||||
salida, err := CompletarTextoIA("ia", sistema, usuario)
|
||||
if err != nil {
|
||||
log.Printf("[Soporte] No se pudo clasificar el correo, se trata como soporte: %v", err)
|
||||
return ClasificacionCorreo{EsSoporte: true, Categoria: "soporte", Motivo: "la clasificación falló"}
|
||||
return ClasificacionCorreo{EsSoporte: true, Categoria: "otro", Prioridad: "media", Motivo: "la clasificación falló"}
|
||||
}
|
||||
|
||||
var out ClasificacionCorreo
|
||||
if err := json.Unmarshal([]byte(soloJSON(salida)), &out); err != nil {
|
||||
log.Printf("[Soporte] Clasificación ilegible (%q), se trata como soporte", recortar(salida, 120))
|
||||
return ClasificacionCorreo{EsSoporte: true, Categoria: "soporte", Motivo: "respuesta ilegible del modelo"}
|
||||
return ClasificacionCorreo{EsSoporte: true, Categoria: "otro", Prioridad: "media", Motivo: "respuesta ilegible del modelo"}
|
||||
}
|
||||
if out.Categoria == "" {
|
||||
out.Categoria = "otro"
|
||||
}
|
||||
switch out.Prioridad {
|
||||
case "baja", "media", "alta", "urgente":
|
||||
default:
|
||||
out.Prioridad = "media"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user