# Regla: Patrones de Arquitectura — Next.js (App Router)

El App Router de Next.js cambia fundamentalmente dónde y cómo se cargan datos.
Estos patrones aprovechan el modelo de Server Components para reducir el JavaScript
enviado al cliente, mejorar el rendimiento y simplificar la lógica de estado.

---

## Server Components: fetch en el servidor

La forma correcta de cargar datos es en el servidor, no en el cliente con `useEffect`.

```tsx
// MAL — useEffect para cargar datos (patrón del Pages Router)
'use client'
export default function PaginaFacturas() {
  const [facturas, setFacturas] = useState<Factura[]>([])
  const [cargando, setCargando] = useState(true)

  useEffect(() => {
    fetch('/api/facturas')
      .then(r => r.json())
      .then(data => { setFacturas(data); setCargando(false) })
  }, [])

  if (cargando) return <p>Cargando...</p>
  return <ListaFacturas facturas={facturas} />
}

// BIEN — Server Component, fetch directo en el servidor
export default async function PaginaFacturas() {
  const facturas = await obtenerFacturas() // llama directamente a la BD o API interna
  return <ListaFacturas facturas={facturas} />
}
```

- `obtenerFacturas()` puede llamar directamente a la BD, a un ORM o a un servicio
  interno. No necesita pasar por una API REST si está en el mismo servidor.
- Next.js deduplica y cachea las llamadas automáticamente dentro del mismo render.

---

## Server Actions para mutaciones

Las mutaciones de datos van en Server Actions, no en Route Handlers cuando
son invocadas desde formularios o componentes del mismo servidor.

```tsx
// app/facturas/actions.ts
'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { z } from 'zod'

const EmitirFacturaSchema = z.object({
  clienteId: z.coerce.number().int().positive(),
})

export async function emitirFactura(formData: FormData) {
  const validado = EmitirFacturaSchema.safeParse({
    clienteId: formData.get('clienteId'),
  })

  if (!validado.success) {
    return { error: validado.error.flatten().fieldErrors }
  }

  await facturaService.emitir(validado.data.clienteId)
  revalidatePath('/facturas')
  redirect('/facturas')
}
```

- Las Server Actions validan sus inputs server-side con zod. NUNCA asumir que
  el cliente envió datos válidos.
- `revalidatePath()` o `revalidateTag()` para invalidar el caché después de mutaciones.

---

## Streaming con Suspense

- Envolver partes lentas del UI en `<Suspense>` para no bloquear el render completo:

```tsx
import { Suspense } from 'react'

export default function Dashboard() {
  return (
    <main>
      <ResumenRapido />                             {/* datos rápidos, sin Suspense */}
      <Suspense fallback={<EsqueletoGrafica />}>
        <GraficaVentasMensuales />                  {/* query lenta */}
      </Suspense>
      <Suspense fallback={<EsqueletoTabla />}>
        <TablaTopClientes />                        {/* query lenta independiente */}
      </Suspense>
    </main>
  )
}
```

- Cada `<Suspense>` permite que los demás componentes se rendericen sin esperar.

---

## ISR para contenido semi-estático

```tsx
// Revalidar cada hora — ideal para catálogos de productos, precios, etc.
export const revalidate = 3600

// Revalidar bajo demanda con tag
fetch(url, { next: { tags: ['catalogo-productos'] } })

// En una Server Action o Route Handler:
revalidateTag('catalogo-productos')
```

- Usar ISR para contenido que cambia con menos frecuencia que las visitas.
- No usar ISR para datos que deben estar en tiempo real (inventario en compra,
  saldo bancario). Para esos casos, `cache: 'no-store'`.

---

## Parallel Routes para dashboards

```
app/dashboard/
  @resumen/
    page.tsx
  @alertas/
    page.tsx
  @actividad/
    page.tsx
  layout.tsx    ← recibe los tres slots en paralelo
```

```tsx
// layout.tsx
export default function DashboardLayout({
  resumen, alertas, actividad
}: {
  resumen: React.ReactNode
  alertas: React.ReactNode
  actividad: React.ReactNode
}) {
  return (
    <div className="grid grid-cols-3 gap-4">
      {resumen}
      {alertas}
      {actividad}
    </div>
  )
}
```

---

## Intercepting Routes para modales

- Para modales que muestran contenido de otra ruta sin navegar completamente:

```
app/
  facturas/
    page.tsx
    [id]/
      page.tsx        ← vista completa de la factura
  @modal/
    (.)facturas/[id]/
      page.tsx        ← modal al hacer clic en la lista
```

---

## Route Handlers para API endpoints

- Usar `route.ts` para endpoints que son consumidos por clientes externos, móvil,
  o por webhooks. No para datos internos del mismo servidor.

```ts
// app/api/facturas/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const pagina = Number(searchParams.get('pagina') ?? '1')

  const facturas = await obtenerFacturasPaginadas(pagina)
  return Response.json(facturas)
}
```

---

## Middleware para auth, redirects, i18n

```ts
// middleware.ts (raíz del proyecto)
export function middleware(request: NextRequest) {
  const token = request.cookies.get('session')?.value

  if (!token && request.nextUrl.pathname.startsWith('/app')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
}

export const config = {
  matcher: ['/app/:path*', '/api/:path*'],
}
```

- El middleware solo lee cookies y headers. No llama a la BD directamente.
- Para verificación de sesión pesada, usar la función `auth()` del framework
  de autenticación dentro del Server Component, no en el middleware.

---

## Checklist de patrones antes de merge

- [ ] Sin `useEffect` para carga de datos que pueden ir en Server Component
- [ ] Mutaciones en Server Actions con validación zod server-side
- [ ] Secciones lentas del dashboard envueltas en `<Suspense>`
- [ ] `revalidatePath` o `revalidateTag` después de toda mutación
- [ ] Route Handlers solo para APIs externas, no para datos internos
- [ ] Middleware sin llamadas a BD directas
