Arquitectura Frontend Completa - Capitalta

Stack Tecnológico

Core Framework

UI y Estilos

State Management

Formularios y Validación

Visualización de Datos

Animaciones

Utilidades

Testing

Code Quality

Deployment y Monitoring


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:

Variables y Funciones:

TypeScript:

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):

Client Components ('use client'):


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):

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

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

  1. Inicializar proyecto con create-next-app
  2. Configurar Shadcn/ui con npx shadcn-ui@latest init
  3. Instalar dependencias del stack
  4. Configurar ESLint, Prettier, Husky
  5. Crear estructura de carpetas base
  6. Implementar componentes UI básicos
  7. Configurar autenticación y middleware
  8. Implementar páginas principales según wireframes

Última actualización: Diciembre 2025