> ## Documentation Index
> Fetch the complete documentation index at: https://docs.edtools.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Arquitectura

> Una app Next.js con Hono embebido, y por dónde entra cada petición.

EdTools no tiene un backend aparte. La API es una aplicación [Hono](https://hono.dev) montada dentro de la app Next.js 15 en `/api`, desplegada en Vercel como parte del mismo despliegue que el dashboard.

```text theme={null}
Next.js 15 (App Router)          Hono (apps/web/src/server/app.ts)
├── (auth)      login, signup    ├── /api/auth/*       Better Auth
├── (dashboard) administración   ├── /api/v1/*         dashboard (sesión + tenant)
├── (portal)    familias         ├── /api/v1/portal/*  portal (sesión + portalMiddleware)
└── /f/[slug]   formularios      ├── /api/client/v1/*  integraciones (token de servicio)
                                 ├── /api/public/*     internet (sin sesión)
                                 └── /api/internal/*   Trigger.dev (secreto compartido)
```

## El orden en que se atiende una petición

Todo pasa primero por tres middlewares globales: `logger`, `requestId` y `secureHeaders`. El `requestId` es el que verás en cada error y el que debes citar al pedir soporte.

Después, cada superficie tiene su propia cadena:

<AccordionGroup>
  <Accordion title="/api/v1/* — dashboard">
    `sessionMiddleware` resuelve la sesión de Better Auth · `tenantMiddleware` carga el tenant desde el header `x-tenant-slug` que inyecta el middleware de Next · `tenantAccessMiddleware` verifica que ese usuario pertenezca a ese tenant.

    A partir de ahí el handler tiene `c.get('tenant')` y `c.get('session')`, y **toda consulta debe filtrar por `tenantId`**.
  </Accordion>

  <Accordion title="/api/v1/portal/* — portal de familias">
    Lo anterior más `portalMiddleware`. Quien entra es el responsable de la cuenta financiera; `finance_account_members` decide de qué estudiantes ve información. Cambiar de hijo no divide la cuenta: es la misma sesión mirando otro estudiante.
  </Accordion>

  <Accordion title="/api/client/v1/* — integraciones">
    `clientApiAuthMiddleware` valida el token de servicio, resuelve el tenant, comprueba scopes, IP allowlist, rate limit y módulos habilitados. No hay sesión ni cookies: es server-to-server puro.
  </Accordion>

  <Accordion title="/api/public/* — internet">
    Sin sesión. Algunos endpoints reciben `tenantMiddleware` (los del portal público), y cada uno se defiende por su cuenta: token de un solo uso, firma del proveedor, o tope de peticiones por IP.
  </Accordion>

  <Accordion title="/api/internal/* — Trigger.dev">
    `internalApiAuthMiddleware` exige el header con `INTERNAL_API_SHARED_SECRET`. Es el camino de vuelta que usan las tareas en segundo plano para entrar a la app.
  </Accordion>
</AccordionGroup>

## Qué hay alrededor

| Pieza                       | Para qué                                       | Dónde vive                  |
| --------------------------- | ---------------------------------------------- | --------------------------- |
| PostgreSQL (Neon) + Drizzle | Todos los datos, con esquema por tenant lógico | `packages/db`               |
| Better Auth                 | Correo/contraseña, OAuth, SAML, multi-tenant   | `packages/auth`             |
| Trigger.dev                 | Trabajos en segundo plano y agentes            | `packages/agents`           |
| Resend + React Email        | Correo saliente                                | `packages/email`            |
| Cloudflare R2               | Archivos y documentos del Vault                | vía `packages/integrations` |
| Widget embebible            | Canal externo, construido con Vite             | `packages/widget`           |
| SDK `@edtools/client`       | Cliente TypeScript del Client API              | `packages/client`           |

## OpenAPI, y por qué solo una capa lo tiene

El Client API se declara con `@hono/zod-openapi`: cada endpoint lleva su esquema de Zod, y de ahí sale el documento OpenAPI 3.1 que alimenta la pestaña **Referencia**, el Swagger en `/api/client/swagger` y la colección de Postman en `/api/client/postman.json`.

El resto de la API usa Hono a secas. Hay un `app.doc('/openapi.json')` para `/api/v1`, pero **solo se monta cuando `NODE_ENV !== 'production'`**: existe para desarrollo, no como contrato público. Por eso las superficies internas se documentan como catálogos extraídos del código y no como referencia de esquemas.
