Alta de usuarios en un tenant e invited_flow
Crear un usuario de Auth sin user_metadata.invited_flow: true dispara el trigger
handle_new_user_signup, que provisiona un tenant personal ("<nombre> tenant") y una
membership admin. Si acto seguido se enlaza al tenant objetivo, el usuario queda con
dos membresías.
Eso se ve así:
- En la lista de tenants aparece uno extra con el nombre de la persona.
- Al impersonar a ese usuario el menú del dashboard queda vacío: PostgREST no puede
devolver una sola membership,
tenant_idqueda vacío ymenus_accessibleno devuelve ítems.
La app asume un tenant por usuario de producto. El signup público (nueva organización) es el único camino que debe crear tenant + membership admin.
Contrato
Toda llamada a auth.admin.createUser que no sea un signup de organización nueva
debe pasar:
user_metadata: {
full_name: '...',
invited_flow: true,
}
El trigger en producción, si ve ese flag, no crea tenant personal (sí puede crear
profiles). La membership del tenant real la pone el RPC posterior
(link_user_to_tenant_by_role_id, create_tenant_with_admin_user, accept_invitation).
| Camino | Archivo | invited_flow |
|---|---|---|
| Añadir usuario en un tenant | frontend/src/actions/user.ts → createUserInTenant | sí |
| Crear tenant + admin (superadmin) | frontend/src/actions/superadmin.ts → createTenantWithAdmin | sí |
| Aceptar invitación | frontend/src/actions/invitations.ts → acceptInvitation | sí |
| Onboarding de cliente | backend/scripts/lib/onboarding-lib.ts → ensureTenant | sí |
| Signup público | frontend/src/auth/context/supabase/action.tsx → signUp | no (debe crear org) |
No reescribir handle_new_user_signup desde
frontend/bd/sql_database/sign_up_create_user_tenant_function_trigger.sql: ese dump
no incluye el skip y pisa el cuerpo real de producción.
Resolución de tenant (sesión e impersonación)
frontend/src/auth/utils/resolve-tenant-id.ts elige el tenant activo, en este orden:
- Tenant de la impersonación en curso (
impersonation_logs.target_tenant_id). app_metadata.tenant_iddel JWT, si existe.- Memberships: la única fila, o la más reciente si hay varias.
No usar .maybeSingle() sobre memberships por user_id: con dos filas la consulta
falla y el menú queda vacío.
“Entrar al tenant” (superadmin elevated) no depende de memberships del admin impersonado; “Impersonar” sí es la sesión real de ese usuario y usa el orden de arriba.
Baja de usuarios
Quitar a alguien del tenant es remove_user_from_tenant (borra la membership, no la
cuenta de Auth). La autorizan el superadmin de plataforma o quien tenga
dashboard.user.view en ese tenant. Sin el bypass de is_superadmin(), “Entrar al
tenant” muestra el botón pero la RPC falla: el superadmin no es miembro.
Usuarios ya creados con tenant extra
Las altas nuevas ya no generan el tenant personal. Quienes ya lo tienen siguen con dos
memberships; impersonarlos funciona porque gana target_tenant_id. Limpiar huérfanos
es un script aparte (backend/scripts/repair-*-memberships.ts), no forma parte de
este arreglo.