fix(crm): alta de usuarios carga compañías del tenant en vez de slug a mano

La organización destino salía como texto libre (o fallaba al listar todos los
tenants del Hub con un token KC caduco). Ahora se carga desde /v1/auth/my-companies
(mismo origen que el switcher) y se elige la compañía; el tenant_slug se resuelve
de la compañía seleccionada.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ernesto Herrera
2026-07-17 17:03:36 -06:00
parent a14acf59e5
commit 0cffd351df
2 changed files with 26 additions and 40 deletions

View File

@@ -1,27 +1,24 @@
import { fail } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import { getKcAccessToken } from '$lib/server/api';
import { listWorkspaceTenants, createWorkspaceInvite } from '$lib/server/workspace-provision';
import { getUserCompanies } from '$lib/server/api';
import { createWorkspaceInvite } from '$lib/server/workspace-provision';
import { WORKSPACE_INVITE_ROLES } from '$lib/server/workspace-provision.shared';
export const load: PageServerLoad = async ({ cookies, fetch }) => {
const accessToken = getKcAccessToken(cookies);
if (!accessToken) {
return { tenants: [], canListTenants: false, roles: WORKSPACE_INVITE_ROLES };
}
// La organización destino sale de las compañías del usuario (mismo origen que
// el switcher, vía /v1/auth/my-companies). Así no se escribe el slug a mano ni
// se depende de listar todos los tenants del Hub con un token KC que caduca.
const companiesRaw = await getUserCompanies(cookies, fetch);
const companies = (companiesRaw ?? [])
.filter((c) => c && c.tenant_slug)
.map((c) => ({
id: c.id as number,
name: c.name as string,
tenant_slug: c.tenant_slug as string,
tenant_name: (c.tenant_name as string) || (c.tenant_slug as string)
}));
// Los hub_admin pueden listar todos los tenants (dropdown). Un admin de tenant
// no puede listarlos (403) pero sí puede invitar a SU tenant escribiendo el slug.
const res = await listWorkspaceTenants(accessToken, fetch);
if (!res.ok) {
return { tenants: [], canListTenants: false, roles: WORKSPACE_INVITE_ROLES };
}
return {
tenants: res.data.map((t) => ({ slug: t.slug, name: t.display_name || t.name })),
canListTenants: true,
roles: WORKSPACE_INVITE_ROLES
};
return { companies, roles: WORKSPACE_INVITE_ROLES };
};
export const actions: Actions = {
@@ -42,7 +39,7 @@ export const actions: Actions = {
return fail(422, { error: 'Ingresa un email válido para invitar.', values });
}
if (!tenant_slug) {
return fail(422, { error: 'Selecciona (o escribe) el slug de la organización destino.', values });
return fail(422, { error: 'Selecciona la compañía destino.', values });
}
if (!WORKSPACE_INVITE_ROLES.includes(role as (typeof WORKSPACE_INVITE_ROLES)[number])) {
return fail(422, { error: 'Rol inválido.', values });

View File

@@ -75,13 +75,7 @@
<Card.Root>
<Card.Header>
<Card.Title>Nueva invitación</Card.Title>
<Card.Description>
{#if data.canListTenants}
Elige la organización destino y el rol del usuario.
{:else}
Escribe el slug de tu organización y el rol del usuario.
{/if}
</Card.Description>
<Card.Description>Elige la compañía destino y el rol del usuario.</Card.Description>
</Card.Header>
<Card.Content>
<form
@@ -110,24 +104,19 @@
</label>
<label class="flex flex-col gap-1 text-sm">
<span class="font-medium">Organización *</span>
{#if data.canListTenants}
<span class="font-medium">Compañía destino *</span>
{#if data.companies.length > 0}
<select class={inputCls} name="tenant_slug" required value={prev('tenant_slug')}>
<option value="" disabled>Selecciona…</option>
{#each data.tenants as t (t.slug)}
<option value={t.slug}>{t.name} ({t.slug})</option>
{#each data.companies as c (c.id)}
<option value={c.tenant_slug}>{c.name} {c.tenant_name}</option>
{/each}
</select>
{:else}
<input
class="font-mono {inputCls}"
name="tenant_slug"
required
pattern="[a-z0-9-]+"
placeholder="empresa-abc"
title="Slug del tenant (minúsculas, dígitos y guiones)"
value={prev('tenant_slug')}
/>
<span class="rounded-md border border-dashed px-3 py-2 text-xs text-muted-foreground">
No hay compañías disponibles en tu tenant. Crea una en Compañías.
</span>
<input type="hidden" name="tenant_slug" value="" />
{/if}
</label>
@@ -142,7 +131,7 @@
</div>
<div class="mt-6 flex justify-end border-t pt-4">
<Button type="submit" disabled={submitting}>
<Button type="submit" disabled={submitting || data.companies.length === 0}>
{submitting ? 'Enviando…' : 'Enviar invitación'}
</Button>
</div>