Arquitectura multi-tenant
Cada cliente de CÉNIT es una organización (organizations). Todo
dato — players, sesiones GPS, wellness, lesiones, mensajes — está
aislado a nivel de fila usando organization_id y RLS de Supabase.
Un staff de Nacional jamás puede leer datos de otra org, ni siquiera
con el JWT en mano: la base de datos rechaza la query.
Cómo lo usa el staff
Sección titulada «Cómo lo usa el staff»Player surface: transparente. El jugador no ve la arquitectura — solo ve su club.
Acceso y permisos
Sección titulada «Acceso y permisos»- Cada
user_profilestieneorganization_id(FK aorganizations.id). Esa columna define a qué org pertenece el staff. - Cada
playerstambién tieneorganization_id. Un jugador pertenece a una sola org. - Las policies RLS de Supabase comparan
user_profiles.organization_iddel JWT contra<tabla>.organization_iden cada SELECT/INSERT/UPDATE/DELETE.
Planes y límites
Sección titulada «Planes y límites»organizations.plan define la oferta comercial:
| Plan | Atletas | Notas |
|---|---|---|
esencial | 40 | Risk Advisor con texto estático |
pro | Ilimitado | IA en Risk Advisor |
enterprise | Ilimitado | Hoy idéntico a pro — diferenciador pendiente |
expired | 0 | Redirige a /upgrade?expired=true |
Detalles completos en Planes y features.
Clientes Supabase y RLS
Sección titulada «Clientes Supabase y RLS»Hay tres clientes que el código usa según contexto:
lib/supabase/server.ts— cookies de sesión, respeta RLS. Es el cliente por defecto en Server Components y Server Actions.lib/supabase/client.ts— browser, respeta RLS. Para queries reactivas en Client Components.lib/supabase/admin.ts— service role, bypassea RLS. Solo para operaciones privilegiadas (onboarding masivo,/w/[token]sin login, alta de orgs desde superadmin). El caller es responsable de filtrar pororganization_idmanualmente.
Regla absoluta: si una server action usa el admin client, debe
filtrar organization_id explícitamente en cada query. Olvidarse
significa filtrar datos de otras orgs.
Datos y métricas
Sección titulada «Datos y métricas»Tabla organizations
Sección titulada «Tabla organizations»Campos clave: id, name, slug (kebab-case, único, 2–40 chars),
logo_url, primary_color, secondary_color, plan,
md_max_window_days (90–360, default 90), visible_player_tabs
(JSONB con tabs habilitados), acwr_method (rma | ewma).
RPCs SECURITY DEFINER
Sección titulada «RPCs SECURITY DEFINER»Las funciones como get_md_reference_per_player y
get_gps_weekly_aggregation (migrations 100 y 105) son SECURITY DEFINER con RLS hardening interno: reciben
p_organization_id y filtran ellas mismas. El TS caller siempre
pasa el organization_id del staff autenticado.
Integraciones
Sección titulada «Integraciones»- Sistema de planes — el plan vive en
organizations.plany se chequea concanUse(plan, feature)desdelib/plans.ts. - White-label — fase 1 detecta el
slug de la org desde el subdominio y lo propaga via header
x-org-subdomain. - Superadmin — el panel interno de CÉNIT crea, lista y configura las orgs.
Limitaciones / roadmap
Sección titulada «Limitaciones / roadmap»- Audit logs por org — no existen. Si un staff borra un jugador, no hay registro de quién y cuándo. Pendiente, candidato a diferenciador Enterprise.
- Cross-org features — no hay scouting que comparta jugadores
entre orgs. Cada org tiene su propio
external_players. - Soft delete vs hard delete —
playerstienedeleted_atpara bulk delete + restore, pero otras tablas borran en cascada. Auditar antes de prometer “papelera” general.