En cada ticket, "✨ Borrador" propone una respuesta usando los fragmentos más
parecidos de la base de conocimiento del agente de uMind que se elija en la
configuración de soporte, más el hilo de la conversación.
El borrador cae en el cuadro de respuesta y no se manda: lo revisa una persona
y aprieta enviar. Contestarle a un cliente con lo que dijo un modelo, sin que
nadie lo lea, es la forma más rápida de perderlo.
Sin agente configurado el borrador se arma igual, solo con la conversación —
peor, pero mejor que un error. El prompt le prohíbe inventar precios, plazos o
pasos que no estén en la documentación, y le pide decir qué falta preguntar
cuando no alcanza para resolver.
El cuadro de respuesta pasa a textarea: un borrador de varios renglones no
entraba en un input de una línea.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La lista de agentes era un directorio: nombre y activo/inactivo. No decía
si el agente estaba realmente listo para atender.
- Tarjetas en grid con el color de marca del agente (el mismo que ve el
visitante en el widget) y sus iniciales.
- Tres datos por agente: conversaciones de los últimos 7 días, fuentes de
conocimiento y canales activos. Salen de tres consultas agrupadas, no de
tres por agente: el N+1 en un listado es el que después no se saca.
- Badge de estado accionable. "sin conocimiento" cuando el agente está
activo pero no tiene ninguna fuente — ese agente responde de memoria e
inventa datos, que es exactamente el problema de la URL falsa, y hasta
ahora no se veía hasta que un cliente recibía la respuesta inventada.
- Esqueleto de carga: la pantalla hace 3 llamadas y quedaba en blanco el
tiempo suficiente como para parecer rota.
Arregla además el fondo en modo oscuro: index.html traía
<body class="bg-gray-50">, y esa utilidad le ganaba a la regla de body de
la capa base, así que el área principal quedaba clara con el tema oscuro
puesto (medido: --fondo resolvía a 9 13 22 pero el body pintaba 249 250 251).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
El widget hacía `data.respuesta || data.message` sin mirar el status, así
que un 403/404/500 se pintaba en una burbuja como si lo hubiera dicho el
agente. Imposible distinguir "el bot contestó raro" de "el widget está
siendo rechazado", que es justo el lazo en el que se puede quedar alguien
diagnosticando esto.
Además los rechazos del middleware ocurren ANTES del motor, así que no
dejaban rastro en ningún lado: ni en la auditoría del agente ni en el
navegador. De ahí el síntoma "el chat de prueba anda pero el widget no".
- El widget mira r.ok, manda el motivo real a la consola y al visitante le
muestra un mensaje neutro.
- AuthUmindWidget distingue las tres causas que el chat de prueba NO tiene
(agente inactivo, tenant inactivo, dominio no autorizado) y las registra
en la auditoría del agente con el detalle accionable — incluyendo qué
dominio llamó y cuáles están permitidos.
- GetUmindAgentePorSiteKey busca sin filtrar por activo, para poder decir
"está apagado" en vez de "no existe".
- Test del allowlist de dominios: es fail-closed y www.ejemplo.com NO
matchea ejemplo.com, la causa más probable de este síntoma.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Un tenant (negocio/sitio, dueño de los dominios permitidos) puede tener
varios UmindAgente independientes (ej. "Ventas", "Soporte"), cada uno con
su propia config de IA, tono, base de conocimiento, tools, canales y
conexión de correo. El site_key también pasa a ser por agente, así cada
uno tiene su propio <script> de widget embebible y su propio color.
Backend:
- Nuevo modelo UmindAgente (pkg/models/umind_agente.go), con SiteKey,
AiConfigID, Tono, MensajeBienvenida y Color — campos que antes vivían en
UmindTenant y se sacan de ahí (las columnas viejas quedan huérfanas sin
usar, no se hace DROP COLUMN).
- UmindDocumento, UmindChunk, UmindHerramienta, UmindCanal, UmindConexion y
UmindMensaje pasan de TenantID a AgenteID. El campo se agrega sin
"not null" para no romper el ALTER TABLE en Postgres sobre tablas que ya
tienen filas (ej. emetropolitana).
- migrations.MigrarUmindAgentes(): idempotente, crea un agente "Principal"
por cada tenant existente heredando lo que ya tenía configurado, y mueve
sus datos de tenant_id a agente_id. Corre en cada arranque normal, mismo
criterio que los Seed* — nada se rompe para los tenants ya en producción.
- Motor del agente, widget, canales (Telegram/WhatsApp) y OAuth de correo
ahora operan sobre UmindAgente; el tenant solo se consulta para el
chequeo de dominio permitido y el nombre del negocio que ve el visitante.
Frontend: nueva jerarquía de navegación tenant → lista de agentes
(TenantAgentes.vue) → detalle de un agente (AgenteDetail.vue, antes
TenantDetail.vue) con las mismas 6 tabs de siempre, ahora por agente. El
modal de tenant en el sidebar se achica a nombre/dominios/activo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>