Invitación del staff
hop o dir invitan a un nuevo miembro del staff desde
/dashboard/settings → Usuarios. La acción inviteOrgUser(email, role)
genera un link de Supabase Auth, lo manda por Resend, y crea
la membership activa en la org. El usuario cae en
/auth/set-password?next=/dashboard.
Para comercial
Sección titulada «Para comercial»- Problema que resuelve: dar acceso a miembros del cuerpo técnico (S&C, fisio, médico, nutricionista, etc.) con scope correcto según rol, sin requerir setup manual.
- Casos de uso típicos: alta inicial del staff al contratar, incorporación de un fisio nuevo, cambio de rol de un usuario existente (con confirmación explícita).
- Planes que lo incluyen: todos. Esencial permite hasta 5
colaboradores activos (
PLAN_LIMITS.esencial.collaborators = 5); Pro y Enterprise: ilimitado (Infinity).
Cómo lo usa el staff
Sección titulada «Cómo lo usa el staff»Acceso y permisos
Sección titulada «Acceso y permisos»Solo hop y dir ven la sección “Usuarios” en Settings.
Flujos paso a paso
Sección titulada «Flujos paso a paso»Invitar a un usuario nuevo
Sección titulada «Invitar a un usuario nuevo»- Settings → tab “Usuarios” → botón “Invitar”.
- Modal pide email y rol (uno de los roles oficiales en
lib/roles.ts(hop,dir,coord_form,sc,ss,rtp,fisio,med,nut,psi,entrenador)). inviteOrgUser(email, role)ejecuta:- Si el email ya existe en
user_profilesy tiene membership activa con otro rol, devuelveneedsConfirmation: truecon el rol vigente y el rol propuesto. La UI pide confirmación antes de continuar. - Genera el link con
admin.auth.admin.generateLink({ type: 'invite' }), con fallback atype: 'magiclink'si el email ya está en Supabase Auth. - Upsertea
user_profilesconorganization_id,role,planheredado de la org. - Inserta o actualiza la row en
user_org_memberships(statusactive). - Manda el mail con
sendEmail()yinviteEmailHtml({ forPlayer: false }).
- Si el email ya existe en
- El usuario recibe el mail, fija contraseña en
/auth/set-password, y cae en/dashboardcon el rol asignado.
Cambio de rol de un usuario ya activo
Sección titulada «Cambio de rol de un usuario ya activo»Si la UI muestra “Este usuario ya tiene rol X — confirmá si
querés cambiarlo a Y”, el staff debe reinvocar la acción con
{ force: true }. Esto evita degradaciones silenciosas de
privilegios que pasaban en versiones anteriores.
Configuración relacionada
Sección titulada «Configuración relacionada»- El plan que se asigna por defecto al nuevo usuario sale de
organizations.plan. - El branding del mail (logo, nombre) sale de
organizations.nameyorganizations.logo_url.
FAQ / casos límite
Sección titulada «FAQ / casos límite»- Rol inválido: la acción rechaza con “Rol inválido” si no
matchea con
VALID_ROLESdelib/roles.ts. RESEND_API_KEYfaltante: la acción devuelve"No se pudo enviar el email. Verificá la configuración de Resend."y loguea warning en Sentry. La membership igual queda creada, pero el usuario no recibe el link.- Revocación:
revokeOrgUser(userId)baja la membership astatus = 'revoked'(conrevoked_at) sin borrar la cuenta enauth.users. Bloquea revocar al último admin (hop/dir) activo de la org.
Cómo lo ve el jugador
Sección titulada «Cómo lo ve el jugador»Player surface: N/A. Este flujo es solo para staff.
Integraciones
Sección titulada «Integraciones»- Supabase Auth vía
admin.auth.admin.generateLink. - Resend envía el mail final.
- Tablas
user_profilesyuser_org_membershipsse modifican atómicamente desde la action.
Limitaciones / roadmap
Sección titulada «Limitaciones / roadmap»- No hay UI para “reenviar invite” si el link expiró. El workaround
es volver a invocar
inviteOrgUsercon el mismo email — Supabase reusa elauth.usersrow y genera un link nuevo. - Sin auditoría visible de cambios de rol — solo queda registro en
los logs de la action. Para audit log estructurado, ver
/dashboard/audit(interno).