# Regla: Accesibilidad

Esta regla es OBLIGATORIA para todo código frontend sin excepción.
Una interfaz que no puede usarse por personas con discapacidades no es una interfaz completa.
Ninguna feature de UI se considera terminada si viola cualquiera de los puntos aquí listados.
La accesibilidad no es opcional ni un "nice to have" — es un requisito de calidad.

---

## Semántica HTML obligatoria

El HTML debe comunicar estructura y significado, no solo presentación visual.

- Usar elementos semánticos correctos: `<nav>`, `<main>`, `<aside>`, `<header>`, `<footer>`,
  `<section>`, `<article>`, `<h1>`-`<h6>`, `<button>`, `<a>`, `<form>`, `<label>`.
- NUNCA usar `<div>` o `<span>` donde existe un elemento semántico equivalente.
- NUNCA usar `<table>` para layout visual — solo para datos tabulares.
- Los encabezados (`<h1>`-`<h6>`) deben seguir jerarquía lógica. No saltar niveles
  (ej: pasar de `<h1>` a `<h3>` sin `<h2>`) por motivos de estilo visual.
  Si necesitas tamaño diferente sin cambiar jerarquía, usa CSS.
- Un solo `<h1>` por página. Los componentes internos usan `<h2>` en adelante.
- Los botones que envían formularios son `<button type="submit">`.
  Los que realizan acciones son `<button type="button">`.
  NUNCA simular botones con `<div onclick>`.
- Los enlaces (`<a>`) tienen `href` siempre. Si no navegan a ningún lugar, usar `<button>`.

---

## Contraste de color mínimo (WCAG 2.1 AA)

Los colores deben garantizar legibilidad para personas con baja visión o daltonismo.

- **Texto normal** (menor a 18pt / 14pt bold): relación de contraste mínima **4.5:1**
  contra su fondo.
- **Texto grande** (18pt o más / 14pt bold o más): relación de contraste mínima **3:1**.
- **Componentes de UI** (bordes de inputs, iconos funcionales, indicadores de estado):
  relación de contraste mínima **3:1** contra colores adyacentes.
- Verificar contraste con la herramienta: https://webaim.org/resources/contrastchecker/
  o el plugin de Chrome "axe DevTools".
- Los estados de hover y focus deben mantener también el contraste mínimo.
- NUNCA comunicar información solo mediante color (ej: "los campos en rojo son obligatorios").
  Acompañar siempre con texto o ícono.
- El texto sobre imágenes debe tener una capa de fondo semiopaco para garantizar contraste.

---

## Navegación por teclado completa

Toda funcionalidad disponible por mouse debe ser accesible solo con teclado.

- Todos los elementos interactivos (botones, enlaces, inputs, selectores, checkboxes,
  toggles, menús) deben ser alcanzables con `Tab` y operables con `Enter` o `Space`.
- El orden de tabulación debe ser lógico y seguir el orden visual de la página.
  NUNCA usar `tabindex` mayor que 0 — rompe el orden natural y confunde.
  `tabindex="0"` para hacer focusable un elemento no interactivo.
  `tabindex="-1"` para elementos que solo reciben focus programáticamente.
- `tabindex="-1"` NUNCA se usa en botones, inputs o enlaces reales — los saca
  de la navegación por teclado.
- Los menús desplegables deben cerrarse con `Escape`.
- En tablas complejas, las celdas de datos deben ser navegables con flechas del teclado.
- Los sliders y rangos deben funcionar con flechas del teclado.
- Shortcuts de teclado documentados deben ser configurable o desactivables
  si colisionan con shortcuts del sistema operativo o lector de pantalla.

---

## Indicador de foco visible

El foco del teclado debe ser siempre visible. Es el cursor del usuario de teclado.

- NUNCA hacer `outline: none` o `outline: 0` sin reemplazarlo con un indicador
  de foco equivalente o mejor. Esta es la violación de accesibilidad más frecuente.
- El indicador de foco debe tener contraste mínimo de **3:1** contra el fondo adyacente.
- En Angular con Material: no suprimir el foco de Material sin alternativa.
- En Tailwind: usar `focus:ring-2 focus:ring-[color]` en elementos interactivos.
  Nunca `focus:outline-none` sin acompañarlo de `focus:ring-*`.
- El foco debe ser visible en TODOS los estados: hover, activo, deshabilitado no aplica
  (elementos deshabilitados no deben recibir foco).

---

## ARIA roles, labels y propiedades

ARIA complementa la semántica cuando el HTML no es suficiente. No la reemplaza.

- **Regla de oro de ARIA**: no uses ARIA si el HTML semántico lo puede hacer.
  `<button>` es mejor que `<div role="button">`.
- `aria-label` OBLIGATORIO en:
  - Inputs sin `<label>` visible (iconos de búsqueda, campos dentro de tablas)
  - Botones cuyo texto visible es ambiguo ("X", "...", iconos sin texto)
  - Landmarks cuando hay múltiples del mismo tipo (`<nav aria-label="Principal">`)
- `aria-labelledby` para asociar un elemento con su etiqueta visible cuando
  no se puede usar `<label for>`.
- `aria-describedby` para asociar texto de ayuda o mensajes de error a un campo.
- `aria-expanded` en botones que abren/cierran contenido (acordeones, menús).
  Actualizar el valor cuando el estado cambia.
- `aria-hidden="true"` en íconos decorativos para que los lectores de pantalla
  los ignoren. NUNCA en elementos con contenido funcional.
- `aria-live` para anuncios dinámicos (notificaciones, resultados de búsqueda,
  actualizaciones de contenido sin recarga de página). Valores:
  - `aria-live="polite"`: anuncia cuando el usuario esté inactivo (preferido)
  - `aria-live="assertive"`: anuncia inmediatamente (solo para errores críticos)
- `role="alert"` para mensajes de error importantes — implica `aria-live="assertive"`.
- `aria-invalid="true"` + `aria-describedby="[id-del-error]"` en campos con error de validación.
- NUNCA usar roles ARIA inválidos o inventados. Solo los definidos en la especificación WAI-ARIA.

---

## Focus management en modales, drawers y diálogos

El foco debe moverse al abrir contenido superpuesto y regresar al cerrarlo.

- Al abrir un modal:
  1. Mover el foco al primer elemento interactivo dentro del modal, o al contenedor
     del modal si no hay elemento interactivo al que mover.
  2. Atrapar el foco dentro del modal con "focus trap" — Tab y Shift+Tab no deben
     salir del modal mientras está abierto.
  3. El contenido detrás del modal debe tener `aria-hidden="true"` mientras está abierto.
- Al cerrar un modal:
  1. Regresar el foco al elemento que abrió el modal (el botón que lo activó).
  2. Quitar `aria-hidden` del contenido de fondo.
- El modal debe cerrarse con `Escape`.
- Usar `role="dialog"` y `aria-modal="true"` en el contenedor del modal.
- En Angular Material: usar `MatDialog` que maneja focus trap automáticamente.
  NUNCA crear modales custom sin implementar focus trap.

---

## Skip navigation links (enlaces de salto)

Permiten a usuarios de teclado saltar el menú de navegación repetitivo en cada página.

- Implementar un enlace "Saltar al contenido principal" como primer elemento de la página.
- El enlace puede estar visualmente oculto y mostrarse solo al recibir foco.
- El enlace debe apuntar al `<main>` o al contenido principal con un `id`.
- Implementación mínima en HTML:

  ```html
  <a href="#main-content" class="skip-link">Saltar al contenido principal</a>
  ...
  <main id="main-content">
  ```

  Con CSS:
  ```css
  .skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    background: #000;
    color: #fff;
    padding: 8px;
    text-decoration: none;
    z-index: 100;
  }
  .skip-link:focus {
    top: 0;
  }
  ```

- En aplicaciones con múltiples regiones (`<nav>` principal, `<nav>` de breadcrumbs,
  `<nav>` de paginación), agregar enlaces de salto para cada sección mayor.

---

## Diseño responsive y compatibilidad con zoom

La interfaz debe funcionar en distintos tamaños de pantalla y niveles de zoom.

- El diseño debe funcionar correctamente a **200% de zoom** sin pérdida de funcionalidad.
  Probar en Chrome con Ctrl+= o Cmd+= hasta 200%.
- NUNCA fijar el viewport con `user-scalable=no` o `maximum-scale=1`:
  ```html
  <!-- PROHIBIDO -->
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

  <!-- CORRECTO -->
  <meta name="viewport" content="width=device-width, initial-scale=1">
  ```
- Las áreas táctiles (botones, enlaces) deben tener mínimo **44×44 píxeles** de
  área de toque para usuarios de dispositivos móviles y baja moticidad.
- Los textos deben usar unidades relativas (`rem`, `em`) no `px` fijas, para que
  respeten la preferencia de tamaño de fuente del sistema operativo del usuario.
- NUNCA usar `overflow: hidden` en el `<body>` permanentemente — interfiere con
  el zoom en iOS.
- El contenido no debe requerir scroll horizontal a ningún ancho estándar
  (320px, 375px, 768px, 1024px, 1440px).

---

## Multimedia con subtítulos y transcripciones

El contenido audiovisual debe ser accesible para personas sordas o con baja audición.

- Todo video con audio debe tener **subtítulos** (captions) sincronizados.
  Los subtítulos automáticos de YouTube no son suficientes — deben revisarse y corregirse.
- Los videos informativos (no solo decorativos) deben tener también una **transcripción**
  de texto completa disponible en la página.
- El audio solo (podcasts, grabaciones) debe tener transcripción de texto.
- El contenido de video puramente visual (sin audio relevante) debe tener
  **descripción de audio** (audiodescripción) que narre el contenido visual.
- Los GIFs animados y videos que reproducen automáticamente deben tener opción
  de pausa. NUNCA reproducir audio de forma automática sin interacción del usuario.
- Si se usan imágenes informativas, el atributo `alt` debe describir la información
  que comunica la imagen, no solo su aspecto visual.
  Imágenes decorativas: `alt=""` (alt vacío, no omitir el atributo).

---

## Validación automatizada con axe-core o pa11y

La revisión manual no es suficiente. Automatizar la detección de errores básicos.

- Integrar **axe-core** o **pa11y** en el pipeline de CI/CD.
  Fallo en la pipeline si se detectan violaciones de nivel A o AA de WCAG 2.1.
- Configuración mínima para pa11y en CI:
  ```bash
  npx pa11y --standard WCAG2AA [url]
  ```
- Para Angular: usar `@axe-core/angular` en el entorno de desarrollo:
  ```typescript
  // main.ts — solo en development
  if (!environment.production) {
    import('@axe-core/angular').then(axe => {
      axe.default(React, ReactDOM, 1000);
    });
  }
  ```
- La validación automatizada detecta aproximadamente 30-40% de los problemas
  de accesibilidad. El resto requiere prueba manual y con lectores de pantalla.
- Prueba mensual con lector de pantalla: NVDA (Windows) o VoiceOver (Mac/iOS).
- Los errores de accesibilidad detectados por axe/pa11y tienen la misma prioridad
  que los bugs funcionales — no se difieren indefinidamente.

---

## Formularios accesibles

Los formularios son uno de los puntos más críticos de accesibilidad.

- Cada campo de formulario DEBE tener un `<label>` asociado mediante `for`/`id`
  o estar envuelto en un `<label>`.
  NUNCA usar `placeholder` como único label — desaparece al escribir.
- Los mensajes de error deben:
  - Aparecer junto al campo que los causó, no solo arriba del formulario
  - Ser descriptivos: "El correo electrónico no tiene formato válido" no "Error"
  - Estar asociados al campo con `aria-describedby`
  - Notificarse al lector de pantalla con `aria-live` o `role="alert"`
- Los campos obligatorios se marcan con `aria-required="true"` y visualmente
  (asterisco u otro indicador con leyenda explicativa).
- Los grupos de campos relacionados (radios, checkboxes) se agrupan en `<fieldset>`
  con `<legend>` descriptiva.
- En Angular Material: `mat-form-field` + `mat-error` maneja la mayoría de estos
  requisitos automáticamente si se usa correctamente.

---

## Checklist antes de entregar cualquier feature de UI

- [ ] Todos los elementos interactivos son alcanzables y operables con teclado
- [ ] El indicador de foco es visible en todos los elementos interactivos
- [ ] Los contrastes de color cumplen WCAG 2.1 AA (4.5:1 texto normal, 3:1 texto grande)
- [ ] Todos los inputs tienen `<label>` o `aria-label`
- [ ] Los modales tienen focus trap y regresan el foco al cerrarse
- [ ] Los botones con solo ícono tienen `aria-label`
- [ ] Las imágenes tienen `alt` apropiado (descriptivo o vacío si decorativas)
- [ ] No hay `outline: none` sin reemplazo visible
- [ ] La validación automatizada con axe pasa sin errores de nivel A/AA
- [ ] Se probó la funcionalidad básica a 200% de zoom
