Modelo conceptual
Tenant (Institución)
├── Users (alumnos, profesores, coordinadores, admin institucional)
├── Courses → Modules → Lessons
├── ClassGroups (grupos/clases)
├── MediaAssets (biblioteca de medios)
├── Automations
├── EmailTemplates
├── VideoProviderConfig (BBB/Zoom/Teams/Meet)
├── TenantApiKey (claves propias de IA)
├── TenantSubscription (billing)
├── ...todas las demás entidades
Los usuarios sin tenantId son B2C (plataforma directa). Los usuarios con tenantId pertenecen a una institución.
Aislamiento de datos — 3 capas
Capa 1: Filtro obligatorio en queries
Toda query Prisma en el código de aplicación filtra por tenantId:
const { tenantId } = requireTenant(user);
const courses = await prisma.course.findMany({
where: { tenantId }, // OBLIGATORIO
});
requireTenant() en apps/web/lib/tenant.ts retorna NextResponse 403 si el usuario no tiene tenant. Sin esta llamada, es imposible acceder a datos B2B.
Capa 2: RLS en Supabase
Como red de seguridad contra bugs, las políticas RLS en Supabase refuerzan el aislamiento incluso en queries directas. Si un código olvida el filtro tenantId, la base de datos lo rechaza.
Capa 3: Auditoría de admin global
Cuando un admin global necesita acceder a los datos de un tenant (soporte, debug), utiliza suplantación vía cookie HMAC con:
- TTL fijo de 1h (no prorrogable)
- Firma HMAC-SHA256 con
IMPERSONATION_SECRET - Auditoría en
AdminAuditLog(acciónimpersonate.start, IP, user-agent) getUserProfile()retornaisImpersonating: true+ overlay en memoria- La auth de Supabase NUNCA se modifica (solo overlay)
- Banner sticky en /institution/ alerta al admin durante la sesión
El panel admin SaaS global incluye suplantación para soporte.
API keys por tenant
Por defecto, todas las llamadas LLM utilizan las claves globales (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.) de Studeia. Los costos se imputan al tenant vía metering.
Pero las instituciones que ya tienen cuentas en Anthropic/OpenAI/Google pueden usar sus propias claves (TenantApiKey, cifrada AES-256-GCM en la base de datos):
- Los costos van DIRECTAMENTE a la cuenta del tenant en Anthropic/OpenAI
- Studeia no cobra margen de IA — solo la suscripción mensual
- Resolución automática:
TenantApiKey→ProviderApiKeyglobal →process.env(cascada enapps/web/lib/api-key-resolver.ts)
NUNCA se establece process.env en runtime (entraría en conflicto entre tenants) — la clave se pasa vía opciones del SDK.
White-label completo
Cada tenant puede personalizar:
| Aspecto | Cómo |
|---|---|
| Logo, favicon | Subida en settings |
| Colores (primary, accent, background) | Editor con preview |
| Fuente (Google Fonts) | Dropdown |
| Tema visual (de 9 opciones) | Toggle por usuario o predeterminado del tenant |
| CSS personalizado | Saneado, máx. 10KB |
| Dominio personalizado | DNS CNAME + TXT _studeia-verify + TLS automático Caddy (on-demand) |
| Logo en el email | Por plantilla |
| Email de envío (SMTP/Resend/SendGrid) | TenantEmailConfig |
| Ocultar marca Studeia | Sí (plan enterprise) |
Roles y permisos
| Rol | Alcance | Puede hacer |
|---|---|---|
student | Propio progreso | Chat tutor, cursos, simulacros, gamificación |
parent | Hijos vinculados | Ver progreso, alertas, informes |
teacher | Propios grupos/cursos | Crear cursos, subir material, ver alumnos de los grupos |
coordinator | Todos los grupos del tenant | Gestionar grupos, ver todos los alumnos |
pedagogue | Todos los alumnos del tenant | Orientación educativa, informes |
institution_admin | Todo el tenant | Config IA, API keys, white-label, usuarios |
admin | Global (plataforma) | Todo + gestionar tenants |
Límites por plan
Límites aplicados vía checkTenantResourceLimit(tenantId, resource) en apps/web/lib/plan-limits.ts:
| Plan | Alumnos máx. | Profesores | Cursos | IA |
|---|---|---|---|---|
| Demo | 1 | 1 | 1 | Solo Haiku, 10 msgs/día |
| Mini | 10 | Ilimitado | Ilimitado | Todos los providers |
| Crescimento | 50 | Ilimitado | Ilimitado | Todos los providers |
| Escala | 100 | Ilimitado | Ilimitado | Todos los providers |
| Enterprise | Custom (maxStudentsOverride) | Ilimitado | Ilimitado | Todos los providers |
7 puntos de aplicación (todos auditados el 2026-04-11):
POST /api/courses/[courseId]/enroll— auto-inscripción del alumnoPOST /api/institution/courses/[id]/clone— clonar cursoPOST /api/institution/courses/import— importar IMS CCexecuteEnrollUser()en automatizaciónPOST /api/scim/v2/Users— aprovisionamiento SCIMPOST /api/institution/users— vincular usuario existentePATCH /api/institution/users/[id]— promover rol
Cómo solicitar override enterprise
Los tenants enterprise pueden tener Tenant.maxStudentsOverride: Int? ajustado por el admin global. Sin override (null), enterprise = ilimitado. Contacto comercial vía contact@studeia.com.
Limitaciones
- Un User pertenece a UN tenant. Para profesores que atienden múltiples escuelas, crear usuarios separados.
- El intercambio de cursos entre tenants no es nativo (cada tenant tiene su CMS aislado). Roadmap: marketplace de cursos con licenciamiento.
- La migración de un tenant a otro plan es instantánea, pero el downgrade que viola los límites actuales queda bloqueado hasta que el tenant ajuste sus recursos.