Design
WCAG AA
Sistema de Diseño
Para qué

Las decisiones visuales canónicas del producto: paleta, tipografía, componentes, movimiento y accesibilidad.

Audiencia

Diseño e ingeniería frontend.

Accessibility Specifications

ADEN: Plataforma de Medicina Preventiva Personalizada

Propósito: Especificaciones de accesibilidad para una app de salud donde la mala interpretación de datos puede causar daño real
Impacto: Crítico — afecta seguridad del paciente, cumplimiento regulatorio, y confianza
Estándar: WCAG 2.1 AA mínimo, con aspiración AAA en componentes de datos de salud


1. Objetivo de Accesibilidad

Por Qué una App de Salud Tiene Mayor Responsabilidad

Las apps de salud operan en un nivel de riesgo superior al de apps convencionales:

  • Un color malinterpretado puede hacer que un paciente ignore un resultado crítico
  • Un número de glucosa sin contexto puede generar pánico innecesario
  • Un botón inaccesible puede impedir que alguien agende una cita urgente
  • El 36% de adultos tiene health literacy limitada (CDC/NAAL)
  • El 12% de hombres y 0.5% de mujeres no distinguen rojo de verde

Estándar Objetivo

Nivel Requisito Aplicación en ADEN
WCAG 2.1 AA Mínimo obligatorio Toda la aplicación
WCAG 2.1 AAA Aspiracional Datos de salud, resultados de lab, scores
ADA/Section 508 Cumplimiento legal Toda la aplicación

Audiencia Prioritaria

Persona Edad Necesidades de Accesibilidad
Paciente Activado (P4) 40-55 Presbicia común, menor contraste visual, posible daltonismo
Cuidador Familiar 35-65 Puede usar la app en nombre de otro, necesita claridad extrema
Adulto Mayor Acompanado 55-75 Font scaling, touch targets grandes, lectura simple

2. Color y Contraste

Ratios Minimos Requeridos

Elemento Ratio Mínimo (AA) Ratio Ideal (AAA) Norma
Texto normal (<24px) 4.5:1 7:1 WCAG 1.4.3 / 1.4.6
Texto grande (>=24px o >=19px bold) 3:1 4.5:1 WCAG 1.4.3 / 1.4.6
Componentes UI (bordes, iconos) 3:1 WCAG 1.4.11
Focus indicators 3:1 WCAG 2.4.7

Validación de Contraste: Dark Mode

Background principal: #0A0F1E (Deep Navy)

Elemento Color Hex Ratio vs #0A0F1E AA AAA
Text Primary White #F9FAFB 18.1:1 PASS PASS
Text Secondary Gray Light #9CA3AF 6.2:1 PASS NO
Text Muted Gray #6B7280 3.9:1 FAIL texto normal NO
Glow Blue Blue #3B82F6 4.8:1 PASS NO
State Óptimo Emerald #10B981 5.9:1 PASS NO
State Atención Red Soft #F87171 5.7:1 PASS NO
State Revisar Blue #3B82F6 4.8:1 PASS NO
Primary CTA Electric Blue #0f2fc7 2.1:1 FAIL como texto

Acciones requeridas:

  • #6B7280 (Text Muted): NO usar para texto normal. Solo para labels decorativos de >24px o con acompañamiento de texto legible
  • #0f2fc7 sobre #0A0F1E: NO usar como texto. Solo como fondo de botones con texto blanco encima
  • #0f2fc7 como fondo con #F9FAFB texto: ~8.6:1 (PASS AA y AAA)

Validación de Contraste: Light Mode

Background principal: #F5F5F0 (Cream)

Elemento Color Hex Ratio vs #F5F5F0 AA AAA
Text Primary Neutral 900 #171717 15.8:1 PASS PASS
Text Secondary Gray 600 #4B5563 7.2:1 PASS PASS
Text Muted Gray 400 #9CA3AF 2.8:1 FAIL FAIL
Primary Blue Blue #0f2fc7 7.4:1 PASS PASS
State Óptimo Emerald #10B981 2.9:1 FAIL como texto
Error Red Red #ef4444 3.9:1 FAIL texto normal
Card Background White #FFFFFF 1.1:1 N/A (fondo)

Acciones requeridas:

  • #9CA3AF sobre cream: NO usar como texto. Buscar alternativa más oscura (#6B7280 = 4.5:1, pasa AA)
  • #10B981 (Emerald) como texto sobre cream: NO usar solo. Siempre acompañar con icono y label de texto en color que pase
  • #ef4444 como texto: Solo para texto grande (>=24px). Para texto normal, usar #DC2626 (ratio 5.3:1)
  • Card white #FFFFFF sobre cream #F5F5F0: Delimitar cards con borde de 1px mínimo (3:1 ratio)

Regla Crítica: Color No Es Suficiente

WCAG 1.4.1: El color NUNCA puede ser el único medio visual para transmitir información de salud.

Cada estado de salud DEBE tener tres canales simultaneos:

CORRECTO:

  [CheckmarkCircle02Icon]  En rango óptimo    ← Icono + Texto + Color verde
  Glucosa: 92 mg/dL                         


INCORRECTO:

   Glucosa: 92 mg/dL                        ← Solo círculo verde (inaccesible)

Estado Color Icono Requerido Label de Texto
Óptimo Emerald #10B981 CheckmarkCircle02Icon “En rango óptimo”
Revisar Blue #3B82F6 InformationCircleIcon “Para revisar”
Atención Red Soft #F87171 AlertCircleIcon “Necesita atención”
Pendiente Gray #9CA3AF Clock01Icon “Pendiente”

Daltonismo: Alternativas Seguras

ADEN ya usa azul en lugar de verde/rojo para estados, lo cual es correcto. Reglas adicionales:

Combinación Seguridad Uso en ADEN
Azul + Naranja Universalmente distinguible Gráficos comparativos, tendencias
Azul + Amarillo Alta diferenciación Alertas vs información
Rojo + Verde PROHIBIDO como único diferenciador Nunca sin icono + texto
Formas + Patrones Siempre seguro Gráficos de línea (solida vs punteada vs dashed)

Herramientas de prueba obligatorias:

  • Stark (plugin Figma) — Simulación de daltonismo en tiempo real
  • Color Oracle (desktop) — Simulación a nivel de sistema operativo
  • Sim Daltonism (macOS) — Filtro de pantalla en tiempo real

3. Soporte para Screen Readers

Principios Generales

  • Toda imagen necesita alt text descriptivo y contextualizado
  • Elementos decorativos: alt="" y aria-hidden="true"
  • Orden de lectura debe coincidir con orden visual
  • Todos los iconos HugeIcons deben tener aria-label o estar marcados aria-hidden="true" si son decorativos

Patrones ARIA para Datos de Salud

Health Score (Anillo de Progreso)

<div
  role="meter"
  aria-label="Tu Nivel de Salud"
  aria-valuenow="78"
  aria-valuemin="0"
  aria-valuemax="100"
  aria-valuetext="Tu Nivel de Salud: 78 de 100, categoría Bueno"
>
  <!-- Visual ring animation -->
  <span aria-hidden="true" class="score-ring">78</span>
  <!-- Screen reader text -->
  <span class="sr-only">Tu Nivel de Salud: 78 de 100, categoría Bueno</span>
</div>

Resultado de Biomarcador

<div role="group" aria-label="Resultado de Glucosa">
  <span class="sr-only">
    Glucosa: 92 miligramos por decilitro, en rango óptimo.
    Tu tendencia de los últimos 90 días se ve bien.
  </span>
  <div aria-hidden="true">
    <!-- Visual presentation -->
    <span class="metric-value">92</span>
    <span class="metric-unit">mg/dL</span>
    <span class="metric-badge badge-optimal">En rango óptimo</span>
  </div>
</div>

Resultado Fuera de Rango

<div role="group" aria-label="Resultado de Glucosa que necesita atención">
  <span class="sr-only">
    Glucosa: 142 miligramos por decilitro. Tu nivel necesita atención.
    Tu equipo de salud ya fue notificado.
    Te recomendamos agendar una cita.
  </span>
  <!-- Visual presentation with aria-hidden="true" -->
</div>

Gráficos y Visualizaciones

Cada gráfico DEBE tener un resumen de texto debajo:

<figure role="img" aria-label="Tendencia de glucosa en los últimos 90 días">
  <div aria-hidden="true">
    <!-- Chart SVG/Canvas -->
  </div>
  <figcaption>
    <p class="chart-summary">
      Tu glucosa se mantuvo estable entre 85 y 95 mg/dL en los últimos 90 días.
      La tendencia es positiva comparada con tu ciclo anterior.
    </p>
    <!-- Optional: data table for screen readers -->
    <details>
      <summary>Ver datos en tabla</summary>
      <table>
        <caption>Lecturas de glucosa, últimos 90 días</caption>
        <!-- Table data -->
      </table>
    </details>
  </figcaption>
</figure>

Alertas y Notificaciones

<!-- Alerta NO crítica (resultados listos, recordatorios) -->
<div
  role="status"
  aria-live="polite"
  aria-atomic="true"
>
  Tus exámenes estan listos. Veamos como te fue.
</div>

<!-- Alerta CRITICA (resultado que necesita atención, error de sistema) -->
<div
  role="alert"
  aria-live="assertive"
  aria-atomic="true"
>
  Tu nivel de glucosa necesita atención. Tu equipo de salud fue notificado.
</div>

<!-- Notificación de estado (guardado, sincronizado) -->
<div
  role="status"
  aria-live="polite"
>
  Check-in completo. Llevas 23 días consecutivos.
</div>

Errores en Formularios

<div class="form-field">
  <label for="email-input" id="email-label">
    Correo electrónico
    <span aria-hidden="true" class="required-indicator">*</span>
  </label>
  <input
    id="email-input"
    type="email"
    aria-required="true"
    aria-invalid="true"
    aria-describedby="email-error email-hint"
    aria-labelledby="email-label"
  />
  <span id="email-hint" class="input-hint">
    Ejemplo: tu@correo.com
  </span>
  <span id="email-error" role="alert" class="error-message">
    El correo no tiene un formato válido. Revisa que tenga @ y un dominio.
  </span>
</div>

Modales

<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
>
  <h2 id="modal-title">Resultado de exámenes</h2>
  <p id="modal-description">
    Tus exámenes del 15 de marzo estan listos para revisar.
  </p>
  <!-- Content -->
  <button aria-label="Cerrar dialogo">
    <Cancel01Icon aria-hidden="true" />
  </button>
</div>

Comportamiento del modal:

  • Focus trap: Tab cicla dentro del modal, no sale
  • Al abrir: focus va al primer elemento interactivo (o al título)
  • Al cerrar: focus regresa al elemento que abrió el modal
  • Escape cierra el modal
  • Click fuera del modal lo cierra (con backdrop)
  • aria-hidden="true" en todo el contenido detrás del modal

4. Navegación por Teclado

Requisitos Generales

Tecla Acción Contexto
Tab Siguiente elemento interactivo Global
Shift + Tab Elemento interactivo anterior Global
Enter Activar botón o link Botones, links
Space Activar botón, toggle checkbox Botones, checkboxes
Escape Cerrar modal, drawer, dropdown Overlays
Arrow Up/Down Navegar opciones Selects, menus, radio groups
Arrow Left/Right Cambiar tab, mover slider Tabs, date pickers, sliders
Home Ir al primer item Listas, tabs
End Ir al último item Listas, tabs
<body>
  <a href="#main-content" class="skip-link">
    Saltar al contenido principal
  </a>
  <!-- Navigation -->
  <main id="main-content" tabindex="-1">
    <!-- Page content -->
  </main>
</body>
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  z-index: 100;
  padding: 8px 16px;
  background: #0f2fc7;
  color: #F9FAFB;
  font-size: 16px;
  text-decoration: none;
  transition: top 150ms ease;
}

.skip-link:focus {
  top: 0;
}

Tab Order

El orden de tabulación debe seguir el flujo visual:

  1. Skip-to-content link
  2. Header / navegación principal
  3. Contenido hero (score principal, alerta crítica)
  4. Cards de acción (izquierda a derecha, arriba a abajo)
  5. Contenido secundario
  6. Footer / navegación secundaria

Nunca usar tabindex mayor a 0. Solo tabindex="0" (orden natural) o tabindex="-1" (focusable por script, no por tab).

Focus Indicators

/* Focus visible para todos los elementos interactivos */
:focus-visible {
  outline: 2px solid #3B82F6;
  outline-offset: 2px;
  border-radius: 4px;
}

/* Dark mode: outline azul claro sobre fondo oscuro */
[data-theme="dark"] :focus-visible {
  outline-color: #60A5FA;
}

/* Light mode: outline azul oscuro sobre fondo claro */
[data-theme="light"] :focus-visible {
  outline-color: #0f2fc7;
}

/* NUNCA eliminar outline sin reemplazo */
/* PROHIBIDO: :focus { outline: none; } */

El indicador de focus debe:

  • Ser visible en ambos modos (dark y light)
  • Tener ratio de contraste 3:1 mínimo contra el fondo
  • No depender solo de color (el outline es un cambio de forma)
  • Tener outline-offset de al menos 2px para no confundirse con el borde del elemento

5. Touch Targets

Tamanos Minimos

Estándar Tamaño Mínimo Aplicación
WCAG 2.5.5 (AAA) 44x44px Objetivo para ADEN
Material Design 48x48dp Objetivo ideal
Apple HIG 44x44pt iOS nativo

Reglas de Implementación

/* Tamano mínimo para todos los elementos interactivos */
button,
a,
input[type="checkbox"],
input[type="radio"],
.interactive {
  min-width: 44px;
  min-height: 44px;
}

/* Spacing entre targets tocables */
.action-group > * + * {
  margin-left: 8px; /* Mínimo 8px entre targets */
}

Spacing Entre Targets

Contexto Spacing Mínimo Razón
Botones adyacentes 8px Prevenir toque accidental
Items de lista 8px vertical Facilitar selección
Iconos de acción en toolbar 12px Mayor precisión requerida
Links en texto corrido 4px padding extra Touch target expansión

Zona del Pulgar

En dispositivos moviles, las acciones primarias deben estar en la zona de alcance comodo del pulgar:


                          
      Zona difícil          ← Información, navegación
                          

                          
      Zona media            ← Contenido, datos
                          

                          
      Zona comoda           ← CTAs, navegación tab, check-in
                          

Aplicación en ADEN:

  • Botón de check-in diario: zona comoda inferior
  • Tab navigation: zona comoda inferior
  • Score/métricas: zona media (lectura, no acción)
  • Settings/perfil: zona superior (acceso infrecuente)

Gestos con Alternativa

Todo gesto de swipe DEBE tener un botón alternativo visible:

Gesto Alternativa Requerida
Swipe para descartar alerta Botón “X” o “Descartar” visible
Swipe entre tabs Tabs tocables visibles arriba
Pull-to-refresh Botón “Actualizar” visible
Pinch-to-zoom en gráficos Botones “+” / “-” visibles

6. Health Literacy

Nivel de Lectura Objetivo

Fuente Recomendación
AMA Grado 6 máximo
CDC Grado 3-5
ADEN objetivo Grado 6 máximo (español colombiano)

8 Reglas de Comunicación Clara

  1. Una idea por oración — máximo 15-20 palabras
  2. Voz activa siempre — “Toma tu suplemento” no “El suplemento debe ser tomado”
  3. Palabras cotidianas — “Tus exámenes” no “Biomarcadores”
  4. Listas cortas — 3 a 7 items máximo por lista
  5. Números claros — “3 de cada 10” no “30%”
  6. Verbos de acción — “Haz”, “Toma”, “Ve”, no “Considere”
  7. Sin doble negativo — “Es seguro” no “No es inseguro”
  8. Contexto antes de dato — Explicar que mide ANTES de dar el número

Glosario de Terminos Médicos

Disponible en toda la app vía tooltip o icono de información:

Termino Médico Explicación en ADEN
Biomarcadores Tus exámenes
Perfil lipídico Niveles de colesterol
HbA1c Control de azucar a largo plazo
HOMA-IR Resistencia a insulina
Health Score Tu Nivel de Salud
Adherencia Tu constancia / Tu progreso
Protocolo Tu plan personalizado
Baseline Tu punto de partida

Implementación del glosario:

<!-- Tooltip contextual para terminos médicos -->
<span class="medical-term">
  <span>Tu control de azucar a largo plazo</span>
  <button
    aria-label="Más información sobre control de azúcar a largo plazo"
    aria-expanded="false"
    aria-controls="tooltip-hba1c"
  >
    <InformationCircleIcon aria-hidden="true" />
  </button>
  <div
    id="tooltip-hba1c"
    role="tooltip"
    hidden
  >
    El HbA1c mide tu nivel promedio de azucar en sangre
    durante los últimos 2-3 meses. Es como una foto de
    como ha estado tu azucar, no solo hoy sino en general.
  </div>
</span>

Comunicación de Resultados de Salud

Todo resultado debe seguir este patrón:

1. CONTEXTO   → Que se midio (en palabras simples)
2. RESULTADO  → El número con su unidad
3. ESTADO     → Que significa para ti (icono + color + texto)
4. ACCION     → Que puedes hacer

Ejemplo completo accesible:

<article aria-label="Resultado de glucosa en ayunas">
  <h3>Tu azucar en ayunas</h3>
  <p class="result-value" aria-hidden="true">92 mg/dL</p>
  <p class="sr-only">92 miligramos por decilitro</p>
  <div class="result-status" role="status">
    <CheckmarkCircle02Icon aria-hidden="true" />
    <span>En rango óptimo</span>
  </div>
  <p class="result-context">
    Tu tendencia de los últimos 90 días se ve bien.
  </p>
  <a href="/paciente/exámenes/glucosa">
    Ver historial completo
  </a>
</article>

7. Movimiento y Sensibilidad Vestibular

Respetar prefers-reduced-motion

/* Base: animaciones normales */
.score-ring {
  animation: ring-fill 1.2s ease-out forwards;
}

.fade-in-up {
  animation: fadeInUp 260ms cubic-bezier(0.4, 0, 0.2, 1);
}

.skeleton-shimmer {
  animation: shimmer 500ms infinite;
}

/* Reduced motion: saltar al estado final */
@media (prefers-reduced-motion: reduce) {
  .score-ring {
    animation: none;
    /* Mostrar estado final directamente */
  }

  .fade-in-up {
    animation: none;
    opacity: 1;
    transform: none;
  }

  .skeleton-shimmer {
    animation: none;
    /* Mostrar placeholder estatico */
  }

  /* Transiciones suaves reducidas a instantaneas */
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Reglas de Movimiento

Elemento Comportamiento Normal Comportamiento Reduced Motion
Score ring Animación de llenado 1.2s Mostrar valor final inmediatamente
FadeInUp cards Delay escalonado 0.2s Todas visibles inmediatamente
Skeleton shimmer Pulso infinito Fondo estatico gris
Modal enter Slide-up 200ms Aparición instantanea
Button hover TranslateY 150ms Cambio de color instantaneo
Gráficos de tendencia Dibujo progresivo de línea Línea completa instantanea
Celebration confetti Particulas animadas Badge estatico “Completado”

Prohibiciones Absolutas

  • NO autoplay de video — Siempre requiere acción del usuario
  • NO parallax scrolling — En ninguna sección
  • NO animaciones de fondo continuas — Solo animaciones disparadas por acción
  • NO parpadeo o flash — Cero elementos que parpadeen >3 veces por segundo (WCAG 2.3.1)
  • NO carruseles automaticos — Solo avance manual

8. Tipografia para Accesibilidad

Tamanos Minimos

Elemento Tamaño Mínimo Peso Line Height
Body text 16px 400 1.5 (24px)
Labels / captions 14px 500 1.4 (20px)
Metric values 24px+ 700 1.2
Headlines (h1) 28px+ 700 1.3
Headlines (h2) 22px+ 600 1.3
Button text 16px 500-600 1.0
Error messages 14px 500 1.4
Tooltip text 14px 400 1.4

Font Scaling

/* NUNCA fijar font-size en px a nivel de root */
/* CORRECTO: usar rem que respeta configuración del usuario */
html {
  font-size: 100%; /* Respeta setting del sistema */
}

body {
  font-size: 1rem;    /* 16px default, escala con usuario */
  line-height: 1.5;
}

h1 { font-size: 1.75rem; }  /* 28px default */
h2 { font-size: 1.375rem; } /* 22px default */

/* Métricas de salud: ligeramente más grandes */
.metric-value {
  font-size: 1.5rem;  /* 24px default */
  font-weight: 700;
  font-variant-numeric: tabular-nums; /* Alineacion de números */
}

Reglas críticas:

  • Soportar font scaling hasta 200% sin perdida de contenido ni overlap
  • NO usar max-height con overflow: hidden en contenedores de texto
  • Los contenedores deben expandirse con el contenido
  • Números de métricas DEBEN usar font-variant-numeric: tabular-nums para alineación correcta
  • Peso tipografico: suficiente contraste entre niveles de jerarquía (ej: 400 body vs 700 heading, nunca 400 vs 500 en elementos adyacentes)

Espaciado de Texto

Propiedad Valor Mínimo Norma
Line height 1.5x font size WCAG 1.4.12
Paragraph spacing 2x font size WCAG 1.4.12
Letter spacing 0.12x font size WCAG 1.4.12
Word spacing 0.16x font size WCAG 1.4.12

9. Formularios e Inputs

Labels Visibles

<!-- CORRECTO: Label visible siempre -->
<label for="weight-input">Tu peso actual (kg)</label>
<input
  id="weight-input"
  type="number"
  inputmode="decimal"
  aria-required="true"
  autocomplete="off"
  placeholder="Ej: 72.5"
/>

<!-- INCORRECTO: Solo placeholder como label -->
<input placeholder="Peso" />

Regla: El placeholder NUNCA reemplaza al label. Es solo una pista adicional que desaparece al escribir.

Mensajes de Error: Específicos y Accionables

Tipo de Error Mensaje Incorrecto Mensaje Correcto
Campo vacio “Campo requerido” “Escribe tu correo para continuar”
Formato inválido “Formato inválido” “El correo debe tener @ y un dominio (ej: tu@correo.com)”
Fuera de rango “Valor inválido” “El peso debe estar entre 30 y 300 kg”
Contraseña debil “Contraseña no válida” “Tu contraseña necesita al menos 8 caracteres y un número”

Atributos Requeridos

<input
  id="field-id"
  type="text"
  aria-required="true"           <!-- Campo obligatorio -->
  aria-invalid="false"           <!-- Cambiar a true si hay error -->
  aria-describedby="field-error field-hint"  <!-- Vincula hint y error -->
  aria-labelledby="field-label"  <!-- Vincula label externo -->
  autocomplete="email"           <!-- Autocompletado semantico -->
/>

Autocomplete para Campos Estándar

Campo Atributo autocomplete
Nombre completo name
Email email
Teléfono tel
Fecha de nacimiento bday
Dirección street-address
Ciudad address-level2
Código postal postal-code

Input Masks

Cuando un campo requiere formato específico:

<label for="phone-input">Tu celular</label>
<input
  id="phone-input"
  type="tel"
  inputmode="numeric"
  autocomplete="tel"
  aria-describedby="phone-format"
  placeholder="300 123 4567"
/>
<span id="phone-format" class="input-hint">
  Formato: 10 digitos sin indicativo de pais
</span>

10. Checklist de Testing

Testing Manual

  • [ ] Screen reader (VoiceOver iOS): Navegar toda la app solo con VoiceOver. Verificar que cada pantalla se lea de forma lógica y completa
  • [ ] Screen reader (TalkBack Android): Misma prueba en Android. Verificar anuncios de roles, estados, y valores
  • [ ] Navegación solo con teclado: Completar un flujo crítico (onboarding, check-in, ver resultados) sin mouse. Verificar que el focus sea siempre visible y lógico
  • [ ] Auditoria de contraste de color (Dark Mode): Verificar todos los pares de color texto/fondo contra ratios AA mínimo
  • [ ] Auditoria de contraste de color (Light Mode): Misma verificación para light mode
  • [ ] Sim Daltonism — Deuteranopia: Verificar que ningun estado de salud dependa solo de rojo/verde
  • [ ] Sim Daltonism — Protanopia: Verificar gráficos y badges en modo protanopia
  • [ ] Sim Daltonism — Tritanopia: Verificar pares azul/amarillo
  • [ ] Font scaling 200%: Escalar fuentes al 200% en iOS y Android. Verificar que no haya overflow, truncado, ni overlap
  • [ ] Reduced motion: Activar “Reducir movimiento” en sistema. Verificar que score rings, skeletons, y fade-ins se resuelvan instantaneamente
  • [ ] Auditoria de touch targets: Medir todos los botones, links, checkboxes. Ninguno menor a 44x44px
  • [ ] Revisión de health literacy: Enviar copys de resultados de salud a test de legibilidad. Verificar grado 6 máximo
  • [ ] Prueba con usuario real (40-55 años): Persona del segmento Paciente Activado completa flujo crítico sin asistencia

Testing Automatizado

Herramienta Que Detecta Cuando Ejecutar
axe-core Violaciones WCAG en DOM CI/CD en cada PR
Lighthouse Accessibility Score de accesibilidad, issues críticos CI/CD en cada PR
eslint-plugin-jsx-a11y Problemas de accesibilidad en JSX/TSX Linting en desarrollo
Pa11y WCAG 2.1 AA compliance automatizada Nightly en staging
Storybook a11y addon Testing de componentes individuales Desarrollo de componentes

Configuración de axe-core

// cypress/support/commands.js o playwright config
import 'cypress-axe';

// En cada test crítico:
cy.checkA11y(null, {
  rules: {
    'color-contrast': { enabled: true },
    'image-alt': { enabled: true },
    'label': { enabled: true },
    'aria-roles': { enabled: true },
    'aria-required-attr': { enabled: true },
  },
  // ADEN: reglas adicionales para health apps
  tags: ['wcag2a', 'wcag2aa', 'wcag21aa'],
});

Frecuencia de Testing

Test Frecuencia Responsable
axe-core automatizado Cada PR CI/CD
Lighthouse audit Semanal Desarrollo
Screen reader manual Cada release QA
Daltonismo visual Cada nuevo componente Diseño
Font scaling Cada release QA
Health literacy review Cada nuevo copy de salud Contenido
User testing (45+) Cada ciclo mayor UX Research

Resumen de Implementación

Prioridad 1 (Bloquea lanzamiento)

  • Contraste de color AA en todos los textos (ambos modos)
  • Color nunca como único indicador de estado de salud
  • Screen reader labels en todos los resultados de salud
  • Keyboard navigation funcional en flujos críticos
  • Touch targets mínimos de 44x44px
  • Skip-to-content link
  • Focus indicators visibles
  • Errores de formulario vinculados con aria-describedby

Prioridad 2 (Primer mes post-lanzamiento)

  • prefers-reduced-motion implementado en todas las animaciones
  • Glosario de terminos médicos accesible desde cada pantalla
  • Resumenes de texto debajo de cada gráfico
  • Font scaling hasta 200% sin rotura de layout
  • axe-core integrado en CI/CD

Prioridad 3 (Mejora continua)

  • Aspiración AAA en contraste para datos de salud
  • User testing con personas de 40-55 años
  • Pa11y nightly en staging
  • Storybook a11y addon para libreria de componentes
  • Documentación de patrones ARIA para nuevos componentes

Anterior: states – Estados globales y manejo de errores
Siguiente: edge-cases – Situaciones especiales y validaciones