npm package: @jamx-framework/router
Description: JAMX Framework â€” File-based Router
Author: Stiven-21
Latest version: 1.0.1

# @jamx-framework/router

## Descripción
Sistema de enrutamiento basado en archivos para JAMX Framework. Convierte automáticamente la estructura de archivos del proyecto en rutas HTTP (API) y páginas (SSR), escaneando el sistema de archivos para descubrir handlers y componentes. Proporciona matching de rutas con parámetros dinámicos, wildcards, y separación clara entre API endpoints y páginas web.

## Cómo funciona
El router opera en tres fases:

1. **Escaneo**: `FileScanner` recorre el proyecto buscando archivos `*.handler.ts` (API) y `*.page.tsx` (páginas)
2. **Parseo**: `RouteParser` convierte nombres de archivos y carpetas en definiciones de rutas con parámetros
3. **Matching**: `RouteMatcher` empareja URLs entrantes con rutas registradas, extrayendo parámetros
4. **Ejecución**: `ApiHandlerExecutor` o `PageHandlerExecutor` ejecutan el handler correspondiente

## Componentes principales

### FileRouter (`src/router.ts`)
Clase principal que implementa `RequestDispatcher`:
- `initialize()`: Escanea el proyecto y construye la tabla de rutas
- `dispatch(req, res)`: Despacha una request al handler correcto
- `getRoutes()`: Retorna todas las rutas registradas (para compilador)
- `invalidate(filePath)`: Re-escanear proyecto (HMR)

### FileScanner (`src/scanner/file-scanner.ts`)
Escanea el sistema de archivos para descubrir rutas:
- Busca archivos `*.handler.ts` en `src/api/`
- Busca archivos `*.page.tsx` en `src/pages/`
- Construye objetos `Route` con path, kind, filePath

### RouteParser (`src/scanner/route-parser.ts`)
Convierte paths de archivos en patrones de ruta:
- `/api/users.handler.ts` → `/api/users` (API)
- `/pages/users/[id].page.tsx` → `/users/:id` (página con param)
- `/pages/admin/dashboard.page.tsx` → `/admin/dashboard`

### RouteMatcher (`src/matcher/matcher.ts`)
Empareja URLs con rutas registradas:
- Matching exacto para estáticos
- Extracción de parámetros (`:id`, `:slug`)
- Soporte para wildcards (`*`)

### ApiHandlerExecutor (`src/handler/api-handler.ts`)
Ejecuta handlers de API:
- Carga el módulo dinámicamente
- Invoca el método HTTP correcto (GET, POST, etc.)
- Pasa `req` y `res` de JAMX

### PageHandlerExecutor (`src/handler/page-handler.ts`)
Ejecuta componentes de página:
- Carga el módulo dinámicamente
- Usa `SSRRenderer` para renderizar a HTML
- Retorna respuesta con HTML completo

## Convenciones de archivos

### API Handlers
Ubicación: `src/api/`

Patrón: `*.handler.ts`

Ejemplos:
```
src/api/users.handler.ts        → GET /api/users
src/api/users/[id].handler.ts  → GET /api/users/:id
src/api/auth/login.handler.ts  → POST /api/auth/login
```

Estructura del módulo:
```typescript
// src/api/users.handler.ts
export default {
  GET: async (req, res) => {
    res.json({ users: [] });
  },
  POST: async (req, res) => {
    const user = req.body;
    res.created(user);
  },
};
```

### Páginas
Ubicación: `src/pages/`

Patrón: `*.page.tsx`

Ejemplos:
```
src/pages/index.page.tsx        → /
src/pages/about.page.tsx        → /about
src/pages/users/[id].page.tsx   → /users/:id
src/pages/blog/[slug]/[id].page.tsx → /blog/:slug/:id
```

Estructura del módulo:
```typescript
// src/pages/index.page.tsx
import { jsx } from '@jamx-framework/renderer';

export default {
  render(ctx) {
    return jsx('div', {}, 'Página de inicio');
  },
  meta(ctx) {
    return {
      title: 'Inicio - Mi App',
      description: 'Descripción de la página',
    };
  },
};
```

## Uso básico

### Configurar el router en el servidor
```typescript
import { createServer } from '@jamx-framework/server';
import { FileRouter } from '@jamx-framework/router';

const server = await createServer();

// Configurar router file-based
const router = new FileRouter({ projectRoot: './' });
await router.initialize();

// Usar router como middleware
server.use(router.dispatch.bind(router));

await server.listen({ port: 3000 });
```

### Estructura de proyecto
```
my-app/
├── jamx.config.ts
├── src/
│   ├── api/
│   │   ├── users.handler.ts
│   │   └── users/[id].handler.ts
│   └── pages/
│       ├── index.page.tsx
│       ├── about.page.tsx
│       └── users/[id].page.tsx
└── package.json
```

### Definir API endpoints
```typescript
// src/api/users.handler.ts
export default {
  GET: async (req, res) => {
    const users = await db.users.findAll();
    res.json(users);
  },

  POST: async (req, res) => {
    const user = await db.users.create(req.body);
    res.created(user);
  },
};
```

```typescript
// src/api/users/[id].handler.ts
export default {
  GET: async (req, res) => {
    const id = req.params.id;
    const user = await db.users.findById(id);
    if (!user) {
      res.notFound('User not found');
      return;
    }
    res.json(user);
  },

  PUT: async (req, res) => {
    const id = req.params.id;
    const user = await db.users.update(id, req.body);
    res.json(user);
  },

  DELETE: async (req, res) => {
    const id = req.params.id;
    await db.users.delete(id);
    res.noContent();
  },
};
```

### Definir páginas SSR
```typescript
// src/pages/index.page.tsx
import { jsx } from '@jamx-framework/renderer';

export default {
  render(ctx) {
    return jsx('div', { class: 'home' }, [
      jsx('h1', {}, 'Bienvenido a mi app'),
      jsx('p', {}, 'Esta es la página de inicio'),
    ]);
  },

  meta(ctx) {
    return {
      title: 'Inicio',
      description: 'Página de inicio de mi aplicación JAMX',
    };
  },
};
```

```typescript
// src/pages/users/[id].page.tsx
import { jsx } from '@jamx-framework/renderer';

export default {
  async render(ctx) {
    const id = ctx.params.id;
    const user = await db.users.findById(id);

    if (!user) {
      return jsx('div', {}, [
        jsx('h1', {}, 'Usuario no encontrado'),
      ]);
    }

    return jsx('div', { class: 'user-profile' }, [
      jsx('h1', {}, user.name),
      jsx('p', {}, `Email: ${user.email}`),
    ]);
  },

  meta(ctx) {
    return {
      title: 'Perfil de usuario',
    };
  },
};
```

### Layouts compartidos
```typescript
// src/pages/_layout.page.tsx
import { jsx, Fragment } from '@jamx-framework/renderer';

export default {
  render({ children, ctx }) {
    return jsx('html', {}, [
      jsx('head', {}, [
        jsx('title', {}, ctx.meta?.title ?? 'Mi App'),
        jsx('meta', {
          name: 'description',
          content: ctx.meta?.description ?? '',
        }),
        jsx('link', { rel: 'stylesheet', href: '/styles.css' }),
      ]),
      jsx('body', {}, [
        jsx('header', {}, jsx('nav', {}, 'Navegación')),
        jsx('main', {}, children),
        jsx('footer', {}, '© 2024'),
      ]),
    ]);
  },
};
```

## API Reference

### FileRouter

#### Constructor
```typescript
new FileRouter(options: RouterOptions)
```

**RouterOptions:**
```typescript
interface RouterOptions {
  projectRoot: string;  // Ruta raíz del proyecto
}
```

#### Métodos

##### initialize()
```typescript
async initialize(): Promise<void>
```
Escanea el proyecto y construye la tabla de rutas. Debe llamarse antes de `dispatch()`.

##### dispatch()
```typescript
async dispatch(req: JamxRequest, res: JamxResponse): Promise<void>
```
Despacha una request al handler correspondiente. Implementa `RequestDispatcher`.

##### getRoutes()
```typescript
getRoutes(): Route[]
```
Retorna todas las rutas registradas. Usado por el compilador para generar `AppRoutes`.

##### invalidate()
```typescript
async invalidate(filePath: string): Promise<void>
```
Invalida la cache y re-escanea el proyecto. Usado por HMR (Hot Module Replacement).

### Tipos

#### Route
```typescript
interface Route {
  path: string;           // Path con parámetros, ej: /api/users/:id
  kind: RouteKind;        // "page" | "api"
  methods: HttpMethod[];  // Métodos HTTP para API, [] para páginas
  filePath: string;       // Ruta absoluta al archivo
  segments: RouteSegment[]; // Segmentos parseados del path
}
```

#### RouteSegment
```typescript
type RouteSegment =
  | { kind: "static"; value: string }  // Segmento estático como "users"
  | { kind: "param"; name: string }    // Parámetro como ":id"
  | { kind: "wildcard" };              // Wildcard "/*"
```

#### RouteMatch
```typescript
interface RouteMatch {
  route: Route;
  params: Record<string, string>;  // Parámetros extraídos, ej: { id: "123" }
}
```

#### HttpMethod
```typescript
type HttpMethod =
  | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
```

#### RouteKind
```typescript
type RouteKind = "page" | "api";
```

#### ApiHandlerModule
```typescript
interface ApiHandlerModule {
  GET?: ApiHandlerFn;
  POST?: ApiHandlerFn;
  PUT?: ApiHandlerFn;
  PATCH?: ApiHandlerFn;
  DELETE?: ApiHandlerFn;
  OPTIONS?: ApiHandlerFn;
  HEAD?: ApiHandlerFn;
  default?: ApiHandlerFn;  // Handler para métodos no definidos
}
```

#### ApiHandlerFn
```typescript
type ApiHandlerFn = (
  req: JamxRequest,
  res: JamxResponse
) => Promise<void> | void;
```

#### PageModule
```typescript
interface PageModule {
  default: PageComponent;
}
```

#### PageComponent
```typescript
interface PageComponent {
  render: (ctx: RenderContext) => JamxNode;
  meta?: (ctx: RenderContext) => PageMeta;
  layout?: unknown;  // Layout component (por implementar)
}
```

#### PageMeta
```typescript
interface PageMeta {
  title?: string;
  description?: string;
  [key: string]: unknown;
}
```

#### AppRoutesBase
```typescript
type AppRoutesBase = Record<string, RouteDefinition>;
```
Tipo base para `AppRoutes` generado por el compilador.

### FileScanner

#### scanProject()
```typescript
async scanProject(projectRoot: string): Promise<Route[]>
```
Escanea el proyecto y retorna todas las rutas encontradas.

**Lógica de escaneo:**
1. Busca `src/api/**/*.handler.ts` → rutas API
2. Busca `src/pages/**/*.page.tsx` → rutas de página
3. Convierte paths a patrones de ruta
4. Retorna array de `Route`

**Ejemplo:**
```
src/api/users.handler.ts → { path: '/api/users', kind: 'api', methods: ['GET', 'POST'] }
src/pages/about.page.tsx → { path: '/about', kind: 'page', methods: [] }
```

### RouteParser

#### parseRoute()
```typescript
parseRoute(filePath: string, kind: RouteKind): Route
```
Convierte un path de archivo en una definición de ruta.

**Reglas:**
- `api/` → prefijo `/api`
- `pages/` → sin prefijo (rutas públicas)
- `[param].handler/page.tsx` → `:param`
- `*.handler.ts` → elimina extensión
- `*.page.tsx` → elimina extensión

**Ejemplos:**
```
src/api/users.handler.ts           → /api/users
src/api/users/[id].handler.ts     → /api/users/:id
src/pages/blog/[slug]/[id].page.tsx → /blog/:slug/:id
src/pages/admin/dashboard.page.tsx → /admin/dashboard
```

### RouteMatcher

#### setRoutes()
```typescript
setRoutes(routes: Route[]): void
```
Carga las rutas para matching.

#### match()
```typescript
match(path: string, method: HttpMethod): RouteMatch | null
```
Busca una ruta que coincida con el path y método.

**Algoritmo:**
1. Iterar rutas en orden de registro
2. Para cada ruta, comparar segmento por segmento
3. Si encuentra `:param`, extraer valor
4. Si encuentra `*`, hacer match con cualquier resto
5. Si todos los segmentos coinciden, retornar `RouteMatch`
6. Si no hay match, retornar `null`

**Ejemplo:**
```typescript
matcher.setRoutes([
  { path: '/api/users', kind: 'api', methods: ['GET'], ... },
  { path: '/api/users/:id', kind: 'api', methods: ['GET'], ... },
]);

matcher.match('/api/users/123', 'GET');
// → { route: {...}, params: { id: '123' } }
```

### ApiHandlerExecutor

#### execute()
```typescript
async execute(route: Route, req: JamxRequest, res: JamxResponse): Promise<void>
```
Ejecuta el handler de API correspondiente.

**Proceso:**
1. Cargar módulo desde `route.filePath`
2. Obtener handler por método HTTP (`route.methods` + `default`)
3. Si no existe handler para el método → 405
4. Invocar handler con `req` y `res`
5. Manejar errores y enviar respuesta

### PageHandlerExecutor

#### execute()
```typescript
async execute(route: Route, req: JamxRequest, res: JamxResponse): Promise<void>
```
Ejecuta el componente de página y envía HTML.

**Proceso:**
1. Cargar módulo desde `route.filePath`
2. Obtener componente `default export`
3. Crear `RenderContext` desde `req`
4. Invocar `SSRRenderer.render(component, ctx)`
5. Enviar HTML en `res.body` con headers apropiados

## Flujo interno detallado

### 1. Inicialización
```typescript
const router = new FileRouter({ projectRoot: './' });
await router.initialize();
```

** Dentro de `initialize()`:**
```typescript
async initialize() {
  // 1. Escanear proyecto
  this.routes = await this.scanner.scanProject(this.options.projectRoot);

  // 2. Configurar matcher
  this.matcher.setRoutes(this.routes);

  this.initialized = true;
}
```

**`FileScanner.scanProject()`:**
```typescript
async scanProject(root) {
  const routes = [];

  // Buscar API handlers
  const apiFiles = glob(`${root}/src/api/**/*.handler.ts`);
  for (const file of apiFiles) {
    const route = this.parseHandler(file, root);
    routes.push(route);
  }

  // Buscar páginas
  const pageFiles = glob(`${root}/src/pages/**/*.page.tsx`);
  for (const file of pageFiles) {
    const route = this.parsePage(file, root);
    routes.push(route);
  }

  return routes;
}
```

### 2. Despacho de request
```typescript
await router.dispatch(req, res);
```

** Dentro de `dispatch()`:**
```typescript
async dispatch(req, res) {
  // Auto-inicializar si es necesario
  if (!this.initialized) {
    await this.initialize();
  }

  // Parsear query string
  const qIndex = req.url.indexOf("?");
  if (qIndex !== -1) {
    req.query = qs.parse(req.url.slice(qIndex + 1));
  }

  // Buscar ruta
  const match = this.matcher.match(req.path, req.method);

  if (!match) {
    throw new NotFoundException(
      `Cannot ${req.method} ${req.path}`,
      "ROUTE_NOT_FOUND"
    );
  }

  // Inyectar params
  req.params = match.params;

  // Ejecutar handler
  if (match.route.kind === "api") {
    await this.apiHandler.execute(match.route, req, res);
  } else {
    await this.pageHandler.execute(match.route, req, res);
  }
}
```

### 3. Matching de rutas
```typescript
// Ejemplo de rutas
const routes = [
  { path: '/api/users', segments: [{static: 'api'}, {static: 'users'}] },
  { path: '/api/users/:id', segments: [{static: 'api'}, {static: 'users'}, {param: 'id'}] },
];

// Request: GET /api/users/123
match = matcher.match('/api/users/123', 'GET');
// → { route: routes[1], params: { id: '123' } }
```

**Algoritmo de `RouteMatcher.match()`:**
```typescript
match(path, method) {
  const pathSegments = path.split('/').filter(Boolean);

  for (const route of this.routes) {
    // Verificar método para APIs
    if (route.kind === 'api' && !route.methods.includes(method)) {
      continue;
    }

    // Comparar segmentos
    const params = {};
    let matched = true;

    for (let i = 0; i < route.segments.length; i++) {
      const segment = route.segments[i];
      const pathSegment = pathSegments[i];

      if (segment.kind === 'static') {
        if (segment.value !== pathSegment) {
          matched = false;
          break;
        }
      } else if (segment.kind === 'param') {
        params[segment.name] = pathSegment;
      } else if (segment.kind === 'wildcard') {
        // Wildcard captura el resto
        params[segment.name || 'wildcard'] = pathSegments.slice(i).join('/');
        break;
      }
    }

    if (matched && pathSegments.length === route.segments.length) {
      return { route, params };
    }
  }

  return null;
}
```

### 4. Ejecución de API handler
```typescript
// src/api/users.handler.ts
export default {
  GET: async (req, res) => {
    res.json({ users: [] });
  },
};
```

**`ApiHandlerExecutor.execute()`:**
```typescript
async execute(route, req, res) {
  // Cargar módulo dinámicamente
  const mod = await import(route.filePath);
  const handler = mod.default;

  // Buscar método específico
  let fn = handler[req.method];
  if (!fn) {
    fn = handler.default;
  }

  if (!fn) {
    res.json({ error: 'Method Not Allowed' }, 405);
    return;
  }

  // Ejecutar
  await fn(req, res);
}
```

### 5. Ejecución de página
```typescript
// src/pages/index.page.tsx
export default {
  render(ctx) {
    return jsx('div', {}, 'Hello');
  },
};
```

**`PageHandlerExecutor.execute()`:**
```typescript
async execute(route, req, res) {
  // Cargar módulo
  const mod = await import(route.filePath);
  const page = mod.default;

  // Crear contexto de renderizado
  const ctx: RenderContext = {
    env: process.env.NODE_ENV,
    path: req.path,
    url: req.url,
    params: req.params,
    locals: req.locals,
  };

  // Renderizar con SSRRenderer
  const result = await this.renderer.render(page, ctx);

  // Enviar respuesta
  res.status(result.statusCode);
  for (const [key, value] of Object.entries(result.headers)) {
    res.header(key, value);
  }
  res.send(result.html);
}
```

## Configuración

### tsconfig.json
```json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "tsBuildInfoFile": "dist/.tsbuildinfo"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
}
```

### Scripts disponibles
- `pnpm build` - Compila TypeScript a JavaScript
- `pnpm dev` - Compilación en watch mode
- `pnpm test` - Ejecuta tests unitarios
- `pnpm test:watch` - Tests en watch mode
- `pnpm type-check` - Verifica tipos sin compilar
- `pnpm clean` - Limpia archivos compilados

## Testing

### Tests unitarios
```typescript
import { FileRouter } from '@jamx-framework/router';
import { describe, it, expect, beforeEach } from 'vitest';

describe('Router', () => {
  let router: FileRouter;

  beforeEach(async () => {
    router = new FileRouter({ projectRoot: './test-fixtures' });
    await router.initialize();
  });

  it('should match API routes', () => {
    const routes = router.getRoutes();
    const apiRoute = routes.find(r => r.path === '/api/users');
    expect(apiRoute).toBeDefined();
    expect(apiRoute?.kind).toBe('api');
    expect(apiRoute?.methods).toContain('GET');
  });

  it('should match page routes with params', () => {
    const routes = router.getRoutes();
    const pageRoute = routes.find(r => r.path === '/users/:id');
    expect(pageRoute).toBeDefined();
    expect(pageRoute?.kind).toBe('page');
  });
});
```

## Dependencias
- `@jamx-framework/core` - Para `NotFoundException` y tipos base
- `@jamx-framework/server` - Para `JamxRequest`, `JamxResponse`, `RequestDispatcher`
- `@jamx-framework/renderer` - Para `SSRRenderer` en páginas
- `@types/node` - Tipos de Node.js
- `vitest` - Framework de testing
- `rimraf` - Limpieza de directorios

## Ejemplo completo

### Proyecto completo
```typescript
// jamx.config.ts
import { defineConfig } from '@jamx-framework/config';

export default defineConfig({
  targets: ['web'],
});
```

```typescript
// src/api/health.handler.ts
export default {
  GET: async (req, res) => {
    res.json({ status: 'ok', timestamp: Date.now() });
  },
};
```

```typescript
// src/api/users/[id].handler.ts
export default {
  GET: async (req, res) => {
    const id = req.params.id;
    const user = await db.users.findById(id);
    if (!user) {
      res.notFound('User not found');
      return;
    }
    res.json(user);
  },

  PUT: async (req, res) => {
    const id = req.params.id;
    const updates = req.body;
    const user = await db.users.update(id, updates);
    res.json(user);
  },

  DELETE: async (req, res) => {
    const id = req.params.id;
    await db.users.delete(id);
    res.noContent();
  },
};
```

```typescript
// src/pages/index.page.tsx
import { jsx } from '@jamx-framework/renderer';

export default {
  render(ctx) {
    return jsx('div', { class: 'container' }, [
      jsx('h1', {}, 'Mi Aplicación JAMX'),
      jsx('p', {}, 'Bienvenido a la página de inicio'),
      jsx('a', { href: '/about' }, 'Acerca de'),
    ]);
  },

  meta(ctx) {
    return {
      title: 'Inicio',
      description: 'Página de inicio de mi aplicación',
    };
  },
};
```

```typescript
// src/pages/about.page.tsx
import { jsx } from '@jamx-framework/renderer';

export default {
  render(ctx) {
    return jsx('div', { class: 'about' }, [
      jsx('h1', {}, 'Acerca de'),
      jsx('p', {}, 'Esta aplicación usa JAMX Framework'),
    ]);
  },

  meta(ctx) {
    return {
      title: 'Acerca de',
    };
  },
};
```

```typescript
// server/index.ts
import { createServer } from '@jamx-framework/server';
import { FileRouter } from '@jamx-framework/router';

async function main() {
  const server = await createServer();

  const router = new FileRouter({ projectRoot: './' });
  server.use(router.dispatch.bind(router));

  await server.listen({ port: 3000 });
  console.log('Server running on http://localhost:3000');
}

main().catch(console.error);
```

## Limitaciones
- **Solo archivos `.handler.ts` y `.page.tsx`**: No soporta otras extensiones
- **Estructura fija**: Requiere `src/api/` y `src/pages/`
- **Sin middleware por ruta**: Middleware debe ser global en el servidor
- **Carga dinámica**: Los handlers se cargan con `import()` en cada request (cacheable)
- **Sin validación de params**: Los params son strings, no hay validación automática

## Buenas prácticas

### 1. Organización de archivos
```
src/
├── api/
│   ├── auth/
│   │   ├── login.handler.ts
│   │   └── logout.handler.ts
│   ├── users.handler.ts
│   └── users/
│       ├── [id].handler.ts
│       └── [id]/profile.handler.ts
└── pages/
    ├── index.page.tsx
    ├── about.page.tsx
    ├── users/
    │   ├── index.page.tsx
    │   └── [id].page.tsx
    └── admin/
        └── dashboard.page.tsx
```

### 2. Separación de lógica
```typescript
// src/api/users.handler.ts
import { getUserService } from '../services/user-service.js';

export default {
  GET: async (req, res) => {
    const service = getUserService();
    const users = await service.list();
    res.json(users);
  },
};
```

### 3. Manejo de errores
```typescript
// src/api/users/[id].handler.ts
export default {
  GET: async (req, res) => {
    try {
      const user = await db.users.findById(req.params.id);
      if (!user) {
        res.notFound('User not found');
        return;
      }
      res.json(user);
    } catch (err) {
      console.error('Error fetching user:', err);
      res.json({ error: 'Internal Server Error' }, 500);
    },
  },
};
```

### 4. Usar tipos para params
```typescript
// src/api/users/[id].handler.ts
interface UserParams {
  id: string;
}

export default {
  GET: async (req, res) => {
    const params = req.params as UserParams;
    const user = await db.users.findById(params.id);
    res.json(user);
  },
};
```

## Integración con compilador
El router se integra con `@jamx-framework/compiler`:

1. El compilador llama a `router.getRoutes()` para obtener todas las rutas
2. Genera un archivo `AppRoutes` con tipos fuertes
3. Permite navegación type-safe en el cliente

```typescript
// .generated/routes.ts (generado automáticamente)
export const AppRoutes = {
  '/api/users': { method: 'GET' | 'POST' },
  '/api/users/:id': { method: 'GET' | 'PUT' | 'DELETE' },
  '/': { page: true },
  '/about': { page: true },
  '/users/:id': { page: true },
};
```

## Preguntas frecuentes

### ¿Cómo manejar middleware global?
```typescript
server.use(async (req, res, next) => {
  // Logging
  console.log(`${req.method} ${req.path}`);

  // Auth
  const token = req.headers.authorization?.split(' ')[1];
  if (token) {
    req.locals.user = await verifyToken(token);
  }

  next();
});
```

### ¿Cómo manejar 404?
El router lanza `NotFoundException` automáticamente. El servidor debe tener un error handler:

```typescript
server.use(async (err, req, res) => {
  if (err.code === 'ROUTE_NOT_FOUND') {
    res.json({ error: 'Not Found' }, 404);
    return;
  }
  next(err);
});
```

### ¿Puedo tener rutas estáticas (sin handler)?
Sí, usa `server.use(staticFiles)` antes del router:

```typescript
import { staticFiles } from '@jamx-framework/server';

server.use(staticFiles({ root: './public' }));
server.use(router.dispatch.bind(router));
```

### ¿Cómo deshabilitar HMR?
```typescript
const router = new FileRouter({ projectRoot: './' });
// No llamar a invalidate() automáticamente
```

### ¿Puedo pre-cargar handlers?
Sí, llama a `initialize()` al startup:

```typescript
const router = new FileRouter({ projectRoot: './' });
await router.initialize(); // Pre-cargar todas las rutas
```

## Rendimiento
- **Cache de módulos**: Node.js cachea `import()` automáticamente
- **Lazy loading**: Los handlers se cargan solo cuando se solicitan
- **Inicialización una vez**: `initialize()` se llama automáticamente solo la primera vez
- **Matching O(n)**: Busca en orden de registro; para muchas rutas considera un trie

## Comparación con otros routers

| Característica | JAMX Router | Express | Next.js |
|----------------|-------------|---------|---------|
| Enrutamiento | File-based | Code-based | File-based |
| SSR | Sí (con renderer) | No | Sí |
| API routes | Sí | Sí | Sí |
| Parámetros dinámicos | Sí (`:id`) | Sí (`:id`) | Sí (`[id]`) |
| Wildcards | Sí (`*`) | Sí (`*`) | No |
| Middleware | Global | Global + por ruta | Middleware global |
| Hot Reload | Sí (invalidate) | No | Sí |

## Referencia rápida de patrones

| Patrón de archivo | Ruta resultante | Tipo |
|-------------------|-----------------|------|
| `api/users.handler.ts` | `/api/users` | API |
| `api/users/[id].handler.ts` | `/api/users/:id` | API |
| `api/posts/[postId]/comments/[id].handler.ts` | `/api/posts/:postId/comments/:id` | API |
| `pages/index.page.tsx` | `/` | Página |
| `pages/about.page.tsx` | `/about` | Página |
| `pages/users/[id].page.tsx` | `/users/:id` | Página |
| `pages/blog/[year]/[month]/[slug].page.tsx` | `/blog/:year/:month/:slug` | Página |
| `pages/admin/*.page.tsx` | `/admin/*` | Página (wildcard) |

## Archivos importantes
- `src/router.ts` - FileRouter principal
- `src/scanner/file-scanner.ts` - Escaneo de sistema de archivos
- `src/scanner/route-parser.ts` - Conversión de paths a rutas
- `src/matcher/matcher.ts` - Algoritmo de matching
- `src/handler/api-handler.ts` - Ejecutor de API handlers
- `src/handler/page-handler.ts` - Ejecutor de páginas
- `tests/unit/matcher/matcher.test.ts` - Tests de matching
- `tests/unit/scanner/route-parser.test.ts` - Tests de parseo