Ir al contenido

Usuarios

/dashboard/usuarios es el panel de gestión de staff (no jugadores) de la org. Permite invitar nuevos miembros por email, cambiar su rol, resetearles password y revocar el acceso. La fuente de verdad multi-tenant es user_org_memberships. Solo hop y dir acceden.

  • Problema que resuelve: los clubes tienen rotación de staff y necesitan dar acceso (o sacarlo) sin pasar por soporte. Más, necesitan controlar el límite de colaboradores incluido en el plan: esencial está limitado a X usuarios, pro/enterprise no.
  • Casos de uso típicos: invitar al nuevo fisio cuando arranca la pretemporada, dar de baja al S&C que se fue al verano, promover a un sport scientist a HoP cuando hay cambio de comando técnico.
  • Planes que lo incluyen: todos (la gestión existe en todos). Lo que cambia es el límite de colaboradores: getPlanLimits(plan) .collaborators.
  • Diferenciador: revoke “soft” — el auth user queda y su email puede volver a ser invitado por otra org (multi-org listo).
  • Solo hop y dir. Cualquier otro rol que intente entrar a /dashboard/usuarios es redirigido a /dashboard.
  • Las server actions (inviteOrgUser, revokeOrgUser, updateUserRole, resetUserPassword) revalidan el rol del caller en cada invocación con assertCallerRole.
  • Invitar un usuario nuevo. Form “Invitar” → email + rol (hop, dir, sc, ss, rtp, fisio, med, nut, psi) → inviteOrgUser. Genera link tipo invite con Supabase admin (fallback a magiclink si falla), crea user_profiles y la membership activa, y manda el email via Resend apuntando a /auth/set-password?next=/dashboard.
  • Invitar usuario que ya está en otra org. Si el email ya tiene un user_profiles pero sin membership activa en esta org, se crea la membership directo. Si ya tiene membership activa en esta org con otro rol, el action retorna { needsConfirmation: true } con el rol existente y el nuevo — la UI pide confirmación al caller para evitar cambios silenciosos de privilegios.
  • Cambiar rol. Inline en la tabla → updateUserRole. Actualiza user_profiles.role y la membership activa. Toma efecto en el próximo render.
  • Resetear password. Acción admin → resetUserPassword usa admin.auth.admin.updateUserById para setear una password directa (sin email).
  • Revocar acceso. revokeOrgUser marca la membership como revoked (no borra el auth user — el mail queda libre para otra org). Limpia staff_team_assignments y organization_id en user_profiles por compatibilidad con readers legacy. Bloqueo importante: no se puede revocar al último admin (hop o dir) activo de la org — si no, la org queda sin nadie que pueda invitar/revocar y requeriría intervención de superadmin.
  • El límite de colaboradores sale de getPlanLimits(plan) .collaborators. La UI avisa con warning al 80% del límite.
  • La pantalla de invitación complementaria del staff vive también en Settings → Accesos (AccessTab corre las mismas actions de actions-access.ts).

Dentro del flujo de Usuarios hay dos páginas dedicadas al onboarding del plantel (jugadores, no staff):

Nota técnica: las rutas en código viven en /dashboard/onboarding-link y /dashboard/onboarding-status (no anidadas bajo /dashboard/usuarios/). La doc las agrupa acá porque conceptualmente son parte de la gestión de cuentas.

  • Email no llega: chequear que RESEND_API_KEY esté en Workers Secrets. Si no, sendEmail falla silenciosamente y inviteOrgUser retorna 'No se pudo enviar el email...'.
  • Usuario revocado quiere volver: invitarlo de nuevo crea una nueva membership activa. El auth user nunca se borró.

Player surface: N/A. Este módulo gestiona staff. El onboarding del plantel es flujo aparte.

  • user_org_memberships — fuente de verdad. (user_id, organization_id, role, status, revoked_at). Único parcial sobre activos.
  • user_profiles — perfil del staff (legacy reader sincronizado para compatibilidad).
  • staff_team_assignments — qué staff ve qué sub-equipo (se borra al revocar).
  • auth.users (Supabase) — fuente de verdad de emails. Solo se consulta via admin SDK (admin.auth.admin.getUserById).
  • Resend — envío del email de invitación con magic-link.
  • Supabase Admin SDKgenerateLink({ type: 'invite' }) y fallback a magiclink.
  • Settings → Accesos — mismas actions.
  • No hay log de “quién invitó / quién revocó”. Los triggers de audit_log (migration-010) cubren players, injuries, medical_consultations, wellness_entries, rtp_phases y user_profiles — pero NO user_org_memberships, donde viven las invitaciones y revocaciones. Si Enterprise lo pide, agregar trigger sobre user_org_memberships.
  • No hay SSO ni MFA — todos los logins son email/password o magic-link de Supabase.
  • El reset de password lo hace el HoP/Dir manualmente; no hay flujo self-service “olvidé mi password” custom (se delega a Supabase auth nativo).