Arquitectura Frontend Completa - Capitalta
Stack Tecnológico
Core Framework
- Next.js 14.2+ con App Router
- React 18.3+ con Server Components
- TypeScript 5.3+ con strict mode
- Node.js 20 LTS
UI y Estilos
- Shadcn/ui (componentes copiables)
- Tailwind CSS 3.4+ (utility-first CSS)
- Radix UI (primitivos de accesibilidad)
- Lucide React (iconografía)
- class-variance-authority (variantes de componentes)
- tailwind-merge + clsx (merge de clases)
State Management
- Zustand 4.5+ (state global ligero)
- React Query (TanStack Query) 5.x (server state, cache, mutations)
- React Context (temas, auth, configuración)
Formularios y Validación
- React Hook Form 7.x (performance optimizado)
- Zod 3.x (validación type-safe)
- @hookform/resolvers (integración RHF + Zod)
Visualización de Datos
- Recharts 2.x (gráficos para dashboard financiero)
- TanStack Table 8.x (tablas complejas con sorting, filtering, pagination)
- react-chartjs-2 (alternativa para gráficos específicos)
Animaciones
- Framer Motion 11.x (animaciones complejas)
- Tailwind CSS animations (animaciones simples)
Utilidades
- date-fns 3.x (manejo de fechas)
- axios 1.x (HTTP client)
- sonner (toast notifications)
- react-hot-toast (alternativa para notificaciones)
- usehooks-ts (hooks utilitarios)
Testing
- Vitest (unit tests, más rápido que Jest)
- Playwright (e2e tests)
- Testing Library (component tests)
- MSW (Mock Service Worker) (mocking de APIs)
Code Quality
- ESLint 8.x con configuración Next.js
- Prettier 3.x (formateo de código)
- Husky (git hooks)
- lint-staged (linting pre-commit)
- TypeScript strict mode (type safety máximo)
Deployment y Monitoring
- Vercel (hosting optimizado para Next.js)
- Sentry (error tracking)
- Vercel Analytics (web vitals)
- PostHog (product analytics)
Estructura de Carpetas
capitalta/
├── app/ # Next.js 14 App Router
│ ├── (auth)/ # Grupo de rutas con layout de auth
│ │ ├── login/
│ │ │ └── page.tsx
│ │ ├── registro/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── (dashboard)/ # Grupo de rutas con layout de dashboard
│ │ ├── dashboard/
│ │ │ ├── page.tsx # Overview
│ │ │ ├── pipeline/
│ │ │ ├── cartera/
│ │ │ ├── reportes/
│ │ │ └── configuracion/
│ │ └── layout.tsx
│ ├── (marketing)/ # Grupo de rutas con layout de marketing
│ │ ├── page.tsx # Landing page
│ │ ├── productos/
│ │ │ ├── page.tsx
│ │ │ ├── [slug]/
│ │ │ │ └── page.tsx
│ │ ├── proceso/
│ │ ├── requisitos/
│ │ ├── sobre/
│ │ ├── faq/
│ │ ├── contacto/
│ │ └── layout.tsx
│ ├── solicitud/ # Formularios de solicitud
│ │ ├── persona-fisica/
│ │ │ └── page.tsx
│ │ ├── persona-moral/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── legal/ # Páginas legales
│ │ ├── privacidad/
│ │ ├── terminos/
│ │ └── sofom-enr/
│ ├── api/ # API Routes
│ │ ├── auth/
│ │ ├── solicitudes/
│ │ ├── cfdi/
│ │ └── webhooks/
│ ├── layout.tsx # Root layout
│ ├── loading.tsx # Loading UI
│ ├── error.tsx # Error boundary
│ └── not-found.tsx # 404 page
├── components/ # Componentes React
│ ├── ui/ # Componentes Shadcn/ui
│ │ ├── button.tsx
│ │ ├── input.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── toast.tsx
│ │ ├── select.tsx
│ │ ├── checkbox.tsx
│ │ ├── radio-group.tsx
│ │ ├── textarea.tsx
│ │ ├── progress.tsx
│ │ ├── badge.tsx
│ │ ├── alert.tsx
│ │ ├── tooltip.tsx
│ │ ├── drawer.tsx
│ │ ├── breadcrumb.tsx
│ │ └── pagination.tsx
│ ├── layout/ # Componentes de layout
│ │ ├── header.tsx
│ │ ├── footer.tsx
│ │ ├── sidebar.tsx
│ │ ├── navigation.tsx
│ │ └── mobile-nav.tsx
│ ├── forms/ # Componentes de formularios
│ │ ├── solicitud-pf-form.tsx
│ │ ├── solicitud-pm-form.tsx
│ │ ├── contacto-form.tsx
│ │ ├── multi-step-form.tsx
│ │ └── file-upload.tsx
│ ├── dashboard/ # Componentes del dashboard
│ │ ├── kpi-card.tsx
│ │ ├── chart-wrapper.tsx
│ │ ├── solicitud-table.tsx
│ │ ├── cartera-overview.tsx
│ │ └── pipeline-kanban.tsx
│ ├── marketing/ # Componentes de marketing
│ │ ├── hero-section.tsx
│ │ ├── features-grid.tsx
│ │ ├── testimonials.tsx
│ │ ├── cta-section.tsx
│ │ ├── pricing-cards.tsx
│ │ └── faq-accordion.tsx
│ ├── shared/ # Componentes compartidos
│ │ ├── logo.tsx
│ │ ├── loading-spinner.tsx
│ │ ├── empty-state.tsx
│ │ ├── error-message.tsx
│ │ └── status-badge.tsx
│ └── providers/ # Context providers
│ ├── auth-provider.tsx
│ ├── theme-provider.tsx
│ └── query-provider.tsx
├── lib/ # Utilidades y configuración
│ ├── api/ # API client y configuración
│ │ ├── client.ts
│ │ ├── endpoints.ts
│ │ └── types.ts
│ ├── auth/ # Lógica de autenticación
│ │ ├── session.ts
│ │ ├── middleware.ts
│ │ └── utils.ts
│ ├── validations/ # Esquemas de validación Zod
│ │ ├── solicitud.ts
│ │ ├── auth.ts
│ │ └── common.ts
│ ├── utils/ # Funciones utilitarias
│ │ ├── cn.ts # classnames merge
│ │ ├── format.ts # formateo de datos
│ │ ├── date.ts # utilidades de fechas
│ │ └── currency.ts # formateo de moneda
│ └── constants/ # Constantes de la aplicación
│ ├── routes.ts
│ ├── config.ts
│ └── messages.ts
├── hooks/ # Custom React hooks
│ ├── use-auth.ts
│ ├── use-solicitud.ts
│ ├── use-cfdi.ts
│ ├── use-debounce.ts
│ ├── use-media-query.ts
│ └── use-toast.ts
├── store/ # Zustand stores
│ ├── auth-store.ts
│ ├── solicitud-store.ts
│ └── ui-store.ts
├── types/ # TypeScript types y interfaces
│ ├── api.ts
│ ├── solicitud.ts
│ ├── user.ts
│ └── dashboard.ts
├── styles/ # Estilos globales
│ └── globals.css
├── public/ # Assets estáticos
│ ├── images/
│ ├── icons/
│ └── fonts/
├── config/ # Archivos de configuración
│ ├── site.ts # Metadata del sitio
│ └── navigation.ts # Configuración de navegación
├── middleware.ts # Next.js middleware
├── next.config.js
├── tailwind.config.ts
├── tsconfig.json
├── components.json # Shadcn/ui config
├── .env.local
└── package.json
Convenciones de Código
Nomenclatura
Archivos y Carpetas:
- Componentes:
PascalCase(ej:HeroSection.tsx) - Utilidades:
kebab-case(ej:format-currency.ts) - Hooks:
use-prefix (ej:use-auth.ts) - Types:
PascalCase(ej:SolicitudType.ts)
Variables y Funciones:
- Variables:
camelCase(ej:userName) - Constantes:
UPPER_SNAKE_CASE(ej:API_BASE_URL) - Funciones:
camelCase(ej:handleSubmit) - Componentes:
PascalCase(ej:Button)
TypeScript:
- Interfaces:
Iprefix (ej:IUser) - Types:
Tprefix (ej:TResponse) - Enums:
Eprefix (ej:EStatus)
Estructura de Componentes
// Imports
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils/cn';
// Types
interface IMyComponentProps {
title: string;
onSubmit: () => void;
className?: string;
}
// Component
export function MyComponent({ title, onSubmit, className }: IMyComponentProps) {
// Hooks
const [isLoading, setIsLoading] = useState(false);
// Handlers
const handleClick = () => {
setIsLoading(true);
onSubmit();
};
// Render
return (
<div className={cn('container', className)}>
<h1>{title}</h1>
<Button onClick={handleClick} disabled={isLoading}>
Submit
</Button>
</div>
);
}
Server Components vs Client Components
Server Components (por defecto):
- Páginas que no necesitan interactividad
- Componentes que solo muestran datos
- Componentes que hacen fetch de datos
Client Components ('use client'):
- Componentes con estado (useState, useReducer)
- Componentes con efectos (useEffect)
- Componentes con event handlers
- Componentes que usan browser APIs
- Componentes que usan Context
State Management
Zustand para State Global
// store/auth-store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface IAuthState {
user: IUser | null;
token: string | null;
isAuthenticated: boolean;
login: (user: IUser, token: string) => void;
logout: () => void;
}
export const useAuthStore = create<IAuthState>()(
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
login: (user, token) => set({ user, token, isAuthenticated: true }),
logout: () => set({ user: null, token: null, isAuthenticated: false }),
}),
{
name: 'auth-storage',
}
)
);
React Query para Server State
// hooks/use-solicitud.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api/client';
export function useSolicitud(id: string) {
return useQuery({
queryKey: ['solicitud', id],
queryFn: () => api.solicitudes.getById(id),
staleTime: 5 * 60 * 1000, // 5 minutos
});
}
export function useCreateSolicitud() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: ISolicitudCreate) => api.solicitudes.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['solicitudes'] });
},
});
}
Routing y Navegación
App Router con Grupos de Rutas
Grupos de Rutas (Route Groups):
(auth): Login, registro, recuperación de contraseña(marketing): Landing, productos, FAQ, contacto(dashboard): Panel de administración y operacionessolicitud: Formularios de solicitud (sin layout específico)
Middleware para Protección de Rutas:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
const isAuthPage = request.nextUrl.pathname.startsWith('/login');
const isDashboard = request.nextUrl.pathname.startsWith('/dashboard');
// Redirigir a login si intenta acceder a dashboard sin auth
if (isDashboard && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Redirigir a dashboard si ya está autenticado e intenta ir a login
if (isAuthPage && token) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
Data Fetching
Server Components (Recomendado)
// app/(marketing)/productos/page.tsx
import { api } from '@/lib/api/client';
export default async function ProductosPage() {
// Fetch en el servidor
const productos = await api.productos.getAll();
return (
<div>
{productos.map((producto) => (
<ProductCard key={producto.id} producto={producto} />
))}
</div>
);
}
Client Components con React Query
// components/dashboard/solicitud-table.tsx
'use client';
import { useSolicitudes } from '@/hooks/use-solicitudes';
export function SolicitudTable() {
const { data, isLoading, error } = useSolicitudes();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return <Table data={data} />;
}
Manejo de Errores
Error Boundaries
// app/error.tsx
'use client';
import { useEffect } from 'react';
import { Button } from '@/components/ui/button';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log error a servicio de monitoring (Sentry)
console.error(error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold mb-4">Algo salió mal</h2>
<Button onClick={reset}>Intentar de nuevo</Button>
</div>
);
}
API Error Handling
// lib/api/client.ts
import axios from 'axios';
const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
timeout: 10000,
});
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Redirigir a login
window.location.href = '/login';
}
return Promise.reject(error);
}
);
Performance Optimization
Code Splitting
// Lazy loading de componentes pesados
import dynamic from 'next/dynamic';
const ChartComponent = dynamic(() => import('@/components/dashboard/chart'), {
loading: () => <LoadingSpinner />,
ssr: false, // No renderizar en servidor si usa browser APIs
});
Image Optimization
import Image from 'next/image';
<Image
src="/images/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // Para imágenes above the fold
placeholder="blur"
/>
Font Optimization
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
export default function RootLayout({ children }) {
return (
<html lang="es" className={inter.variable}>
<body>{children}</body>
</html>
);
}
Accesibilidad (a11y)
Principios WCAG 2.1 AA
- Contraste de color: Mínimo 4.5:1 para texto normal, 3:1 para texto grande
- Navegación por teclado: Todos los elementos interactivos accesibles con Tab
- ARIA labels: Usar aria-label, aria-labelledby, aria-describedby
- Focus visible: Estados de focus claramente visibles
- Semántica HTML: Usar elementos semánticos correctos
Implementación
<button
aria-label="Cerrar modal"
aria-describedby="modal-description"
onClick={handleClose}
>
<X className="h-4 w-4" />
</button>
Testing Strategy
Unit Tests con Vitest
// components/ui/button.test.tsx
import { render, screen } from '@testing-library/react';
import { Button } from './button';
describe('Button', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('handles click events', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click me</Button>);
screen.getByText('Click me').click();
expect(handleClick).toHaveBeenCalledOnce();
});
});
E2E Tests con Playwright
// tests/e2e/solicitud.spec.ts
import { test, expect } from '@playwright/test';
test('usuario puede crear solicitud', async ({ page }) => {
await page.goto('/solicitud/persona-fisica');
await page.fill('[name="nombre"]', 'Juan Pérez');
await page.fill('[name="rfc"]', 'PEXJ800101XXX');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/solicitud/exito');
});
Environment Variables
# .env.local
# API
NEXT_PUBLIC_API_URL=https://api.capitalta.mx
API_SECRET_KEY=your_secret_key
# Auth
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your_nextauth_secret
# External Services
NEXT_PUBLIC_SENTRY_DSN=your_sentry_dsn
NEXT_PUBLIC_POSTHOG_KEY=your_posthog_key
# Feature Flags
NEXT_PUBLIC_ENABLE_CFDI=true
NEXT_PUBLIC_ENABLE_AVM=true
Build y Deployment
Scripts de Package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write .",
"type-check": "tsc --noEmit",
"test": "vitest",
"test:e2e": "playwright test",
"prepare": "husky install"
}
}
Vercel Configuration
{
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install",
"framework": "nextjs",
"outputDirectory": ".next"
}
Próximos Pasos
- Inicializar proyecto con
create-next-app - Configurar Shadcn/ui con
npx shadcn-ui@latest init - Instalar dependencias del stack
- Configurar ESLint, Prettier, Husky
- Crear estructura de carpetas base
- Implementar componentes UI básicos
- Configurar autenticación y middleware
- Implementar páginas principales según wireframes
Última actualización: Diciembre 2025