Saltar al contenido principal

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í:

  1. En la lista de tenants aparece uno extra con el nombre de la persona.
  2. Al impersonar a ese usuario el menú del dashboard queda vacío: PostgREST no puede devolver una sola membership, tenant_id queda vacío y menus_accessible no 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).

CaminoArchivoinvited_flow
Añadir usuario en un tenantfrontend/src/actions/user.ts → createUserInTenantsí
Crear tenant + admin (superadmin)frontend/src/actions/superadmin.ts → createTenantWithAdminsí
Aceptar invitaciónfrontend/src/actions/invitations.ts → acceptInvitationsí
Onboarding de clientebackend/scripts/lib/onboarding-lib.ts → ensureTenantsí
Signup públicofrontend/src/auth/context/supabase/action.tsx → signUpno (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:

  1. Tenant de la impersonación en curso (impersonation_logs.target_tenant_id).
  2. app_metadata.tenant_id del JWT, si existe.
  3. 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.