# Plan — Tipo de docente + ciclo en el curso (sale de asignaciones)

Fecha: 2026-10-05. Estado: **implementado por Claude** (pedido explícito),
pendiente de desplegar. Decisiones finales en la sección 6; el SQL definitivo
está en `docs/CAMBIOS.md` (2026-10-05). Sistema en producción: el esquema se cambia con
SQL manual + edición de las migraciones originales (mismo criterio que el
tipo de curso `ESPECIFICO`, ver `docs/CAMBIOS.md` 2026-10-05).

## 1. Qué piden

1. **Docentes**: campo nuevo obligatorio "Tipo de docente" — Nombrado /
   Contratado / Asociado.
2. **Cursos**: campo nuevo obligatorio "Ciclo" (cada curso pertenece a un
   ciclo), con columna y filtro por ciclo en la tabla.
3. **Asignaciones**: se quita el ciclo; se toma del curso.
4. Limpiar la tabla de asignaciones en producción y empezar de cero.

## 2. Cambio A — Tipo de docente (chico, aditivo)

Mismo patrón exacto que `academic_department`:

| Pieza | Cambio |
|---|---|
| `users.teacher_type` | `ENUM('NOMBRADO','CONTRATADO','ASOCIADO')` **nullable en BD** (los administradores no lo usan), **obligatorio en el formulario de docentes** |
| `app/Enums/TeacherType.php` | Enum nuevo con `label()` |
| `User` | fillable + cast |
| `Livewire\Admin\Users` + vista | campo en el modal (solo docentes), columna con badge, filtro, orden |
| `UserSeeder` | tipo para los docentes de ejemplo |
| Tests | crear sin tipo → falla; crear/editar/filtrar con tipo |

Los docentes que ya existen en producción quedan con el tipo vacío: la tabla
muestra "—" y el formulario lo exige la próxima vez que se edite a ese
docente (ver duda 3).

## 3. Cambio B — Ciclo en el curso (mediano, toca 5 pantallas)

### Esquema

| Tabla | Cambio |
|---|---|
| `courses` | `cycle_id` FK a `cycles`, `restrictOnDelete`, nullable en BD y obligatorio en el formulario (ver sección 5) |
| `assignments` | se elimina `cycle_id` (columna + FK `assignments_cycle_id_foreign`) |
| `attendance_records` | se elimina `cycle_id` (decisión 4); el índice `attendance_overlap_lookup` queda en `(teacher_id, course_id, class_date)` |

### Código

| Archivo | Cambio |
|---|---|
| `Course` | `cycle()`; fillable |
| `Cycle` | `courses()` |
| `Assignment` | fuera `cycle_id` del fillable; `cycle()` pasa a derivarse del curso |
| `CatalogSimple` (cursos) | select "Ciclo" obligatorio en el modal, columna, filtro, orden |
| `CatalogSimple` (ciclos) | borrar un ciclo con cursos queda bloqueado por la FK — verificar que el mensaje dirija a desactivar |
| `Assignments` + vista | fuera el select "Ciclo", la regla y `cycleId`; el ciclo se muestra como dato de solo lectura del curso; `openAddBlock()` pierde el 4.º parámetro |
| `Kiosk` | `cycleId` sale de `assignment->course->cycle_id`; eager-load `course.cycle` |
| `SyncAttendance` | `cycle_id` del pre-registro sale del curso |
| `AttendanceRecords` (registro manual) | fuera el select "Ciclo"; se toma del curso elegido |
| `AttendanceRecordDetail` (corrección) | fuera el select "Ciclo"; si se corrige el curso, el ciclo lo sigue y queda auditado como un campo más |
| `ClassroomSchedulePdfBuilder` + vista PDF | ciclo vía curso |
| `guide/body.blade.php` | la guía de usuario explica el ciclo dentro de asignaciones — reescribir esos pasos |
| Seeders | `CatalogSeeder` (ciclo por curso), `AssignmentSeeder`, `AttendanceRecordSeeder` |
| Tests | 11 archivos referencian ciclo; los más afectados: `Fase3AssignmentsTest`, `MultisedeTest`, `GroupsResolutionsKioskTest`, `Fase1CatalogsTest` |
| Docs | `MODELO-DATOS.md`, `CAMBIOS.md`, `CLAUDE.md` |

No se tocan: Reportes, Dashboard y exportaciones (no usan ciclo).

### Efecto colateral a favor

El bloqueo de fichaje duplicado compara docente + curso + ciclo + fecha +
horas. Con el ciclo fijo por curso, "mismo curso en dos ciclos el mismo día"
deja de existir como caso; la regla queda igual de correcta y más simple de
razonar.

## 4. Datos reales de producción (backup del 2026-10-05, importado en local)

| Dato | Valor | Consecuencia |
|---|---|---|
| Motor | **MariaDB 10.11** (local es MySQL 8) | El SQL se probó en un MariaDB 10.11 temporal, no solo en local |
| `attendance_records` / evidencias / correcciones | 0 / 0 / 0 | Limpiar asignaciones no pierde historial: el riesgo de la FK desaparece |
| `assignments` | 3 | Se borran |
| `courses` | 23 (20 sin ninguna asignación) | El ciclo **no se puede inferir** — hay que cargarlo |
| `cycles` | 10 (I a X, activos) | — |
| Docentes | 42, ninguno con tipo | Hay que cargarlo |
| `course_academic_period_groups` | 5 | No se tocan |

Con esto el despliegue se simplifica a **una sola tanda de SQL**.

## 5. SQL de producción (probado en MariaDB 10.11 sobre el backup)

```sql
-- 1) Tipo de docente
ALTER TABLE users
  ADD COLUMN teacher_type ENUM('NOMBRADO','CONTRATADO','ASOCIADO') NULL
  AFTER academic_department;

-- 2) Limpiar asignaciones (y cualquier asistencia que dependa de ellas)
SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE TABLE attendance_corrections;
TRUNCATE TABLE attendance_evidence;
TRUNCATE TABLE attendance_records;
TRUNCATE TABLE assignments;
SET FOREIGN_KEY_CHECKS = 1;
UPDATE system_configuration SET last_generated_date = NULL;

-- 3) Ciclo: entra en cursos, sale de asignaciones
ALTER TABLE courses
  ADD COLUMN cycle_id BIGINT UNSIGNED NULL AFTER type,
  ADD CONSTRAINT courses_cycle_id_foreign
      FOREIGN KEY (cycle_id) REFERENCES cycles (id) ON DELETE RESTRICT;

ALTER TABLE assignments
  DROP FOREIGN KEY assignments_cycle_id_foreign,
  DROP COLUMN cycle_id;
```

Resultado verificado: corre sin errores, 0 asignaciones con contador
reiniciado, 23 cursos / 43 usuarios / 5 grupos / 10 aulas intactos, borrar un
ciclo usado por un curso queda bloqueado por la FK. **No es re-ejecutable**
(la segunda corrida falla en la primera línea, sin dañar nada).

Secuencia: backup → cerrar el sistema → SQL → subir código → abrir. Entre el
SQL y la subida del código el panel de asignaciones da error; hacerlo seguido
y sin clases en curso. Si entre hoy y el despliegue alguien ficha, el punto 2
borraría esa asistencia: revisar `SELECT COUNT(*) FROM attendance_records`
antes de correrlo.

`courses.cycle_id` queda **nullable en BD y obligatorio en el formulario**
(mismo criterio que `academic_department` y `teacher_type`): los 23 cursos
existentes no tienen de dónde sacar el ciclo. Mientras un curso no tenga
ciclo, no se puede asignar ni registrar asistencia con él — el sistema lo
dice y manda a completarlo en Cursos.

## 6. Decisiones del usuario (2026-10-05)

1. **Ciclo de los 23 cursos**: lo completa el admin al editar cada curso.
2. **Tipo de los 42 docentes**: todos `CONTRATADO` por SQL.
3. **Valores del tipo**: Nombrado / Contratado / Asociado, tal cual.
4. **`attendance_records.cycle_id`**: se **elimina** (cambia la propuesta
   original de conservarlo); el ciclo se obtiene siempre a través del curso.
   Por eso el SQL de la sección 5 quedó incompleto: el definitivo agrega el
   `UPDATE` de docentes y el `ALTER` de `attendance_records`.

## 7. Criterios de aceptación

- No se puede crear ni editar un docente sin tipo; filtro y columna funcionan.
- No se puede crear ni editar un curso sin ciclo; filtro y columna funcionan.
- El formulario de asignaciones no pide ciclo y lo muestra tomado del curso.
- Kiosko, pre-registro, registro manual, corrección y PDF de horario muestran
  el ciclo del curso.
- `migrate:fresh --seed` limpio, suite completa en verde, Pint limpio.
- El SQL de producción probado antes sobre una copia del esquema actual.
