# convenciones.md — Starter Kit RKM v6

## Lenguaje

- PHP 8.2+ como target; compatible 7.4 (sin atributos PHP 8+, sin `match` sin fallback).
- Todo el código en español: variables, comentarios, mensajes al usuario.
- Tipado estricto en métodos nuevos (`: int`, `: string`, `?array`, etc.).
- Prepared statements obligatorios — nunca concatenar valores en SQL.
- Driver: `mysqli` (no PDO, aunque el instalador lista PDO como opcional).

## Naming

| Elemento | Convención | Ejemplo |
|---|---|---|
| Clases | PascalCase | `AuthService`, `UserModel` |
| Métodos | camelCase | `getByEmail()`, `toggleStatus()` |
| Variables PHP | snake_case | `$user_id`, `$reset_url` |
| Constantes | UPPER_SNAKE | `APP_ROOT`, `MAIL_HOST` |
| Archivos de clase | `NombreClase.php` (igual que la clase) | `AuthController.php` |
| Vistas | snake-case o slug | `forgot-password.php` |
| Tablas SQL | snake_case | `role_permissions`, `audit_logs` |

## Rutas URL

- El router usa `?url=segmento` (reescrito por .htaccess a URL limpia).
- Convención automática: `user-admin` → `UserAdminController` → método `index`.
- Rutas con mapeo explícito declaradas en `Router::$routes[]` (solo para las que no siguen la convención).
- Máximo 2 parámetros posicionales en la URL: `/controlador/método/param1/param2`.

## Vistas y assets

- Separación absoluta: cero `<style>` ni `<script>` embebidos en vistas PHP, salvo bloques de < 5 líneas.
- Por cada vista `views/módulo/nombre.php` → sus recursos en `views/recursos/módulo/nombre/nombre.css` y `.js`.
- Cache busting obligatorio con `filemtime()`:
  ```html
  <link href="archivo.css?v=<?= filemtime(PUBLIC_PATH . '/...') ?>">
  ```
- Toda vista debe incluir `breadcrumb([...])`.
- El layout se compone manualmente con `require` de `layouts/header.php` y `layouts/footer.php`.

## Controller base

- Heredar de `Controller`. Métodos disponibles: `view()`, `json()`, `redirect()`.
- `view($path, $data)` usa `extract($data)` — cuidado con nombres reservados.
- `json($data, $status)` hace `exit` automáticamente.
- `redirect($path)` añade `BASE_URL` y hace `exit`.

## Middleware

- Se llama manualmente al inicio de cada método del controlador:
  ```php
  AuthMiddleware::check();                        // solo autenticación
  PermissionMiddleware::check('permiso_name');    // auth + permiso
  ```
- No hay middleware automático ni registro global — cada método lo declara explícitamente.

## Seguridad

- CSRF: `csrf_token()` para generar, `csrf_verify()` para validar en POST. Token en `$_SESSION['csrf']`.
- Rate limit de login: máximo 5 intentos por IP en ventana de 15 min, usando `$_SESSION['login_attempts']`.
- IP del cliente: siempre via `client_ip()` (prioriza Cloudflare → X-Forwarded-For → REMOTE_ADDR).
- Passwords: `password_hash($pass, PASSWORD_BCRYPT)` / `password_verify()`. Mínimo 6 caracteres.
- Reset tokens: `bin2hex(random_bytes(32))`, TTL 2h, eliminados después de usarse.
- `session_regenerate_id(true)` al hacer login exitoso.
- Logout: vaciar `$_SESSION`, eliminar cookie, `session_destroy()`.

## Eventos

- `EventDispatcher::listen($evento, $callback)` — registrar en `index.php`.
- `EventDispatcher::dispatch($evento, $data)` — disparar desde cualquier servicio.
- Los listeners son closures registrados en memoria; se pierden entre requests.
- Convención de nombres de eventos: snake_case — `offline_synced`.

## Notificaciones

- Canal `internal` → inserta en tabla `notifications`.
- Canal `email` → llama a `MailService`. PHPMailer detectado automáticamente.
- Canal `telegram` → HTTP POST a la API de Telegram (5s timeout, `@file_get_contents`).
- Auditoría: cada canal registra en `notifications` el estado `sent`/`failed`.

## Caché

- Usar `CacheService::remember($key, $ttl, $callback)`.
- Archivos en `/cache/` con prefijo `rkm_` y nombre `md5($key)`.
- Bloqueo con `flock()`. No usar en rutas de alta concurrencia sin evaluar.
- Clave de flush: `(new CacheService())->flush()` — útil en deploys.

## Logs

- `app_log($msg)` → `/logs/app.log` (append, formato `YYYY-MM-DD HH:II:SS | msg`).
- Errores y excepciones → `/logs/error.log` (vía `ErrorHandler`).
- En local (localhost/127.0.0.1) el ErrorHandler muestra stack trace en pantalla.

## Commits / Git

[PENDIENTE: el repo no tiene historial de commits — no hay `.git/`. No se puede inferir convención de mensajes.]

## Tests

No existen. [PENDIENTE: definir estrategia si se añaden.]
