Al navegar de /tenants/2 a /tenants/3, vue-router reusa la misma instancia
del componente porque es la misma ruta con otro parámetro. onMounted no
vuelve a dispararse, así que la vista seguía mostrando los agentes del
tenant anterior.
Las tres vistas tenían el mismo problema: TenantAgentes, AgenteDetail y
Uso. Ahora reaccionan al parámetro (watch con immediate) en vez de al
montaje.
En AgenteDetail se limpia el estado antes de pedir los datos nuevos: si no,
durante la carga se ven los documentos, canales y conversaciones del agente
anterior bajo el nombre del nuevo, que es peor que una pantalla vacía.
Verificado navegando de verdad entre dos tenants con agentes distintos: la
lista pasa de "Ventas T2" a "Soporte T3 / Cobros T3".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cuatro cosas que ya estaban a medio construir y no se usaban como canal.
1. El badge del widget ahora es un enlace con UTM. Cada cliente ya te
estaba dando exposición en su sitio y no se capitalizaba. El host sale
de la URL del propio script, así funciona igual en cualquier entorno.
2. Reporte del mes por agente en Excel: conversaciones atendidas, con qué
las abrió el visitante, y el consumo desglosado. Es lo que el cliente
necesita para justificar el gasto puertas adentro — un panel al que hay
que entrar no sirve para eso, un archivo que se reenvía sí. Reusa el
generador de xlsx del cronograma.
3. Aviso automático de agentes sin base de conocimiento (cron diario). Un
agente sin fuentes responde de memoria e inventa datos, el cliente
concluye que el producto no sirve y se va. Es el punto donde más gente
se cae y se detecta solo. Se avisa UNA vez por agente, usando el log de
auditoría como registro de envío para no necesitar tabla nueva ni
convertir el recordatorio en spam.
4. Tarjeta de uMind en el dashboard del portal: atajo si el cliente ya lo
tiene, oferta si no. Es el punto de contacto más barato que hay — ya
entró, ya confía, y el cobro se suma a la factura que ya recibe. El
mensaje lidera con notas de voz y fotos, que es lo más difícil de
copiar de lo que tenemos.
La consulta de conversaciones agrupa en SQL en vez de traerse el historial
entero para agrupar en Go.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
El SPA pasa a llamarse uMind Studio y vive en /studio (el nombre
"Orquestador" no le decía nada al cliente final, que ahora es quien lo usa).
Diseño:
- Los colores viven una sola vez como variables CSS + clases semánticas
(.card, .input, .btn-*, .badge-*). Antes cada elemento repetía el par
claro/oscuro a mano en cientos de lugares y cambiar un tono era buscar
y reemplazar.
- darkMode pasa de 'media' a 'class' con toggle propio persistido: el
usuario elige, no el sistema operativo. Se aplica antes de montar la
app para que no parpadee.
- Componentes compartidos (UiModal, UiBadge, UiEmptyState) donde antes
había markup duplicado inline.
Nueva pantalla de consumo (/tenants/:id/uso): filtros por fecha con
atajos, total del período, pendiente de facturar, barra contra el tope
del plan, desglose por tipo y gráfico por día. El gráfico son divs con
altura porcentual — no vale traer una librería de charts para esto.
Rename:
- /studio y /portal/studio; /orchestrator redirige 301 conservando la
ruta interna, así los enlaces guardados siguen funcionando. Los assets
del bundle quedan exentos (el `base` de Vite sigue en /orchestrator/):
redirigirlos rompería el SPA.
- El submódulo sembrado migra el mismo registro buscando por las tres
URLs históricas (/app/umind → /orchestrator → /studio) en vez de
dejar entradas duplicadas en el menú.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Acá es donde uMind deja de ser una herramienta interna: el cliente entra
a /portal/studio con su sesión de portal y gestiona lo suyo.
- UmindScopePortal/UmindScopeStaff es el ÚNICO punto donde se decide el
alcance. El del cliente sale de GetClienteIDsForPortalUser, el mismo
que ya autoriza el resto del portal. nil = staff sin restricción,
slice vacío = no ve nada; una ruta sin scope también cae en "no ve
nada" para que olvidarse el middleware falle visible y no abra todo.
- Un solo set de handlers montado bajo /app/umind y /portal/umind
(RegistrarRutasUmind). Duplicarlos sería duplicar las chances de
olvidar un chequeo.
- Guarda de acceso en TODOS los handlers, incluidos los sub-recursos que
llegan por :id (documento, tool, canal, conexión): hay que cargarlos
para saber de quién son, si no un cliente podría borrar el canal de
otro adivinando el id. Responden 404, no 403: un 403 confirmaría que
el recurso existe.
- Cierra un bug preexistente: las lecturas GET /app/umind/* no tenían
SoloAdmin ni pasaban por MenuMiddleware, así que cualquier usuario de
staff podía leer los tenants de todos los clientes.
- Límite de agentes por plan (409 con mensaje claro). Un tenant sin plan
no tiene límite: cortarles de golpe sería peor que dejarlos como estaban.
- /umind/ai-configs reemplaza con alcance a /app/api/ai-config/select,
que devolvía TODAS las configs del sistema.
- El SPA deduce por la URL si es staff o cliente (base del router,
prefijo de API y URL de login) y oculta lo que es solo de staff.
- Test de aislamiento entre clientes: 7 casos, incluido que un scope
vacío no se confunda con staff.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cada canal (Telegram/WhatsApp) de un agente ahora puede activar, de forma
independiente, que los audios entrantes se transcriban con el Whisper ASR
propio y que a las imágenes entrantes se les extraiga texto con el
servicio OCR propio, antes de pasarle el mensaje al agente. Antes esos
mensajes se ignoraban en silencio.
- UmindCanal gana usar_whisper_audio/usar_ocr_imagenes (default off).
- WhatsApp: se descarga el media vía Graph API (resolución de URL + fetch
con el mismo access_token del canal) y se enruta a Whisper/OCR según type.
- Telegram: se descarga el archivo vía getFile + CDN de archivos del bot,
mismo enrutamiento para voice/audio/photo.
- Panel: checkboxes en el alta de canal y toggles inline por canal ya
creado, en la tab Canales del agente.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nueva tabla umind_eventos_log + tab "Auditoría" en el panel: errores y
eventos que hasta ahora solo se podían ver pidiendo los logs del servidor
(fallos al llamar al AI, respuestas sin texto usable, tools/webhooks que
fallan, tokens de correo que no se pueden refrescar, firmas de WhatsApp
inválidas, errores procesando mensajes de Telegram/WhatsApp) quedan
registrados por agente, visibles directo en el panel con el JSON crudo
expandible para diagnosticar sin acceso al servidor.
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>