# MULTISEDE-SOPORTE-Y-DATOS-PRUEBA.md

Fecha: **2026-08-18**. Estado: **vigente**.

Aclara dos cosas que se venían confundiendo: (1) que el sistema **sí**
soporta N sedes a nivel de código/esquema — no es una limitación pendiente
— y (2) que, como convención de esta fase de desarrollo, los **datos de
prueba por defecto** (`php artisan migrate:fresh --seed`) se acotan a
**una sola sede activa** para simplificar QA manual, aunque el modelo
soporte varias.

No reemplaza a `DECISION-MULTISEDE.md` (registro histórico de la decisión
de diseño) ni a `ESQUEMA-ACTUAL.md` — es un complemento centrado en "¿el
código soporta multisede?" + "¿qué datos hay sembrados hoy?".

## 1. El soporte multisede es real, no aspiracional

Verificado contra migraciones y modelos reales (no contra docs):

- **`receiving_entities`** tiene `campus_id` NOT NULL, únicos
  `(campus_id, code)` y `(campus_id, name)`. Es el único catálogo por
  sede porque representa **oficinas físicas reales** (Psicología,
  Servicios Médicos, Bienestar) — cada sede tiene su propia oficina, con
  su propio personal (`receivers`), no un concepto abstracto compartido.
  Una derivación (`referrals`) debe apuntar a la oficina que existe
  físicamente en la sede del estudiante, de ahí el FK directo.
- **`teachers`/`students`** son uno-por-persona (sin `campus_id` propio,
  ver `CLAUDE.md` § multisede); la sede se **deriva de actividad**:
  docente → `cycle_period_teachers` → `cycle_periods.campus_id`;
  estudiante → `enrollments` → `cycle_periods.campus_id`. Una persona
  puede tener actividad en varias sedes simultáneamente sin duplicar
  perfil (`User::allowedCampuses()`, `Teacher::activityCampuses()`,
  `Student::activityCampuses()`).
- **`cycle_periods`** es único por `(cycle_id, academic_period_id,
  campus_id)` — el mismo ciclo/período puede existir en paralelo en cada
  sede, cada uno con sus propios docentes/matrículas.
- **Scope operativo**: header `X-Campus-Id` obligatorio (fail-closed,
  422 si falta) vía middleware `SetActiveCampus`; `admin`/`coordinador`
  operan una sede a la vez, con `campus_user` definiendo a cuáles tienen
  acceso.

Conclusión: **no hace falta ningún cambio de esquema o de lógica para
soportar más sedes** — ya está soportado. `DemoAccessSeeder` (ver §3)
existe precisamente para probar este comportamiento (un docente con
actividad en 2 sedes, un estudiante matriculado en 2 sedes, un admin
multisede vs. uno de una sola sede).

## 2. Convención actual: solo existe la sede Abancay

Decisión del usuario (2026-08-18): mientras el sistema no esté en
producción, **solo debe existir una sede** (`ABA` — Abancay) en todo el
catálogo y los datos, punto. Cuando se active la operación multisede
real, se agregan las sedes nuevas y el sistema **debe funcionar normal**
sin cambios de código — eso es justamente lo que confirma la §1.

**Cambios aplicados:**
- `CampusSeeder.php` — el catálogo `campuses` ahora siembra **solo
  `ABA`**. `TAM`/`GRA` se sacaron por completo (eran datos de prueba
  adelantados, no sedes reales confirmadas todavía). Para activar una
  sede nueva en el futuro, se agrega su fila aquí — nada más en el
  esquema o la lógica necesita tocarse.
- `DatabaseSeeder.php` — `DemoAccessSeeder` (fixture que crea actividad
  cruzada en varias sedes) **se sacó de la lista por defecto**. Sigue
  existiendo como archivo, invocable a mano (ver §3), pero **hoy
  requiere que existan `TAM`/`GRA` en el catálogo para no romper** — no
  correrlo mientras el catálogo tenga una sola sede.
- `AdminUserSeeder` sigue asignando el admin placeholder a **todas** las
  sedes del catálogo (hoy, solo `ABA`) — eso es acceso, no volumen de
  datos de prueba.
- `ReceivingEntitySeeder` ya sembraba solo para una sede por defecto
  (`ABA`, o la primera que exista) — sin cambios, ya era consistente.

**Verificado con el catálogo de una sola sede:** `migrate:fresh --seed`
sin errores, suite completa `php artisan test` → **207 passed (853
assertions)** — ningún test depende de que existan `TAM`/`GRA`.

## 3. Cómo probar escenarios multisede cuando haga falta

`DemoAccessSeeder` no se borró — es el fixture pensado para validar
lógica multisede (derivación de sede por actividad, acceso de admin
multisede vs. mono-sede, etc.) el día que haga falta probarla. Hoy
**no se puede correr tal cual** porque referencia `TAM`/`GRA`, que ya no
existen en el catálogo (§2).

Para usarlo en el futuro, cuando se agreguen sedes nuevas a
`CampusSeeder`:

```
php artisan db:seed --class=DemoAccessSeeder
```

Crea docentes/estudiantes de ejemplo con actividad cruzada entre sedes
(un docente en 2 sedes, un estudiante en 2 sedes, un admin mono-sede y
uno multisede) — pensado para ejercitar `allowedCampuses`,
`activityCampuses` y los scopes de sede.

**Gap conocido para cuando se retome:** `ReceivingEntitySeeder` solo
crea entidades receptoras en la primera sede del catálogo. Si se agregan
sedes nuevas y se corre `DemoAccessSeeder`, una derivación (`referrals`)
de un estudiante activo en la sede nueva no tendrá `receiving_entity`
disponible ahí — hay que sembrar entidades receptoras también en esa
sede (a mano o extendiendo el seeder) antes de probar M4 end-to-end.

## 4. Activar una sede nueva en el futuro

Cuando el sistema pase a operar en más de una sede real, los únicos
pasos son de **datos**, no de código (la lógica ya soporta N sedes, §1):

1. Agregar la fila correspondiente en `CampusSeeder.php`.
2. Sembrar sus `receiving_entities` (Psicología, Servicios Médicos,
   Bienestar, o las que apliquen).
3. Asignar actividad real (docentes vía `cycle_period_teachers`,
   estudiantes vía `enrollments`) en `cycle_periods` de esa sede.

No hace falta migración nueva ni cambios en controllers/servicios.

## 5. Archivos tocados en este cambio

- `database/seeders/CampusSeeder.php` — catálogo reducido a **solo
  `ABA`**; `TAM`/`GRA` eliminados.
- `database/seeders/DatabaseSeeder.php` — se sacó `DemoAccessSeeder` de
  la lista por defecto, con comentario explicando por qué y cómo
  invocarlo a mano.
- `database/seeders/AdminUserSeeder.php` — comentario corregido ("ambas
  sedes" → "todas las sedes existentes del catálogo").
