---
title: "La arquitectura de Éclair"
description: "La arquitectura de Éclair explicada a desarrolladores: vía rápida de index.php, caché estática, archivos planos en data/, clases y cabeceras de seguridad."
url: "https://eclair.academy/es/rutas/experto/arquitectura"
lang: "es"
author: "Thierry Gustin"
published: "2026-11-02"
updated: "2026-11-02"
---

# La arquitectura de Éclair

> **Lo esencial** — La arquitectura de Éclair se basa en un único script público, index.php, que sirve directamente desde cache/pages las páginas ya generadas. Las demás peticiones pasan por app/bootstrap.php y después por App::run, que las encamina al instalador, la administración, el formulario, el conector MCP o el sitio. Los datos son archivos JSON protegidos en data/, escritos de forma atómica y con bloqueo.

La arquitectura de Éclair se resume en una regla: hacer trabajar a PHP solo cuando hace falta. Esta lección sigue una petición de principio a fin, del servidor web a la caché estática, y muestra cómo se guardan los datos sin base de datos y qué clase hace cada cosa. Después podrás depurar, personalizar o alojar Éclair sabiendo cómo funciona por dentro.

> **Antes de empezar** — Duración: 30 minutos. Requisitos: leer PHP 8 y conocer lo básico de HTTP (cabeceras, códigos 200, 301, 304 y 404). Tener un sitio instalado ayuda a seguir la lección: en local con [Desarrollo local y pruebas](https://eclair.academy/es/rutas/experto/desarrollo-local), o en línea después de [Instalar Éclair paso a paso](https://eclair.academy/es/rutas/principiante/instalar-eclair).

## Lo que sabrás hacer

### ⚡ Seguir una petición

Del servidor web a index.php y, de ahí, a la caché o a la aplicación.

### 🔒 Leer la carpeta data/

Qué contiene cada archivo y cómo está protegido.

### 🛠 Encontrar la clase adecuada

Saber en pocos segundos dónde actuar dentro de app/lib/.

## ¿Cómo atraviesa una petición la arquitectura de Éclair?

![Esquema del ciclo de una petición: respuesta desde la caché estática o paso por la aplicación Éclair](https://eclair.academy/media/2026/11/eclair-academy-architecture-es.png)
*La vía rápida sirve las páginas ya generadas; todo lo demás pasa por App::run.*

Todo parte de una constatación: la mayoría de las visitas piden una página que no ha cambiado. Por eso la arquitectura de Éclair la devuelve tal cual, sin leer ni un solo contenido. Este es el recorrido de una petición.

1. **El servidor web.** En Apache o LiteSpeed, el `.htaccess` envía a `index.php` cualquier dirección que no sea un archivo existente. Responde 404 para `app/`, `data/`, `cache/`, los archivos ocultos (salvo `.well-known`) y cualquier `.php` distinto de `index.php`, y transmite la cabecera `Authorization` al conector MCP.
2. **La vía rápida de index.php.** Para un GET o un HEAD, `index.php` lee `cache/runtime.php` y busca en `cache/pages/` la página guardada con el `md5` de la ruta. Si la encuentra, devuelve sus cabeceras guardadas, su ETag y `X-Cache: HIT`, o un `304 Not Modified` si el navegador ya tiene esa versión.
3. **El arranque.** Si no, `app/bootstrap.php` comprueba la versión de PHP (8.1 como mínimo), carga `eclair-config.php` si existe, define `ECLAIR_DATA` y `ECLAIR_DEBUG`, registra la carga automática de las clases `Eclair\…` y llama a `Eclair\App::run()`.
4. **El enrutamiento.** `App::run()` instala la gestión de errores y entrega la petición al módulo adecuado (tabla de abajo).
5. **El renderizado.** `Site::handle()` solo acepta GET y HEAD, impone una sola dirección por contenido (barra final, mayúsculas, `index.php`: redirección 301), sirve los archivos generados (`robots.txt`, `sitemap.xml`, `llms.txt`, feeds, versiones `.md`) y después busca el contenido. `Cache::put()` guarda cada página renderizada con el código 200.

| Ruta solicitada | Módulo que responde |
| --- | --- |
| `/__eclair/ping` | comprobación de la reescritura, prueba de identidad (`Loopback`) |
| cualquier ruta, antes de la instalación | `Installer::handle()` |
| la dirección de la administración y sus subpáginas | `Admin::handle()` |
| `/_eclair/contact` | `Contact::handle()`, el formulario de contacto |
| `/_eclair/edit/<id>` | redirección al editor, si hay una sesión iniciada |
| `/_eclair/…` y `/.well-known/…` | `Mcp::handle()`, mudo mientras el conector esté desactivado |
| todo lo demás | `Site::handle()` |

La búsqueda, la página 404, la vista previa de la apariencia, el modo Visual y la administración nunca se guardan en caché. Para ver la vía rápida en acción, consulta dos veces la misma página:

```bash
# 1.ª llamada: la aplicación genera la página y la guarda
curl -sI https://exemple.fr/contact | grep -i -E 'etag|x-cache'
# ETag: "0745734655e7d523"

# 2.ª llamada: index.php la sirve desde cache/pages/, sin cargar la aplicación
curl -sI https://exemple.fr/contact | grep -i -E 'etag|x-cache'
# ETag: "0745734655e7d523"
# X-Cache: HIT

# El archivo de caché lleva el md5 de la ruta solicitada
php -r 'echo md5("/contact"), PHP_EOL;'   # 90609cfd31a32bdbd3d81843c57d356b
```

## El almacenamiento en archivos planos: la clase Store

La arquitectura de Éclair prescinde de la base de datos: cada dato es un archivo `data/<nom>.php` que empieza por la línea `<?php exit; ?>`, seguida de JSON legible. Si se llama desde la web, no devuelve nada, incluso en un servidor que ignorara los `.htaccess`. Este es el principio de un archivo de menús real:

```php
<?php exit; ?>
{
    "main": [
        {
            "type": "content",
            "id": "af59e5ba0a",
            "label": "",
            "url": ""
        }
    ]
}
```

Cuatro mecanismos hacen que este almacenamiento sea seguro, incluso en un hosting compartido muy cargado:

- **Escritura atómica**: `Store::atomic()` escribe un archivo temporal y después lo renombra, de modo que un lector nunca ve un archivo a medio escribir. Los archivos reciben los permisos 0600, y las carpetas de `data/` y `cache/`, 0700.
- **Ningún código inyectable**: `Store::write()` codifica el JSON con `JSON_HEX_TAG` y `JSON_HEX_AMP`. Un `<` se convierte en `\u003C`: la secuencia `<?php` no puede aparecer en los datos.
- **Bloqueo**: `Store::locked()` pone un bloqueo exclusivo (`flock`) sobre `data/.lock` durante cada operación de lectura, modificación y escritura.
- **Nombres controlados**: `Store::file()` solo acepta minúsculas, números, guiones y barras. Es imposible salir de `data/`.

Todas las clases que escriben siguen este patrón, por ejemplo `Redirects::add()`:

```php
use Eclair\Store;

$menus = Store::read('menus', []);           // lectura, con un valor por defecto
Store::locked(function (): void {            // leer, modificar y escribir sin condiciones de carrera
    $list = Store::read('redirects', []);
    // … modificar $list …
    Store::write('redirects', $list);        // archivo temporal, 0600, renombrado
});
```

## Los archivos de la carpeta data/

La última columna indica lo que conserva la copia de seguridad ZIP de la página **Seguridad**: los secretos y los datos temporales quedan fuera.

| Archivo o carpeta | Contenido | Copia ZIP |
| --- | --- | --- |
| `site.php` | ajustes, combinados con `Settings::DEFAULTS` | sí |
| `users.php` | cuentas, hashes Argon2id, verificación en dos pasos | sí |
| `keys.php` | secreto del sitio y clave de IndexNow, creados en la instalación | sí |
| `index.php` | resumen de cada contenido, para listar y enrutar rápido | sí |
| `content/<id>.php` | una página, un artículo o un producto, con sus bloques | sí |
| `revisions/<id>/` | las 20 últimas versiones de ese contenido | sí |
| `search.php`, `media.php` | texto de búsqueda, registro de la biblioteca de medios | sí |
| `menus.php`, `categories.php`, `redirects.php`, `custom-css.php` | menús, categorías, redirecciones, CSS personalizado | sí |
| `messages/`, `inbox.php` | mensajes del formulario, 500 como máximo | sí |
| `logs/` | registros de errores y de seguridad, limitados a 1 MB | sí |
| `vault.php` | claves de API cifradas | no |
| `ai/` | conversaciones del asistente, propuestas, conexiones MCP | no |
| `sessions/`, `limits/` | sesiones de la administración, contadores contra la fuerza bruta | no |

Las actualizaciones nunca tocan `data/`, que la constante `ECLAIR_DATA` puede incluso situar fuera de la carpeta pública.

## Las clases principales de app/lib/

Todas las clases viven en el espacio de nombres `Eclair\`, un archivo por clase, sin framework ni Composer:

| Clase | Función |
| --- | --- |
| `App` | enrutamiento de las peticiones que no sirve la caché |
| `Req`, `Http` | lectura de la petición, respuestas y redirecciones |
| `Store`, `Settings` | almacenamiento en archivos planos, ajustes y valores por defecto |
| `Content`, `Blocks` | contenidos, índice, revisiones; renderizado de los 18 bloques |
| `Site`, `Theme` | enrutador público; plantillas y CSS en línea |
| `Cache` | páginas estáticas y `cache/runtime.php` |
| `Seo`, `Analyzer` | datos estructurados, sitemap, llms.txt; puntuaciones |
| `Security`, `Session`, `Auth` | cabeceras, tokens CSRF, sesiones, inicio de sesión |
| `Media` | biblioteca de medios, recodificación y WebP |
| `Admin` y `Admin…` | pantallas y acciones de la administración |
| `Ai`, `AiChat`, `AiTools`, `Mcp` | asistente IA, herramientas, conector MCP y OAuth |
| `Integrity`, `Log`, `Keys`, `Vault`, `Loopback` | integridad, registros, secretos, caja fuerte, verificación de la dirección |
| `I18n` | traducciones de la administración |

## ¿Por qué se vacía la caché con cada cambio?

Un título modificado aparece también en un menú, en una tarjeta de artículo o en las migas de pan. En lugar de adivinar esas dependencias, la arquitectura de Éclair lo vacía todo: `Cache::purge()` se ejecuta tras cada guardado de un contenido, de los ajustes, los menús, las categorías, los medios, un perfil o el CSS personalizado. Borra los archivos de `cache/pages/` y reescribe `cache/runtime.php`; cada página se regenera en su primera visita.

```php
<?php exit; ?>
{"on":true,"expires":0,"host":"exemple.fr","https":true}
```

`index.php` lee este pequeño archivo en cada petición:

- `on`: caché activa (**Caché estática de páginas** marcada y modo de depuración apagado);
- `expires`: fecha del próximo artículo programado; a partir de ella `index.php` ignora la caché, que vacía `Cache::checkExpiry()`;
- `host` y `https`: host y protocolo oficiales, una vez verificada la dirección, para no saltarse nunca las redirecciones.

> **Vaciar la caché a mano** — **Ajustes › Avanzado › Vaciar la caché** hace lo mismo que `Cache::purge()`. Es el último paso de cada actualización de Éclair.

## Cabeceras de seguridad y CSP

`Security::headers()` produce tres juegos de cabeceras: `site`, `admin` y `text` (sitemap, `llms.txt`, feeds). Dependen de los ajustes, nunca de la petición: nadie puede envenenar la caché con una cabecera falsificada. Estas son las de una página pública servida por HTTP, tomadas de Éclair 1.8:

```text
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'sha256-…'; img-src 'self' data: https:; media-src 'self' https:; frame-src https://www.youtube-nocookie.com https://player.vimeo.com; connect-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), browsing-topics=()
Cross-Origin-Opener-Policy: same-origin
```

`Theme::hash()` calcula la huella `sha256` sobre el CSS insertado en la página: solo se autoriza esa etiqueta `<style>`, no se permite ningún script en línea y el único script público es `assets/site/site.js`. Con HTTPS se añaden `upgrade-insecure-requests` y, si la opción HSTS está marcada, `Strict-Transport-Security`. La administración es aún más estricta: ningún estilo en línea, `frame-ancestors 'none'`, `noindex` y `Cache-Control: no-store, private`.

## Las traducciones

- **Administración**: cada texto se escribe en francés en el código, con `t()`. `I18n::t()` busca su traducción en `app/lang/<langue>.json.gz`, compilado por `tools/i18n.php` e indexado por la huella CRC32 del texto francés. Sin la extensión zlib, la administración se queda en francés.
- **Sitio público**: `Theme::t('clé', $lang)` lee el array `Theme::STRINGS` («Índice», «Lo esencial», «Leer en»…) en seis idiomas, y `Theme::words()` da las palabras de las direcciones (página, autor, búsqueda, tienda) en el idioma del sitio.

> **No modifiques app/ a la ligera** — Cualquier archivo de `app/` se sustituye en la próxima actualización y el control de integridad lo señala. Para el estilo, usa el CSS personalizado; para la estructura del HTML, sigue [Personalizar las plantillas del tema](https://eclair.academy/es/rutas/experto/personalizar-tema).

## Para recordar

- En la arquitectura de Éclair, `index.php` sirve una página ya generada desde `cache/pages/` sin cargar la aplicación: `X-Cache: HIT` lo demuestra.
- Lo demás pasa por `app/bootstrap.php` y después por `App::run()`, que lo encamina al instalador, la administración, el contacto, el MCP o el sitio.
- Los datos son archivos JSON detrás de `<?php exit; ?>`, escritos de forma atómica, con permisos 0600 y con bloqueo.
- La caché se vacía por completo con cada cambio y se reconstruye en la visita siguiente.
- Las cabeceras de seguridad solo dependen de los ajustes: la caché no puede envenenarse.

## Para saber más

- [Desarrollo local y pruebas](https://eclair.academy/es/rutas/experto/desarrollo-local): ejecutar Éclair en tu máquina y repetir sus 550 comprobaciones.
- [Personalizar las plantillas del tema](https://eclair.academy/es/rutas/experto/personalizar-tema): actuar sobre el renderizado sin romper la CSP.
- [Nginx, VPS, proxy y CDN](https://eclair.academy/es/rutas/experto/nginx-vps): reproducir las reglas del `.htaccess` fuera de Apache.
- [Seguridad avanzada y mantenimiento](https://eclair.academy/es/rutas/experto/seguridad-avanzada): integridad, copias de seguridad y actualizaciones.
- Documentación de PHP: [flock()](https://www.php.net/manual/es/function.flock.php) y [rename()](https://www.php.net/manual/es/function.rename.php), en el corazón de las escrituras de `Store`.
- OWASP: [hoja de referencia sobre la Content Security Policy](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html), en inglés.

## Preguntas frecuentes

### ¿Puede funcionar Éclair sin su caché estática?

Sí: desmarca **Caché estática de páginas** en **Ajustes › Avanzado**, o activa el modo de depuración. Cada visita genera entonces la página, lo que consume más tiempo de servidor.

### ¿Por qué guardar JSON en archivos .php?

Para que la línea `<?php exit; ?>` proteja cada archivo aunque el servidor ignore el `.htaccess`. Si se llama desde la web, no muestra nada, mientras que Éclair lee el JSON que sigue a esa primera línea.

### ¿Qué pasa si dos personas guardan a la vez?

Las escrituras pasan por `Store::locked()`, que pone un bloqueo exclusivo sobre `data/.lock`. Los dos guardados se ejecutan uno detrás de otro, y la versión anterior pasa al historial de revisiones.

### ¿Se puede colocar la carpeta data/ fuera de la carpeta pública?

Sí. Primero mueve la carpeta y después indica su nueva ubicación con `define('ECLAIR_DATA', '/chemin/vers/eclair-data');` en `eclair-config.php`, junto a `index.php`. La vía rápida no depende de ella: solo lee `cache/`.
