# Frontend de Quasar 🎮

## Visión general

El frontend de Quasar es una Single Page Application (SPA) moderna construida con React + Vite, utilizando shadcn/ui para componentes UI y Tailwind CSS para estilos. La aplicación está diseñada con una estética de modo oscuro sofisticada, priorizando la accesibilidad, la responsividad y un excelente rendimiento.

### Stack tecnológico

| Categoría      | Tecnología      | Versión  | Propósito                          |
| -------------- | --------------- | -------- | ---------------------------------- |
| Framework      | React           | ^18.3.0  | Framework base                     |
| Build tool     | Vite            | ^5.0.0   | Build tool y dev server            |
| Lenguaje       | TypeScript      | ^5.3.0   | Type safety                        |
| Estado         | TanStack Query  | ^5.0.0   | Gestión de estado y cache de datos |
| Routing        | TanStack Router | ^1.0.0   | Routing con carga de datos         |
| Formularios    | TanStack Form   | ^0.0.0   | Manejo de formularios              |
| Validación     | Zod             | ^3.0.0   | Validación de esquemas             |
| Componentes UI | shadcn/ui       | latest   | Componentes UI reutilizables       |
| Estilos        | Tailwind CSS    | ^3.4.0   | Framework de estilos               |
| Iconos         | lucide-react    | ^0.300.0 | Iconos                             |
| HTTP           | axios           | latest   | Cliente HTTP                       |
| Fechas         | date-fns        | latest   | Manipulación de fechas             |
| Utils          | clsx            | ^2.0.0   | Utilidad de clases condicionales   |
| Utils          | tailwind-merge  | ^2.0.0   | Fusión de clases Tailwind          |

## Arquitectura del sistema

### Diagrama de arquitectura

```mermaid
graph TB
    subgraph Browser
        UI[Componentes UI]
        Router[TanStack Router]
        Query[TanStack Query]
        Forms[TanStack Form + Zod]
    end

    subgraph Services
        API[API Client]
        Auth[Auth Service]
    end

    UI --> Router
    UI --> Query
    UI --> Forms
    Router --> Query
    Query --> API
    Forms --> Query

    API --> Backend[Backend API]

    style UI fill:#1e293b
    style Router fill:#334155
    style Query fill:#334155
    style Forms fill:#334155
    style API fill:#475569
    style Backend fill:#64748b
```

### Estructura de carpetas

```
frontend/
├── src/
│   ├── components/
│   │   ├── ui/              # Componentes shadcn/ui
│   │   ├── layout/          # Componentes de layout
│   │   ├── games/           # Componentes específicos de juegos
│   │   ├── roms/            # Componentes específicos de ROMs
│   │   ├── shared/          # Componentes compartidos
│   │   └── ...              # Otros componentes específicos
│   ├── pages/               # Páginas de la aplicación
│   │   ├── DashboardPage.tsx
│   │   ├── GamesPage.tsx
│   │   ├── GameDetailPage.tsx
│   │   ├── GamesNewPage.tsx
│   │   ├── ImportPage.tsx
│   │   ├── PlatformsPage.tsx
│   │   ├── TagsPage.tsx
│   │   ├── SettingsPage.tsx
│   │   └── ExportPage.tsx
│   ├── api/                 # Servicios de API y tipos
│   │   ├── client.ts        # Cliente HTTP base
│   │   ├── games.ts         # Servicios de juegos
│   │   ├── platforms.ts     # Servicios de plataformas
│   │   ├── tags.ts          # Servicios de etiquetas
│   │   ├── import.ts        # Servicios de importación
│   │   ├── settings.ts      # Servicios de configuración
│   │   └── types.ts         # Tipos de API
│   ├── query/               # Configuración de TanStack Query
│   │   └── client.tsx       # Cliente de Query
│   ├── form/                # Configuración de TanStack Form
│   │   └── config.tsx        # Configuración de Form + Zod
│   ├── router/              # Configuración de TanStack Router
│   │   └── router.tsx       # Configuración del router
│   ├── types/               # Definiciones TypeScript
│   │   └── index.ts         # Tipos globales
│   ├── hooks/               # Custom hooks
│   ├── lib/                 # Utilidades y configuraciones
│   │   ├── utils.ts         # Utilidades generales
│   │   └── ...              # Otras utilidades
│   ├── styles/              # Estilos globales
│   │   └── globals.css      # CSS global con variables
│   └── layout/              # Componentes de layout
│       ├── Header.tsx       # Header principal
│       ├── Sidebar.tsx      # Sidebar de navegación
│       └── Layout.tsx       # Layout principal
├── public/                  # Assets estáticos
├── index.html              # HTML entry point
├── package.json            # Dependencias y scripts
├── tsconfig.json           # Configuración TypeScript
├── vite.config.ts          # Configuración Vite
├── tailwind.config.ts      # Configuración Tailwind
└── postcss.config.js       # Configuración PostCSS
```

## Sistema de diseño

### Principios de diseño

1. **Modo oscuro por defecto**: La aplicación está diseñada para funcionar principalmente en modo oscuro
2. **Alto contraste**: Asegurar legibilidad excelente en todos los contextos
3. **Consistencia**: Componentes y patrones consistentes en toda la aplicación
4. **Accesibilidad**: Cumplimiento con WCAG AA
5. **Performance**: Componentes optimizados para rendimiento
6. **Responsividad**: Mobile-first con breakpoints claros

### Paleta de colores (Modo oscuro)

#### Colores semánticos

```css
/* Primary - Violet (Acción principal) */
--primary-50: #f5f3ff;
--primary-100: #ede9fe;
--primary-200: #ddd6fe;
--primary-300: #c4b5fd;
--primary-400: #a78bfa;
--primary-500: #8b5cf6;
--primary-600: #7c3aed; /* Color principal */
--primary-700: #6d28d9;
--primary-800: #5b21b6;
--primary-900: #4c1d95;
--primary-950: #2e1065;

/* Secondary - Slate (Acciones secundarias) */
--secondary-50: #f8fafc;
--secondary-100: #f1f5f9;
--secondary-200: #e2e8f0;
--secondary-300: #cbd5e1;
--secondary-400: #94a3b8;
--secondary-500: #64748b;
--secondary-600: #475569;
--secondary-700: #334155;
--secondary-800: #1e293b;
--secondary-900: #0f172a;
--secondary-950: #020617;

/* Success - Emerald (Estados positivos) */
--success-50: #ecfdf5;
--success-100: #d1fae5;
--success-200: #a7f3d0;
--success-300: #6ee7b7;
--success-400: #34d399;
--success-500: #10b981;
--success-600: #059669;
--success-700: #047857;
--success-800: #065f46;
--success-900: #064e3b;
--success-950: #022c22;

/* Warning - Amber (Estados de advertencia) */
--warning-50: #fffbeb;
--warning-100: #fef3c7;
--warning-200: #fde68a;
--warning-300: #fcd34d;
--warning-400: #fbbf24;
--warning-500: #f59e0b;
--warning-600: #d97706;
--warning-700: #b45309;
--warning-800: #92400e;
--warning-900: #78350f;
--warning-950: #451a03;

/* Error - Red (Estados de error) */
--error-50: #fef2f2;
--error-100: #fee2e2;
--error-200: #fecaca;
--error-300: #fca5a5;
--error-400: #f87171;
--error-500: #ef4444;
--error-600: #dc2626;
--error-700: #b91c1c;
--error-800: #991b1b;
--error-900: #7f1d1d;
--error-950: #450a0a;

/* Info - Sky (Información) */
--info-50: #f0f9ff;
--info-100: #e0f2fe;
--info-200: #bae6fd;
--info-300: #7dd3fc;
--info-400: #38bdf8;
--info-500: #0ea5e9;
--info-600: #0284c7;
--info-700: #0369a1;
--info-800: #075985;
--info-900: #0c4a6e;
--info-950: #082f49;
```

#### Uso de colores

| Propósito      | Color      | Hex     |
| -------------- | ---------- | ------- |
| Background     | Slate-950  | #020617 |
| Card           | Slate-950  | #020617 |
| Card hover     | Slate-900  | #0f172a |
| Border         | Slate-800  | #1e293b |
| Primary action | Violet-600 | #7c3aed |
| Primary hover  | Violet-700 | #6d28d9 |
| Text primary   | Slate-50   | #f8fafc |
| Text secondary | Slate-400  | #94a3b8 |
| Text muted     | Slate-500  | #64748b |

### Tipografía

#### Font family

```css
font-family:
  'Inter',
  system-ui,
  -apple-system,
  sans-serif;
```

#### Escala de tamaños

| Token     | Tamaño | Line-height | Uso                       |
| --------- | ------ | ----------- | ------------------------- |
| text-xs   | 12px   | 16px        | Labels pequeños, captions |
| text-sm   | 14px   | 20px        | Texto secundario          |
| text-base | 16px   | 24px        | Texto de cuerpo           |
| text-lg   | 18px   | 28px        | Texto de cuerpo grande    |
| text-xl   | 20px   | 28px        | Subtítulos                |
| text-2xl  | 24px   | 32px        | Títulos pequeños          |
| text-3xl  | 30px   | 40px        | Títulos medianos          |
| text-4xl  | 36px   | 44px        | Títulos grandes           |
| text-5xl  | 48px   | 56px        | Display                   |

#### Font weights

| Token         | Weight | Uso              |
| ------------- | ------ | ---------------- |
| font-normal   | 400    | Texto de cuerpo  |
| font-medium   | 500    | Texto de énfasis |
| font-semibold | 600    | Subtítulos       |
| font-bold     | 700    | Títulos          |

### Espaciado

#### Escala de espaciado (base 4px)

| Token      | Valor | Uso                          |
| ---------- | ----- | ---------------------------- |
| p-0, m-0   | 0px   | Sin espaciado                |
| p-1, m-1   | 4px   | Micro espaciado              |
| p-2, m-2   | 8px   | Espaciado pequeño            |
| p-3, m-3   | 12px  | Espaciado medio-pequeño      |
| p-4, m-4   | 16px  | Espaciado medio              |
| p-5, m-5   | 20px  | Espaciado medio-grande       |
| p-6, m-6   | 24px  | Espaciado grande             |
| p-8, m-8   | 32px  | Espaciado extra grande       |
| p-10, m-10 | 40px  | Espaciado extra extra grande |
| p-12, m-12 | 48px  | Espaciado masivo             |

## Rutas de la aplicación

### Diagrama de rutas

```mermaid
graph LR
    A[/] --> B[Dashboard]
    C[/games] --> D[Lista de juegos]
    E[/games/:id] --> F[Detalle de juego]
    G[/games/new] --> H[Crear juego]
    I[/import] --> J[Importar ROMs]
    K[/platforms] --> L[Gestión de plataformas]
    M[/tags] --> N[Gestión de etiquetas]
    O[/settings] --> P[Configuración]
    Q[/export] --> R[Exportar/Importar]

    style A fill:#7c3aed
    style C fill:#7c3aed
    style E fill:#7c3aed
    style G fill:#7c3aed
    style I fill:#7c3aed
    style K fill:#7c3aed
    style M fill:#7c3aed
    style O fill:#7c3aed
    style Q fill:#7c3aed
```

### Descripción de rutas

| Ruta         | Componente     | Descripción                                             |
| ------------ | -------------- | ------------------------------------------------------- |
| `/`          | Dashboard      | Vista general con estadísticas y juegos recientes       |
| `/games`     | GamesPage      | Lista paginada de juegos con filtros y búsqueda         |
| `/games/:id` | GameDetailPage | Vista detallada de un juego con metadata, ROMs, compras |
| `/games/new` | GamesNewPage   | Formulario para crear un nuevo juego                    |
| `/import`    | ImportPage     | Interfaz para escanear e importar ROMs locales          |
| `/platforms` | PlatformsPage  | Gestión de plataformas                                  |
| `/tags`      | TagsPage       | Gestión de etiquetas                                    |
| `/settings`  | SettingsPage   | Configuración de la aplicación                          |
| `/export`    | ExportPage     | Exportar/importar colección                             |

## Componentes UI (shadcn/ui)

### Componentes base

| Componente   | Uso principal                      |
| ------------ | ---------------------------------- |
| Button       | Acciones principales y secundarias |
| Card         | Contenedores de contenido          |
| Input        | Entradas de texto                  |
| Select       | Selección de opciones              |
| Dialog       | Modales y diálogos                 |
| Table        | Tablas de datos                    |
| Badge        | Etiquetas y estados                |
| Avatar       | Avatares de usuario                |
| DropdownMenu | Menús desplegables                 |
| Tabs         | Navegación por pestañas            |
| Form         | Formularios                        |
| Separator    | Separadores visuales               |
| ScrollArea   | Áreas con scroll                   |
| Tooltip      | Información contextual             |
| Skeleton     | Estados de carga                   |
| AlertDialog  | Confirmaciones                     |
| Toast        | Notificaciones                     |

### Variantes y tamaños

#### Button

**Variantes:**

- `default`: Acción principal (violet)
- `destructive`: Acción destructiva (rojo)
- `outline`: Acción secundaria con borde
- `secondary`: Acción secundaria (slate)
- `ghost`: Acción sutil sin borde
- `link`: Estilo de enlace

**Tamaños:**

- `sm`: 32px de altura
- `default`: 40px de altura
- `lg`: 48px de altura
- `icon`: 32px x 32px (cuadrado)

#### Card

```tsx
<Card>
  <CardHeader>
    <CardTitle>Título</CardTitle>
    <CardDescription>Descripción opcional</CardDescription>
  </CardHeader>
  <CardContent>Contenido principal</CardContent>
  <CardFooter>Acciones</CardFooter>
</Card>
```

#### Badge

**Variantes:**

- `default`: Violet
- `secondary`: Slate
- `destructive`: Rojo
- `outline`: Borde solo
- `success`: Verde
- `warning`: Amarillo

## Componentes específicos

### Componentes de Juegos

#### GameCard

Componente de tarjeta para mostrar un juego en una lista o grid.

```typescript
interface GameCardProps {
  game: Game;
  onClick?: () => void;
  onEdit?: () => void;
  onDelete?: () => void;
}
```

**Estructura:**

```tsx
<Card className="group hover:border-primary/50 transition-colors cursor-pointer">
  <CardHeader className="pb-3">
    <div className="flex items-start justify-between gap-3">
      <div className="flex items-center gap-3">
        <GameCover gameId={game.id} className="h-16 w-12 rounded" />
        <div className="min-w-0">
          <CardTitle className="line-clamp-1">{game.title}</CardTitle>
          <CardDescription className="line-clamp-1">
            {game.platforms.map((p) => p.name).join(', ')}
          </CardDescription>
        </div>
      </div>
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          <Button variant="ghost" size="icon" className="opacity-0 group-hover:opacity-100">
            <MoreHorizontal className="h-4 w-4" />
          </Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end">
          <DropdownMenuItem onClick={onEdit}>
            <Edit className="mr-2 h-4 w-4" />
            Editar
          </DropdownMenuItem>
          <DropdownMenuSeparator />
          <DropdownMenuItem onClick={onDelete} className="text-destructive">
            <Trash2 className="mr-2 h-4 w-4" />
            Eliminar
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  </CardHeader>
  <CardContent className="pb-3">
    <div className="flex flex-wrap gap-2">
      {game.tags.slice(0, 3).map((tag) => (
        <Badge key={tag.id} variant="secondary" className="text-xs">
          {tag.name}
        </Badge>
      ))}
      {game.tags.length > 3 && (
        <Badge variant="secondary" className="text-xs">
          +{game.tags.length - 3}
        </Badge>
      )}
    </div>
  </CardContent>
  <CardFooter className="pt-0">
    <div className="flex items-center gap-4 text-xs text-muted-foreground">
      <div className="flex items-center gap-1">
        <HardDrive className="h-3 w-3" />
        {game.romFiles.length} ROMs
      </div>
      {game.releaseDate && (
        <div className="flex items-center gap-1">
          <Calendar className="h-3 w-3" />
          {formatDate(game.releaseDate)}
        </div>
      )}
    </div>
  </CardFooter>
</Card>
```

#### GameList

Componente de lista de juegos con filtros y paginación.

```typescript
interface GameListProps {
  filters?: GameFilters;
  onFiltersChange?: (filters: GameFilters) => void;
}
```

**Estructura:**

```tsx
<div className="space-y-6">
  <GameFilters filters={filters} onChange={onFiltersChange} />
  <GameGrid games={games} isLoading={isLoading} />
  <GamePagination pagination={pagination} onPageChange={onPageChange} />
</div>
```

#### GameDetail

Página de detalle de un juego.

```tsx
<div className="space-y-6">
  <GameDetailHeader game={game} />
  <Tabs defaultValue="overview">
    <TabsList>
      <TabsTrigger value="overview">Resumen</TabsTrigger>
      <TabsTrigger value="roms">ROMs</TabsTrigger>
      <TabsTrigger value="purchases">Compras</TabsTrigger>
      <TabsTrigger value="metadata">Metadata</TabsTrigger>
    </TabsList>
    <TabsContent value="overview">
      <GameOverview game={game} />
    </TabsContent>
    <TabsContent value="roms">
      <GameRoms game={game} />
    </TabsContent>
    <TabsContent value="purchases">
      <GamePurchases game={game} />
    </TabsContent>
    <TabsContent value="metadata">
      <GameMetadata game={game} />
    </TabsContent>
  </Tabs>
</div>
```

### Componentes de ROMs

#### RomFileCard

Componente de tarjeta para mostrar un archivo ROM.

```typescript
interface RomFileCardProps {
  rom: RomFile;
  game?: Game;
  onLink?: () => void;
  onUnlink?: () => void;
}
```

#### ImportRomWizard

Wizard para importar ROMs desde el sistema de archivos.

**Pasos del wizard:**

1. **Configurar rutas**: Seleccionar directorios para escanear
2. **Escanear**: Ejecutar escaneo de archivos
3. **Revisar**: Revisar resultados y detectar duplicados
4. **Importar**: Importar ROMs seleccionadas

### Componentes de Metadata

#### MetadataSearch

Componente para buscar metadata de APIs externas (IGDB, RAWG).

```typescript
interface MetadataSearchProps {
  onSelect: (metadata: ExternalMetadata) => void;
  game?: Game;
}
```

**Estructura:**

```tsx
<div className="space-y-4">
  <div className="flex gap-2">
    <Input
      placeholder="Buscar en IGDB, RAWG..."
      value={searchQuery}
      onChange={(e) => setSearchQuery(e.target.value)}
    />
    <Button onClick={handleSearch}>
      <Search className="mr-2 h-4 w-4" />
      Buscar
    </Button>
  </div>
  {isLoading && <MetadataSearchResultsSkeleton />}
  {results && (
    <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
      {results.map((result) => (
        <MetadataResultCard key={result.id} result={result} onSelect={() => onSelect(result)} />
      ))}
    </div>
  )}
</div>
```

### Componentes de Dashboard

#### DashboardStats

Tarjetas de estadísticas del dashboard.

```typescript
interface DashboardStatsProps {
  stats: {
    totalGames: number;
    totalRoms: number;
    totalPlatforms: number;
    recentGames: number;
  };
}
```

**Estructura:**

```tsx
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
  <StatCard
    title="Total de juegos"
    value={stats.totalGames}
    icon={<Gamepad2 className="h-4 w-4" />}
  />
  <StatCard
    title="Total de ROMs"
    value={stats.totalRoms}
    icon={<HardDrive className="h-4 w-4" />}
  />
  <StatCard
    title="Plataformas"
    value={stats.totalPlatforms}
    icon={<Monitor className="h-4 w-4" />}
  />
  <StatCard
    title="Juegos recientes"
    value={stats.recentGames}
    icon={<Clock className="h-4 w-4" />}
  />
</div>
```

## Layout Components

### AppLayout

Layout principal de la aplicación.

```typescript
interface AppLayoutProps {
  children: React.ReactNode;
}
```

**Estructura:**

```tsx
<div className="min-h-screen bg-background">
  <Header />
  <div className="flex">
    <Sidebar />
    <main className="flex-1 p-6">{children}</main>
  </div>
</div>
```

### Header

Header de la aplicación.

```typescript
interface HeaderProps {
  onMenuToggle?: () => void;
}
```

**Estructura:**

```tsx
<header className="sticky top-0 z-50 w-full border-b border-border bg-background/95 backdrop-blur">
  <div className="container flex h-14 items-center gap-4">
    <Logo />
    <GlobalSearch />
    <div className="ml-auto flex items-center gap-2">
      <ThemeToggle />
      <UserMenu />
    </div>
  </div>
</header>
```

### Sidebar

Sidebar de navegación.

```typescript
interface SidebarProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
}
```

**Estructura:**

```tsx
<aside className="fixed inset-y-0 left-0 z-40 w-64 border-r border-border bg-background">
  <div className="flex h-full flex-col">
    <SidebarHeader />
    <SidebarNav />
    <SidebarFooter />
  </div>
</aside>
```

## Gestión de estado

### TanStack Query

- **Cache de datos**: Cache automático de respuestas de API
- **Invalidación**: Invalidación inteligente de cache
- **Optimistic updates**: Actualizaciones optimistas
- **Background refetch**: Refetch en background

```typescript
// Ejemplo de query
const gamesQuery = useQuery({
  queryKey: ['games', { page, limit, search }],
  queryFn: () => fetchGames({ page, limit, search }),
  staleTime: 5 * 60 * 1000, // 5 minutos
});

// Ejemplo de mutation
const createGameMutation = useMutation({
  mutationFn: createGame,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['games'] });
  },
});
```

### TanStack Router

- **File-based routing**: Rutas basadas en archivos
- **Route loaders**: Carga de datos antes de renderizar
- **Type-safe routing**: Tipado de parámetros y búsqueda
- **Code splitting**: División de código automática

```typescript
// Ejemplo de route loader
export const loader = ({ params }: LoaderArgs) => {
  return queryClient.ensureQueryData({
    queryKey: ['game', params.id],
    queryFn: () => fetchGame(params.id),
  });
};
```

### TanStack Form + Zod

```typescript
// Esquema de validación
const gameSchema = z.object({
  title: z.string().min(1, 'El título es requerido'),
  description: z.string().optional(),
  releaseDate: z.date().optional(),
  platforms: z.array(z.string()).min(1, 'Selecciona al menos una plataforma'),
  tags: z.array(z.string()).default([]),
})

type GameFormData = z.infer<typeof gameSchema>

// Componente de formulario
function GameForm() {
  const form = useForm({
    defaultValues: {
      title: '',
      description: '',
      platforms: [],
      tags: [],
    },
    validators: {
      onChange: gameSchema,
    },
  })

  const { mutate: createGame } = useCreateGame()

  const handleSubmit = (data: GameFormData) => {
    createGame(data)
  }

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        form.handleSubmit()
      }}
    >
      <form.Field
        name="title"
        validators={{
          onChange: ({ value }) =>
            !value ? 'El título es requerido' : undefined,
        }}
      >
        {(field) => (
          <div>
            <label htmlFor={field.name}>Título</label>
            <input
              id={field.name}
              name={field.name}
              value={field.state.value}
              onChange={(e) => field.handleChange(e.target.value)}
            />
            {field.state.meta.errors && (
              <span>{field.state.meta.errors.join(', ')}</span>
            )}
          </div>
        )}
      </form.Field>
      <button type="submit">Guardar</button>
    </form>
  )
}
```

## API Client

```typescript
// API client base
const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
  timeout: 10000,
});

// Interceptor para manejo de errores
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Redirigir a login
    }
    return Promise.reject(error);
  }
);

// Servicios de API
export const gamesApi = {
  list: (params: GamesListParams) => apiClient.get('/games', { params }),
  get: (id: string) => apiClient.get(`/games/${id}`),
  create: (data: CreateGameDto) => apiClient.post('/games', data),
  update: (id: string, data: UpdateGameDto) => apiClient.patch(`/games/${id}`, data),
  delete: (id: string) => apiClient.delete(`/games/${id}`),
};
```

## Accesibilidad

### WCAG AA Compliance

- **Contraste**: Mínimo 4.5:1 para texto normal, 3:1 para texto grande
- **Keyboard navigation**: Navegación completa por teclado
- **Focus indicators**: Indicadores visuales de foco
- **Screen reader support**: ARIA labels y roles apropiados
- **Semantic HTML**: Uso correcto de elementos semánticos

### Implementación

```typescript
// Ejemplo de componente accesible
<Button
  aria-label="Crear nuevo juego"
  type="button"
  onClick={handleCreate}
>
  <Plus className="mr-2 h-4 w-4" />
  Nuevo juego
</Button>
```

## Responsividad

### Breakpoints

```css
/* Tailwind breakpoints */
sm: 640px   /* Móvil grande */
md: 768px   /* Tablet */
lg: 1024px  /* Desktop pequeño */
xl: 1280px  /* Desktop */
2xl: 1536px /* Desktop grande */
```

### Estrategia mobile-first

```typescript
// Ejemplo de componente responsivo
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {games.map((game) => (
    <GameCard key={game.id} game={game} />
  ))}
</div>
```

## Optimizaciones

### Performance

- **Code splitting**: División de código por rutas
- **Lazy loading**: Carga diferida de componentes
- **Image optimization**: Optimización de imágenes
- **Memoization**: Uso de React.memo y useMemo
- **Virtual scrolling**: Para listas largas

### Bundle size

- **Tree shaking**: Eliminación de código no usado
- **Compression**: Gzip/Brotli compression
- **CDN**: Uso de CDN para dependencias

## Configuración

### Scripts de desarrollo

```json
{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "format": "prettier --write \"src/**/*.{ts,tsx}\"",
    "type-check": "tsc --noEmit",
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:e2e": "playwright test"
  }
}
```

### Variables de entorno

```env
# API
VITE_API_URL=http://localhost:3000

# Feature flags
VITE_ENABLE_ANALYTICS=false
VITE_ENABLE_TELEMETRY=false
```

### Configuración de Tailwind

```typescript
import type { Config } from 'tailwindcss';

const config: Config = {
  darkMode: ['class'],
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      colors: {
        border: 'hsl(var(--border))',
        input: 'hsl(var(--input))',
        ring: 'hsl(var(--ring))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
        secondary: {
          DEFAULT: 'hsl(var(--secondary))',
          foreground: 'hsl(var(--secondary-foreground))',
        },
        destructive: {
          DEFAULT: 'hsl(var(--destructive))',
          foreground: 'hsl(var(--destructive-foreground))',
        },
        muted: {
          DEFAULT: 'hsl(var(--muted))',
          foreground: 'hsl(var(--muted-foreground))',
        },
        accent: {
          DEFAULT: 'hsl(var(--accent))',
          foreground: 'hsl(var(--accent-foreground))',
        },
        popover: {
          DEFAULT: 'hsl(var(--popover))',
          foreground: 'hsl(var(--popover-foreground))',
        },
        card: {
          DEFAULT: 'hsl(var(--card))',
          foreground: 'hsl(var(--card-foreground))',
        },
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)',
      },
    },
  },
  plugins: [],
};

export default config;
```

### Configuración de Vite

```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
    },
  },
});
```

## Testing

### Estrategia de testing

- **Unit tests**: Componentes y hooks individuales
- **Integration tests**: Flujos de usuario
- **E2E tests**: Flujos completos con Playwright

### Herramientas

- **Vitest**: Unit tests
- **Testing Library**: Testing de componentes React
- **Playwright**: E2E tests

## Patrones de diseño

### Page Layout

```tsx
<div className="min-h-screen bg-background">
  <Header />
  <div className="flex">
    <Sidebar />
    <main className="flex-1 p-6">
      <PageHeader />
      <PageContent />
    </main>
  </div>
</div>
```

### Card Grid

```tsx
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
  {items.map((item) => (
    <Card key={item.id}>{/* Card content */}</Card>
  ))}
</div>
```

### Form Layout

```tsx
<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
    <FormField
      control={form.control}
      name="title"
      render={({ field }) => (
        <FormItem>
          <FormLabel>Título</FormLabel>
          <FormControl>
            <Input placeholder="Escribe el título..." {...field} />
          </FormControl>
          <FormDescription>Descripción del campo</FormDescription>
          <FormMessage />
        </FormItem>
      )}
    />
    <div className="flex justify-end gap-2">
      <Button type="button" variant="outline" onClick={onCancel}>
        Cancelar
      </Button>
      <Button type="submit">Guardar</Button>
    </div>
  </form>
</Form>
```

### Estados de carga

#### Skeleton Loading

```tsx
{isLoading ? (
  <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
    {Array.from({ length: 6 }).map((_, i) => (
      <Card key={i}>
        <CardHeader>
          <Skeleton className="h-4 w-3/4" />
        </CardHeader>
        <CardContent>
          <Skeleton className="h-20 w-full" />
        </CardContent>
      </Card>
    ))}
  </div>
) : (
  // Actual content
)}
```

#### Empty State

```tsx
<div className="flex flex-col items-center justify-center py-12">
  <div className="rounded-full bg-muted p-4">
    <Inbox className="h-8 w-8 text-muted-foreground" />
  </div>
  <h3 className="mt-4 text-lg font-semibold">No hay elementos</h3>
  <p className="mt-2 text-sm text-muted-foreground">Comienza añadiendo tu primer elemento.</p>
  <Button className="mt-4">
    <Plus className="mr-2 h-4 w-4" />
    Añadir elemento
  </Button>
</div>
```

## Iconos

Se utiliza `lucide-react` para todos los iconos. Iconos comunes:

| Icono          | Uso           |
| -------------- | ------------- |
| `Home`         | Dashboard     |
| `Gamepad2`     | Juegos        |
| `Upload`       | Importar      |
| `Settings`     | Configuración |
| `Monitor`      | Plataformas   |
| `Tag`          | Etiquetas     |
| `Download`     | Exportar      |
| `Search`       | Búsqueda      |
| `Plus`         | Crear nuevo   |
| `Edit`         | Editar        |
| `Trash2`       | Eliminar      |
| `Filter`       | Filtros       |
| `SortAsc`      | Ordenar       |
| `ChevronRight` | Navegación    |
| `Menu`         | Menú móvil    |
| `X`            | Cerrar        |
| `Check`        | Confirmar     |
| `AlertCircle`  | Advertencia   |
| `Info`         | Información   |
| `Loader2`      | Carga         |

## Implementación

### Prerrequisitos

- Node.js 18+
- Yarn 4.x (package manager)

### Instalación

```bash
# Navegar al directorio frontend
cd frontend

# Instalar dependencias
yarn install

# Iniciar desarrollo
yarn dev
```

### Build para producción

```bash
# Build
yarn build

# Preview
yarn preview
```

## Metadatos

**Autor**: Quasar Frontend Team
**Última actualización**: 2026-02-22
**Versión**: 1.0.0
**Estado**: Implementación completa en progreso
