# AGENTS.md

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

## Dependencies

`composer install` before anything else. Third-party libs (`phpmailer/phpmailer`, `setasign/fpdf`) are managed via Composer — `admin_alumnos/material.php`, `registrar-pago.php`, `procesos/encuesta-pdf.php` and `procesos/cons-asi.php` load them through `vendor/autoload.php`, not the old hand-vendored `includes/PHPMailerMaster/` or `admin_alumnos/fpdf/` (neither exists in the repo anymore).

## CLI Tools

```bash
# Get (or lazily create) a school's fixed access token
php gen-token.php <id_colegio>

# Backfill token_micolegio for every school missing one
php backfill-tokens.php

# Verify a school is ready to receive traffic
php verificar_colegio.php <id_colegio>

# Migrate constants from a legacy inicia.php into colegio_config
php migrar_inicia.php <ruta/al/inicia.php> <id_colegio>

# Apply the initial multi-tenant schema to siscev
# mysql -u root siscev < db/fase1-esquema.sql
```

## Environment

Copy `.env.example` (none yet) or set these in `.env` (gitignored):

```
APP_DEBUG=0 # 1 muestra errores PHP en pantalla (solo dev/staging); 0 o ausente en producción
DB_HOST=localhost
DB_USER=root
DB_PASS=
DB_SISCEV=siscev
PORTAL_URL=https://micolegio.jesistemas.com.ve # override en local, e.g. http://micolegio.local
HOSTMAILTODOS= # fallback local del buzón compartido; ver cargar_mail_todos()
MAILUSERTODOS=
CLAVEMAILTODOS=
```

`cargar_mail_todos()` (en `config.php`) intenta primero `/var/www/todos/inicia.php` (legacy, buzón SMTP compartido entre todos los colegios en el hosting original); si no existe (dev local), cae a `HOSTMAILTODOS`/`MAILUSERTODOS`/`CLAVEMAILTODOS` del `.env`. Usado por `admin_alumnos/material.php` y `admin_alumnos/registrar-pago.php` antes de armar el `PHPMailer`.

## Architecture

### Two-database model

Every request operates on **two MySQL databases simultaneously**:

- **`siscev`** — central registry (schools, modules, config). Accessed via `db_siscev()`.
- **Per-school DB** — student data (grades, payments, documents). Name stored in `colegio.bdColeg`. Accessed via `db_colegio($bdColeg)` or the legacy wrapper `Conectarse()`.

Both connections are lazy-initialised singletons in `config.php`.

### Boot sequence (`bootstrap.php`)

Every student-portal page must include `bootstrap.php`, which runs:
1. `resolver_tenant()` — reads `?c=<token>` (fixed per-school token, `colegio.token_micolegio`, no expiry) or existing session, sets `$_SESSION['id_colegio']` and `$_SESSION['tenant_bdColeg']`.
2. `cargar_config_legacy($id_colegio)` — loads school constants (name, address, SMTP…) from `colegio_config` and columns of `colegio` into PHP `define()` constants (`NKXS`, `EKKS`, `RIFCOLM`, etc.).
3. `cargar_modulos($id_colegio)` — loads enabled module slugs into `$_SESSION['modulos']`.

### Module gating (`core/modulos.php`)

Each module page calls `requiere_modulo('slug')` early. If the module isn't in the school's enabled list, the user is redirected to `home.php`.

Two gating constants:
- `MODULOS_CON_MOROSO` — modules that *can* restrict late-payment students. Gate enforced by `verificar_no_moroso('slug')`.
- `MODULOS_CON_PDF` — subset of document modules that support a per-school configurable PDF template path (`config_modulo('slug', 'ruta_formato_pdf')`).

Per-module per-school config lives in `colegio_modulo_config` and is read via `config_modulo($slug, $clave, $default)`.

### PDF generation per school (`core/formato_pdf.php`)

For each `MODULOS_CON_PDF` slug, `admin_alumnos/procesos/<script>.php` is a thin router: session check → `verificar_no_moroso($slug)` → `cargar_formato_pdf($slug)`. That helper reads `colegio_modulo_config['ruta_formato_pdf']` for the current `$_SESSION['id_colegio']` (e.g. `colegiojuanxxiii/cons-est.php`), validates the resolved path stays inside `admin_alumnos/colegios/` (blocks traversal), then requires it.

Each school gets its own subfolder under `admin_alumnos/colegios/<carpeta>/` containing the full PDF template(s) — data query + FPDF layout + that school's `logo.png`/`fondoagua.jpg`. `colegiojuanxxiii/` is the reference example (colegio 11). To onboard a new school: copy that folder, adjust the layout/text, drop in its logo/watermark images, and set `ruta_formato_pdf` per module in `panel_admin`.

Known gap: student/representative photo uploads (`fotoalu/`, `fotorep/`, referenced by `planilla.php`) aren't migrated to the multi-tenant layout yet — that's a separate upload-feature task.

### Legacy compatibility layer

The student portal (`admin_alumnos/`) was migrated from a per-school deployment to this multi-tenant setup. The shims in place:

| File | Purpose |
|------|---------|
| `inicia.php` | Shim: legacy modules that `include "../inicia.php"` — ensures `NKXS` is defined via `cargar_config_legacy`. |
| `bridge-portal.php` | For hybrid modules (already include `inicia.php`): resolves `id_colegio` from the `DOMINIO` constant when session is missing. Include **after** `inicia.php`, **before** `conexion.php`. |
| `core/conexion.php` | Provides `Conectarse()` / `Conectarse3()` / `Conectarse1()` matching legacy signatures, now wrapping the multi-tenant connection. |

`cfg('CONSTANT_NAME', $default)` is a helper to safely read any constant loaded by `cargar_config_legacy`.

### Student portal (`admin_alumnos/`)

UI theme: SB Admin 2 (Bootstrap 4 + Font Awesome). Layout rendered via `admin_alumnos/layout/base.php`. Each module is a standalone PHP file; it sets `$pageTitle` and `$activePage`, calls `ob_start()`, outputs content, then the layout captures the buffer.

Morosos compatibility: `es_moroso()` checks both `$_SESSION['moroso']` (new) and `$_SESSION['morosida']` (legacy).

### Super-admin panel (`panel_admin/`)

Protected by `pa_auth()` from `_g.php`. Session-based rate-limit (5 failures → 15 min lockout). CSRF token required on all POST mutations. Capabilities: activate/deactivate schools, toggle modules per school, set `colegio_config` constants, configure `permite_morosos` and `ruta_formato_pdf` per module per school, test DB connectivity, generate short-lived login links.

### Token / URL flow

Each school has a fixed token in `colegio.token_micolegio` (24 random bytes, hex) — it never expires and is generated once, lazily, by `token_colegio($id)`.

Super admin fetches it: `php gen-token.php <id>` → token (creates it on first call, stable after that).  
Student URL: `<PORTAL_URL>/admin_alumnos/login.php?c=<token>`

### DB schema migrations

SQL files in `db/` are applied manually in order:
- `fase1-esquema.sql` — multi-tenant tables (`modulos`, `colegio_modulos`, `colegio_modulo_config`, `colegio_config`), plus the 25-module seed.
- `fase6-super-admin.sql` — `super_admin` table.
- `fase7-estado-migracion.sql` — migration-state tracking.
- `fase8-token-fijo.sql` — unique index on `colegio.token_micolegio` (fixed per-school login token, no TTL); see `backfill-tokens.php`.

## QA Obligatorio Post-Cambio

Antes de dar por terminada cualquier modificación, verificar:

### 1. Aislamiento multi-tenant (crítico)
- [ ] La query usa la conexión correcta (`db_siscev()` vs `db_colegio($bdColeg)`) — nunca mezclar
- [ ] No hay forma de que un colegio acceda a datos de otro (vía sesión, parámetro manipulado, o `id_colegio` hardcodeado)
- [ ] `resolver_tenant()` / `$_SESSION['id_colegio']` se respeta en todo el flujo, no se asume un colegio fijo

### 2. Seguridad
- [ ] Queries con prepared statements (`mysqli`/PDO con bind), nunca concatenación de `$_GET`/`$_POST`/`$_SESSION` en SQL
- [ ] Output escapado en HTML (`htmlspecialchars`) para prevenir XSS
- [ ] CSRF token verificado en cualquier POST nuevo dentro de `panel_admin/`
- [ ] Si se toca el token/`?c=`: es el token fijo por colegio (`colegio.token_micolegio`), único y no adivinable — nunca expuesto en logs/respuestas
- [ ] Rate-limit de `pa_auth()` no se rompe ni se puede bypassear

### 3. Módulos y gating
- [ ] Nuevo módulo/página llama `requiere_modulo('slug')` al inicio
- [ ] Si el módulo puede restringir morosos, se agregó a `MODULOS_CON_MOROSO` y se llama `verificar_no_moroso('slug')`
- [ ] `es_moroso()` sigue revisando ambas claves de sesión (`moroso` y `morosida`) si el cambio toca ese flujo

### 4. Capa de compatibilidad legacy
- [ ] Si el módulo usa `inicia.php`/`bridge-portal.php`, el orden de includes se respeta (inicia → bridge-portal → conexion.php)
- [ ] Constantes legacy (`NKXS`, `EKKS`, etc.) se leen con `cfg()`, no se asume que siempre existen
- [ ] `Conectarse()`/`Conectarse3()`/`Conectarse1()` no se duplican ni se rompe su firma

### 5. Estructura y código
- [ ] Sigue convenciones del repo (helpers existentes en `core/`, no reinventar conexión o auth)
- [ ] Sin código muerto, debug prints (`var_dump`, `echo` de prueba) olvidado
- [ ] Manejo de errores consistente (no exponer errores SQL/PHP crudos al usuario final)

### 6. Base de datos
- [ ] Si se modifica esquema, el SQL se agrega como nuevo archivo en `db/` (no se edita uno ya aplicado)
- [ ] Cambios de esquema no rompen `colegio_modulos`/`colegio_modulo_config` existentes
- [ ] Probado contra al menos un colegio real/de prueba, no solo en abstracto

### 7. UX/UI (`admin_alumnos/`, `panel_admin/`, `admin_docentes/`)
- [ ] Layout sigue el patrón `ob_start()` + `$pageTitle`/`$activePage` + `base.php`
- [ ] Consistente con SB Admin 2 (Bootstrap 4 + Font Awesome), no se mezclan estilos
- [ ] Mensajes de error/éxito visibles y claros para el usuario final

**Regla:** si algún punto no se puede confirmar, decirlo explícitamente — no asumir que "debería funcionar".

## QA con navegador (`admin_alumnos/`, `panel_admin/`, `admin_docentes/`)

Cada perfil de usuario tiene su carpeta de documentación de módulos:
- admin_alumnos/documentacion/modulos/*.md
- admin_docentes/documentacion/modulos/*.md
- panel_admin/documentacion/modulos/*.md

Cada .md tiene los pasos esperados y los mensajes de error/advertencia 
que deben aparecer en cada caso ("⚠️ Si algo no funciona").

Al hacer QA de un módulo: sigue los pasos tal cual usando el .md de la 
carpeta correspondiente al perfil, y fuerza también los casos de error 
para verificar que el mensaje/comportamiento coincida con lo descrito.

URLs locales:
- Alumnos: https://micolegio.local/admin_alumnos 
- Docentes: https://micolegio.local/admin_docentes
- Admin: https://micolegio.local/panel_admin/