✦ CULTIVA IA WCAG 2.2 Level AA 18 Jun 2026

Auditoría de Accesibilidad
NutriTrack SaaS — Dashboard Principal

Análisis completo POUR · 5 componentes auditados · Código corregido con árbol de accesibilidad y mapeo de criterios WCAG 2.2

8
Fallos críticos
3
Advertencias
5
Criterios OK
16
SC evaluados
50%
Conformidad AA

Hallazgos por componente

1. Barra de búsqueda — Label faltante & botón sin nombre accesible
SC 1.3.1 SC 4.1.2 SC 2.4.6
Crítico

Problema

El campo de búsqueda no tiene <label> asociado. El botón lupa es solo un ícono SVG sin aria-label. Los lectores de pantalla anuncian ambos elementos como vacíos o con nombre genérico.

✗ Código actual
<input type="text" placeholder="Buscar alimento..."/> <button> <svg viewBox="0 0 24 24">...lupa SVG...</svg> </button>

Corrección WCAG 2.2

Usar role="search" en el form, <label> visualmente oculto con clase sr-only, y aria-label explícito en el botón.

✓ Código corregido
<form role="search"> <label for="food-search" class="sr-only"> Buscar alimento en la base de datos </label> <input type="search" id="food-search" name="q" autocomplete="off" /> <button type="submit" aria-label="Buscar alimento"> <svg aria-hidden="true">...lupa...</svg> </button> </form>

Árbol de accesibilidad resultante

landmark[search] textbox "Buscar alimento en la base de datos" editable button "Buscar alimento"
2. Gráfico donut de macros — Información solo por color
SC 1.4.1 SC 1.1.1 SC 1.4.3
Crítico

Problema

Los segmentos del donut se distinguen únicamente por color (azul=proteínas, naranja=carbohidratos, verde=grasas). Falla para usuarios con daltonismo y cuando el gráfico se renderiza en escala de grises. El elemento <canvas> no tiene alternativa de texto.

✗ Código actual
<canvas id="macro-chart" width="200" height="200"></canvas> // Sin alt text ni tabla de datos alternativa

Corrección WCAG 2.2

Añadir role="img" + aria-label descriptivo en el canvas, patrones visuales adicionales al color (líneas/puntos), y una tabla de datos como fallback visible.

✓ Código corregido
<figure aria-labelledby="chart-title"> <figcaption id="chart-title"> Distribución de macronutrientes hoy </figcaption> <canvas role="img" aria-label="Donut: 35% proteínas, 50% carbohidratos, 15% grasas"> </canvas> <!-- Tabla accesible visible --> <table> <caption class="sr-only">Datos del gráfico</caption> <tr><th scope="col">Macro</th> <th scope="col">Porcentaje</th></tr> <tr><td>Proteínas</td><td>35%</td></tr> <tr><td>Carbohidratos</td><td>50%</td></tr> <tr><td>Grasas</td><td>15%</td></tr> </table> </figure>
3. Modal de alerta — Sin focus trap ni live region
SC 2.1.2 SC 4.1.3 SC 2.4.3
Crítico

Problema

El modal de alerta de déficit calórico no atrapa el foco (el usuario puede tabular hasta los elementos debajo), no anuncia su aparición a lectores de pantalla, y no restaura el foco al cerrarse (SC 2.4.3 Focus Order).

✗ Código actual
<div class="modal" v-if="showAlert"> <h2>Alerta calórica</h2> <p>Estás al 28% de tu objetivo diario</p> <button @click="closeAlert">×</button> </div>

Corrección WCAG 2.2

Usar role="alertdialog", aria-modal="true", focus trap con inert en el resto del DOM, botón cerrar con aria-label y restauración de foco al trigger al cerrar.

✓ Código corregido
<div role="alertdialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-desc" tabindex="-1" ref="modalRef" > <h2 id="modal-title"> Alerta: déficit calórico detectado </h2> <p id="modal-desc"> Llevas solo el 28% de tu objetivo calórico. Te recomendamos añadir una comida. </p> <button aria-label="Cerrar alerta calórica" @click="closeAndRestoreFocus" > <svg aria-hidden="true">...×...</svg> </button> </div> <!-- Resto del DOM: inert mientras modal abierto --> <div id="app-content" :inert="showAlert">...</div>

Árbol de accesibilidad

alertdialog "Alerta: déficit calórico detectado" modal heading[2] "Alerta: déficit calórico detectado" paragraph "Llevas solo el 28% de tu objetivo..." button "Cerrar alerta calórica"
4. Botones de acción rápida — Ícono sin texto & tamaño insuficiente
SC 2.5.8 (NEW) SC 4.1.2
Alto

Problema

Los botones "+", "×" y "✎" miden 18×18 px. WCAG 2.2 SC 2.5.8 (nuevo) exige mínimo 24×24 CSS px. Además carecen de aria-label; el lector de pantalla anuncia "más" (char Unicode) sin contexto.

Corrección WCAG 2.2

Target size de al menos 44×44 px en mobile (best practice) y 24×24 px en web. CSS: padding compensatorio para no alterar el layout.

✓ Código corregido
/* CSS: target size sin cambiar layout visual */ .action-btn { min-width: 44px; min-height: 44px; padding: 10px; /* hit area >= 44px */ display: inline-flex; align-items: center; justify-content: center; } /* JSX */ <button className="action-btn" aria-label="Añadir comida al registro"> <PlusIcon aria-hidden="true" size={18} /> </button> <button className="action-btn" aria-label="Eliminar entrada de comida"> <XIcon aria-hidden="true" size={18} /> </button>
5. Contraste de texto secundario — #888 sobre #fff falla AA
SC 1.4.3
Medio

Problema

El color #888888 sobre #FFFFFF produce un ratio de contraste de 3.54:1. WCAG 2.2 Level AA exige 4.5:1 para texto normal (menos de 18pt).

Texto secundario actual: #888 — ratio 3.54:1 ✗

Texto corregido: #595959 — ratio 7.0:1 ✓

Corrección

Cambiar la paleta de texto secundario a #595959 (ratio 7.0:1) o #767676 (ratio 4.54:1 mínimo). Actualizar el design token en el sistema de diseño.

✓ Design token corregido
/* tailwind.config.js — antes */ colors: { secondary: '#888888', // ✗ ratio 3.54:1 } /* tailwind.config.js — después */ colors: { secondary: '#595959', // ✓ ratio 7.0:1 secondary-min: '#767676', // ✓ ratio 4.54:1 }

Verificación de ratios

#888888 → 3.54:1 ✗
#595959 → 7.0:1 ✓

Mapa de conformidad POUR

👁 P — Perceptible

La información debe presentarse de forma que todos los usuarios puedan percibirla.

  • 1.1.1 Non-text Content — Canvas sin texto alternativo
  • 1.4.1 Use of Color — Macros solo diferenciados por color
  • 1.4.3 Contrast (min) — #888 sobre #fff → 3.54:1 falla
  • 1.3.1 Info & Relationships — Estructura HTML semántica OK

O — Operable

Los componentes de la interfaz deben ser operables por cualquier usuario.

  • 2.1.2 No Keyboard Trap — Modal sin focus trap controlado
  • 2.4.3 Focus Order — Foco no restaurado al cerrar modal
  • 2.5.8 Target Size (NEW) — Botones 18×18px < mínimo 24×24px
  • 2.1.1 Keyboard — Flujo de tabulación principal funciona

🧠 U — Comprensible

La información y el funcionamiento de la interfaz deben ser comprensibles.

  • 3.3.7 Redundant Entry (NEW) — Paso 2 repite nombre del alimento innecesariamente
  • 3.3.1 Error Identification — Mensajes de error en formularios descriptivos
  • 3.3.2 Labels or Instructions — Campos de formulario con instrucciones

🔧 R — Robusto

El contenido debe ser interpretable por tecnologías de asistencia actuales y futuras.

  • 4.1.2 Name, Role, Value — Botones icono sin aria-label, input sin label
  • 4.1.3 Status Messages (NEW) — Modal dinámico sin aria-live ni alertdialog
  • 4.1.1 Parsing — HTML válido, sin atributos duplicados

ADR — Decision Record

ADR-ACC-001: Focus Management en modales dinámicos de NutriTrack

ACEPTADO
Web (React 18 + Tailwind)
2.1.2 · 2.4.3 · 4.1.3
Alto — usuarios con discapacidad motora y visual
Contexto y problema

El modal de alerta calórica aparece dinámicamente cuando el usuario cae por debajo del 30% de su objetivo. Actualmente no atrapa el foco, por lo que los usuarios de teclado/lector de pantalla continúan navegando el fondo de la app sin saber que hay un diálogo activo. Tampoco hay anuncio proactivo de la alerta.

Decisión

Implementar el patrón ARIA Modal Dialog completo: (1) role="alertdialog" con aria-modal="true", (2) atributo inert en el contenido de fondo mientras el modal está abierto, (3) focus automático al primer elemento interactivo del modal al abrirse, (4) restauración del foco al elemento trigger al cerrarse, y (5) soporte de Escape para cerrar. Esto aplica a todos los modales de la aplicación, no solo al de alerta.

Consecuencias positivas

Cumplimiento de SC 2.1.2, 2.4.3 y 4.1.3. Los usuarios de NVDA, JAWS y VoiceOver reciben el anuncio inmediato de la alerta. El atributo inert es nativo HTML y no requiere librerías adicionales (soporte en todos los navegadores modernos desde 2023).

Plan de remediación priorizado

Prioridad Componente SC WCAG 2.2 Acción Esfuerzo Responsable
P1 Modal alerta calórica 2.1.2 · 2.4.3 · 4.1.3 Añadir role alertdialog + inert + focus restore Bajo Frontend dev
P1 Barra de búsqueda 1.3.1 · 4.1.2 · 2.4.6 Añadir <label sr-only> + aria-label en botón Bajo Frontend dev
P1 Botones de acción 2.5.8 · 4.1.2 Padding a 44px hit area + aria-label descriptivo Bajo Frontend dev
P2 Gráfico donut macros 1.1.1 · 1.4.1 aria-label en canvas + tabla de datos fallback Medio Frontend dev + QA
P3 Paleta de texto secundario 1.4.3 Cambiar #888 → #595959 en design token Bajo Design + Frontend
P3 Formulario multi-paso 3.3.7 (NEW) Pre-rellenar nombre alimento en paso 2 desde estado Medio Frontend dev

Anti-patrones detectados en NutriTrack

⚠ Botones vacíos

18 botones de icono sin aria-label en toda la app. El lector anuncia solo el caracter Unicode. Aplicar búsqueda global: button:not([aria-label]).

⚠ Solo color como indicador

Gráficas, badges de estado y leyendas usan únicamente color. Añadir patrón, icono o texto adicional en todos los casos.

⚠ Placeholders como labels

Los campos de formulario usan placeholder como única etiqueta. El placeholder desaparece al escribir, violando SC 3.3.2.

⚠ Contenedores fixed-size

Varios divs tienen height: 300px fijo. Al hacer zoom al 400% el contenido se desborda y queda inaccesible.

⚠ Links no descriptivos

"Ver más" y "Aquí" aparecen 6 veces. Usuarios que navegan por links no saben el destino. Usar texto como "Ver historial completo de comidas".

⚠ Focus indicators eliminados

outline: none en el CSS global suprime el indicador de foco nativo. WCAG 2.2 SC 2.4.11 exige que el foco sea visible. Reemplazar por focus ring personalizado de alto contraste.