# Multisede — Sede Abancay / Filial Tambobamba

Plan para implementación (diseñado con el usuario vía `/grill-me`,
2026-10-01, en dos rondas, antes de tocar código). Reemplaza la
decisión "mono-sede confirmado" (`CLAUDE.md` §6) — esa sección se
actualiza cuando se cierre este plan.

## Objetivo

Hoy DocenTrack es mono-sede (sin ninguna noción de sede en el modelo de
datos). La Facultad opera en dos sedes reales — **Sede Abancay** y
**Filial Tambobamba** — y el problema concreto que lo dispara: en el
periodo 2026-II, el curso X tiene 2 grupos en Abancay pero solo 1 en
Tambobamba. Hoy no hay forma de representar eso.

## Decisiones acordadas (grilled 2026-10-01, 2 rondas)

1. **Curso y resolución: catálogo compartido, sin cambios.** Lo que
   varía por sede no es el curso, es **cuántos grupos** tiene ese curso
   en un periodo dado.
2. **La sede vive en la asignación, nunca en el docente ni en el
   curso.** Un docente no se amarra a una sede — se infiere mirando en
   qué sede están sus asignaciones (puede dictar en una, la otra, o
   ambas el mismo periodo).
3. **El aula es la fuente real de la sede.** Cada aula pertenece a
   exactamente una sede (`classrooms.campus_id`); la asignación obtiene
   su sede a través del aula elegida — nunca un campo propio que pueda
   quedar inconsistente con el aula.
4. **La configuración de grupos por curso+periodo pasa a ser también
   por sede**: `course_academic_period_groups` se vuelve único por
   `(course_id, academic_period_id, campus_id, class_group_id)`. La
   etiqueta de grupo (A/B/C/D/E/U) sigue siendo un catálogo compartido
   y chico — lo que cambia es qué combinaciones existen.
5. **El kiosko NO filtra por sede del kiosko físico.** El docente se
   identifica igual que hoy (huella/documento) y ve todas sus clases de
   hoy de **todas** las sedes donde dicte — elige la correcta, mismo
   criterio que ya usa para elegir entre varias clases del mismo día.
   Único cambio en el kiosko: la tarjeta de cada clase muestra de qué
   sede es (para que elegir entre sedes mezcladas sea claro).
6. **Choque de horario — se construye ahora, dos checks independientes,
   ambos bloqueantes**, dentro del mismo `academic_period_id` (no hay
   fechas reales en `academic_periods` para comparar entre periodos de
   forma confiable):
   - Mismo docente, mismo `day_of_week`, horas que se solapan.
   - Misma aula, mismo `day_of_week`, horas que se solapan (este ya
     estaba pendiente desde Fase 3, nunca se construyó, independiente
     de multisede).
   Mismo cálculo de solape que ya usa `Kiosk::hasOverlappingRecord()`.
7. **Filtro de sede en todas las pantallas que ya tienen filtros** —
   Asignaciones, Registros de asistencia, Reportes, Dashboard, y el
   selector de grupos por curso+periodo — todo en este mismo plan, sin
   segunda pasada.

## Hallazgo real encontrado al diseñar esto

`classrooms.name` es único a nivel global en la BD actual. Con aulas
que se repiten de nombre entre sedes (ej. "202" en ambas), eso tiene
que cambiar a único por `(name, campus_id)`.

## Modelo de datos

### `campuses` (nueva)
- `id`, `name` (string, único), `active` (boolean, default `true`),
  timestamps. Mismo patrón que otros catálogos chicos (`cycles`,
  `resolutions`) — CRUD vía `CatalogSimple` genérico, sin borrado físico
  si tiene dependencias (mismo criterio no-negociable de todo el
  sistema).
- Seed: "Sede Abancay", "Filial Tambobamba".

### `classrooms`
- Agregar `campus_id` (FK → `campuses`, `NOT NULL` tras backfill).
- Backfill: todas las aulas existentes hoy se asignan a **Sede
  Abancay** por defecto (es la sede principal, "Filial" en el nombre
  de la otra sugiere que es la secundaria) — ajustable a mano después
  desde el admin si alguna en realidad es de Tambobamba. Lo señalo
  explícito porque es una decisión que tomé yo, no el usuario, y es
  fácil de corregir si está mal.
- Cambiar el único de `name` solo, a único por `(name, campus_id)`.
- CRUD (`Classrooms.php`): selector de sede + columna + filtro.

### `course_academic_period_groups`
- Agregar `campus_id` (FK → `campuses`, `NOT NULL` tras backfill, mismo
  default de migración que `classrooms`).
- Cambiar el único de `(course_id, academic_period_id, class_group_id)`
  a `(course_id, academic_period_id, campus_id, class_group_id)`.
- UI (dentro de `CatalogSimple` para cursos, donde ya vive esta
  configuración — no hay pantalla aparte, el route `/admin/course-groups`
  hoy redirige ahí): selector de sede antes de elegir los grupos — los
  grupos configurados son por curso+periodo+sede, no solo curso+periodo.

### `assignments`
- **Sin columna nueva** — la sede se deriva de `classroom_id` (punto 3).
  Nuevo método/relación en el modelo `Assignment`:
  `campus(): BelongsTo` vía `classroom.campus`, o un accessor
  `campus()` que delega — se decide el detalle exacto al implementar.
- UI (`Assignments.php`): el formulario de crear/editar pasa a tener un
  selector de **Sede** que filtra en cascada: (a) el selector de Aula
  (solo aulas de esa sede), (b) el selector de Grupo (solo los grupos
  configurados para ese curso+periodo+sede, vía
  `course_academic_period_groups`). La sede en sí no se guarda en
  `assignments` — es un filtro de formulario, el dato real queda en el
  aula elegida.
- **Validación nueva al guardar** (punto 6): bloquea si hay choque de
  docente o de aula dentro del mismo periodo académico, con mensaje
  claro señalando con qué asignación existente choca.

### `attendance_records`
- Agregar `campus_id` (copiado al generar/fichar, mismo criterio que ya
  usan `course_id`/`cycle_id`/`classroom_id`: valor confirmado al
  momento, no recalculado después si el aula cambiara de sede). Se
  deriva de `classroom_id` en el momento de crear/actualizar la fila
  (kiosko, `attendance:sync`, alta manual del admin).
- Necesario para que el filtro de sede en Registros de
  asistencia/Reportes/Dashboard sea una columna directa, no un join por
  cada consulta.

## Kiosko

- `Kiosk::assignmentOptions()` incluye el nombre de la sede de cada
  asignación (vía `classroom.campus`).
- `kiosk-assignment-card` (componente ya existente): nuevo chip de sede
  junto a los de grupo/ciclo/hora que ya tiene.
- Nada más cambia — sigue mostrando todas las asignaciones de hoy del
  docente identificado, de cualquier sede, igual que hoy.

## Pantallas con filtro de sede nuevo

- `/admin/assignments` — filtro de sede (vía aula).
- `/admin/attendance-records` — filtro de sede (columna directa).
- `/admin/reports` — agrupar/filtrar por sede.
- `/admin/dashboard` — contadores por sede (o al menos filtro).
- Configuración de grupos por curso+periodo (dentro de `CatalogSimple`
  de cursos) — selector de sede, ya cubierto arriba.

## Testing

- Migraciones: `classrooms`/`course_academic_period_groups` con
  backfill correcto, únicos nuevos funcionando (dos aulas con el mismo
  nombre en sedes distintas no chocan; misma sede sí).
- `course_academic_period_groups`: mismo curso+periodo con grupos
  distintos en cada sede (el caso real que disparó esto).
- Choque de horario: mismo docente/aula, horas solapadas, mismo
  periodo → bloqueado; periodos distintos → no bloquea; horas que no
  se solapan → no bloquea.
- Formulario de asignación: cambiar de sede limpia/filtra aula y grupo
  correctamente.
- Kiosko: la tarjeta de clase muestra la sede correcta; un docente con
  asignaciones en ambas sedes ve todas mezcladas, sin filtrar.
- Cada pantalla con filtro nuevo: el filtro de sede de verdad acota los
  resultados.
- Regresión completa: la suite existente (141 tests) sigue en verde.

## Riesgos / pendientes de decidir en la implementación

- Qué aulas existentes son en realidad de Tambobamba (el backfill las
  pone todas en Abancay por defecto) — corrección manual del usuario
  después de implementado, no bloqueante para el plan.
- Mensaje exacto del error de choque de horario — se define al
  implementar.
- `CLAUDE.md` §6 ("mono-sede confirmado") se reemplaza por el resumen
  de este plan una vez implementado y probado.
