From b7c52f174a0826206aa24641645db7dc4f22f83c Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Mon, 24 Aug 2026 22:04:03 -0500 Subject: [PATCH] =?UTF-8?q?fix(umind):=20auditor=C3=ADa=20de=20las=20cinco?= =?UTF-8?q?=20features=20=E2=80=94=20cinco=20defectos=20y=20un=20guard=20p?= =?UTF-8?q?ermanente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revisión de coherencia de todo lo que entró. Cinco cosas mal, ninguna detectable compilando: 1. LA PUERTA DE TOOLS DECIDÍA AL REVÉS. executeUmindTool encolaba antes de clasificar, así que en un canal público CUALQUIER nombre de herramienta terminaba como pendiente de aprobación — incluidos los que el modelo inventa y los internos, cuyos guards ("solo el dueño") quedaban inalcanzables porque vivían más abajo en el flujo. Un visitante del widget podía llenarle al dueño la bandeja de solicitudes para ejecutar cosas que no existen. Ahora se clasifica primero: lectura ejecuta, interna se rechaza si el pedido es público, acción se encola, desconocida se rechaza. Verificado inyectando la falla: el test falla y vuelve a pasar al arreglarlo. 2. El enlace "Ver el documento" de la bandeja estaba muerto: archivo_id no lo escribía nadie. Ahora la acción aprobada engancha el PDF que produjo. 3. Los PDF generados esquivaban la cuota de almacenamiento. El tope del plan se evitaba pidiéndole documentos al agente en vez de subiendo archivos. 4. La vista de Consumo no conocía el tipo "documento" — el rubro nuevo salía sin nombre ni color. Y el icono que le puse no existía en el set, así que habría quedado invisible: el mismo bug de los iconos de hace unas semanas, que compila y se ve vacío. 5. Las seis lecturas nuevas se registraron con w() en vez de r(), exigiendo SoloAdmin para leer. No era una fuga —era más estricto— pero un usuario staff sin admin veía la lista de agentes y 403 en sus avisos. Y el guard que evita el próximo: TestTodoHandlerUmindValidaAlcance recorre los 65 handlers de uMind y exige que cada uno valide el alcance del tenant, o esté en una lista de exenciones con el motivo escrito. Los mismos handlers se montan para staff y para clientes; el middleware de scope deja los tenants permitidos en el contexto pero no mira el :id de la URL — eso lo hace cada handler, y el que se olvide compila igual. Al escribirlo el propio test encontró que mi lista de exenciones sobraba en dos, y verifiqué con un handler con fuga deliberada que lo detecta. Co-Authored-By: Claude Opus 5 --- orchestrator/src/views/Uso.vue | 1 + pkg/models/umind_accion.go | 6 ++ pkg/models/umind_archivo.go | 17 ++++ pkg/services/umind_agent_service.go | 19 +++- pkg/services/umind_aprobacion_service.go | 41 ++++++++ pkg/services/umind_aprobacion_test.go | 56 +++++++++++ pkg/services/umind_documento_tools.go | 5 + .../{index-BTIDeKXh.js => index-NgTBLrrq.js} | 2 +- public/orchestrator/index.html | 2 +- rest/controllers/umind_alcance_test.go | 93 +++++++++++++++++++ rest/routes/umind.go | 12 +-- 11 files changed, 244 insertions(+), 10 deletions(-) rename public/orchestrator/assets/{index-BTIDeKXh.js => index-NgTBLrrq.js} (91%) create mode 100644 rest/controllers/umind_alcance_test.go diff --git a/orchestrator/src/views/Uso.vue b/orchestrator/src/views/Uso.vue index 7712ea5..72a9d65 100644 --- a/orchestrator/src/views/Uso.vue +++ b/orchestrator/src/views/Uso.vue @@ -21,6 +21,7 @@ const ETIQUETAS = { ia: { nombre: 'Inteligencia artificial', icono: 'cerebro', color: '#8eb02f' }, ocr: { nombre: 'Lectura de imágenes', icono: 'imagen', color: '#2f7fb0' }, whisper: { nombre: 'Transcripción de audio', icono: 'microfono', color: '#b0752f' }, + documento: { nombre: 'Documentos generados', icono: 'archivo', color: '#7b5fb0' }, } const etiqueta = (t) => ETIQUETAS[t] || { nombre: t, icono: 'grafico', color: '#94a3b8' } diff --git a/pkg/models/umind_accion.go b/pkg/models/umind_accion.go index 43f1a76..79afd78 100644 --- a/pkg/models/umind_accion.go +++ b/pkg/models/umind_accion.go @@ -114,3 +114,9 @@ func VencerAccionesViejas() int64 { Update("estado", "vencida") return res.RowsAffected } + +// VincularArchivoAAccion engancha el documento producido a la acción. +func VincularArchivoAAccion(accionID, archivoID uint) { + app.Http.Database.DB.Model(&UmindAccionPendiente{}).Where("id = ?", accionID). + Update("archivo_id", archivoID) +} diff --git a/pkg/models/umind_archivo.go b/pkg/models/umind_archivo.go index 4c4b325..8ff6019 100644 --- a/pkg/models/umind_archivo.go +++ b/pkg/models/umind_archivo.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/sujit-baniya/fiber-boilerplate/app" @@ -67,3 +68,19 @@ func VincularArchivoADocumento(archivoID, documentoID uint) error { // AhoraUnix da el prefijo de tiempo para los nombres de archivo en disco. func AhoraUnix() int64 { return time.Now().Unix() } + +// HayEspacioUmind dice si entra un archivo más en el espacio del plan. +func HayEspacioUmind(tenantID uint, bytes int64) error { + tenant, err := GetUmindTenantByID(tenantID) + if err != nil || tenant.PlanID == nil { + return nil // sin plan asignado no se limita + } + plan, err := GetUmindPlanByID(*tenant.PlanID) + if err != nil || plan.MaxAlmacenamientoMB <= 0 { + return nil + } + if EspacioUsadoUmind(tenantID)+bytes > int64(plan.MaxAlmacenamientoMB)<<20 { + return fmt.Errorf("el espacio de archivos del plan está lleno (%d MB)", plan.MaxAlmacenamientoMB) + } + return nil +} diff --git a/pkg/services/umind_agent_service.go b/pkg/services/umind_agent_service.go index 7283ee0..388181a 100644 --- a/pkg/services/umind_agent_service.go +++ b/pkg/services/umind_agent_service.go @@ -155,8 +155,23 @@ func umindEmailTools() []agentTool { // esperando a una persona. El trabajo real vive en ejecutarHerramienta, que es // lo que corre después una aprobación. func executeUmindTool(agenteID uint, sessionID string, sesionInterna bool, name string, args map[string]interface{}) string { - if RequiereAprobacion(sesionInterna, name) { - return EncolarAccion(agenteID, sessionID, name, args) + switch CategoriaTool(agenteID, name) { + case "desconocida": + // Un nombre que no existe se rechaza acá y no se encola. Si no, el + // modelo alucinando un nombre le llena la bandeja al dueño de + // solicitudes para ejecutar herramientas que nunca existieron. + return fmt.Sprintf(`{"error": "herramienta desconocida: %s"}`, name) + case "interna": + // Programar avisos y vigilancias gasta el plan del dueño. No se + // encolan para que las apruebe: desde un canal público directamente + // no se piden. + if !sesionInterna { + return `{"error": "eso solo puede pedirlo el dueño desde su canal privado"}` + } + case "accion": + if !sesionInterna { + return EncolarAccion(agenteID, sessionID, name, args) + } } return ejecutarHerramienta(agenteID, sessionID, sesionInterna, name, args) } diff --git a/pkg/services/umind_aprobacion_service.go b/pkg/services/umind_aprobacion_service.go index 1816942..3feff8a 100644 --- a/pkg/services/umind_aprobacion_service.go +++ b/pkg/services/umind_aprobacion_service.go @@ -104,6 +104,11 @@ func EjecutarAccionAprobada(accion *models.UmindAccionPendiente, quien string) e if err := models.CerrarAccion(accion.ID, estado, quien, "", resultado); err != nil { return err } + // Si la acción produjo un documento, se engancha a la acción para que el + // que aprobó pueda abrirlo desde la misma fila donde apretó el botón. + if id := archivoDelResultado(resultado); id != nil { + models.VincularArchivoAAccion(accion.ID, *id) + } if estado == "fallida" { models.RegistrarEventoUmind(accion.AgenteID, "error", "aprobacion", "Una acción aprobada falló al ejecutarse: "+accion.Resumen, resultado) @@ -148,3 +153,39 @@ func avisarAccionPendiente(agente *models.UmindAgente, accion *models.UmindAccio } } } + +// CategoriaTool clasifica la llamada ANTES de decidir qué hacer con ella: +// +// lectura — no sale nada del negocio, se ejecuta siempre +// interna — gasta el plan del dueño, solo desde su canal privado +// accion — sale hacia afuera, espera aprobación si el pedido es público +// desconocida — el nombre no existe: se rechaza, nunca se encola +// +// La clasificación existe porque sin ella todo lo que no fuera lectura se +// encolaba, incluidos los nombres que el modelo inventa. +func CategoriaTool(agenteID uint, name string) string { + switch name { + case "buscar_conocimiento", "leer_bandeja": + return "lectura" + case "programar_aviso", "listar_avisos", "cancelar_aviso", + "crear_vigilancia", "listar_vigilancias", "cancelar_vigilancia": + return "interna" + case "enviar_correo", "generar_documento": + return "accion" + } + if _, err := models.GetUmindHerramientaByNombre(agenteID, name); err == nil { + return "accion" + } + return "desconocida" +} + +// archivoDelResultado saca el archivo_id que devuelve generar_documento. +func archivoDelResultado(resultado string) *uint { + var r struct { + ArchivoID uint `json:"archivo_id"` + } + if err := json.Unmarshal([]byte(resultado), &r); err != nil || r.ArchivoID == 0 { + return nil + } + return &r.ArchivoID +} diff --git a/pkg/services/umind_aprobacion_test.go b/pkg/services/umind_aprobacion_test.go index 04c698b..2459abc 100644 --- a/pkg/services/umind_aprobacion_test.go +++ b/pkg/services/umind_aprobacion_test.go @@ -82,3 +82,59 @@ func TestAccionAprobadaUsaElPayloadGuardado(t *testing.T) { t.Error("falta el reclamo: sin él, dos aprobaciones simultáneas ejecutan dos veces") } } + +// La clasificación es lo que decide entre ejecutar, encolar y rechazar. Sin +// ella, todo lo que no fuera lectura se encolaba —incluidos los nombres que el +// modelo inventa—, y la bandeja del dueño se llenaba de solicitudes para +// ejecutar herramientas que nunca existieron. +func TestCategoriaTool(t *testing.T) { + casos := map[string]string{ + "buscar_conocimiento": "lectura", + "leer_bandeja": "lectura", + "programar_aviso": "interna", + "cancelar_aviso": "interna", + "listar_avisos": "interna", + "crear_vigilancia": "interna", + "listar_vigilancias": "interna", + "enviar_correo": "accion", + "generar_documento": "accion", + } + for tool, quiero := range casos { + // agenteID 0: las tools con nombre fijo no consultan la base. + if got := CategoriaTool(0, tool); got != quiero { + t.Errorf("CategoriaTool(%q) = %q, quiero %q", tool, got, quiero) + } + } +} + +// El camino completo desde un canal público, que es donde escribe cualquiera. +func TestPuertaDeToolsDesdeCanalPublico(t *testing.T) { + b, err := os.ReadFile("umind_agent_service.go") + if err != nil { + t.Fatal(err) + } + s := string(b) + i := strings.Index(s, "func executeUmindTool") + cuerpo := s[i:] + cuerpo = cuerpo[:strings.Index(cuerpo, "\nfunc ejecutarHerramienta")] + + // La clasificación tiene que decidir ANTES de encolar. + cat := strings.Index(cuerpo, "CategoriaTool") + enc := strings.Index(cuerpo, "EncolarAccion") + if cat < 0 || enc < 0 || cat > enc { + t.Fatal("la clasificación tiene que ir antes de encolar") + } + // Un nombre inexistente se rechaza, no se encola. + desc := cuerpo[strings.Index(cuerpo, `case "desconocida":`):strings.Index(cuerpo, `case "interna":`)] + if strings.Contains(desc, "EncolarAccion") { + t.Error("una herramienta desconocida no puede encolarse") + } + // Las internas se rechazan desde un canal público, no se encolan. + inter := cuerpo[strings.Index(cuerpo, `case "interna":`):strings.Index(cuerpo, `case "accion":`)] + if strings.Contains(inter, "EncolarAccion") { + t.Error("las tools internas no pueden encolarse desde un canal público") + } + if !strings.Contains(inter, "!sesionInterna") { + t.Error("falta el rechazo de tools internas en canal público") + } +} diff --git a/pkg/services/umind_documento_tools.go b/pkg/services/umind_documento_tools.go index 4d621ce..64fa94a 100644 --- a/pkg/services/umind_documento_tools.go +++ b/pkg/services/umind_documento_tools.go @@ -116,6 +116,11 @@ func renderPDFDesdePlantilla(contenidoHTML string, datos map[string]interface{}) // guardarPDFEnRepositorio deja el documento en los archivos del espacio, para // que quede a la vista y descargable como cualquier otro. func guardarPDFEnRepositorio(tenantID uint, nombreBase string, pdf []byte) (*models.UmindArchivo, error) { + // Un PDF generado ocupa lo mismo que uno subido: si no contara para la + // cuota, el tope del plan se esquivaría pidiéndole documentos al agente. + if err := models.HayEspacioUmind(tenantID, int64(len(pdf))); err != nil { + return nil, err + } dir := filepath.Join("uploads", "umind", strconv.FormatUint(uint64(tenantID), 10)) if err := os.MkdirAll(dir, 0o755); err != nil { return nil, err diff --git a/public/orchestrator/assets/index-BTIDeKXh.js b/public/orchestrator/assets/index-NgTBLrrq.js similarity index 91% rename from public/orchestrator/assets/index-BTIDeKXh.js rename to public/orchestrator/assets/index-NgTBLrrq.js index 89bf09e..c0c3019 100644 --- a/public/orchestrator/assets/index-BTIDeKXh.js +++ b/public/orchestrator/assets/index-NgTBLrrq.js @@ -24,6 +24,6 @@ * (c) 2025 Eduardo San Martin Morote * @license MIT */let Ju=()=>location.protocol+"//"+location.host;function Br(e,t){const{pathname:n,search:s,hash:o}=t,a=e.indexOf("#");if(a>-1){let l=o.includes(e.slice(a))?e.slice(a).length:1,i=o.slice(l);return i[0]!=="/"&&(i="/"+i),sa(i,"")}return sa(n,e)+s+o}function Yu(e,t,n,s){let o=[],a=[],l=null;const i=({state:f})=>{const _=Br(e,location),k=n.value,A=t.value;let I=0;if(f){if(n.value=_,t.value=f,l&&l===k){l=null;return}I=A?f.position-A.position:0}else s(_);o.forEach(y=>{y(n.value,k,{delta:I,type:Qs.pop,direction:I?I>0?Ms.forward:Ms.back:Ms.unknown})})};function u(){l=n.value}function d(f){o.push(f);const _=()=>{const k=o.indexOf(f);k>-1&&o.splice(k,1)};return a.push(_),_}function c(){if(document.visibilityState==="hidden"){const{history:f}=window;if(!f.state)return;f.replaceState(he({},f.state,{scroll:ys()}),"")}}function p(){for(const f of a)f();a=[],window.removeEventListener("popstate",i),window.removeEventListener("pagehide",c),document.removeEventListener("visibilitychange",c)}return window.addEventListener("popstate",i),window.addEventListener("pagehide",c),document.addEventListener("visibilitychange",c),{pauseListeners:u,listen:d,destroy:p}}function ia(e,t,n,s=!1,o=!1){return{back:e,current:t,forward:n,replaced:s,position:window.history.length,scroll:o?ys():null}}function Zu(e){const{history:t,location:n}=window,s={value:Br(e,n)},o={value:t.state};o.value||a(s.value,{back:null,current:s.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function a(u,d,c){const p=e.indexOf("#"),f=p>-1?(n.host&&document.querySelector("base")?e:e.slice(p))+u:Ju()+e+u;try{t[c?"replaceState":"pushState"](d,"",f),o.value=d}catch(_){console.error(_),n[c?"replace":"assign"](f)}}function l(u,d){a(u,he({},t.state,ia(o.value.back,u,o.value.forward,!0),d,{position:o.value.position}),!0),s.value=u}function i(u,d){const c=he({},o.value,t.state,{forward:u,scroll:ys()});a(c.current,c,!0),a(u,he({},ia(s.value,u,null),{position:c.position+1},d),!1),s.value=u}return{location:s,state:o,push:i,replace:l}}function Xu(e){e=Nu(e);const t=Zu(e),n=Yu(e,t.state,t.location,t.replace);function s(a,l=!0){l||n.pauseListeners(),history.go(a)}const o=he({location:"",base:e,go:s,createHref:Vu.bind(null,e)},t,n);return Object.defineProperty(o,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(o,"state",{enumerable:!0,get:()=>t.state.value}),o}let Qt=(function(e){return e[e.Static=0]="Static",e[e.Param=1]="Param",e[e.Group=2]="Group",e})({});var Ie=(function(e){return e[e.Static=0]="Static",e[e.Param=1]="Param",e[e.ParamRegExp=2]="ParamRegExp",e[e.ParamRegExpEnd=3]="ParamRegExpEnd",e[e.EscapeNext=4]="EscapeNext",e})(Ie||{});const ec={type:Qt.Static,value:""},tc=/[a-zA-Z0-9_]/;function nc(e){if(!e)return[[]];if(e==="/")return[[ec]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(_){throw new Error(`ERR (${n})/"${d}": ${_}`)}let n=Ie.Static,s=n;const o=[];let a;function l(){a&&o.push(a),a=[]}let i=0,u,d="",c="";function p(){d&&(n===Ie.Static?a.push({type:Qt.Static,value:d}):n===Ie.Param||n===Ie.ParamRegExp||n===Ie.ParamRegExpEnd?(a.length>1&&(u==="*"||u==="+")&&t(`A repeatable param (${d}) must be alone in its segment. eg: '/:ids+.`),a.push({type:Qt.Param,value:d,regexp:c,repeatable:u==="*"||u==="+",optional:u==="*"||u==="?"})):t("Invalid state to consume buffer"),d="")}function f(){d+=u}for(;it.length?t.length===1&&t[0]===He.Static+He.Segment?1:-1:0}function Wr(e,t){let n=0;const s=e.score,o=t.score;for(;n0&&t[t.length-1]<0}const lc={strict:!1,end:!0,sensitive:!1};function ic(e,t,n){const s=ac(nc(e.path),n),o=he(s,{record:e,parent:t,children:[],alias:[]});return t&&!o.record.aliasOf==!t.record.aliasOf&&t.children.push(o),o}function uc(e,t){const n=[],s=new Map;t=na(lc,t);function o(p){return s.get(p)}function a(p,f,_){const k=!_,A=fa(p);A.aliasOf=_&&_.record;const I=na(t,p),y=[A];if("alias"in p){const x=typeof p.alias=="string"?[p.alias]:p.alias;for(const $ of x)y.push(fa(he({},A,{components:_?_.record.components:A.components,path:$,aliasOf:_?_.record:A})))}let w,g;for(const x of y){const{path:$}=x;if(f&&$[0]!=="/"){const U=f.record.path,F=U[U.length-1]==="/"?"":"/";x.path=f.record.path+($&&F+$)}if(w=ic(x,f,I),_?_.alias.push(w):(g=g||w,g!==w&&g.alias.push(w),k&&p.name&&!pa(w)&&l(p.name)),Gr(w)&&u(w),A.children){const U=A.children;for(let F=0;F{l(g)}:In}function l(p){if(Fr(p)){const f=s.get(p);f&&(s.delete(p),n.splice(n.indexOf(f),1),f.children.forEach(l),f.alias.forEach(l))}else{const f=n.indexOf(p);f>-1&&(n.splice(f,1),p.record.name&&s.delete(p.record.name),p.children.forEach(l),p.alias.forEach(l))}}function i(){return n}function u(p){const f=fc(p,n);n.splice(f,0,p),p.record.name&&!pa(p)&&s.set(p.record.name,p)}function d(p,f){let _,k={},A,I;if("name"in p&&p.name){if(_=s.get(p.name),!_)throw pn(Ce.MATCHER_NOT_FOUND,{location:p});I=_.record.name,k=he(da(f.params,_.keys.filter(g=>!g.optional).concat(_.parent?_.parent.keys.filter(g=>g.optional):[]).map(g=>g.name)),p.params&&da(p.params,_.keys.map(g=>g.name))),A=_.stringify(k)}else if(p.path!=null)A=p.path,_=n.find(g=>g.re.test(A)),_&&(k=_.parse(A),I=_.record.name);else{if(_=f.name?s.get(f.name):n.find(g=>g.re.test(f.path)),!_)throw pn(Ce.MATCHER_NOT_FOUND,{location:p,currentLocation:f});I=_.record.name,k=he({},f.params,p.params),A=_.stringify(k)}const y=[];let w=_;for(;w;)y.unshift(w.record),w=w.parent;return{name:I,path:A,params:k,matched:y,meta:dc(y)}}e.forEach(p=>a(p));function c(){n.length=0,s.clear()}return{addRoute:a,resolve:d,removeRoute:l,clearRoutes:c,getRoutes:i,getRecordMatcher:o}}function da(e,t){const n={};for(const s of t)s in e&&(n[s]=e[s]);return n}function fa(e){const t={path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:e.aliasOf,beforeEnter:e.beforeEnter,props:cc(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}};return Object.defineProperty(t,"mods",{value:{}}),t}function cc(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const s in e.components)t[s]=typeof n=="object"?n[s]:n;return t}function pa(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function dc(e){return e.reduce((t,n)=>he(t,n.meta),{})}function fc(e,t){let n=0,s=t.length;for(;n!==s;){const a=n+s>>1;Wr(e,t[a])<0?s=a:n=a+1}const o=pc(e);return o&&(s=t.lastIndexOf(o,s-1)),s}function pc(e){let t=e;for(;t=t.parent;)if(Gr(t)&&Wr(e,t)===0)return t}function Gr({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function ma(e){const t=lt(_s),n=lt(xo),s=le(()=>{const u=Re(e.to);return t.resolve(u)}),o=le(()=>{const{matched:u}=s.value,{length:d}=u,c=u[d-1],p=n.matched;if(!c||!p.length)return-1;const f=p.findIndex(fn.bind(null,c));if(f>-1)return f;const _=va(u[d-2]);return d>1&&va(c)===_&&p[p.length-1].path!==_?p.findIndex(fn.bind(null,u[d-2])):f}),a=le(()=>o.value>-1&&bc(n.params,s.value.params)),l=le(()=>o.value>-1&&o.value===n.matched.length-1&&qr(n.params,s.value.params));function i(u={}){if(gc(u)){const d=t[Re(e.replace)?"replace":"push"](Re(e.to)).catch(In);return e.viewTransition&&typeof document<"u"&&"startViewTransition"in document&&document.startViewTransition(()=>d),d}return Promise.resolve()}return{route:s,href:le(()=>s.value.href),isActive:a,isExactActive:l,navigate:i}}function mc(e){return e.length===1?e[0]:e}const vc=tr({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"},viewTransition:Boolean},useLink:ma,setup(e,{slots:t}){const n=fs(ma(e)),{options:s}=lt(_s),o=le(()=>({[ha(e.activeClass,s.linkActiveClass,"router-link-active")]:n.isActive,[ha(e.exactActiveClass,s.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const a=t.default&&mc(t.default(n));return e.custom?a:Dr("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:o.value},a)}}}),hc=vc;function gc(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function bc(e,t){for(const n in t){const s=t[n],o=e[n];if(typeof s=="string"){if(s!==o)return!1}else if(!ft(o)||o.length!==s.length||s.some((a,l)=>a.valueOf()!==o[l].valueOf()))return!1}return!0}function va(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const ha=(e,t,n)=>e??t??n,xc=tr({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const s=lt(Ys),o=le(()=>e.route||s.value),a=lt(la,0),l=le(()=>{let d=Re(a);const{matched:c}=o.value;let p;for(;(p=c[d])&&!p.components;)d++;return d}),i=le(()=>o.value.matched[l.value]);Kn(la,le(()=>l.value+1)),Kn(Ku,i),Kn(Ys,o);const u=j();return Ge(()=>[u.value,i.value,e.name],([d,c,p],[f,_,k])=>{c&&(c.instances[p]=d,_&&_!==c&&d&&d===f&&(c.leaveGuards.size||(c.leaveGuards=_.leaveGuards),c.updateGuards.size||(c.updateGuards=_.updateGuards))),d&&c&&(!_||!fn(c,_)||!f)&&(c.enterCallbacks[p]||[]).forEach(A=>A(d))},{flush:"post"}),()=>{const d=o.value,c=e.name,p=i.value,f=p&&p.components[c];if(!f)return ga(n.default,{Component:f,route:d});const _=p.props[c],k=_?_===!0?d.params:typeof _=="function"?_(d):_:null,I=Dr(f,he({},k,t,{onVnodeUnmounted:y=>{y.component.isUnmounted&&(p.instances[c]=null)},ref:u}));return ga(n.default,{Component:I,route:d})||I}}});function ga(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const yc=xc;function _c(e){const t=uc(e.routes,e),n=e.parseQuery||Gu,s=e.stringifyQuery||ra,o=e.history,a=xn(),l=xn(),i=xn(),u=Sl(Ut);let d=Ut;tn&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const c=Ts.bind(null,O=>""+O),p=Ts.bind(null,Iu),f=Ts.bind(null,jn);function _(O,G){let W,ee;return Fr(O)?(W=t.getRecordMatcher(O),ee=G):ee=O,t.addRoute(ee,W)}function k(O){const G=t.getRecordMatcher(O);G&&t.removeRoute(G)}function A(){return t.getRoutes().map(O=>O.record)}function I(O){return!!t.getRecordMatcher(O)}function y(O,G){if(G=he({},G||u.value),typeof O=="string"){const C=Os(n,O,G.path),T=t.resolve({path:C.path},G),M=o.createHref(C.fullPath);return he(C,T,{params:f(T.params),hash:jn(C.hash),redirectedFrom:void 0,href:M})}let W;if(O.path!=null)W=he({},O,{path:Os(n,O.path,G.path).path});else{const C=he({},O.params);for(const T in C)C[T]==null&&delete C[T];W=he({},O,{params:p(C)}),G.params=p(G.params)}const ee=t.resolve(W,G),ue=O.hash||"";ee.params=c(f(ee.params));const m=Du(s,he({},O,{hash:Au(ue),path:ee.path})),h=o.createHref(m);return he({fullPath:m,hash:ue,query:s===ra?zu(O.query):O.query||{}},ee,{redirectedFrom:void 0,href:h})}function w(O){return typeof O=="string"?Os(n,O,u.value.path):he({},O)}function g(O,G){if(d!==O)return pn(Ce.NAVIGATION_CANCELLED,{from:G,to:O})}function x(O){return F(O)}function $(O){return x(he(w(O),{replace:!0}))}function U(O,G){const W=O.matched[O.matched.length-1];if(W&&W.redirect){const{redirect:ee}=W;let ue=typeof ee=="function"?ee(O,G):ee;return typeof ue=="string"&&(ue=ue.includes("?")||ue.includes("#")?ue=w(ue):{path:ue},ue.params={}),he({query:O.query,hash:O.hash,params:ue.path!=null?{}:O.params},ue)}}function F(O,G){const W=d=y(O),ee=u.value,ue=O.state,m=O.force,h=O.replace===!0,C=U(W,ee);if(C)return F(he(w(C),{state:typeof C=="object"?he({},ue,C.state):ue,force:m,replace:h}),G||W);const T=W;T.redirectedFrom=G;let M;return!m&&Tu(s,ee,W)&&(M=pn(Ce.NAVIGATION_DUPLICATED,{to:T,from:ee}),nt(ee,ee,!0,!1)),(M?Promise.resolve(M):te(T,ee)).catch(D=>At(D)?At(D,Ce.NAVIGATION_GUARD_REDIRECT)?D:Ke(D):pe(D,T,ee)).then(D=>{if(D){if(At(D,Ce.NAVIGATION_GUARD_REDIRECT))return F(he({replace:h},w(D.to),{state:typeof D.to=="object"?he({},ue,D.to.state):ue,force:m}),G||T)}else D=Pe(T,ee,!0,h,ue);return B(T,ee,D),D})}function H(O,G){const W=g(O,G);return W?Promise.reject(W):Promise.resolve()}function me(O){const G=Ze.values().next().value;return G&&typeof G.runWithContext=="function"?G.runWithContext(O):O()}function te(O,G){let W;const[ee,ue,m]=Qu(O,G);W=Ns(ee.reverse(),"beforeRouteLeave",O,G);for(const C of ee)C.leaveGuards.forEach(T=>{W.push(qt(T,O,G))});const h=H.bind(null,O,G);return W.push(h),Qe(W).then(()=>{W=[];for(const C of a.list())W.push(qt(C,O,G));return W.push(h),Qe(W)}).then(()=>{W=Ns(ue,"beforeRouteUpdate",O,G);for(const C of ue)C.updateGuards.forEach(T=>{W.push(qt(T,O,G))});return W.push(h),Qe(W)}).then(()=>{W=[];for(const C of m)if(C.beforeEnter)if(ft(C.beforeEnter))for(const T of C.beforeEnter)W.push(qt(T,O,G));else W.push(qt(C.beforeEnter,O,G));return W.push(h),Qe(W)}).then(()=>(O.matched.forEach(C=>C.enterCallbacks={}),W=Ns(m,"beforeRouteEnter",O,G,me),W.push(h),Qe(W))).then(()=>{W=[];for(const C of l.list())W.push(qt(C,O,G));return W.push(h),Qe(W)}).catch(C=>At(C,Ce.NAVIGATION_CANCELLED)?C:Promise.reject(C))}function B(O,G,W){i.list().forEach(ee=>me(()=>ee(O,G,W)))}function Pe(O,G,W,ee,ue){const m=g(O,G);if(m)return m;const h=G===Ut,C=tn?history.state:{};W&&(ee||h?o.replace(O.fullPath,he({scroll:h&&C&&C.scroll},ue)):o.push(O.fullPath,ue)),u.value=O,nt(O,G,W,h),Ke()}let J;function ze(){J||(J=o.listen((O,G,W)=>{if(!pt.listening)return;const ee=y(O),ue=U(ee,pt.currentRoute.value);if(ue){F(he(ue,{replace:!0,force:!0}),ee).catch(In);return}d=ee;const m=u.value;tn&&qu(aa(m.fullPath,W.delta),ys()),te(ee,m).catch(h=>At(h,Ce.NAVIGATION_ABORTED|Ce.NAVIGATION_CANCELLED)?h:At(h,Ce.NAVIGATION_GUARD_REDIRECT)?(F(he(w(h.to),{force:!0}),ee).then(C=>{At(C,Ce.NAVIGATION_ABORTED|Ce.NAVIGATION_DUPLICATED)&&!W.delta&&W.type===Qs.pop&&o.go(-1,!1)}).catch(In),Promise.reject()):(W.delta&&o.go(-W.delta,!1),pe(h,ee,m))).then(h=>{h=h||Pe(ee,m,!1),h&&(W.delta&&!At(h,Ce.NAVIGATION_CANCELLED)?o.go(-W.delta,!1):W.type===Qs.pop&&At(h,Ce.NAVIGATION_ABORTED|Ce.NAVIGATION_DUPLICATED)&&o.go(-1,!1)),B(ee,m,h)}).catch(In)}))}let Vt=xn(),Se=xn(),ve;function pe(O,G,W){Ke(O);const ee=Se.list();return ee.length?ee.forEach(ue=>ue(O,G,W)):console.error(O),Promise.reject(O)}function at(){return ve&&u.value!==Ut?Promise.resolve():new Promise((O,G)=>{Vt.add([O,G])})}function Ke(O){return ve||(ve=!O,ze(),Vt.list().forEach(([G,W])=>O?W(O):G()),Vt.reset()),O}function nt(O,G,W,ee){const{scrollBehavior:ue}=e;if(!tn||!ue)return Promise.resolve();const m=!W&&Fu(aa(O.fullPath,0))||(ee||!W)&&history.state&&history.state.scroll||null;return uo().then(()=>ue(O,G,m)).then(h=>h&&Lu(h)).catch(h=>pe(h,O,G))}const De=O=>o.go(O);let $t;const Ze=new Set,pt={currentRoute:u,listening:!0,addRoute:_,removeRoute:k,clearRoutes:t.clearRoutes,hasRoute:I,getRoutes:A,resolve:y,options:e,push:x,replace:$,go:De,back:()=>De(-1),forward:()=>De(1),beforeEach:a.add,beforeResolve:l.add,afterEach:i.add,onError:Se.add,isReady:at,install(O){O.component("RouterLink",hc),O.component("RouterView",yc),O.config.globalProperties.$router=pt,Object.defineProperty(O.config.globalProperties,"$route",{enumerable:!0,get:()=>Re(u)}),tn&&!$t&&u.value===Ut&&($t=!0,x(o.location).catch(ee=>{}));const G={};for(const ee in Ut)Object.defineProperty(G,ee,{get:()=>u.value[ee],enumerable:!0});O.provide(_s,pt),O.provide(xo,Ba(G)),O.provide(Ys,u);const W=O.unmount;Ze.add(O),O.unmount=function(){Ze.delete(O),Ze.size<1&&(d=Ut,J&&J(),J=null,u.value=Ut,$t=!1,ve=!1),W()}}};function Qe(O){return O.reduce((G,W)=>G.then(()=>me(W)),Promise.resolve())}return pt}function yo(){return lt(_s)}function zr(e){return lt(xo)}const Gn=window.location.pathname.startsWith("/portal/"),Ye={esPortal:Gn,baseRuta:Gn?"/portal/studio/":"/studio/",apiBase:Gn?"/portal":"/app",urlLogin:Gn?"/portal/login":"/login"};function K(e){return Ye.apiBase+e}async function yn(e,t={}){const n=t.body instanceof FormData,s=await fetch(e,{...t,headers:n?t.headers:{"Content-Type":"application/json",...t.headers}}),o=s.headers.get("content-type")||"";if(s.redirected||!o.includes("application/json"))throw window.location.href=Ye.urlLogin,new Error("Sesión expirada");const a=await s.json();if(!s.ok){const l=typeof(a==null?void 0:a.error)=="string"?a.error:a==null?void 0:a.message;throw new Error(l||"Error de servidor")}return a}const Q={get:e=>yn(e),postForm:(e,t)=>yn(e,{method:"POST",body:t,headers:{}}),post:(e,t)=>yn(e,{method:"POST",body:JSON.stringify(t)}),put:(e,t)=>yn(e,{method:"PUT",body:JSON.stringify(t)}),del:e=>yn(e,{method:"DELETE"})},kn=j(!1),wc={class:"h-14 px-4 flex items-center gap-2 border-b border-borde"},kc={key:0,class:"px-3 pt-3"},$c={key:1,class:"px-3 pt-2 text-xs text-red-600 dark:text-red-400"},Cc={class:"flex-1 overflow-y-auto px-2 py-3 space-y-0.5"},Ac={key:0,class:"px-2 text-xs text-tenue"},Sc={key:1,class:"px-2 text-xs text-tenue"},Ec={class:"truncate"},Ic={class:"flex items-center gap-1 mt-0.5"},Rc={class:"text-[11px] text-tenue"},Pc={key:0,class:"flex opacity-0 group-hover:opacity-100 transition-opacity pr-1.5 gap-0.5"},Dc=["onClick"],Tc=["onClick"],Oc={class:"card w-full max-w-lg p-6 animate-escalar shadow-2xl"},Mc={class:"font-semibold text-texto mb-4"},Nc={key:0,class:"grid grid-cols-2 gap-3"},jc=["value"],Vc={class:"text-[11px] text-tenue mt-1"},Uc=["value"],Lc={class:"text-[11px] text-tenue mt-1"},qc={class:"flex items-center gap-2 text-sm text-texto"},Fc={class:"flex justify-end gap-2 pt-2"},Hc={__name:"Sidebar",setup(e,{expose:t}){const n=zr(),s=yo(),o=j([]),a=j([]),l=j([]),i=j(!0),u=j(""),d=j(!1),c=j(null),p=j(_()),f=le(()=>n.params.tenantId||n.params.id);Ge(()=>n.fullPath,()=>{kn.value=!1});function _(){return{nombre:"",dominios_permitidos:"",activo:!0,cliente_id:null,plan_id:null}}async function k(){i.value=!0,u.value="";try{const $=await Q.get(K("/umind/tenants"));o.value=$.items||[]}catch($){u.value=$.message}finally{i.value=!1}}function A($){return Array.isArray($)?$:($==null?void 0:$.items)||($==null?void 0:$.registros)||[]}async function I(){if(Ye.esPortal)return;const[$,U]=await Promise.allSettled([Q.get("/app/api/clientes/select"),Q.get("/app/umind-planes/list")]);a.value=$.status==="fulfilled"?A($.value):[],l.value=U.status==="fulfilled"?A(U.value):[];const F=[];$.status==="rejected"&&F.push("clientes"),U.status==="rejected"&&F.push("planes"),F.length&&(u.value=`No se pudo cargar la lista de ${F.join(" ni ")}.`)}function y(){c.value=null,p.value=_(),d.value=!0}function w($){c.value=$,p.value={nombre:$.nombre,dominios_permitidos:$.dominios_permitidos,activo:$.activo,cliente_id:$.cliente_id??null,plan_id:$.plan_id??null},d.value=!0}async function g(){const $={...p.value,dominios_permitidos:p.value.dominios_permitidos.split(",").map(U=>U.trim()).filter(Boolean)};try{if(c.value)await Q.put(K(`/umind/tenants/${c.value.ID}`),$),d.value=!1,await k();else{const U=await Q.post(K("/umind/tenants"),$);d.value=!1,await k(),s.push(`/tenants/${U.id}`)}}catch(U){u.value=U.message}}async function x($){confirm(`¿Eliminar el espacio "${$.nombre}"? Esto borra también sus agentes. No se puede deshacer.`)&&(await Q.del(K(`/umind/tenants/${$.ID}`)),f.value===String($.ID)&&s.push("/"),await k())}return t({recargar:k}),hs(()=>{k(),I()}),($,U)=>{const F=Wt("router-link");return v(),b(Z,null,[Re(kn)?(v(),b("div",{key:0,class:"fixed inset-0 bg-black/50 z-30 md:hidden",onClick:U[0]||(U[0]=H=>kn.value=!1)})):V("",!0),r("aside",{class:ie(["w-64 shrink-0 flex flex-col border-r border-borde bg-superficie fixed inset-y-0 left-0 z-40 transition-transform duration-200 md:static md:h-screen md:sticky md:top-0 md:translate-x-0",Re(kn)?"translate-x-0":"-translate-x-full"])},[r("div",wc,[ne(F,{to:"/",class:"flex items-center gap-2 text-base font-semibold text-texto"},{default:dt(()=>[...U[8]||(U[8]=[r("svg",{viewBox:"0 0 96 96",class:"w-6 h-6 shrink-0","aria-hidden":"true"},[r("rect",{width:"96",height:"96",rx:"22",fill:"#8eb02f"}),r("path",{d:"M32,42 V58 A14,14 0 0 0 60,58 V42",fill:"none",stroke:"#fff","stroke-width":"10","stroke-linecap":"round","stroke-linejoin":"round"}),r("path",{d:"M60,58 V64",fill:"none",stroke:"#fff","stroke-width":"10","stroke-linecap":"round"}),r("circle",{cx:"60",cy:"28",r:"7",fill:"#fff"})],-1),r("span",null,[Y("uMind "),r("span",{class:"text-brand"},"Studio")],-1)])]),_:1})]),Re(Ye).esPortal?V("",!0):(v(),b("div",kc,[r("button",{class:"btn-primary w-full",onClick:y}," + Nuevo espacio ")])),u.value?(v(),b("p",$c,S(u.value),1)):V("",!0),r("nav",Cc,[i.value?(v(),b("p",Ac,"Cargando...")):o.value.length===0?(v(),b("p",Sc,S(Re(Ye).esPortal?"Todavía no tenés ningún espacio asignado. Escribinos y lo activamos.":"Sin espacios todavía."),1)):V("",!0),(v(!0),b(Z,null,ce(o.value,H=>(v(),b("div",{key:H.ID,class:ie(["group flex items-center rounded-lg transition-colors",f.value===String(H.ID)?"bg-brand/10":"hover:bg-elevado"])},[ne(F,{to:`/tenants/${H.ID}`,class:ie(["flex-1 min-w-0 px-2.5 py-2 text-sm",f.value===String(H.ID)?"text-brand font-medium":"text-texto"])},{default:dt(()=>[r("div",Ec,S(H.nombre),1),r("div",Ic,[r("span",{class:ie(["w-1.5 h-1.5 rounded-full",H.activo?"bg-green-500":"bg-tenue/40"])},null,2),r("span",Rc,S(H.activo?"activo":"inactivo"),1)])]),_:2},1032,["to","class"]),Re(Ye).esPortal?V("",!0):(v(),b("div",Pc,[r("button",{class:"p-1 text-tenue hover:text-texto",title:"Editar",onClick:me=>w(H)},[ne(Ne,{nombre:"lapiz",tam:14})],8,Dc),r("button",{class:"p-1 text-tenue hover:text-red-600",title:"Eliminar",onClick:me=>x(H)},[ne(Ne,{nombre:"cerrar",tam:14})],8,Tc)]))],2))),128))])],2),d.value?(v(),b("div",{key:1,class:"fixed inset-0 bg-black/40 flex items-center justify-center p-4 z-50",onClick:U[7]||(U[7]=Ae(H=>d.value=!1,["self"]))},[r("div",Oc,[r("h2",Mc,S(c.value?"Editar espacio":"Nuevo espacio"),1),U[17]||(U[17]=r("p",{class:"text-xs text-tenue mb-3"}," Un espacio es el negocio o sitio dueño de los dominios permitidos. La config de IA, tono y demás se configuran por agente, dentro del tenant. ",-1)),r("form",{class:"space-y-3",onSubmit:Ae(g,["prevent"])},[r("div",null,[U[9]||(U[9]=r("label",{class:"label"},"Nombre",-1)),z(r("input",{"onUpdate:modelValue":U[1]||(U[1]=H=>p.value.nombre=H),required:"",class:"input"},null,512),[[re,p.value.nombre]])]),r("div",null,[U[10]||(U[10]=r("label",{class:"label"},"Dominios permitidos (separados por coma)",-1)),z(r("input",{"onUpdate:modelValue":U[2]||(U[2]=H=>p.value.dominios_permitidos=H),placeholder:"ejemplo.com, www.ejemplo.com",required:"",class:"input"},null,512),[[re,p.value.dominios_permitidos]])]),Re(Ye).esPortal?V("",!0):(v(),b("div",Nc,[r("div",null,[U[12]||(U[12]=r("label",{class:"label"},"Cliente",-1)),z(r("select",{"onUpdate:modelValue":U[3]||(U[3]=H=>p.value.cliente_id=H),class:"input"},[U[11]||(U[11]=r("option",{value:null},"— sin asignar —",-1)),(v(!0),b(Z,null,ce(a.value,H=>(v(),b("option",{key:H.ID,value:H.ID},S(H.nombre),9,jc))),128))],512),[[Nt,p.value.cliente_id]]),r("p",Vc,S(a.value.length?"Define quién ve este espacio desde el portal.":"No hay clientes activos — creá uno en Clientes."),1)]),r("div",null,[U[14]||(U[14]=r("label",{class:"label"},"Plan",-1)),z(r("select",{"onUpdate:modelValue":U[4]||(U[4]=H=>p.value.plan_id=H),class:"input"},[U[13]||(U[13]=r("option",{value:null},"— sin plan —",-1)),(v(!0),b(Z,null,ce(l.value,H=>(v(),b("option",{key:H.ID,value:H.ID},S(H.nombre)+" ("+S(H.max_agentes===0?"∞":H.max_agentes)+" agentes) ",9,Uc))),128))],512),[[Nt,p.value.plan_id]]),r("p",Lc,S(l.value.length?"Límite de agentes y precios de consumo.":"No hay planes — creá uno en uMind Planes."),1)])])),r("label",qc,[z(r("input",{"onUpdate:modelValue":U[5]||(U[5]=H=>p.value.activo=H),type:"checkbox"},null,512),[[st,p.value.activo]]),U[15]||(U[15]=Y(" Activo ",-1))]),r("div",Fc,[r("button",{type:"button",class:"btn-ghost",onClick:U[6]||(U[6]=H=>d.value=!1)}," Cancelar "),U[16]||(U[16]=r("button",{type:"submit",class:"btn-primary"}," Guardar ",-1))])],32)])])):V("",!0)],64)}}},Kr="umind-tema";function Bc(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"oscuro":"claro"}const ln=j(localStorage.getItem(Kr)||Bc());function Qr(){document.documentElement.classList.toggle("dark",ln.value==="oscuro")}function ba(){ln.value=ln.value==="oscuro"?"claro":"oscuro",localStorage.setItem(Kr,ln.value),Qr()}Qr();const xa="umind_despertar_visto",ya=1250,Wc={__name:"DespertarUmind",setup(e){const t=j(null),n=j(!1);let s=null,o=null;function a(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}return hs(()=>{a()||sessionStorage.getItem(xa)||(sessionStorage.setItem(xa,"1"),n.value=!0,o=setTimeout(()=>n.value=!1,ya),requestAnimationFrame(()=>{const l=t.value;if(!l)return;const i=l.getContext("2d"),u=Math.min(window.devicePixelRatio||1,2),d=window.innerWidth,c=window.innerHeight;l.width=Math.round(d*u),l.height=Math.round(c*u),i.setTransform(u,0,0,u,0,0);const p=d<640?22:46,f=Array.from({length:p},()=>({x:Math.random()*d,y:Math.random()*c,r:1+Math.random()*1.6,demora:Math.random()*.45})),_=performance.now(),k=A=>{const I=Math.min(1,(A-_)/ya);i.clearRect(0,0,d,c);const y=I<.75?1:1-(I-.75)/.25;for(let w=0;w170||(i.strokeStyle=`rgba(142, 176, 47, ${.16*(1-me/170)*x*y})`,i.lineWidth=1,i.beginPath(),i.moveTo(g.x,g.y),i.lineTo(U.x,U.y),i.stroke())}i.beginPath(),i.arc(g.x,g.y,g.r*x,0,Math.PI*2),i.fillStyle=`rgba(142, 176, 47, ${.5*x*y})`,i.fill()}}I<1&&(s=requestAnimationFrame(k))};s=requestAnimationFrame(k)}))}),or(()=>{s&&cancelAnimationFrame(s),o&&clearTimeout(o)}),(l,i)=>n.value?(v(),b("canvas",{key:0,ref_key:"lienzo",ref:t,class:"fixed inset-0 w-full h-full pointer-events-none z-50","aria-hidden":"true"},null,512)):V("",!0)}},Gc={class:"min-h-screen flex"},zc={class:"flex-1 min-w-0 flex flex-col"},Kc={class:"h-14 shrink-0 flex items-center gap-1 px-4 sm:px-6 border-b border-borde"},Qc=["title"],Jc={class:"flex-1 max-w-5xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8 animate-aparecer"},Yc={__name:"App",setup(e){return(t,n)=>{const s=Wt("router-view");return v(),b("div",Gc,[ne(Wc),ne(Hc),r("main",zc,[r("header",Kc,[r("button",{class:"btn-ghost !px-2 !py-1.5 md:hidden","aria-label":"Abrir menú",onClick:n[0]||(n[0]=o=>kn.value=!0)},[...n[2]||(n[2]=[r("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor","stroke-width":"2",viewBox:"0 0 24 24"},[r("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"M4 6h16M4 12h16M4 18h16"})],-1)])]),n[3]||(n[3]=r("span",{class:"flex-1"},null,-1)),r("button",{class:"btn-ghost !px-2.5 !py-1.5",title:Re(ln)==="oscuro"?"Cambiar a claro":"Cambiar a oscuro",onClick:n[1]||(n[1]=(...o)=>Re(ba)&&Re(ba)(...o))},[ne(Ne,{nombre:Re(ln)==="oscuro"?"sol":"luna",tam:17},null,8,["nombre"])],8,Qc)]),r("div",Jc,[ne(s)])])])}}},Zc=(e,t)=>{const n=e.__vccOpts||e;for(const[s,o]of t)n[s]=o;return n},Xc=["width","height","aria-label"],ed=["fill"],td=["stroke"],nd=["cy"],sd=["cy"],od=["cy"],ad=["cy"],rd={key:1,d:"M41,66 h12 M67,66 h12",class:"umi-linea","stroke-width":"5","stroke-linecap":"round",fill:"none"},ld={key:4,d:"M50,78 q10,8 20,0",class:"umi-linea","stroke-width":"4",fill:"none","stroke-linecap":"round"},id={__name:"UiMascota",props:{estado:{type:String,default:"normal"},tam:{type:[Number,String],default:72}},setup(e){return(t,n)=>(v(),b("svg",{width:e.tam,height:e.tam,viewBox:"0 0 120 120",class:"shrink-0",role:"img","aria-label":`Umi, la mascota de uMind (${e.estado})`},[r("circle",{cx:"60",cy:"26",r:"6",fill:e.estado==="alerta"?"#dc2626":"currentColor",class:ie(e.estado==="pensando"?"umi-late":"")},null,10,ed),r("line",{x1:"60",y1:"32",x2:"60",y2:"44",stroke:e.estado==="alerta"?"#dc2626":"currentColor","stroke-width":"4","stroke-linecap":"round"},null,8,td),n[7]||(n[7]=r("path",{d:"M30,46 V76 A30,30 0 0 0 90,76 V46 Z",fill:"currentColor"},null,-1)),e.estado==="normal"||e.estado==="contenta"||e.estado==="alerta"?(v(),b(Z,{key:0},[r("circle",{cx:"47",cy:e.estado==="contenta"?64:66,r:"6",class:"umi-ojo"},null,8,nd),r("circle",{cx:"73",cy:e.estado==="contenta"?64:66,r:"6",class:"umi-ojo"},null,8,sd),r("circle",{cx:"48",cy:e.estado==="contenta"?65:67,r:"3",class:"umi-pupila"},null,8,od),r("circle",{cx:"74",cy:e.estado==="contenta"?65:67,r:"3",class:"umi-pupila"},null,8,ad)],64)):e.estado==="durmiendo"?(v(),b("path",rd)):e.estado==="pensando"?(v(),b(Z,{key:2},[n[0]||(n[0]=r("circle",{cx:"45",cy:"66",r:"4.5",class:"umi-ojo umi-p1"},null,-1)),n[1]||(n[1]=r("circle",{cx:"60",cy:"66",r:"4.5",class:"umi-ojo umi-p2"},null,-1)),n[2]||(n[2]=r("circle",{cx:"75",cy:"66",r:"4.5",class:"umi-ojo umi-p3"},null,-1))],64)):e.estado==="buscando"?(v(),b(Z,{key:3},[n[3]||(n[3]=r("circle",{cx:"47",cy:"66",r:"6",class:"umi-ojo"},null,-1)),n[4]||(n[4]=r("circle",{cx:"73",cy:"66",r:"6",class:"umi-ojo"},null,-1)),n[5]||(n[5]=r("circle",{cx:"50",cy:"66",r:"3",class:"umi-pupila"},null,-1)),n[6]||(n[6]=r("circle",{cx:"76",cy:"66",r:"3",class:"umi-pupila"},null,-1))],64)):V("",!0),e.estado==="contenta"?(v(),b("path",ld)):V("",!0)],8,Xc))}},Vn=Zc(id,[["__scopeId","data-v-551897a0"]]),ud={key:0,class:"flex flex-col items-center justify-center py-24 text-sm text-tenue"},cd={key:1,class:"max-w-md mx-auto text-center py-20"},dd={key:2,class:"flex flex-col items-center justify-center text-center py-24"},fd={class:"text-lg font-medium text-texto"},pd={class:"text-sm text-tenue mt-1"},md={__name:"Home",setup(e){const t=yo(),n=j(Ye.esPortal),s=j(!1);async function o(){if(Ye.esPortal)try{const l=(await Q.get(K("/umind/tenants"))).items||[];if(l.length===0){s.value=!0;return}if(l.length!==1)return;const i=l[0].ID,d=(await Q.get(K(`/umind/agentes?tenant_id=${i}`))).items||[];if(d.length===1){t.replace(`/tenants/${i}/agentes/${d[0].ID}`);return}t.replace(`/tenants/${i}`)}catch{}finally{n.value=!1}}return hs(o),(a,l)=>n.value?(v(),b("div",ud," Abriendo tu asistente… ")):s.value?(v(),b("div",cd,[ne(Vn,{estado:"durmiendo",tam:80,class:"mx-auto mb-4 text-brand"}),l[0]||(l[0]=go('

Todavía no tenés un asistente activo

uMind contesta por vos en WhatsApp y en tu sitio, con la información de tu negocio. Entiende las notas de voz y lee las fotos y archivos que te mandan tus clientes.

Quiero activarlo

Volver al portal

',4))])):(v(),b("div",dd,[ne(Vn,{estado:"normal",tam:64,class:"mx-auto mb-4 text-brand"}),r("h1",fd,S(Re(Ye).esPortal?"Elegí tu espacio de la izquierda":"Elegí un espacio de la izquierda"),1),r("p",pd,S(Re(Ye).esPortal?"Adentro vas a poder crear y configurar tus agentes.":"o creá uno nuevo para empezar a configurar su agente."),1)]))}},vd={class:"flex flex-col items-center justify-center text-center py-12 px-6"},hd={key:0,class:"text-3xl mb-3 opacity-70"},gd={class:"text-sm font-medium text-texto"},bd={key:2,class:"text-xs text-tenue mt-1.5 max-w-sm leading-relaxed"},xd={key:3,class:"mt-4"},qe={__name:"UiEmptyState",props:{estado:{type:String,default:"durmiendo"},titulo:String,detalle:String,icono:{type:String,default:""}},setup(e){return(t,n)=>(v(),b("div",vd,[e.icono?(v(),b("div",hd,S(e.icono),1)):(v(),de(Vn,{key:1,estado:e.estado,tam:64,class:"mb-3 text-brand"},null,8,["estado"])),r("p",gd,S(e.titulo),1),e.detalle?(v(),b("p",bd,S(e.detalle),1)):V("",!0),t.$slots.default?(v(),b("div",xd,[Xl(t.$slots,"default")])):V("",!0)]))}},yd={class:"flex gap-1 flex-wrap mb-6 border-b border-borde pb-2"},_d={key:0,class:"px-1.5 py-0.5 rounded-full bg-amber-500 text-white text-[10px] font-semibold leading-none"},vn={__name:"NavEspacio",props:{tenantId:{type:[String,Number],required:!0}},setup(e){const t=e,n=j(0);async function s(){try{const a=await Q.get(K(`/umind/acciones?tenant_id=${t.tenantId}`));n.value=a.pendientes||0}catch{n.value=0}}Ge(()=>t.tenantId,s,{immediate:!0});const o=[["tenant-agentes","","Agentes"],["pendientes","/pendientes","Pendientes"],["archivos","/archivos","Archivos"],["plantillas","/plantillas","Plantillas"],["uso","/uso","Consumo"],["ai-propia","/ia","Tu IA"]];return(a,l)=>{const i=Wt("router-link");return v(),b("nav",yd,[(v(),b(Z,null,ce(o,([u,d,c])=>ne(i,{key:u,to:`/tenants/${e.tenantId}${d}`,class:ie(["px-3 py-1.5 rounded-lg text-sm transition-colors inline-flex items-center gap-1.5",a.$route.name===u?"bg-brand/10 text-brand font-medium":"text-tenue hover:text-texto hover:bg-elevado"])},{default:dt(()=>[Y(S(c)+" ",1),u==="pendientes"&&n.value>0?(v(),b("span",_d,S(n.value),1)):V("",!0)]),_:2},1032,["to","class"])),64))])}}},wd={key:0,class:"mb-4"},kd={class:"flex items-start justify-between gap-4"},$d={class:"text-xl font-semibold text-texto"},Cd={class:"text-xs text-tenue mt-1"},Ad={key:1,class:"text-sm text-red-600 dark:text-red-400 mb-4"},Sd={class:"flex items-center justify-between mb-4"},Ed={class:"flex items-center gap-2"},Id={key:0,class:"badge-alerta"},Rd={key:1,class:"badge-neutro"},Pd=["disabled"],Dd={key:2,class:"text-xs text-tenue -mt-2 mb-4"},Td={key:3,class:"text-xs text-tenue -mt-2 mb-4"},Od={key:4,class:"grid gap-3 sm:grid-cols-2"},Md=["disabled"],Nd={key:6,class:"grid gap-3 sm:grid-cols-2"},jd={class:"flex items-start gap-3"},Vd={class:"min-w-0 flex-1"},Ud={class:"flex items-center gap-2"},Ld={class:"font-medium text-texto truncate"},qd={class:"text-xs text-tenue mt-0.5 truncate"},Fd={class:"flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0"},Hd=["onClick"],Bd=["onClick"],Wd=["onClick"],Gd={class:"flex items-center gap-4 mt-3.5 pt-3 border-t border-borde text-xs text-tenue"},zd={class:"tabular-nums"},Kd={class:"text-texto font-medium"},Qd={class:"tabular-nums"},Jd={class:"text-texto font-medium"},Yd={class:"tabular-nums"},Zd={class:"text-texto font-medium"},Xd={class:"bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-xl p-6 w-full max-w-lg max-h-[calc(100vh-2rem)] overflow-y-auto"},ef={class:"font-semibold text-gray-800 dark:text-gray-100 mb-4"},tf={key:0},nf={class:"grid gap-1.5"},sf=["value"],of={class:"min-w-0"},af={class:"block text-sm text-texto"},rf={class:"block text-xs text-tenue"},lf={key:0,class:"block text-xs text-tenue mt-0.5"},uf=["value"],cf={class:"flex items-center gap-2"},df={class:"flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300"},ff={class:"flex justify-end gap-2 pt-2"},pf={type:"submit",class:"bg-brand hover:bg-brand-dark text-white text-sm font-medium px-4 py-2 rounded-lg"},mf={__name:"TenantAgentes",props:{id:{type:String,required:!0}},setup(e){const t=e,n=le(()=>Number(t.id)),s=yo(),o=j(null),a=j([]),l=j([]),i=j(""),u=j(!1),d=j(null),c=j(w()),p=j(null),f=j({}),_=j(!0);function k(te){const B=f.value[te.ID]||{};return te.activo?B.documentos>0?{tipo:"ok",texto:"listo"}:{tipo:"alerta",texto:"sin conocimiento"}:{tipo:"neutro",texto:"inactivo"}}function A(te){const B=f.value[te.ID]||{};return{documentos:B.documentos||0,canales:B.canales||0,conversaciones:B.conversaciones_7d||0}}function I(te){return String(te||"?").trim().split(/\s+/).slice(0,2).map(B=>B[0]).join("").toUpperCase()}const y=le(()=>{if(!p.value)return{sinPlan:!0};const te=p.value.max_agentes||0;return{sinPlan:!1,nombre:p.value.nombre,ilimitado:te<=0,max:te,usados:a.value.length,lleno:te>0&&a.value.length>=te}});function w(){return{nombre:"",ai_config_id:null,tono:"",mensaje_bienvenida:"",color:"#8eb02f",activo:!0,plantilla_rubro:""}}const g=j([]);async function x(){i.value="",_.value=!0;try{const[te,B,Pe,J]=await Promise.all([Q.get(K("/umind/tenants")),Q.get(K(`/umind/agentes?tenant_id=${t.id}`)),Q.get(K("/umind/ai-configs")),Q.get(K("/umind/plantillas-rubro"))]);o.value=(te.items||[]).find(ze=>String(ze.ID)===t.id)||null,a.value=B.items||[],p.value=B.plan||null,f.value=B.resumen||{},l.value=Pe.items||[],g.value=J.items||[]}catch(te){i.value=te.message}finally{_.value=!1}}async function $(te){const B=prompt(`Nombre de la copia de "${te.nombre}":`,`${te.nombre} (copia)`);if(B!==null){i.value="";try{const Pe=await Q.post(K(`/umind/agentes/${te.ID}/duplicar`),{tenant_id:Number(n.value),nombre:B.trim()});await x(),Pe.aviso&&alert(Pe.aviso)}catch(Pe){i.value=Pe.message}}}function U(){d.value=null,c.value=w(),u.value=!0}function F(te){d.value=te,c.value={nombre:te.nombre,ai_config_id:te.ai_config_id,tono:te.tono,mensaje_bienvenida:te.mensaje_bienvenida,color:te.color||"#8eb02f",activo:te.activo},u.value=!0}async function H(){try{if(d.value)await Q.put(K(`/umind/agentes/${d.value.ID}`),{tenant_id:n.value,...c.value}),u.value=!1,await x();else{const te=await Q.post(K("/umind/agentes"),{tenant_id:n.value,...c.value});u.value=!1,s.push(`/tenants/${n.value}/agentes/${te.id}`)}}catch(te){i.value=te.message}}async function me(te){confirm(`¿Eliminar el agente "${te.nombre}"? Esto no se puede deshacer.`)&&(await Q.del(K(`/umind/agentes/${te.ID}`)),await x())}return Ge(()=>t.id,x,{immediate:!0}),(te,B)=>{const Pe=Wt("router-link");return v(),b("div",null,[o.value?(v(),b("div",wd,[r("div",kd,[r("div",null,[r("h1",$d,S(o.value.nombre),1),r("p",Cd,S(o.value.dominios_permitidos||"sin dominios configurados"),1)])])])):V("",!0),ne(vn,{"tenant-id":n.value??e.id},null,8,["tenant-id"]),i.value?(v(),b("p",Ad,S(i.value),1)):V("",!0),r("div",Sd,[r("div",Ed,[B[10]||(B[10]=r("h2",{class:"text-sm font-medium text-tenue"},"Agentes",-1)),y.value.sinPlan?(v(),b("span",Id,"sin plan · sin límite")):y.value.ilimitado?(v(),b("span",Rd,S(y.value.nombre)+" · ilimitado",1)):(v(),b("span",{key:2,class:ie(y.value.lleno?"badge-alerta":"badge-neutro")},S(y.value.nombre)+" · "+S(y.value.usados)+" de "+S(y.value.max),3))]),r("button",{class:"btn-primary",disabled:y.value.lleno,onClick:U},"+ Nuevo agente",8,Pd)]),y.value.sinPlan&&!Re(Ye).esPortal?(v(),b("p",Dd," Este espacio no tiene plan asignado, así que no se le aplica ningún límite de agentes. Asignale uno desde el lápiz del espacio, en la barra izquierda. ")):y.value.lleno?(v(),b("p",Td," Alcanzaste el máximo de agentes de tu plan. ")):V("",!0),_.value?(v(),b("div",Od,[(v(),b(Z,null,ce(2,J=>r("div",{key:J,class:"card p-4 animate-pulse"},[...B[11]||(B[11]=[go('
',2)])])),64))])):a.value.length===0?(v(),de(qe,{key:5,class:"card",estado:"durmiendo",titulo:"Todavía no hay agentes",detalle:"Creá el primero y cargale su base de conocimiento para que empiece a responder."},{default:dt(()=>[r("button",{class:"btn-primary",disabled:y.value.lleno,onClick:U},"+ Crear el primer agente",8,Md)]),_:1})):(v(),b("div",Nd,[(v(!0),b(Z,null,ce(a.value,J=>(v(),de(Pe,{key:J.ID,to:`/tenants/${n.value}/agentes/${J.ID}`,class:"card p-4 relative group hover:shadow-lg hover:-translate-y-0.5 transition-all overflow-hidden"},{default:dt(()=>[r("span",{class:"absolute inset-x-0 top-0 h-1",style:Ft({background:J.color||"#8eb02f"})},null,4),r("div",jd,[r("span",{class:"w-10 h-10 rounded-xl flex items-center justify-center text-white text-sm font-semibold shrink-0",style:Ft({background:J.color||"#8eb02f",opacity:J.activo?1:.4})},S(I(J.nombre)),5),r("div",Vd,[r("div",Ud,[r("span",Ld,S(J.nombre),1),r("span",{class:ie(`badge-${k(J).tipo}`)},S(k(J).texto),3)]),r("p",qd,S(J.tono||"sin tono definido"),1)]),r("div",Fd,[r("button",{class:"p-1 text-tenue hover:text-texto",title:"Editar",onClick:Ae(ze=>F(J),["prevent","stop"])},[ne(Ne,{nombre:"lapiz",tam:14})],8,Hd),r("button",{class:"p-1 text-tenue hover:text-texto",title:"Duplicar: copia el conocimiento y las herramientas a un agente nuevo",onClick:Ae(ze=>$(J),["prevent","stop"])},[ne(Ne,{nombre:"copiar",tam:14})],8,Bd),r("button",{class:"p-1 text-tenue hover:text-red-600",title:"Eliminar",onClick:Ae(ze=>me(J),["prevent","stop"])},[ne(Ne,{nombre:"cerrar",tam:14})],8,Wd)])]),r("div",Gd,[r("span",zd,[r("b",Kd,S(A(J).conversaciones),1),B[12]||(B[12]=Y(" conversaciones · 7d",-1))]),r("span",Qd,[r("b",Jd,S(A(J).documentos),1),B[13]||(B[13]=Y(" fuentes",-1))]),r("span",Yd,[r("b",Zd,S(A(J).canales),1),B[14]||(B[14]=Y(" canales",-1))])])]),_:2},1032,["to"]))),128))])),u.value?(v(),b("div",{key:7,class:"fixed inset-0 bg-black/40 flex items-center justify-center p-4 z-50",onClick:B[9]||(B[9]=Ae(J=>u.value=!1,["self"]))},[r("div",Xd,[r("h2",ef,S(d.value?"Editar agente":"Nuevo agente"),1),r("form",{class:"space-y-3",onSubmit:Ae(H,["prevent"])},[r("div",null,[B[15]||(B[15]=r("label",{class:"label"},"Nombre",-1)),z(r("input",{"onUpdate:modelValue":B[0]||(B[0]=J=>c.value.nombre=J),required:"",placeholder:"ej: Ventas, Soporte",class:"input"},null,512),[[re,c.value.nombre]])]),!d.value&&g.value.length?(v(),b("div",tf,[B[16]||(B[16]=r("label",{class:"label"},"Arrancar con",-1)),r("div",nf,[(v(!0),b(Z,null,ce([{clave:"",nombre:"Agente en blanco",descripcion:"Sin conocimiento cargado. Lo escribís vos desde cero."},...g.value],J=>(v(),b("label",{key:J.clave,class:ie(["flex gap-2.5 p-2.5 rounded-lg border cursor-pointer transition-colors",c.value.plantilla_rubro===J.clave?"border-brand bg-brand/5":"border-borde hover:border-brand/40"])},[z(r("input",{"onUpdate:modelValue":B[1]||(B[1]=ze=>c.value.plantilla_rubro=ze),type:"radio",value:J.clave,class:"mt-1 text-brand focus:ring-brand"},null,8,sf),[[zs,c.value.plantilla_rubro]]),r("span",of,[r("span",af,S(J.nombre),1),r("span",rf,S(J.descripcion),1),J.notas?(v(),b("span",lf,S(J.notas)+" notas listas para editar · "+S(J.resumen),1)):V("",!0)])],2))),128))]),B[17]||(B[17]=r("p",{class:"text-xs text-tenue mt-1.5"}," Las notas vienen con ejemplos entre corchetes — abrilas y reemplazalas por tus datos reales. ",-1))])):V("",!0),r("div",null,[B[19]||(B[19]=r("label",{class:"label"},"Config de IA",-1)),z(r("select",{"onUpdate:modelValue":B[2]||(B[2]=J=>c.value.ai_config_id=J),class:"input"},[B[18]||(B[18]=r("option",{value:null},"— sin asignar —",-1)),(v(!0),b(Z,null,ce(l.value,J=>(v(),b("option",{key:J.ID,value:J.ID},S(J.nombre)+" ("+S(J.provider)+")",9,uf))),128))],512),[[Nt,c.value.ai_config_id]])]),r("div",null,[B[20]||(B[20]=r("label",{class:"label"},"Tono / personalidad",-1)),z(r("textarea",{"onUpdate:modelValue":B[3]||(B[3]=J=>c.value.tono=J),rows:"2",class:"input"},null,512),[[re,c.value.tono]])]),r("div",null,[B[21]||(B[21]=r("label",{class:"label"},"Mensaje de bienvenida",-1)),z(r("input",{"onUpdate:modelValue":B[4]||(B[4]=J=>c.value.mensaje_bienvenida=J),class:"input"},null,512),[[re,c.value.mensaje_bienvenida]])]),r("div",null,[B[22]||(B[22]=r("label",{class:"label"},"Color del widget",-1)),r("div",cf,[z(r("input",{"onUpdate:modelValue":B[5]||(B[5]=J=>c.value.color=J),type:"color",class:"w-10 h-9 border border-gray-300 dark:border-gray-700 rounded cursor-pointer bg-white dark:bg-gray-800"},null,512),[[re,c.value.color]]),z(r("input",{"onUpdate:modelValue":B[6]||(B[6]=J=>c.value.color=J),type:"text",pattern:"#[0-9a-fA-F]{6}",class:"flex-1 border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-100 rounded-lg px-3 py-2 text-sm font-mono"},null,512),[[re,c.value.color]])])]),r("label",df,[z(r("input",{"onUpdate:modelValue":B[7]||(B[7]=J=>c.value.activo=J),type:"checkbox"},null,512),[[st,c.value.activo]]),B[23]||(B[23]=Y(" Activo ",-1))]),r("div",ff,[r("button",{type:"button",class:"px-4 py-2 text-sm text-gray-500 dark:text-gray-400",onClick:B[8]||(B[8]=J=>u.value=!1)},"Cancelar"),r("button",pf,S(d.value?"Guardar":"Crear"),1)])],32)])])):V("",!0)])}}},vf={class:"card divide-y divide-borde max-h-[32rem] overflow-y-auto"},hf={class:"cursor-pointer flex items-center gap-2 text-sm"},gf={class:"text-tenue text-xs shrink-0"},bf={class:"text-texto truncate"},xf={class:"text-tenue text-xs ml-auto shrink-0"},yf={key:0,class:"mt-2 bg-elevado border border-borde rounded-lg p-2 text-xs text-gray-600 dark:text-gray-400 overflow-x-auto whitespace-pre-wrap"},_f={__name:"TabAuditoria",props:{eventos:{type:Array,default:()=>[]}},setup(e){const t=le(()=>s=>({error:"badge-error",warn:"badge-alerta"})[s]||"badge-neutro");function n(s){try{return new Date(s).toLocaleString()}catch{return s}}return(s,o)=>(v(),b("div",null,[o[0]||(o[0]=r("p",{class:"label mb-4"}," Errores y eventos técnicos de este agente — fallos al llamar a la IA, a una herramienta, al correo o a los canales. Últimos 100. ",-1)),r("div",vf,[e.eventos.length===0?(v(),de(qe,{key:0,estado:"contenta",titulo:"Ningún problema registrado",detalle:"Acá aparecen los errores: una herramienta que no responde, una fuente que no se pudo leer. Que esté vacío es buena señal."})):V("",!0),(v(!0),b(Z,null,ce(e.eventos,a=>(v(),b("details",{key:a.ID,class:"p-3"},[r("summary",hf,[r("span",{class:ie(["px-1.5 py-0.5 rounded text-xs shrink-0",t.value(a.nivel)])},S(a.nivel),3),r("span",gf,S(a.origen),1),r("span",bf,S(a.mensaje),1),r("span",xf,S(n(a.CreatedAt)),1)]),a.detalle?(v(),b("pre",yf,S(a.detalle),1)):V("",!0)]))),128))])]))}},wf={class:"space-y-6"},kf={key:0,class:"text-sm text-red-600 dark:text-red-400"},$f={key:1,class:"label"},Cf={class:"card divide-y divide-borde"},Af={class:"min-w-0"},Sf={class:"font-medium text-texto"},Ef={class:"text-xs text-tenue mt-0.5"},If={key:0,class:"text-xs text-tenue mt-1"},Rf=["onClick"],Pf={class:"card divide-y divide-borde"},Df={class:"min-w-0"},Tf={class:"flex items-center gap-2 flex-wrap"},Of={class:"font-medium text-texto"},Mf={key:0,class:"px-1.5 py-0.5 rounded text-xs badge-neutro"},Nf={key:1,class:"px-1.5 py-0.5 rounded text-xs bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300"},jf={class:"text-xs text-tenue mt-0.5"},Vf={key:0,class:"text-xs text-red-600 dark:text-red-400 mt-1"},Uf=["onClick"],Lf={__name:"TabAcciones",props:{agenteId:{type:Number,required:!0}},setup(e){const t=e,n=j([]),s=j([]),o=j(!0),a=j("");async function l(){o.value=!0,a.value="";try{const f=await Q.get(K(`/umind/avisos?agente_id=${t.agenteId}`));n.value=(f.items||[]).filter(_=>_.estado==="pendiente")}catch(f){a.value=f.message}try{const f=await Q.get(K(`/umind/vigilancias?agente_id=${t.agenteId}`));s.value=f.items||[]}catch{s.value=[]}o.value=!1}Ge(()=>t.agenteId,l,{immediate:!0});async function i(f){confirm(`¿Cancelar el recordatorio "${f.titulo}"?`)&&(await Q.del(K(`/umind/avisos/${f.ID}`)),await l())}async function u(f){confirm(`¿Apagar la vigilancia "${f.nombre}"?`)&&(await Q.del(K(`/umind/vigilancias/${f.ID}`)),await l())}const d={diario:"todos los días",semanal:"cada semana",mensual:"cada mes",anual:"cada año"};function c(f){return new Date(f).toLocaleString("es",{day:"numeric",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"})}function p(f){return!f||f.startsWith("panel:")?"te llega por correo":f.startsWith("tg:")?"te llega por Telegram":f.startsWith("mail:")?`te llega a ${f.slice(5)}`:"te llega por correo"}return(f,_)=>(v(),b("div",wf,[a.value?(v(),b("p",kf,S(a.value),1)):V("",!0),o.value?(v(),b("p",$f,"Cargando…")):(v(),b(Z,{key:2},[r("section",null,[_[0]||(_[0]=r("h3",{class:"text-sm font-medium text-texto mb-1"},"Recordatorios",-1)),_[1]||(_[1]=r("p",{class:"label mb-3"},[Y(" Pediselos hablando: "),r("em",null,"«avisame el 15 de marzo que vence la póliza de Acme, y todos los años»"),Y(". ")],-1)),r("div",Cf,[n.value.length===0?(v(),de(qe,{key:0,titulo:"Sin recordatorios",detalle:"Escribile desde tu canal privado y programá el primero. Te avisa por donde se lo pediste."})):V("",!0),(v(!0),b(Z,null,ce(n.value,k=>(v(),b("div",{key:k.ID,class:"p-4 flex items-center justify-between gap-4"},[r("div",Af,[r("span",Sf,S(k.titulo),1),r("p",Ef,[Y(S(c(k.proximo_at))+" ",1),k.repetir?(v(),b(Z,{key:0},[Y(" · se repite "+S(d[k.repetir]||k.repetir),1)],64)):V("",!0),Y(" · "+S(p(k.destino)),1)]),k.detalle?(v(),b("p",If,S(k.detalle),1)):V("",!0)]),r("button",{class:"text-sm text-red-500 hover:text-red-700 shrink-0",onClick:A=>i(k)},"Cancelar",8,Rf)]))),128))])]),r("section",null,[_[2]||(_[2]=r("h3",{class:"text-sm font-medium text-texto mb-1"},"Vigilancias",-1)),_[3]||(_[3]=r("p",{class:"label mb-3"}," Consultan una de tus herramientas cada tanto y te avisan cuando pasa algo. Solo avisan al entrar en la condición, no cada vez que la revisan. ",-1)),r("div",Pf,[s.value.length===0?(v(),de(qe,{key:0,titulo:"Sin vigilancias",detalle:"Si tenés una herramienta conectada, pedile algo como «avisame cuando esta API devuelva stock en cero»."})):V("",!0),(v(!0),b(Z,null,ce(s.value,k=>(v(),b("div",{key:k.ID,class:"p-4 flex items-center justify-between gap-4"},[r("div",Df,[r("div",Tf,[r("span",Of,S(k.nombre),1),k.activa?k.en_condicion?(v(),b("span",Nf,"se está cumpliendo")):V("",!0):(v(),b("span",Mf,"apagada"))]),r("p",jf,S(k.condicion)+" · revisa cada "+S(k.intervalo_min)+" min · usa "+S(k.herramienta),1),k.ultimo_error?(v(),b("p",Vf,S(k.ultimo_error),1)):V("",!0)]),r("button",{class:"text-sm text-red-500 hover:text-red-700 shrink-0",onClick:A=>u(k)},"Apagar",8,Uf)]))),128))])])],64))]))}},qf={class:"flex flex-wrap gap-2 mb-4"},Ff={class:"grid sm:grid-cols-2 gap-3"},Hf={key:0,class:"grid sm:grid-cols-3 gap-3"},Bf={key:1,class:"text-sm text-red-600 dark:text-red-400"},Wf={class:"flex items-center justify-between"},Gf=["disabled"],zf={class:"card divide-y divide-borde"},Kf={class:"flex items-center justify-between gap-3"},Qf={class:"min-w-0"},Jf={class:"font-medium text-texto"},Yf={class:"ml-2 text-xs text-tenue"},Zf={class:"flex items-center gap-3 shrink-0 text-sm"},Xf=["disabled","onClick"],ep=["onClick"],tp={__name:"TabConexiones",props:{agenteId:{type:Number,required:!0},conexiones:{type:Array,default:()=>[]}},emits:["recargar"],setup(e,{emit:t}){const n=e,s=t,o=j(!1),a=j(!1),l=j(""),i=j(!1),u=j({email:"",password:"",host:"",puerto:993,encriptado:"ssl"}),d=j(null),c=j({});function p(I){return{google:"Google",microsoft:"Outlook",imap:"Casilla propia"}[I]||I}function f(I){window.location.href=K(`/umind/conexiones/conectar?agente_id=${n.agenteId}&proveedor=${I}`)}async function _(){a.value=!0,l.value="";try{await Q.post(K("/umind/conexiones/imap"),{agente_id:n.agenteId,email:u.value.email,password:u.value.password,host:i.value?u.value.host:"",puerto:i.value?Number(u.value.puerto):0,encriptado:u.value.encriptado}),o.value=!1,u.value={email:"",password:"",host:"",puerto:993,encriptado:"ssl"},i.value=!1,s("recargar")}catch(I){l.value=I.message,!i.value&&/conexión|conectar|resolver/i.test(I.message)&&(i.value=!0)}finally{a.value=!1}}async function k(I){d.value=I.ID,c.value={...c.value,[I.ID]:null};try{const y=await Q.post(K(`/umind/conexiones/${I.ID}/probar`),{});c.value={...c.value,[I.ID]:{ok:!0,texto:y.message}}}catch(y){c.value={...c.value,[I.ID]:{ok:!1,texto:y.message}}}finally{d.value=null}}async function A(I){confirm(`¿Desconectar la cuenta ${I.email||I.proveedor}?`)&&(await Q.del(K(`/umind/conexiones/${I.ID}`)),s("recargar"))}return(I,y)=>(v(),b("div",null,[y[15]||(y[15]=r("p",{class:"label mb-4"},[Y(" Las casillas que el agente puede consultar cuando se lo pedís — por ejemplo: "),r("em",null,"«revisame los correos de hoy y haceme un resumen»"),Y(". Solo lectura: nunca marca nada como leído ni manda nada. Podés conectar varias. ")],-1)),r("div",qf,[r("button",{class:"border border-borde hover:border-brand text-sm font-medium px-4 py-2 rounded-lg text-gray-700 dark:text-gray-200 transition-colors",onClick:y[0]||(y[0]=w=>f("google"))}," Conectar Google "),r("button",{class:"border border-borde hover:border-brand text-sm font-medium px-4 py-2 rounded-lg text-gray-700 dark:text-gray-200 transition-colors",onClick:y[1]||(y[1]=w=>f("microsoft"))}," Conectar Outlook "),r("button",{class:"btn-primary",onClick:y[2]||(y[2]=w=>o.value=!o.value)}," + Casilla propia ")]),o.value?(v(),b("form",{key:0,class:"card p-4 mb-4 space-y-3",onSubmit:Ae(_,["prevent"])},[r("div",Ff,[r("div",null,[y[8]||(y[8]=r("label",{class:"label"},"Correo",-1)),z(r("input",{"onUpdate:modelValue":y[3]||(y[3]=w=>u.value.email=w),type:"email",required:"",placeholder:"ventas@tunegocio.com",class:"input"},null,512),[[re,u.value.email]])]),r("div",null,[y[9]||(y[9]=r("label",{class:"label"},"Contraseña",-1)),z(r("input",{"onUpdate:modelValue":y[4]||(y[4]=w=>u.value.password=w),type:"password",autocomplete:"new-password",required:"",class:"input"},null,512),[[re,u.value.password]])])]),i.value?(v(),b("div",Hf,[r("div",null,[y[10]||(y[10]=r("label",{class:"label"},"Servidor IMAP",-1)),z(r("input",{"onUpdate:modelValue":y[5]||(y[5]=w=>u.value.host=w),placeholder:"mail.tunegocio.com",class:"input font-mono"},null,512),[[re,u.value.host]])]),r("div",null,[y[11]||(y[11]=r("label",{class:"label"},"Puerto",-1)),z(r("input",{"onUpdate:modelValue":y[6]||(y[6]=w=>u.value.puerto=w),type:"number",class:"input"},null,512),[[re,u.value.puerto]])]),r("div",null,[y[13]||(y[13]=r("label",{class:"label"},"Encriptación",-1)),z(r("select",{"onUpdate:modelValue":y[7]||(y[7]=w=>u.value.encriptado=w),class:"input"},[...y[12]||(y[12]=[r("option",{value:"ssl"},"SSL (993)",-1),r("option",{value:"starttls"},"STARTTLS (143)",-1)])],512),[[Nt,u.value.encriptado]])])])):V("",!0),l.value?(v(),b("p",Bf,S(l.value),1)):V("",!0),r("div",Wf,[y[14]||(y[14]=r("p",{class:"text-xs text-tenue"}," Se prueba la conexión antes de guardar. Gmail y Outlook van por sus botones de arriba. ",-1)),r("button",{type:"submit",class:"btn-primary",disabled:a.value},S(a.value?"Probando…":"Conectar"),9,Gf)])],32)):V("",!0),r("div",zf,[e.conexiones.length===0?(v(),de(qe,{key:0,titulo:"Sin cuentas conectadas",detalle:"Conectá una casilla y vas a poder pedirle al agente que te revise el correo y te lo resuma, sin abrir el webmail."})):V("",!0),(v(!0),b(Z,null,ce(e.conexiones,w=>(v(),b("div",{key:w.ID,class:"p-4"},[r("div",Kf,[r("div",Qf,[r("span",Jf,S(w.email),1),r("span",Yf,S(p(w.proveedor)),1),r("span",{class:ie(["ml-2 px-1.5 py-0.5 rounded text-xs",w.activo?"badge-ok":"badge-neutro"])},S(w.activo?"activa":"inactiva"),3),c.value[w.ID]?(v(),b("p",{key:0,class:ie(["text-xs mt-1",c.value[w.ID].ok?"text-brand":"text-red-600 dark:text-red-400"])},S(c.value[w.ID].texto),3)):V("",!0)]),r("div",Zf,[w.proveedor==="imap"?(v(),b("button",{key:0,class:"text-brand hover:underline disabled:opacity-40",disabled:d.value===w.ID,onClick:g=>k(w)},S(d.value===w.ID?"Probando…":"Probar"),9,Xf)):V("",!0),r("button",{class:"text-red-500 hover:text-red-700",onClick:g=>A(w)},"Desconectar",8,ep)])])]))),128))])]))}},np={class:"card p-4 flex flex-col h-[28rem]"},sp={class:"flex-1 overflow-y-auto space-y-2 mb-3"},op={key:0,class:"text-sm text-tenue"},ap={key:1,class:"flex items-center gap-2"},rp=["disabled"],_a={__name:"TabChat",props:{agenteId:{type:Number,required:!0}},setup(e){const t=e,n=`staff-preview-${Math.random().toString(36).slice(2)}`,s=j([]),o=j(""),a=j(!1);async function l(){const i=o.value.trim();if(!(!i||a.value)){o.value="",s.value.push({role:"user",content:i}),a.value=!0;try{const u=await Q.post(K("/umind/chat"),{agente_id:t.agenteId,session_id:n,mensaje:i});s.value.push({role:"assistant",content:u.respuesta})}catch(u){s.value.push({role:"assistant",content:`⚠ ${u.message}`})}finally{a.value=!1}}}return(i,u)=>(v(),b("div",np,[r("div",sp,[s.value.length===0?(v(),b("p",op," Probá este agente tal cual lo va a ver un visitante — usa la misma config de IA, las mismas herramientas y la misma base de conocimiento. ")):V("",!0),(v(!0),b(Z,null,ce(s.value,(d,c)=>(v(),b("div",{key:c,class:ie(["max-w-[80%] px-3 py-2 rounded-lg text-sm whitespace-pre-wrap",d.role==="user"?"bg-brand text-white ml-auto":"bg-elevado text-texto"])},S(d.content),3))),128)),a.value?(v(),b("div",ap,[ne(Vn,{estado:"pensando",tam:30,class:"text-brand"}),u[1]||(u[1]=r("span",{class:"text-xs text-tenue"},"Pensando…",-1))])):V("",!0)]),r("form",{class:"flex gap-2",onSubmit:Ae(l,["prevent"])},[z(r("input",{"onUpdate:modelValue":u[0]||(u[0]=d=>o.value=d),placeholder:"Escribí un mensaje de prueba...",class:"flex-1 input"},null,512),[[re,o.value]]),r("button",{type:"submit",disabled:a.value,class:"btn-primary disabled:opacity-50 transition-colors"}," Enviar ",8,rp)],32)]))}},lp={class:"grid lg:grid-cols-[minmax(16rem,22rem)_1fr] gap-4 items-start"},ip={class:"card divide-y divide-borde max-h-[30rem] overflow-y-auto"},up=["onClick"],cp={class:"text-sm text-texto line-clamp-2"},dp={class:"flex items-center gap-2 mt-1.5 text-xs text-tenue"},fp={class:"tabular-nums"},pp={class:"ml-auto tabular-nums"},mp={key:2,class:"flex items-center gap-2 p-3 text-xs text-tenue cursor-pointer select-none"},vp={class:"card p-4 max-h-[30rem] overflow-y-auto"},hp={key:1,class:"text-sm text-tenue"},gp={key:2,class:"space-y-2"},bp={__name:"TabConversaciones",props:{agenteId:{type:Number,required:!0},sesiones:{type:Array,default:()=>[]}},setup(e){const t=e,n=j([]),s=j(null),o=j(!1);function a(f){const _=String(f||"");return _.startsWith("wa:")?{nombre:"WhatsApp",icono:"mensaje",clase:"badge-ok"}:_.startsWith("tg:")?{nombre:"Telegram",icono:"mensaje",clase:"badge-neutro"}:_.startsWith("staff-preview")?{nombre:"Prueba",icono:"escribir",clase:"badge-alerta"}:{nombre:"Tu web",icono:"sitio",clase:"badge-neutro"}}const l=j(!1),i=le(()=>t.sesiones.filter(f=>!String(f.session_id).startsWith("staff-preview"))),u=le(()=>t.sesiones.filter(f=>String(f.session_id).startsWith("staff-preview"))),d=le(()=>l.value?t.sesiones:i.value);function c(f){if(!f)return"";const _=new Date(f),A=Math.floor((new Date().setHours(0,0,0,0)-new Date(_).setHours(0,0,0,0))/864e5),I=new Date(f).toLocaleTimeString("es",{hour:"2-digit",minute:"2-digit"});return A<=0?`hoy ${I}`:A===1?`ayer ${I}`:A<7?`hace ${A} días`:new Date(f).toLocaleDateString("es",{day:"numeric",month:"short"})}async function p(f){s.value=f,o.value=!0;try{const _=await Q.get(K(`/umind/historial?agente_id=${t.agenteId}&session_id=${f}`));n.value=_.items||[]}finally{o.value=!1}}return(f,_)=>(v(),b("div",lp,[r("div",ip,[d.value.length===0&&u.value.length===0?(v(),de(qe,{key:0,titulo:"Nadie escribió todavía",detalle:"Cuando alguien le hable a tu asistente —por WhatsApp, Telegram o tu web— la conversación aparece acá, con lo que preguntó y lo que se le respondió."})):d.value.length===0?(v(),de(qe,{key:1,estado:"durmiendo",titulo:"Ningún cliente escribió todavía",detalle:`Hay ${u.value.length} ${u.value.length===1?"conversación de prueba tuya":"conversaciones de prueba tuyas"} — activá el filtro de abajo para verlas.`},null,8,["detalle"])):V("",!0),(v(!0),b(Z,null,ce(d.value,k=>(v(),b("button",{key:k.session_id,class:ie(["w-full text-left p-3 hover:bg-elevado transition-colors",s.value===k.session_id?"bg-gray-50 dark:bg-gray-800":""]),onClick:A=>p(k.session_id)},[r("div",cp,S(k.primer_mensaje||"(sin mensaje)"),1),r("div",dp,[r("span",{class:ie(["px-1.5 py-0.5 rounded",a(k.session_id).clase])},S(a(k.session_id).nombre),3),r("span",fp,S(k.mensajes)+" msj",1),r("span",pp,S(c(k.ultimo)),1)])],10,up))),128)),u.value.length?(v(),b("label",mp,[z(r("input",{"onUpdate:modelValue":_[0]||(_[0]=k=>l.value=k),type:"checkbox",class:"rounded border-borde text-brand focus:ring-brand"},null,512),[[st,l.value]]),Y(" "+S(u.value.length===1?"Mostrar mi prueba":`Mostrar mis ${u.value.length} pruebas`),1)])):V("",!0)]),r("div",vp,[s.value?o.value?(v(),b("p",hp,"Cargando…")):(v(),b("div",gp,[(v(!0),b(Z,null,ce(n.value,k=>(v(),b("div",{key:k.ID,class:ie(k.role==="user"?"text-right":"")},[r("div",{class:ie(["inline-block max-w-[80%] px-3 py-2 rounded-lg text-sm text-left whitespace-pre-wrap",k.role==="user"?"bg-brand text-white":"bg-elevado text-texto"])},S(k.content),3),r("div",{class:ie(["text-[10px] text-tenue mt-0.5 tabular-nums",k.role==="user"?"pr-1":"pl-1"])},S(c(k.CreatedAt)),3)],2))),128))])):(v(),de(qe,{key:0,estado:"normal",titulo:"Elegí una conversación",detalle:"Vas a ver el intercambio completo: lo que escribió la persona y lo que contestó tu asistente. Si una respuesta no te gusta, corregí su información en «Lo que sabe»."}))])]))}},xp={class:"card divide-y divide-borde"},yp={class:"text-sm text-texto font-mono"},_p={class:"label mt-0.5"},wp={class:"text-xs text-tenue mt-0.5"},kp={key:0,class:"ml-1 text-green-600 dark:text-green-400"},$p={key:1,class:"ml-1 text-gray-400"},Cp={class:"flex gap-3 text-sm shrink-0"},Ap=["onClick"],Sp=["onClick"],Ep={class:"card p-6 w-full max-w-xl max-h-[85vh] overflow-y-auto"},Ip={class:"font-semibold text-texto mb-4"},Rp={class:"border border-borde rounded-lg p-3 space-y-2"},Pp=["onUpdate:modelValue"],Dp=["onUpdate:modelValue"],Tp=["onUpdate:modelValue"],Op={class:"label flex items-center gap-1"},Mp=["onUpdate:modelValue"],Np=["onClick"],jp={key:0,class:"text-xs text-gray-400"},Vp={class:"border border-borde rounded-lg p-3 space-y-2"},Up={class:"flex items-center gap-2 label"},Lp={class:"flex items-center gap-2 text-sm text-texto"},qp={class:"flex justify-end gap-2 pt-2"},Fp={__name:"TabHerramientas",props:{agenteId:{type:Number,required:!0},tools:{type:Array,default:()=>[]}},emits:["recargar","error"],setup(e,{emit:t}){const n=e,s=t,o=j(!1),a=j(null),l=j(i());function i(){return{nombre:"",descripcion:"",url:"",auth_header_nombre:"",auth_header_valor:"",tocarAuth:!1,parametros:[],activa:!0}}function u(){a.value=null,l.value=i(),o.value=!0}function d(k){a.value=k;let A=[];try{A=JSON.parse(k.parametros_json||"[]")||[]}catch{A=[]}l.value={nombre:k.nombre,descripcion:k.descripcion,url:k.url,auth_header_nombre:k.auth_header_nombre,auth_header_valor:"",tocarAuth:!1,parametros:A,activa:k.activa},o.value=!0}function c(){l.value.parametros.push({nombre:"",tipo:"string",descripcion:"",requerido:!1})}function p(k){l.value.parametros.splice(k,1)}async function f(){const k={agente_id:n.agenteId,nombre:l.value.nombre.trim(),descripcion:l.value.descripcion,url:l.value.url.trim(),auth_header_nombre:l.value.auth_header_nombre,parametros:l.value.parametros,activa:l.value.activa};l.value.tocarAuth&&(k.auth_header_valor=l.value.auth_header_valor);try{a.value?await Q.put(K(`/umind/tools/${a.value.ID}`),k):await Q.post(K("/umind/tools"),k),o.value=!1,s("recargar")}catch(A){s("error",A.message)}}async function _(k){confirm(`¿Eliminar la herramienta "${k.nombre}"?`)&&(await Q.del(K(`/umind/tools/${k.ID}`)),s("recargar"))}return(k,A)=>(v(),b("div",null,[r("div",{class:"flex justify-between items-center mb-4"},[A[9]||(A[9]=r("p",{class:"label"},"Máximo 10 herramientas activas por agente.",-1)),r("button",{class:"btn-primary",onClick:u}," + Nueva herramienta ")]),r("div",xp,[e.tools.length===0?(v(),de(qe,{key:0,titulo:"Sin herramientas conectadas",detalle:"Las herramientas le dejan consultar tus sistemas mientras conversa: stock, estado de un pedido, disponibilidad de turnos. Sin ninguna, responde solo con lo que tiene cargado."})):V("",!0),(v(!0),b(Z,null,ce(e.tools,I=>(v(),b("div",{key:I.ID,class:"p-4 flex items-center justify-between"},[r("div",null,[r("div",yp,S(I.nombre),1),r("div",_p,S(I.descripcion),1),r("div",wp,[Y(S(I.url)+" ",1),I.auth_configurado?(v(),b("span",kp,"· auth configurada")):V("",!0),I.activa?V("",!0):(v(),b("span",$p,"· inactiva"))])]),r("div",Cp,[r("button",{class:"text-tenue hover:text-gray-800 dark:hover:text-gray-100",onClick:y=>d(I)},"Editar",8,Ap),r("button",{class:"text-red-500 hover:text-red-700",onClick:y=>_(I)},"Eliminar",8,Sp)])]))),128))]),o.value?(v(),b("div",{key:0,class:"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50",onClick:A[8]||(A[8]=Ae(I=>o.value=!1,["self"]))},[r("div",Ep,[r("h2",Ip,S(a.value?"Editar herramienta":"Nueva herramienta"),1),r("form",{class:"space-y-3",onSubmit:Ae(f,["prevent"])},[r("div",null,[A[10]||(A[10]=r("label",{class:"label"},"Nombre (identificador, ej: consultar_stock)",-1)),z(r("input",{"onUpdate:modelValue":A[0]||(A[0]=I=>l.value.nombre=I),required:"",pattern:"[a-z][a-z0-9_]{2,63}",class:"input font-mono"},null,512),[[re,l.value.nombre]])]),r("div",null,[A[11]||(A[11]=r("label",{class:"label"},"Descripción (esto lo lee el modelo para decidir cuándo usarla)",-1)),z(r("textarea",{"onUpdate:modelValue":A[1]||(A[1]=I=>l.value.descripcion=I),rows:"2",required:"",class:"input"},null,512),[[re,l.value.descripcion]])]),r("div",null,[A[12]||(A[12]=r("label",{class:"label"},"URL del webhook (https)",-1)),z(r("input",{"onUpdate:modelValue":A[2]||(A[2]=I=>l.value.url=I),type:"url",required:"",placeholder:"https://...",class:"input"},null,512),[[re,l.value.url]])]),r("div",Rp,[r("div",{class:"flex items-center justify-between"},[A[13]||(A[13]=r("label",{class:"label"},"Parámetros que completa el modelo",-1)),r("button",{type:"button",class:"text-xs text-brand",onClick:c},"+ agregar")]),(v(!0),b(Z,null,ce(l.value.parametros,(I,y)=>(v(),b("div",{key:y,class:"flex gap-2 items-center"},[z(r("input",{"onUpdate:modelValue":w=>I.nombre=w,placeholder:"nombre",class:"flex-1 border border-borde bg-white dark:bg-gray-800 text-texto rounded px-2 py-1 text-xs font-mono"},null,8,Pp),[[re,I.nombre]]),z(r("select",{"onUpdate:modelValue":w=>I.tipo=w,class:"border border-borde bg-white dark:bg-gray-800 text-texto rounded px-2 py-1 text-xs"},[...A[14]||(A[14]=[r("option",{value:"string"},"string",-1),r("option",{value:"number"},"number",-1),r("option",{value:"boolean"},"boolean",-1)])],8,Dp),[[Nt,I.tipo]]),z(r("input",{"onUpdate:modelValue":w=>I.descripcion=w,placeholder:"descripción",class:"flex-1 border border-borde bg-white dark:bg-gray-800 text-texto rounded px-2 py-1 text-xs"},null,8,Tp),[[re,I.descripcion]]),r("label",Op,[z(r("input",{"onUpdate:modelValue":w=>I.requerido=w,type:"checkbox"},null,8,Mp),[[st,I.requerido]]),A[15]||(A[15]=Y(" req. ",-1))]),r("button",{type:"button",class:"text-red-400 text-xs",onClick:w=>p(y)},[ne(Ne,{nombre:"cerrar",tam:13})],8,Np)]))),128)),l.value.parametros.length===0?(v(),b("p",jp,"Sin parámetros.")):V("",!0)]),r("div",Vp,[A[16]||(A[16]=r("label",{class:"label"},"Autenticación saliente (opcional)",-1)),z(r("input",{"onUpdate:modelValue":A[3]||(A[3]=I=>l.value.auth_header_nombre=I),placeholder:"Nombre del header, ej: Authorization",class:"input"},null,512),[[re,l.value.auth_header_nombre]]),r("label",Up,[z(r("input",{"onUpdate:modelValue":A[4]||(A[4]=I=>l.value.tocarAuth=I),type:"checkbox"},null,512),[[st,l.value.tocarAuth]]),Y(" "+S(a.value?"Cambiar el valor del secreto":"Configurar valor"),1)]),l.value.tocarAuth?z((v(),b("input",{key:0,"onUpdate:modelValue":A[5]||(A[5]=I=>l.value.auth_header_valor=I),type:"password",placeholder:"Valor del header (ej: Bearer xxxx)",class:"input"},null,512)),[[re,l.value.auth_header_valor]]):V("",!0)]),r("label",Lp,[z(r("input",{"onUpdate:modelValue":A[6]||(A[6]=I=>l.value.activa=I),type:"checkbox"},null,512),[[st,l.value.activa]]),A[17]||(A[17]=Y(" Activa ",-1))]),r("div",qp,[r("button",{type:"button",class:"btn-ghost",onClick:A[7]||(A[7]=I=>o.value=!1)},"Cancelar"),A[18]||(A[18]=r("button",{type:"submit",class:"btn-primary"},"Guardar",-1))])],32)])])):V("",!0)]))}},Hp={class:"card p-4 mb-4"},Bp={class:"flex items-center justify-between mb-2"},Wp={class:"bg-elevado border border-borde rounded-lg p-2.5 text-xs text-texto overflow-x-auto"},Gp={class:"card divide-y divide-borde"},zp={class:"flex items-center justify-between"},Kp={class:"font-medium text-texto capitalize"},Qp={key:0,class:"ml-2 px-1.5 py-0.5 rounded text-xs badge-neutro"},Jp={class:"flex gap-3 text-sm"},Yp=["onClick"],Zp=["onClick"],Xp={class:"flex gap-4 mt-2 text-xs"},em={class:"flex items-center gap-1.5 text-texto cursor-pointer"},tm=["checked","onChange"],nm={class:"flex items-center gap-1.5 text-texto cursor-pointer"},sm=["checked","onChange"],om={class:"flex items-center gap-1.5 text-texto cursor-pointer"},am=["checked","onChange"],rm={key:0,class:"label mt-1"},lm={key:1,class:"label mt-1 break-all"},im={class:"bg-elevado px-1 rounded"},um={key:2,class:"text-xs text-tenue mt-1"},cm={key:3,class:"text-xs text-red-600 dark:text-red-400 mt-1"},dm={class:"card p-6 w-full max-w-md"},fm={key:0},pm={class:"grid sm:grid-cols-2 gap-3"},mm={class:"flex flex-col gap-1.5 mt-1"},vm={class:"flex items-center gap-2 text-sm text-texto cursor-pointer"},hm={class:"flex items-center gap-2 text-sm text-texto cursor-pointer"},gm={class:"text-xs text-tenue"},bm={class:"grid sm:grid-cols-2 gap-3 mt-2"},xm={class:"rounded-lg border border-borde p-3 mt-1"},ym={class:"flex items-start gap-2 text-sm text-texto cursor-pointer"},_m={class:"flex flex-col gap-2 pt-1"},wm={class:"flex items-center gap-2 text-sm text-texto cursor-pointer"},km={class:"flex items-center gap-2 text-sm text-texto cursor-pointer"},$m={class:"flex items-center gap-2 text-sm text-texto cursor-pointer"},Cm={class:"flex justify-end gap-2 pt-2"},Am={__name:"TabCanales",props:{agenteId:{type:Number,required:!0},siteKey:{type:String,default:""},canales:{type:Array,default:()=>[]}},emits:["recargar","error"],setup(e,{emit:t}){const n=e,s=t,o=j(!1),a=j(d()),l=j(!1),i=le(()=>{const w=n.siteKey||"TU_SITE_KEY";return` +