# UX Design — Patrones de Referencia Completos

## Jerarquía Visual — Escala Tipográfica

```css
/* Escala tipográfica clara */
:root {
  --text-xs:   0.75rem;    /* 12px — metadatos, badges */
  --text-sm:   0.875rem;   /* 14px — texto secundario */
  --text-base: 1rem;       /* 16px — cuerpo */
  --text-lg:   1.125rem;   /* 18px — subtítulos */
  --text-xl:   1.25rem;    /* 20px — títulos de sección */
  --text-2xl:  1.5rem;     /* 24px — títulos de página */
  --text-3xl:  1.875rem;   /* 30px — hero / KPIs */

  /* Pesos */
  --font-normal:   400;
  --font-medium:   500;
  --font-semibold: 600;
  --font-bold:     700;

  /* Color como jerarquía */
  --color-text-primary:   #111827;  /* Contenido principal */
  --color-text-secondary: #6b7280;  /* Contenido de apoyo */
  --color-text-disabled:  #9ca3af;  /* Deshabilitado */
}
```

---

## Estados de UI — Implementaciones Completas

### Estado de Carga (Loading)

```html
<!-- Skeleton loader — mejor que spinner para listas -->
<div class="skeleton-card" aria-busy="true" aria-label="Cargando expedientes...">
  <div class="skeleton-line w-3/4 h-4 mb-2"></div>
  <div class="skeleton-line w-1/2 h-3 mb-1"></div>
  <div class="skeleton-line w-2/3 h-3"></div>
</div>

<!-- Spinner inline para acciones puntuales -->
<button disabled>
  <svg class="animate-spin h-4 w-4" viewBox="0 0 24 24" aria-hidden="true">...</svg>
  Guardando...
</button>
```

```css
/* Skeleton con animación de shimmer */
.skeleton-line {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}

@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
```

### Estado Vacío (Empty State)

```html
<!-- BIEN: empty state con contexto y acción -->
<div class="empty-state" role="status">
  <img src="assets/empty-expedientes.svg" alt="" aria-hidden="true" />
  <h3>No hay expedientes registrados</h3>
  <p>Crea el primer expediente para comenzar a gestionar tu equipo.</p>
  <a routerLink="/expedientes/nuevo" class="btn-primary">
    Crear expediente
  </a>
</div>
```

### Estado de Error

```html
<!-- Error inline en formulario -->
<div class="error-message" role="alert">
  <svg class="error-icon" aria-hidden="true">...</svg>
  <div>
    <strong>No se pudo guardar el expediente</strong>
    <p>El número de empleado ya existe en el sistema. Verifica el campo e intenta nuevamente.</p>
  </div>
  <button onclick="reintentar()">Reintentar</button>
</div>

<!-- Error de página completa (403, 404, 500) -->
<div class="error-page">
  <span class="error-code">404</span>
  <h1>Página no encontrada</h1>
  <p>La URL que buscas no existe o fue movida.</p>
  <div class="acciones">
    <a routerLink="/">Ir al inicio</a>
    <button onclick="history.back()">Regresar</button>
  </div>
</div>
```

---

## Micro-interacciones

```css
/* Feedback visual en acciones */

/* Botón con confirmación visual de éxito */
.btn-guardar.exito {
  background-color: #16a34a;
  transition: background-color 0.3s ease;
}

/* Hover suave en cards clicables */
.card-empleado {
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  cursor: pointer;
}
.card-empleado:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

/* Animación de entrada para notificaciones */
@keyframes slide-in {
  from { transform: translateX(100%); opacity: 0; }
  to   { transform: translateX(0);   opacity: 1; }
}
.toast {
  animation: slide-in 0.25s ease-out;
}

/* Reducir movimiento para usuarios con vestibular disorders */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
```

---

## Formularios — UX Específica

```
Orden de campos: del más familiar al más específico
1. Nombre -> Apellido -> Email (todos conocen esto)
2. Departamento -> Puesto (contexto)
3. RFC -> CURP -> NSS (datos específicos de RRHH)

Reglas de campo:
- Label siempre visible (no solo placeholder)
- Hint text debajo del campo, no encima
- Error mensaje específico, no genérico
- Marcar REQUERIDO con asterisco (*) y leyenda al inicio
- Marcar OPCIONAL explícitamente (no asumir que el usuario lo sabe)
- Autocompletar habilitado para datos personales (autocomplete="name", etc.)
- Tab order lógico (de izquierda a derecha, arriba a abajo)
- Formato de fecha en contexto mexicano: dd/mm/aaaa
```

---

## Mobile First

```css
/* Base: mobile (320px+) */
.grid-empleados {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

/* Tablet (768px+) */
@media (min-width: 768px) {
  .grid-empleados {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Desktop (1024px+) */
@media (min-width: 1024px) {
  .grid-empleados {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Área táctil mínima en mobile */
@media (pointer: coarse) {
  button, a, input[type="checkbox"], input[type="radio"] {
    min-height: 44px;
    min-width: 44px;
  }
}
```

---

## Anti-patrones UX Comunes

| Anti-patrón | Efecto en usuario | Solución |
|-------------|------------------|----------|
| Dark pattern: pre-marcar checkboxes de suscripción | Suscripciones no deseadas | Checkboxes desmarcados por defecto |
| Confirmar con "OK" y "Cancelar" sin contexto | Usuario hace clic en el incorrecto | "Eliminar expediente" y "Cancelar" |
| Formulario pierde datos al navegar atrás | Frustración, trabajo perdido | Guardar borrador automáticamente |
| Paginación sin indicar cuántos resultados hay | Usuario no sabe cuánto falta | "Mostrando 1-20 de 347 expedientes" |
| Spinner que gira indefinidamente | Usuario no sabe si hay error | Timeout + mensaje de error |
| Botón de submit duplicado | Doble envío de formulario | Deshabilitar tras primer clic |
| Menú desplegable para 2-3 opciones | Fricción innecesaria | Radio buttons o tabs visibles |
| Placeholder como label (desaparece al escribir) | Usuario olvida qué debe ir ahí | Label real + placeholder como ejemplo |
