From 706263db9229a3f846909892a33fb4cc96d3dc21 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:48:41 -0500 Subject: [PATCH] feat(studio): "Lo que hace", la bandeja de pendientes y la barra del espacio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La estructura que faltaba para que todo lo nuevo se encuentre. NIVEL ESPACIO — barra propia: Agentes · Pendientes · Archivos · Plantillas · Consumo · Tu IA. Antes Consumo y Tu IA eran dos enlaces sueltos en el header que desaparecían al entrar. El badge de Pendientes solo aparece cuando hay algo esperando: un cero permanente enseña a ignorar el lugar donde después va lo importante. NIVEL AGENTE — nueva zona "Lo que hace", al lado de "Lo que sabe" y "Dónde atiende". Tres preguntas distintas: de dónde saca las respuestas, qué es capaz de hacer, por dónde lo encuentran. Ahí viven los recordatorios y las vigilancias — se crean hablando, pero se ven y se cancelan acá: un agente que agenda cosas invisibles es un agente en el que no se confía. Los recordatorios dicen por dónde van a llegar ("te llega por Telegram"), no el sessionID crudo. Las vigilancias muestran si la condición se está cumpliendo ahora mismo. En los canales, el checkbox que define todo el modelo de permisos: "este canal es mío, no de mis clientes". Explicado por lo que significa, no por cómo funciona — marcarlo habilita acciones directas, no marcarlo hace que todo lo que salga espere el visto bueno. Archivos muestra la cuota consumida con barra, marca cuáles ya son conocimiento, y ofrece "usar como conocimiento" en un clic. Plantillas permite subir el contrato que el cliente ya usa y que la IA lo convierta. api.postForm: el multipart ya se escribía a mano en dos lugares y este era el tercero. Sin Content-Type fijado — el boundary lo pone el navegador. Verificado con capturas de las cuatro pantallas nuevas. Co-Authored-By: Claude Opus 5 --- orchestrator/src/components/NavEspacio.vue | 51 +++++ orchestrator/src/lib/api.js | 8 +- orchestrator/src/router.js | 6 + orchestrator/src/views/AgenteDetail.vue | 4 + orchestrator/src/views/AiPropia.vue | 3 + orchestrator/src/views/Archivos.vue | 170 +++++++++++++++++ orchestrator/src/views/Pendientes.vue | 136 ++++++++++++++ orchestrator/src/views/Plantillas.vue | 174 ++++++++++++++++++ orchestrator/src/views/TenantAgentes.vue | 8 +- orchestrator/src/views/Uso.vue | 3 + orchestrator/src/views/agente/TabAcciones.vue | 125 +++++++++++++ orchestrator/src/views/agente/TabCanales.vue | 18 +- public/orchestrator/assets/index-B4CrdUeB.js | 27 --- public/orchestrator/assets/index-BTIDeKXh.js | 29 +++ public/orchestrator/assets/index-CfqdBqVI.css | 1 + public/orchestrator/assets/index-DsqIzlWM.css | 1 - public/orchestrator/index.html | 4 +- rest/controllers/umind_admin_controller.go | 6 + 18 files changed, 738 insertions(+), 36 deletions(-) create mode 100644 orchestrator/src/components/NavEspacio.vue create mode 100644 orchestrator/src/views/Archivos.vue create mode 100644 orchestrator/src/views/Pendientes.vue create mode 100644 orchestrator/src/views/Plantillas.vue create mode 100644 orchestrator/src/views/agente/TabAcciones.vue delete mode 100644 public/orchestrator/assets/index-B4CrdUeB.js create mode 100644 public/orchestrator/assets/index-BTIDeKXh.js create mode 100644 public/orchestrator/assets/index-CfqdBqVI.css delete mode 100644 public/orchestrator/assets/index-DsqIzlWM.css diff --git a/orchestrator/src/components/NavEspacio.vue b/orchestrator/src/components/NavEspacio.vue new file mode 100644 index 0000000..704783b --- /dev/null +++ b/orchestrator/src/components/NavEspacio.vue @@ -0,0 +1,51 @@ + + + diff --git a/orchestrator/src/lib/api.js b/orchestrator/src/lib/api.js index 1bbcb94..d3fcedf 100644 --- a/orchestrator/src/lib/api.js +++ b/orchestrator/src/lib/api.js @@ -5,9 +5,12 @@ import { contexto } from './contexto.js' // PortalAuth() redirigen al login devolviendo HTML en vez de un 401 JSON — // fetch sigue ese redirect solo, así que lo detectamos por el content-type. async function request(path, options = {}) { + const esFormData = options.body instanceof FormData const res = await fetch(path, { ...options, - headers: { 'Content-Type': 'application/json', ...options.headers }, + headers: esFormData + ? options.headers + : { 'Content-Type': 'application/json', ...options.headers }, }) const contentType = res.headers.get('content-type') || '' @@ -29,6 +32,9 @@ async function request(path, options = {}) { export const api = { get: (path) => request(path), + // Sin Content-Type a mano: en un multipart el boundary lo pone el navegador, + // y fijarlo nosotros deja el body ilegible del lado del servidor. + postForm: (path, formData) => request(path, { method: 'POST', body: formData, headers: {} }), post: (path, body) => request(path, { method: 'POST', body: JSON.stringify(body) }), put: (path, body) => request(path, { method: 'PUT', body: JSON.stringify(body) }), del: (path) => request(path, { method: 'DELETE' }), diff --git a/orchestrator/src/router.js b/orchestrator/src/router.js index 7e1613e..2cf9d4b 100644 --- a/orchestrator/src/router.js +++ b/orchestrator/src/router.js @@ -4,6 +4,9 @@ import TenantAgentes from './views/TenantAgentes.vue' import AgenteDetail from './views/AgenteDetail.vue' import Uso from './views/Uso.vue' import AiPropia from './views/AiPropia.vue' +import Pendientes from './views/Pendientes.vue' +import Archivos from './views/Archivos.vue' +import Plantillas from './views/Plantillas.vue' import { contexto } from './lib/contexto.js' const router = createRouter({ @@ -13,6 +16,9 @@ const router = createRouter({ { path: '/tenants/:id', name: 'tenant-agentes', component: TenantAgentes, props: true }, { path: '/tenants/:id/uso', name: 'uso', component: Uso, props: true }, { path: '/tenants/:id/ia', name: 'ai-propia', component: AiPropia, props: true }, + { path: '/tenants/:id/pendientes', name: 'pendientes', component: Pendientes, props: true }, + { path: '/tenants/:id/archivos', name: 'archivos', component: Archivos, props: true }, + { path: '/tenants/:id/plantillas', name: 'plantillas', component: Plantillas, props: true }, { path: '/tenants/:tenantId/agentes/:agenteId', name: 'agente-detail', diff --git a/orchestrator/src/views/AgenteDetail.vue b/orchestrator/src/views/AgenteDetail.vue index d16fa30..b155186 100644 --- a/orchestrator/src/views/AgenteDetail.vue +++ b/orchestrator/src/views/AgenteDetail.vue @@ -6,6 +6,7 @@ import { apiUmind, contexto } from '../lib/contexto.js' import UiEmptyState from '../components/ui/UiEmptyState.vue' import UiIcono from '../components/ui/UiIcono.vue' import TabAuditoria from './agente/TabAuditoria.vue' +import TabAcciones from './agente/TabAcciones.vue' import TabConexiones from './agente/TabConexiones.vue' import TabChat from './agente/TabChat.vue' import TabConversaciones from './agente/TabConversaciones.vue' @@ -268,6 +269,7 @@ async function cargarConexiones() { const ZONAS = [ ['conversaciones', 'Conversaciones'], ['conocimiento', 'Lo que sabe'], + ['acciones', 'Lo que hace'], ['canales', 'Dónde atiende'], ] @@ -663,6 +665,8 @@ watch( /> + + diff --git a/orchestrator/src/views/AiPropia.vue b/orchestrator/src/views/AiPropia.vue index 9ec0ba2..691078c 100644 --- a/orchestrator/src/views/AiPropia.vue +++ b/orchestrator/src/views/AiPropia.vue @@ -4,6 +4,7 @@ import { api } from '../lib/api.js' import { apiUmind } from '../lib/contexto.js' import UiEmptyState from '../components/ui/UiEmptyState.vue' import UiIcono from '../components/ui/UiIcono.vue' +import NavEspacio from '../components/NavEspacio.vue' const props = defineProps({ id: { type: String, required: true } }) @@ -121,6 +122,8 @@ async function borrar(c) {