From eddec45857d2a491729d813ea1461bb3be42b4b1 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sun, 23 Aug 2026 20:08:46 -0500 Subject: [PATCH] =?UTF-8?q?feat(umind):=20el=20correo=20entra=20al=20agent?= =?UTF-8?q?e=20por=20dos=20puertas=20distintas=20=E2=80=94=20y=20son=20dis?= =?UTF-8?q?tintas=20a=20prop=C3=B3sito?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit En "Dónde atiende", el canal correo: el agente lee una casilla y responde solo los correos que llegan, con su base de conocimiento, igual que atiende WhatsApp. Como el correo no tiene webhook, se revisa por intervalo — y el intervalo lo elige el cliente por canal (2 a 60 minutos): una inmobiliaria quiere 2, a un estudio contable con 30 le sobra. El cron corre cada minuto pero cada casilla se revisa solo cuando le toca, con un pool de 8 para que 100 casillas no salgan a la red en el mismo instante. Las guardas que separan "asistente" de "incidente", cada una con su test: nunca responde correo automático ni se responde a sí mismo (el bucle con otro autoresponder); la revisión se marca ANTES de conectar, así una contraseña cambiada no martilla el login cada minuto hasta que el host del cliente nos bloquea; y el correo se marca leído recién cuando la respuesta salió — si el envío falla, queda sin leer y se reintenta. En "Lo que sabe", las cuentas de correo: casillas que el agente consulta a pedido — "revisame los correos de hoy y haceme un resumen" — sin nada de fondo. Solo lectura en serio: Peek, INBOX en read-only, y un test que falla si alguien le agrega un marcado. Ahora se pueden conectar varias por agente; con más de una, el modelo pregunta cuál en vez de adivinar — resumirle a alguien la casilla que no pidió no es un error menor. El alta pide correo y contraseña: el host se deduce (mail.) y el campo técnico aparece recién si eso falla. Se prueba la conexión antes de guardar, con la persona mirando. Enviar por una cuenta conectada está bloqueado a propósito: para responder correos está el canal, con sus guardas. Una tool de envío sin límites es una máquina de spam con el dominio del cliente. La navegación acompaña: "Lo que sabe" agrupa Información, Cuentas de correo y Herramientas — tres formas de saber, no tres pantallas sueltas — y Avanzado queda solo con Problemas. El SMTP se unificó en una sola implementación que comparten soporte y el canal: el bug de STARTTLS que abría dos conexiones ya se pagó una vez. Co-Authored-By: Claude Opus 5 --- orchestrator/src/views/AgenteDetail.vue | 29 +++- .../src/views/agente/EstadoAgente.vue | 2 +- orchestrator/src/views/agente/TabCanales.vue | 82 +++++++-- .../src/views/agente/TabConexiones.vue | 145 ++++++++++++++-- pkg/models/umind_canal.go | 27 +++ pkg/models/umind_conexion.go | 17 +- pkg/services/cron_service.go | 7 + pkg/services/smtp_envio.go | 93 ++++++++++ pkg/services/soporte_service.go | 75 +------- pkg/services/umind_agent_service.go | 71 ++++++-- pkg/services/umind_canal_correo_service.go | 151 ++++++++++++++++ pkg/services/umind_canal_correo_test.go | 78 +++++++++ pkg/services/umind_conexion_imap.go | 162 ++++++++++++++++++ public/orchestrator/assets/index-CPU0iTo1.js | 26 +++ public/orchestrator/assets/index-CeuZ8PUd.css | 1 - public/orchestrator/assets/index-DsqIzlWM.css | 1 + public/orchestrator/assets/index-kEapBzai.js | 26 --- public/orchestrator/index.html | 4 +- rest/controllers/umind_admin_controller.go | 28 ++- .../umind_conexion_imap_controller.go | 103 +++++++++++ rest/routes/umind.go | 2 + 21 files changed, 987 insertions(+), 143 deletions(-) create mode 100644 pkg/services/smtp_envio.go create mode 100644 pkg/services/umind_canal_correo_service.go create mode 100644 pkg/services/umind_canal_correo_test.go create mode 100644 pkg/services/umind_conexion_imap.go create mode 100644 public/orchestrator/assets/index-CPU0iTo1.js delete mode 100644 public/orchestrator/assets/index-CeuZ8PUd.css create mode 100644 public/orchestrator/assets/index-DsqIzlWM.css delete mode 100644 public/orchestrator/assets/index-kEapBzai.js create mode 100644 rest/controllers/umind_conexion_imap_controller.go diff --git a/orchestrator/src/views/AgenteDetail.vue b/orchestrator/src/views/AgenteDetail.vue index 4f01596..d16fa30 100644 --- a/orchestrator/src/views/AgenteDetail.vue +++ b/orchestrator/src/views/AgenteDetail.vue @@ -271,12 +271,19 @@ const ZONAS = [ ['canales', 'Dónde atiende'], ] -const AVANZADO = [ +// "Lo que sabe" agrupa todo lo que alimenta las respuestas: la información +// cargada, las casillas que puede consultar y las herramientas que puede +// llamar. Son tres formas de saber, no tres pantallas sueltas. +const SUBSECCIONES_SABE = [ + ['conocimiento', 'Información'], + ['conexiones', 'Cuentas de correo'], ['herramientas', 'Herramientas'], - ['conexiones', 'Cuentas conectadas'], - ['auditoria', 'Problemas'], ] +const enLoQueSabe = computed(() => SUBSECCIONES_SABE.some(([k]) => k === tab.value)) + +const AVANZADO = [['auditoria', 'Problemas']] + // La guía se muestra hasta que sabe algo Y atiende en algún lado. Probar es // deseable pero no bloquea: nadie queda preso de la guía por no haber chateado. const configurando = computed( @@ -368,7 +375,7 @@ watch( v-for="[key, label] in ZONAS" :key="key" class="px-3.5 py-1.5 rounded-full text-sm whitespace-nowrap transition-colors" - :class="tab === key + :class="(key === 'conocimiento' ? enLoQueSabe : tab === key) ? 'bg-brand text-white font-medium' : 'bg-white dark:bg-gray-900 text-gray-600 dark:text-gray-400 border border-borde hover:border-brand/50'" @click="tab = key" @@ -387,6 +394,20 @@ watch( +
+ +
+
-

+ +

+ Revisa la casilla cada {{ c.intervalo_minutos >= 60 ? '1 hora' : `${c.intervalo_minutos} minutos` }} +

+

Webhook: {{ c.webhook_url }}

@@ -179,6 +200,7 @@ async function eliminarCanal(c) { +