# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

SGETECBA is an academic management system (Sistema de Gestión Estudiantil) for TECBA institution. It handles student registration, academic structure, attendance, grades, dropouts, Excel imports, pensum management, and institutional reporting.

## Commands

### Backend

```bash
cd backend

# Run dev server
python -m uvicorn app.main:app --host localhost --port 8000 --reload

# Run tests
pytest

# Run a single test file
pytest tests/test_auth_service.py -v

# DB migrations
alembic upgrade head
alembic revision --autogenerate -m "description"

# Seed initial data
python seed_initial_users.py
```

The backend requires a `backend/.env` file with at minimum `JWT_SECRET_KEY`, `DB_PASSWORD`, and DB connection vars (see `backend/app/core/config.py` for all accepted env vars).

### Frontend

```bash
cd frontend

npm run dev        # dev server on port 5173
npm run build      # TypeScript check + Vite build
npm run preview    # serve built dist
```

Frontend requires `frontend/.env` with `VITE_API_URL=http://localhost:8000/api/v1` (and optionally `VITE_REALTIME_URL`).

## Architecture

### Backend (`backend/`)

FastAPI + SQLAlchemy 2 + PostgreSQL (psycopg3) + Alembic. Python virtual environment is expected to be set up manually.

```
backend/app/
├── main.py              # FastAPI app, CORS, middleware registration
├── core/
│   ├── config.py        # Pydantic-settings (reads from .env); JWT_SECRET_KEY must be non-empty
│   ├── database.py      # SQLAlchemy engine + Base + get_db()
│   ├── deps.py          # get_current_user(), require_roles() dependency factories
│   ├── roles.py         # Role constants + role group tuples (see Roles section)
│   └── security.py      # bcrypt hashing + JWT encode/decode
├── models/
│   └── entities.py      # All SQLAlchemy ORM models in one file
├── schemas/             # Pydantic v2 request/response schemas
├── crud/                # DB query helpers
├── services/            # Business logic layer
├── middleware/
│   └── auditoria.py     # Middleware that logs all write operations to audit_logs
└── api/v1/
    ├── api.py           # Router aggregator
    └── routes/          # One file per domain (see Routes section below)
```

All API routes are prefixed `/api/v1`. Authorization uses `require_roles(...)` as a FastAPI dependency — unauthorized access returns 403.

### Backend Routes (`backend/app/api/v1/routes/`)

| Archivo | Prefijo | Descripción |
|---------|---------|-------------|
| `auth.py` | `/auth` | Login, JWT |
| `estudiantes.py` | `/estudiantes` | CRUD estudiantes |
| `carreras.py` | `/carreras` | CRUD carreras |
| `facultades.py` | `/facultades` | CRUD facultades |
| `asignaturas.py` | `/asignaturas` | CRUD asignaturas |
| `pensum.py` | `/pensum` | Pensum multi-versión (anio 2023/2025) |
| `periodos_academicos.py` | `/periodos-academicos` | Períodos + modalidades + anio_pensum |
| `oferta_asignaturas.py` | `/oferta-asignaturas` | Apertura de asignaturas por periodo/turno/grupo |
| `matriculaciones.py` | `/matriculaciones` | Inscripción de estudiantes en ofertas |
| `matriculas.py` | `/matriculas` | Matrículas alternativas |
| `grupos.py` | `/grupos` | Grupos (lectura: ADMIN + AA; escritura: solo ADMIN) |
| `turnos.py` | `/turnos` | Turnos (mañana, tarde, noche) |
| `periodos_clase.py` | `/periodos-clase` | Bloques horarios por turno |
| `niveles_academicos.py` | `/niveles-academicos` | Niveles / semestres |
| `sesiones.py` | `/sesiones` | Sesiones de asistencia |
| `asistencias.py` | `/asistencias` | Registro de asistencia |
| `calificaciones.py` | `/calificaciones` | Notas (PARCIAL, FINAL, RECUPERATORIO, HABILITACION) |
| `convalidaciones.py` | `/convalidaciones` | Convalidación de materias |
| `historial_academico.py` | `/historial-academico` | Historial por estudiante |
| `docentes.py` | `/docentes` | CRUD docentes (admin) |
| `docente.py` | `/docente` | Self-service docente: mis-asignaturas, horario, mis-estudiantes |
| `horario_clases.py` | `/horario-clases` | Horario de clases planificado |
| `clases_planificadas.py` | `/clases-planificadas` | Clases planificadas |
| `dashboard.py` | `/dashboard` | Resúmenes: `/resumen`, `/docente/resumen`, `/mkt/resumen` |
| `reportes.py` | `/reportes` | Reportes gerenciales por facultad/carrera/modalidad |
| `reglas_promocion.py` | `/reglas-promocion` | Reglas de promoción por modalidad |
| `parametros_institucionales.py` | `/parametros-institucionales` | Parámetros globales del sistema (solo ADMIN/AA) |
| `usuarios.py` | `/usuarios` | CRUD usuarios (solo ADMIN) |
| `importacion.py` | `/importacion` | Importación Excel two-step |
| `abandonos.py` | `/abandonos` | Registro de abandonos |
| `faltas.py` | `/faltas` | Faltas |
| `realtime.py` | `/realtime` | SSE stream |

### Frontend (`frontend/src/`)

React 18 + TypeScript + Vite + Tailwind CSS + React Router v6 + axios.

```
frontend/src/
├── api/
│   └── client.ts              # ALL API calls + axios instance + authStorage (localStorage)
├── auth/
│   └── roles.ts               # Frontend role constants matching backend roles
├── routes/
│   └── AppRouter.tsx          # React Router v6 route definitions + RolGuard
├── pages/
│   ├── Dashboard.tsx
│   ├── Estudiantes.tsx        # CRUD + plan_pago, trabaja, lugar/horario_trabajo
│   ├── Carreras.tsx
│   ├── Facultades.tsx
│   ├── Asignaturas.tsx
│   ├── Matriculaciones.tsx
│   ├── PanelAlumnos.tsx       # Panel de estado académico por alumno
│   ├── Calificaciones.tsx     # Vista docente de calificaciones
│   ├── CalificacionesAdmin.tsx # Vista admin de calificaciones
│   ├── AsignacionHorarios.tsx
│   ├── Grupos.tsx
│   ├── Docentes.tsx
│   ├── Periodos.tsx
│   ├── Ofertas.tsx
│   ├── NivelesAcademicos.tsx
│   ├── Asistencias.tsx
│   ├── Reportes.tsx           # Reportes gerenciales por facultad/carrera/modalidad
│   ├── Usuarios.tsx
│   ├── Login.tsx
│   ├── docente/
│   │   ├── HorarioDocente.tsx # Solo lectura — horario del docente autenticado
│   │   └── DashboardDocente.tsx
│   ├── mkt/
│   │   ├── DashboardMKT.tsx
│   │   ├── InscripcionMKT.tsx
│   │   └── InscritosMKT.tsx
│   └── secretaria/
│       ├── AsistenciasSecretaria.tsx
│       └── DashboardSecretaria.tsx
├── layouts/
│   ├── AdminLayout.tsx
│   ├── SecretariaLayout.tsx   # Asistente Académico
│   ├── DocenteLayout.tsx
│   ├── DireccionLayout.tsx
│   └── MKTLayout.tsx
├── components/
│   ├── Sidebar.tsx
│   ├── Combobox.tsx           # Autocomplete genérico
│   ├── DataTable.tsx
│   ├── PageHeader.tsx
│   ├── SearchBar.tsx
│   ├── PaginationControls.tsx
│   ├── StatCard.tsx
│   ├── Toast.tsx
│   └── ImportarDatos.tsx
├── realtime/                  # SSE/WebSocket: RealtimeProvider, useRealtimeRefresh,
│                              # useRealtimeSubscription
├── hooks/                     # Custom React hooks
├── utils/
│   ├── colors.ts
│   └── format.ts
└── types/
    └── api.ts                 # TypeScript interfaces for all API responses
```

**Auth flow**: `POST /auth/login` → token stored in `localStorage` under key `sge_tecba_token`, user object under `sge_tecba_user`. The axios instance in `client.ts` auto-attaches the Bearer token and redirects to `/login` on 401.

### Database

PostgreSQL only (no SQLite in production). Connection built from `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD` env vars, or a full `DATABASE_URL`. Schema: `sgetecba`. 55 tablas activas.

All ORM models are defined in `backend/app/models/entities.py`. Alembic autogenerate targets `Base.metadata`.

**Tablas principales:**

| Tabla | Descripción |
|-------|-------------|
| `estudiantes` | Estudiantes — incluye `carrera_id` (NOT NULL), `trabaja`, `lugar_trabajo`, `horario_trabajo`, `plan_pago_id` |
| `carreras` | Carreras — `facultad_id` FK a `facultades` |
| `facultades` | Facultades institucionales (8 registros) |
| `pensum` | Pensum multi-versión — columnas `anio` (2023/2025), `codigo_en_carrera`, `es_obligatoria` |
| `periodos_academicos` | Períodos — columna `anio_pensum` indica qué versión del pensum rige |
| `estudiante_carreras` | FK estudiante–carrera — columna `anio_pensum` por alumno |
| `ofertas_asignaturas` | Apertura: asignatura + carrera + periodo + nivel + turno + grupo |
| `inscripciones` | Inscripción de estudiante en una oferta |
| `matriculaciones` | Matrícula formal |
| `calificaciones` | Notas: PARCIAL, FINAL, RECUPERATORIO, HABILITACION |
| `sesiones_asistencia` | Sesiones de clase |
| `detalle_asistencia` | Asistencia por estudiante por sesión |
| `docentes` | Docentes — `usuario_id` FK a `usuarios` |
| `asignaciones_docente` | Asignación docente–oferta |
| `horario_clases` | Horario planificado |
| `periodos_clase` | Bloques horarios por turno |
| `parametros_institucionales` | Parámetros globales clave–valor |
| `convalidaciones` | Convalidaciones de materias |
| `historial_academico` | Historial académico por estudiante |
| `auditoria_logs` | Log automático de todas las operaciones de escritura |

**Backups en** `BD/backups/`. **Migraciones aplicadas:** 001–013 + `migration_pensum_calificaciones`.

## Roles & Access Control

| Rol | Código | Permisos clave |
|-----|--------|----------------|
| Administrador | `ADMIN` | Acceso completo a todos los módulos |
| Asistente Académico | `ASISTENTE_ACADEMICO` | Estudiantes, carreras, matriculaciones, asistencias, sesiones, oferta, importación. Lee grupos. |
| Docente | `DOCENTE` | Dashboard propio, mis-asignaturas, horario, mis-estudiantes, asistencias, calificaciones |
| Dirección/Gerencia | `DIRECCION` | Dashboard, reportes gerenciales, asistencias, historial. Solo lectura. |
| Marketing | `MKT` | Reportes gerenciales (lectura), registrar estudiantes, catálogos |

**Grupos de roles definidos en** `backend/app/core/roles.py`:

```python
ACADEMIC_READ_ROLES   = (ADMIN, ASISTENTE_ACADEMICO, DIRECCION)
ACADEMIC_WRITE_ROLES  = (ADMIN, ASISTENTE_ACADEMICO)
ATTENDANCE_READ_ROLES = (ADMIN, ASISTENTE_ACADEMICO, DIRECCION, DOCENTE)
CALIFICACION_READ_ROLES  = (ADMIN, ASISTENTE_ACADEMICO, DIRECCION, DOCENTE)
CALIFICACION_WRITE_ROLES = (ADMIN, ASISTENTE_ACADEMICO, DOCENTE)
INSCRIPTION_ROLES     = (ADMIN, ASISTENTE_ACADEMICO, MKT)
CATALOG_READ_ROLES    = (ADMIN, ASISTENTE_ACADEMICO, DIRECCION, MKT)
GERENCIAL_READ_ROLES  = (ADMIN, DIRECCION, MKT)
ADMIN_ONLY            = (ADMIN,)
```

**Usuarios de prueba** (contraseña universal: `Admin123*`):

| Usuario | Rol |
|---------|-----|
| `admin` | ADMIN |
| `asistente_academico` | ASISTENTE_ACADEMICO |
| `docente` | DOCENTE — vinculado a `docentes.id=1` |
| `decano` | DIRECCION |
| `marketing` | MKT |

## Key Patterns

- **Pagination**: API returns `PaginatedResponse<T>` with `items`, `meta.total`, `meta.page`, `meta.page_size`, `meta.total_pages`. Frontend passes `page` and `page_size` query params.
- **Audit logging**: All write operations are captured by `AuditoriaMiddleware` — avoid bypassing it.
- **Excel import**: Two-step: `POST /importacion` (preview, returns batch_id) → confirm. Only validated rows without errors are committed.
- **Realtime**: SSE stream at `/api/v1/realtime/stream`; `RealtimeProvider` in frontend manages the connection. Use `useRealtimeRefresh` to subscribe page-level refreshes.
- **Oferta de asignatura**: The join entity that connects `asignatura + carrera + periodo + nivel + turno + grupo`. Matriculaciones reference this entity.
- **Pensum multi-versión**: Table `pensum` has column `anio` (e.g. 2023, 2025). `periodos_academicos.anio_pensum` determines which pensum version applies to a period. `estudiante_carreras.anio_pensum` tracks which version each student follows.
- **Docente self-service**: Routes under `/docente` require a matching record in `sgetecba.docentes` with `usuario_id` set. Returns 404 otherwise.
- **Grupos**: Readable by ADMIN and ASISTENTE_ACADEMICO. Write/delete restricted to ADMIN only.
- **React autocomplete**: Components use `onMouseDown` handlers — to trigger via eval use `__reactProps$...` key on the DOM element, not native `.click()`.

## Sesiones de trabajo

| Fecha | Documento |
|-------|-----------|
| 2026-06-18 | `BD/MD/SESION_18-06-2026.md` |
| 2026-06-20 | `BD/MD/SESION_20-06-2026.md` |
| 2026-06-22 | `BD/MD/SESION_22-06-2026.md` |
| Changelog MirkoALS2 | `docs/MD Desarrollo/CHANGELOG_MirkoALS2_20-06-2026.md` |
