> ## Documentation Index
> Fetch the complete documentation index at: https://docs.edtools.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-tenancy

> Cómo se resuelve la institución en cada petición, y qué significa el Source of Record.

Una sola aplicación atiende a todas las instituciones. Cuál es la de esta petición se decide por el host.

```text theme={null}
colegio-abc.app.edtools.co          → dashboard del tenant colegio-abc
colegio-abc.app.edtools.co/portal   → portal de familias del mismo tenant
colegio-xyz.app.edtools.co          → otro tenant, la misma app
institucion.edu.co                  → dominio propio, también soportado
```

## La cadena de resolución

<Steps>
  <Step title="El middleware de Next lee el host">
    `apps/web/src/middleware.ts` extrae el slug del subdominio (o resuelve el dominio propio) y lo inyecta como header `x-tenant-slug` en la petición.
  </Step>

  <Step title="El middleware de Hono carga el tenant">
    `apps/web/src/server/middleware/tenant.ts` lee ese header, busca la institución y la deja en el contexto: `c.get('tenant')` trae `{ id, slug, name, ... }`.
  </Step>

  <Step title="Se valida el acceso">
    `tenantAccessMiddleware` comprueba que el usuario de la sesión pertenezca a ese tenant. Sin eso, la petición no llega al handler.
  </Step>

  <Step title="Cada consulta filtra por tenantId">
    El aislamiento es responsabilidad del código de acceso a datos. Una consulta sin `eq(tabla.tenantId, tenant.id)` es una fuga entre instituciones.
  </Step>
</Steps>

<Warning>
  En el Client API el tenant **no** se manda por parámetro: sale del token. Un token de servicio pertenece a una institución y no puede leer otra. No hay forma de cambiar de tenant con el mismo token.
</Warning>

## Probar en local

El ruteo por subdominio funciona igual en desarrollo:

```text theme={null}
http://colegio-test.localhost:3000
```

## Source of Record: quién manda sobre cada dato

Cuando una institución ya tiene un SIS, EdTools no asume que es el dueño de la verdad. Cada tenant declara, **por dominio de datos**, quién es el maestro:

| Dominio      | Qué cubre               |
| ------------ | ----------------------- |
| `students`   | La ficha del estudiante |
| `grades`     | Calificaciones          |
| `payments`   | Pagos y obligaciones    |
| `enrollment` | Matrículas              |

Y para cada uno, uno de tres modos:

<CardGroup cols={3}>
  <Card title="edtools_master" icon="database">
    EdTools es el dueño. Es el valor por defecto en instituciones nuevas, en los cuatro dominios.
  </Card>

  <Card title="external_master" icon="arrow-right-arrow-left">
    Manda un sistema externo. Solo escribe el token que figura como ese maestro.
  </Card>

  <Card title="orchestrate_only" icon="route">
    EdTools no es dueño ni acepta ser escrito: solo orquesta procesos sobre datos ajenos.
  </Card>
</CardGroup>

Esto no es decorativo: el Client API lo aplica en cada escritura, y la regla es exactamente esta:

| Modo del dominio                  | ¿Puede escribir el token?                                 |
| --------------------------------- | --------------------------------------------------------- |
| `edtools_master` (o sin declarar) | Sí.                                                       |
| `external_master`                 | Solo si el token lleva ese dominio en su `masterDomains`. |
| `orchestrate_only`                | No, para nadie.                                           |

Cuando no pasa, la respuesta es `403 SOR_WRITE_FORBIDDEN` con el dominio en el detalle. `GET /v1/me` te devuelve las dos mitades de la ecuación: `sorByDomain` (lo que declaró la institución) y `masterDomains` (lo que reconoce tu token).

<Info>
  Los dominios de SoR son cuatro, pero el Client API solo puede declarar maestro sobre tres: `students`, `enrollment` y `payments`. `grades` se configura por SoR pero no se escribe por esta API.
</Info>

<Tip>
  Antes de escribir una sola línea de integración, llama a `GET /api/client/v1/me`. Ahí está todo lo que decide si tus escrituras van a pasar: scopes, módulos habilitados, SoR del tenant y dominios maestros del token.
</Tip>
