feat(studio): muestra el cupo de agentes del plan en pantalla

Se podían crear 2 agentes con un plan de 1 sin que nada lo explicara. El
límite del backend sí funciona, pero solo aplica si el tenant tiene un
plan asignado — y hasta 115f167 los campos Cliente y Plan no aparecían en
el formulario del tenant, así que en la práctica quedaban todos sin plan
y sin límite.

El problema de fondo era que ese estado no se veía por ningún lado: un
tenant sin plan se comportaba igual que un límite roto.

- La lista de agentes devuelve también el plan y cuántos hay usados.
- La pantalla muestra "Básico · 1 de 1", "Pro · ilimitado" o "sin plan ·
  sin límite", y en este último caso explica que hay que asignarle uno.
- El botón + Nuevo agente se deshabilita al llegar al tope, en vez de
  dejar que el límite aparezca recién como un error al guardar.

El backend sigue siendo la autoridad: esto es feedback, no la validación.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-14 22:05:32 -05:00
co-authored by Claude Sonnet 5
parent 5c931c9400
commit 45f773fb62
7 changed files with 73 additions and 32 deletions
+34 -2
View File
@@ -17,6 +17,24 @@ const showForm = ref(false)
const editing = ref(null)
const form = ref(vacio())
const plan = ref(null)
// max_agentes 0 = ilimitado. Sin plan asignado tampoco hay límite: es
// deliberado (los tenants que existían antes de los planes no se rompen),
// pero hay que decirlo, o parece que el límite está fallando.
const cupo = computed(() => {
if (!plan.value) return { sinPlan: true }
const max = plan.value.max_agentes || 0
return {
sinPlan: false,
nombre: plan.value.nombre,
ilimitado: max <= 0,
max,
usados: agentes.value.length,
lleno: max > 0 && agentes.value.length >= max,
}
})
function vacio() {
return { nombre: '', ai_config_id: null, tono: '', mensaje_bienvenida: '', color: '#8eb02f', activo: true }
}
@@ -31,6 +49,7 @@ async function cargar() {
])
tenant.value = (t.items || []).find((x) => String(x.ID) === props.id) || null
agentes.value = a.items || []
plan.value = a.plan || null
aiConfigs.value = ai.items || []
} catch (e) {
error.value = e.message
@@ -96,9 +115,22 @@ onMounted(cargar)
<p v-if="error" class="text-sm text-red-600 dark:text-red-400 mb-4">{{ error }}</p>
<div class="flex items-center justify-between mb-4">
<h2 class="text-sm font-medium text-tenue">Agentes</h2>
<button class="btn-primary" @click="nuevoAgente">+ Nuevo agente</button>
<div class="flex items-center gap-2">
<h2 class="text-sm font-medium text-tenue">Agentes</h2>
<span v-if="cupo.sinPlan" class="badge-alerta">sin plan · sin límite</span>
<span v-else-if="cupo.ilimitado" class="badge-neutro">{{ cupo.nombre }} · ilimitado</span>
<span v-else :class="cupo.lleno ? 'badge-alerta' : 'badge-neutro'">
{{ cupo.nombre }} · {{ cupo.usados }} de {{ cupo.max }}
</span>
</div>
<button class="btn-primary" :disabled="cupo.lleno" @click="nuevoAgente">+ Nuevo agente</button>
</div>
<p v-if="cupo.sinPlan && !contexto.esPortal" class="text-xs text-tenue -mt-2 mb-4">
Este tenant no tiene plan asignado, así que no se le aplica ningún límite de agentes. Asignale uno desde el lápiz del tenant en la barra izquierda.
</p>
<p v-else-if="cupo.lleno" class="text-xs text-tenue -mt-2 mb-4">
Alcanzaste el máximo de agentes de tu plan.
</p>
<div class="card divide-y divide-borde overflow-hidden">
<UiEmptyState