130 lines
4.4 KiB
Markdown
130 lines
4.4 KiB
Markdown
# Guía 4 — Patrones Arquitectónicos Aprobados
|
|
## Catálogo de patrones de desarrollo seguro y limpio para Next.js App Router
|
|
|
|
> **Propósito:** Esta guía documenta los patrones de código oficiales del proyecto. Cualquier desvío de estos patrones debe estar debidamente motivado y registrado en Engram.
|
|
|
|
---
|
|
|
|
## 1. El Patrón Server-First (Server Components por defecto)
|
|
|
|
En Next.js App Router, todos los componentes de la carpeta `app/` son **React Server Components** por defecto. Esto significa que se renderizan en el servidor, reduciendo el tamaño del bundle de JavaScript en el cliente y protegiendo accesos a base de datos o APIs downstream.
|
|
|
|
### Reglas del Patrón:
|
|
1. **Fetching Directo:** Las peticiones al backend Spring Boot se realizan directamente en la función asíncrona del componente, sin necesidad de usar `useEffect` o estados de React en el cliente.
|
|
2. **Seguridad Integrada:** Se verifica la sesión del usuario al inicio del renderizado usando `serverRequireAuth()`.
|
|
|
|
```typescript
|
|
// app/dashboard/page.tsx
|
|
import { serverRequireAuth } from '@/lib/auth/middleware';
|
|
import { fetchCuentasUsuario } from '@/standards/03-headers-contract';
|
|
|
|
export default async function DashboardPage() {
|
|
// 1. Validar sesión en servidor
|
|
const user = await serverRequireAuth();
|
|
|
|
// 2. Fetching asíncrono seguro
|
|
const cuentas = await fetchCuentasUsuario(user.sessionReference);
|
|
|
|
return (
|
|
<div>
|
|
<h1>Portal de Cuentas de {user.name}</h1>
|
|
<ul className="space-y-4">
|
|
{cuentas.map((cta) => (
|
|
<li key={cta.id} className="p-4 bg-base-100 rounded-box shadow">
|
|
Cuenta: {cta.numero} - Saldo: ${cta.saldo}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. El Patrón de Hidratación de Componentes del Cliente
|
|
|
|
Cuando requerimos interactividad del usuario (clicks, formularios reactivos, modales dinámicos), declaramos un **Client Component** usando la directiva `'use client'` al principio del archivo.
|
|
|
|
### Reglas de Diseño:
|
|
- **Área Limpia:** Mantén el Client Component lo más atómico y libre de fetching posible. Pasa los datos iniciales obtenidos en el Server Component como *Props*.
|
|
- **Estado Global:** Si el componente altera el estado de la sesión, actualiza el store de Zustand.
|
|
|
|
```typescript
|
|
// components/BotonTransferencia.tsx
|
|
'use client';
|
|
import { useState } from 'react';
|
|
import { logInfo } from '@/lib/logger/client-logger';
|
|
import { useAuthStore } from '@/lib/auth/client/useAuthStore';
|
|
|
|
interface BotonProps {
|
|
cuentaOrigenId: string;
|
|
}
|
|
|
|
export default function BotonTransferencia({ cuentaOrigenId }: BotonProps) {
|
|
const [loading, setLoading] = useState(false);
|
|
const { user } = useAuthStore();
|
|
|
|
const handleTransfer = async () => {
|
|
setLoading(true);
|
|
logInfo('Acción de transferencia disparada', { cuentaOrigenId, userId: user?.id });
|
|
|
|
// Simular llamada de API local
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
setLoading(false);
|
|
};
|
|
|
|
return (
|
|
<button
|
|
onClick={handleTransfer}
|
|
disabled={loading}
|
|
className="btn btn-primary"
|
|
>
|
|
{loading ? 'Procesando...' : 'Transferir Saldo'}
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Gobernanza de Estado Cliente con Zustand
|
|
|
|
Evitamos el uso de React Context APIs redundantes o Redux pesado. El estado de la sesión cliente y configuraciones UI se centralizan en Zustand stores ubicados en `lib/auth/client/useAuthStore.ts`.
|
|
|
|
### Reglas del Patrón:
|
|
1. **Reactividad Fina:** Usa selectores en tus componentes para suscribirte solo a las propiedades requeridas (evita re-renders innecesarios).
|
|
2. **Acciones Claras:** Toda lógica que muta el estado debe encapsularse en las acciones del store.
|
|
|
|
```typescript
|
|
// lib/auth/client/useAuthStore.ts
|
|
import { create } from 'zustand';
|
|
|
|
interface User {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
isAuthenticated: boolean;
|
|
login: (user: User) => void;
|
|
logout: () => void;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set) => ({
|
|
user: null,
|
|
isAuthenticated: false,
|
|
login: (user) => set({ user, isAuthenticated: true }),
|
|
logout: () => set({ user: null, isAuthenticated: false }),
|
|
}));
|
|
```
|
|
|
|
---
|
|
|
|
## 4. El Patrón de Logging Proxy para el Cliente
|
|
|
|
Como el cliente no tiene acceso al sistema de archivos local (`logs/app.log`), canalizamos todas las trazas de Client Components mediante el proxy seguro de Next.js `/api/logs`.
|
|
|
|
* Ver `standards/01-log-tracing.md` para el flujo de llamadas y enrutamiento técnico de este patrón.
|