fix(studio): los campos Cliente y Plan no aparecían al crear un tenant

/app/api/clientes/select devuelve el array pelado y /app/umind-planes/list
lo envuelve en {items}. El código leía solo .registros/.items, así que con
el array quedaba vacío — y el bloque estaba detrás de v-if="clientes.length",
o sea que Cliente y Plan simplemente no se veían y no había forma de
asignarlos.

- lista() acepta las dos formas (verificado contra ambas y contra
  vacío/null/undefined).
- Se cargan con allSettled: que falle uno ya no vacía el otro.
- El bloque se muestra siempre para el staff, y cada select dice a dónde ir
  si su lista está vacía. Antes un fallo de red se veía exactamente igual
  que "no hay nada configurado", que es lo que enmascaró este bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-14 21:49:32 -05:00
co-authored by Claude Sonnet 5
parent 693ff608b1
commit 115f1675ad
4 changed files with 58 additions and 44 deletions
+31 -17
View File
@@ -37,21 +37,31 @@ async function cargar() {
} }
} }
// Clientes y planes solo los necesita el staff para asignarlos; si el endpoint // Los endpoints del panel no comparten una forma de respuesta: el de clientes
// no está disponible (portal del cliente) el formulario sigue funcionando. // devuelve el array pelado y el de planes lo envuelve en {items}. Aceptar las
// dos evita que un select quede vacío sin que nadie se entere.
function lista(r) {
if (Array.isArray(r)) return r
return r?.items || r?.registros || []
}
// Clientes y planes solo los necesita el staff para asignarlos; en el portal
// del cliente esos endpoints no se alcanzan y el formulario no los muestra.
// Se cargan por separado a propósito: que falle uno no debe vaciar el otro.
async function cargarAsignables() { async function cargarAsignables() {
if (contexto.esPortal) return // endpoints de staff: el cliente no los alcanza if (contexto.esPortal) return
try { const [c, p] = await Promise.allSettled([
const [c, p] = await Promise.all([ api.get('/app/api/clientes/select'),
api.get('/app/api/clientes/select'), api.get('/app/umind-planes/list'),
api.get('/app/umind-planes/list'), ])
]) clientes.value = c.status === 'fulfilled' ? lista(c.value) : []
clientes.value = c.registros || c.items || [] planes.value = p.status === 'fulfilled' ? lista(p.value) : []
planes.value = p.items || [] // Sin esto un fallo se veía igual que "no hay nada configurado", que es
} catch { // justo lo que hizo que los campos Cliente y Plan no aparecieran.
clientes.value = [] const fallos = []
planes.value = [] if (c.status === 'rejected') fallos.push('clientes')
} if (p.status === 'rejected') fallos.push('planes')
if (fallos.length) error.value = `No se pudo cargar la lista de ${fallos.join(' ni ')}.`
} }
function nuevoTenant() { function nuevoTenant() {
@@ -202,7 +212,7 @@ onMounted(() => {
class="input" class="input"
/> />
</div> </div>
<div v-if="clientes.length" class="grid grid-cols-2 gap-3"> <div v-if="!contexto.esPortal" class="grid grid-cols-2 gap-3">
<div> <div>
<label class="label">Cliente</label> <label class="label">Cliente</label>
<select <select
@@ -212,7 +222,9 @@ onMounted(() => {
<option :value="null"> sin asignar </option> <option :value="null"> sin asignar </option>
<option v-for="c in clientes" :key="c.ID" :value="c.ID">{{ c.nombre }}</option> <option v-for="c in clientes" :key="c.ID" :value="c.ID">{{ c.nombre }}</option>
</select> </select>
<p class="text-[11px] text-tenue mt-1">Define quién ve este tenant desde el portal.</p> <p class="text-[11px] text-tenue mt-1">
{{ clientes.length ? 'Define quién ve este tenant desde el portal.' : 'No hay clientes activos — creá uno en Clientes.' }}
</p>
</div> </div>
<div> <div>
<label class="label">Plan</label> <label class="label">Plan</label>
@@ -225,7 +237,9 @@ onMounted(() => {
{{ p.nombre }} ({{ p.max_agentes === 0 ? '' : p.max_agentes }} agentes) {{ p.nombre }} ({{ p.max_agentes === 0 ? '' : p.max_agentes }} agentes)
</option> </option>
</select> </select>
<p class="text-[11px] text-tenue mt-1">Límite de agentes y precios de consumo.</p> <p class="text-[11px] text-tenue mt-1">
{{ planes.length ? 'Límite de agentes y precios de consumo.' : 'No hay planes — creá uno en uMind Planes.' }}
</p>
</div> </div>
</div> </div>
<label class="flex items-center gap-2 text-sm text-texto"> <label class="flex items-center gap-2 text-sm text-texto">
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>uMind Studio</title> <title>uMind Studio</title>
<script type="module" crossorigin src="/orchestrator/assets/index-DP70_OF3.js"></script> <script type="module" crossorigin src="/orchestrator/assets/index-D9xKBSe-.js"></script>
<link rel="stylesheet" crossorigin href="/orchestrator/assets/index-CkF90HJJ.css"> <link rel="stylesheet" crossorigin href="/orchestrator/assets/index-CkF90HJJ.css">
</head> </head>
<body class="bg-gray-50"> <body class="bg-gray-50">