Auditoría de Accesibilidad WCAG 2.2 — Informe Ejecutivo

NutriTrack SaaS — Reporte de Cumplimiento

Análisis estático de código fuente React + Next.js · Nivel A y AA
Cliente NutriTrack SaaS
Fecha 12 de junio 2026
Estándar WCAG 2.2 Nivel AA
Framework React 18 + Next.js 14
Archivos auditados 3 componentes
Auditado por CULTIVA IA · a11y-audit
26
Total Issues
10
Critical
10
Serious
6
Moderate
0
Minor
F
WCAG

Estado: No cumple con WCAG 2.2 AA

Se detectaron 10 violaciones críticas y 10 serias que bloquean el acceso para usuarios con discapacidad y que impiden el cumplimiento de la normativa EN 301 549 requerida para contratos con el sector público. Las 26 incidencias deben corregirse antes del lanzamiento v2.0.

❌ No apto para producción — requiere remediación inmediata
Desglose por Categoría

Formularios

9 violaciones

Landmarks

8 violaciones

Teclado

4 violaciones

Tablas

2 violaciones

Imágenes

1 violación

Encabezados

1 violación

Links

1 violación

Color / Contraste

0 violaciones
Archivos Escaneados
Componente Violaciones Críticas Serias Moderadas SLA
PatientDashboard.tsx 13 4 5 4 Bloqueo release
PatientForm.tsx 12 6 4 2 Bloqueo release
NutritionChart.tsx 3 0 2 1 Sprint actual
Hallazgos Críticos — Bloqueo de Release
Critical · 10 issues Deben corregirse antes del merge a main
1
Imagen sin texto alternativo — <img> missing alt
📂 PatientDashboard.tsx:8 🏷 img-alt-missing
Critical
WCAG 1.1.1 Non-text Content
❌ Antes
<img src={patient.avatar} />
✅ Después
<img src={patient.avatar} alt={`Foto de perfil de ${patient.name}`} />
Fix: Añadir alt descriptivo. Si es decorativa, usar alt="".
2
<div onClick> sin soporte de teclado — PatientDashboard
📂 PatientDashboard.tsx:7 🏷 keyboard-click-no-key
Critical
WCAG 2.1.1 Keyboard
❌ Antes
<div onClick={() => navigate(`/patient/${patient.id}`)}> <img ... /> <div>{patient.name}</div> </div>
✅ Después
<a href={`/patient/${patient.id}`} className="patient-card" aria-label={`Ver ficha de ${patient.name}`} > <img ... /> <div>{patient.name}</div> </a>
Fix: Sustituir <div onClick> por <a> o <button> nativos que incluyen soporte de teclado y foco por defecto.
3
3 inputs de formulario sin label — PatientForm
📂 PatientForm.tsx:12, 16, 20 🏷 form-input-no-label
Critical
WCAG 1.3.1 Info and Relationships · 4.1.2 Name, Role, Value
❌ Antes
<input type="text" placeholder="Nombre completo" onChange={...} />
✅ Después
<label htmlFor="patient-name"> Nombre completo </label> <input id="patient-name" type="text" placeholder="Ej. María García" onChange={...} />
Fix: Todos los inputs requieren <label htmlFor> o aria-label. El placeholder no es sustituto de label.
4
<select> y <textarea> sin nombre accesible — PatientForm
📂 PatientForm.tsx:24, 32 🏷 form-select-no-label
Critical
WCAG 4.1.2 Name, Role, Value
❌ Antes
<select> <option value="">Seleccionar objetivo...</option> ... </select>
✅ Después
<label htmlFor="patient-goal"> Objetivo nutricional </label> <select id="patient-goal" aria-label="Objetivo nutricional"> <option value="">Seleccionar objetivo...</option> ... </select>
Fix: <select> y <textarea> necesitan id + <label> o al menos aria-label para que los lectores de pantalla los anuncien correctamente.
5
Botón "Registrar paciente" implementado como <div onClick>
📂 PatientForm.tsx:49 🏷 keyboard-click-no-key
Critical
WCAG 2.1.1 Keyboard
❌ Antes
<div onClick={() => onSubmit(form)} style={{ background: '#4CAF50', cursor: 'pointer' }} > Registrar paciente </div>
✅ Después
<button type="submit" onClick={() => onSubmit(form)} className="btn-primary" > Registrar paciente </button>
Fix: <button type="submit"> es activable por teclado (Enter/Space) sin código extra y comunica el rol correcto a los lectores de pantalla.
Hallazgos Serios — Sprint Actual
Serious · 10 issues Impacto significativo — corregir dentro del sprint actual
6
Checkboxes sin <fieldset>/<legend> — PatientDashboard & PatientForm
📂 PatientDashboard.tsx:24 · PatientForm.tsx:45 🏷 form-missing-fieldset
Serious
WCAG 1.3.1 Info and Relationships
❌ Antes
<div> <input type="checkbox" /> Gluten <input type="checkbox" /> Lactosa <input type="checkbox" /> Frutos secos </div>
✅ Después
<fieldset> <legend>Alergias e intolerancias</legend> <label><input type="checkbox" name="gluten" /> Gluten</label> <label><input type="checkbox" name="lactosa" /> Lactosa</label> <label><input type="checkbox" name="frutos" /> Frutos secos</label> </fieldset>
Fix: Los grupos de checkbox/radio necesitan <fieldset> + <legend> para que el lector de pantalla anuncie el contexto del grupo.
7
tabindex="5" positivo — altera el orden de foco
📂 PatientForm.tsx:62 🏷 keyboard-tabindex-positive
Serious
WCAG 2.4.3 Focus Order
❌ Antes
<div tabindex="5"> <a href="#">Cancelar</a> </div>
✅ Después
<button type="button" onClick={onCancel} > Cancelar </button>
Fix: Nunca usar tabindex positivo. Reestructura el DOM si el orden de foco no es el correcto. Usa tabindex="0" o "-1" únicamente.
8
3 archivos sin skip-link ni landmark <main> — NutriTrack
📂 PatientDashboard.tsx · PatientForm.tsx · NutritionChart.tsx 🏷 landmark-no-main · landmark-no-skip-link
Serious
WCAG 1.3.1 · 2.4.1 Bypass Blocks
❌ Antes (layout raíz)
<div className="app"> <Navbar /> <div className="content"> <PatientDashboard /> </div> </div>
✅ Después
<a href="#main-content" className="sr-only focus:not-sr-only"> Saltar al contenido principal </a> <div className="app"> <nav aria-label="Navegación principal"> <Navbar /> </nav> <main id="main-content"> <PatientDashboard /> </main> </div>
Fix: Añadir en el layout raíz (layout.tsx de Next.js): skip-link, <nav> y <main>. Los componentes individuales no necesitan replicarlo.
9
Tabla de macronutrientes sin <th> headers
📂 PatientDashboard.tsx:38 🏷 table-no-headers
Serious
WCAG 1.3.1 Info and Relationships
❌ Antes
<table> <tr><td>Proteínas</td><td>120g</td></tr> <tr><td>Carbohidratos</td><td>200g</td></tr> </table>
✅ Después
<table aria-label="Resumen de macronutrientes"> <thead> <tr> <th scope="col">Nutriente</th> <th scope="col">Cantidad</th> </tr> </thead> <tbody> <tr><td>Proteínas</td><td>120g</td></tr> <tr><td>Carbohidratos</td><td>200g</td></tr> </tbody> </table>
Fix: Añadir <thead> con <th scope="col"> y un aria-label descriptivo a la tabla.
Hallazgos Moderados — Próximos 2 Sprints
Moderate · 6 issues Fricción de usabilidad — corregir en el siguiente ciclo
10
Heading salta de <h1> a <h3> en sección de Alergias
📂 PatientForm.tsx:54 🏷 heading-skipped
Moderate
WCAG 1.3.1 Info and Relationships
❌ Antes
<h3>Alergias e intolerancias</h3>
✅ Después
<h2>Alergias e intolerancias</h2>
Fix: Los niveles de encabezado deben ser secuenciales. Usar <h2> como subsección del <h1> "Registro de Paciente".
11
Link href="#" como acción — usar <button>
📂 PatientForm.tsx:63 🏷 link-empty-fragment
Moderate
WCAG 2.4.4 Link Purpose
❌ Antes
<a href="#" style={{ color: '#999' }}>Cancelar</a>
✅ Después
<button type="button" onClick={onCancel} className="btn-ghost"> Cancelar </button>
Fix: Los <a> son para navegación. Las acciones son siempre <button>.
12
Tabla sin <caption> ni aria-label — PatientDashboard
📂 PatientDashboard.tsx:38 🏷 table-no-caption
Moderate
WCAG 1.3.1 Info and Relationships
Fix: Añadir aria-label="Resumen de macronutrientes" al elemento <table> (ya incluido en el fix C9 anterior).
13–14
3 archivos sin landmark <nav> — pérdida de orientación
📂 Todos los componentes 🏷 landmark-no-nav
Moderate
WCAG 1.3.1 Info and Relationships
Fix: Igual que el fix S3: añadir <nav aria-label="..."> en el layout raíz. No duplicar en cada componente de página.
Plan de Remediación
Prioridad Esfuerzo estimado Issues Responsable sugerido Estado
P0 — Bloqueo release 3–4 h 10 críticas (img alt, click-no-key, form labels, submit button) Dev Frontend ⏳ Pendiente
P1 — Sprint actual 4–6 h 10 serias (fieldset, tabindex, landmarks, table headers, skip-link) Dev Frontend + Tech Lead ⏳ Pendiente
P2 — Próximos 2 sprints 2–3 h 6 moderadas (headings, links, nav) Dev Frontend ⏳ Backlog
CI/CD Integration 2 h Configurar GitHub Actions con exit code no-zero en critical/serious DevOps / Tech Lead Recomendado
Integración CI/CD — Prevenir Regresiones
✅ .github/workflows/a11y.yml — GitHub Actions
name: WCAG 2.2 Accessibility Check on: [pull_request] jobs: a11y-audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: { python-version: '3.12' } - name: Run WCAG scanner run: | python .cultiva/skills/a11y_scanner.py src/ --ci # exit 1 on critical/serious → blocks merge