Estado actual: Nivel AA incompleto
GreenMind cumple el 50% de los criterios WCAG 2.2 AA auditados. Se detectaron 24 violaciones en 4 módulos del producto. Las 5 violaciones críticas bloquean completamente el acceso a usuarios con discapacidad motora, visual y cognitiva, e impiden alcanzar el nivel A mínimo.
Ubicación y evidencia
Dashboard principal → barra de acciones (botones exportar, filtrar, configurar). 12 botones con solo icono SVG sin aria-label ni texto visible.
<!-- ❌ Incorrecto -->
<button class="icon-btn">
<svg>...icono-engranaje...</svg>
</button>
- Lectores de pantalla (NVDA, VoiceOver): leen "botón" sin nombre
- Usuarios con baja visión: sin referencia semántica
- Control por voz (Dragon): no pueden activar el elemento
Remediación
Añadir aria-label descriptivo a cada botón. Alternativa: texto visualmente oculto con clase .sr-only.
<!-- ✅ Correcto — aria-label --> <button class="icon-btn" aria-label="Exportar informe a PDF"> <svg aria-hidden="true">...</svg> </button> <!-- ✅ Correcto — texto oculto --> <button class="icon-btn"> <svg aria-hidden="true">...</svg> <span class="sr-only">Exportar PDF</span> </button> /* CSS .sr-only */ .sr-only { position:absolute; width:1px; height:1px; clip:rect(0,0,0,0); overflow:hidden; white-space:nowrap; }
⏱ Esfuerzo: 2h · Impacto: bloquea usuarios con lector de pantalla
Ubicación y evidencia
Formulario de alta empresa → 14 campos usan solo placeholder como etiqueta. Al recibir foco, el placeholder desaparece y el usuario pierde el contexto del campo.
<!-- ❌ Incorrecto -->
<input type="email"
placeholder="correo@empresa.com" />
<input type="tel"
placeholder="Teléfono de contacto" />
Remediación
<!-- ✅ Correcto — label visible -->
<label for="email-empresa">
Correo electrónico
<span aria-hidden="true">*</span>
<span class="sr-only">(obligatorio)</span>
</label>
<input id="email-empresa"
type="email"
autocomplete="email"
aria-required="true"
placeholder="correo@empresa.com" />
⏱ Esfuerzo: 4h · Impacto: bloquea usuarios con lector de pantalla y cognitivos
Ubicación y evidencia
Dashboard → 6 gráficas Chart.js renderizadas en <canvas> sin descripción alternativa. Los datos son inaccesibles para usuarios de lector de pantalla.
<!-- ❌ Incorrecto -->
<canvas id="emissions-chart"
width="600" height="300">
</canvas>
Remediación
<!-- ✅ Correcto --> <canvas id="emissions-chart" role="img" aria-label="Evolución emisiones CO₂: Ene 12t, Feb 11t, Mar 9t, Abr 8t"> <!-- Tabla de datos de respaldo --> <table> <caption>Emisiones mensuales CO₂</caption> <tr><th>Mes</th><th>Toneladas</th></tr> <tr><td>Enero</td><td>12</td></tr> ... </table> </canvas>
⏱ Esfuerzo: 6h · Impacto: datos principales inaccesibles
Ubicación y evidencia
Modal de configuración de empresa → al navegar con Tab, el foco sale del modal y llega a elementos del fondo. Esc no cierra el diálogo. Usuario de teclado queda atrapado.
- Usuarios que navegan solo con teclado
- Usuarios de switch access
- Usuarios con temblor (no pueden usar ratón)
Remediación
// Focus trap + Esc handler
function trapFocus(modal) {
const focusable = modal.querySelectorAll(
'a,button,input,select,textarea,[tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length-1];
modal.addEventListener('keydown', e => {
if (e.key === 'Escape') closeModal();
if (e.key === 'Tab') {
if (e.shiftKey && doc.activeElement===first)
{ last.focus(); e.preventDefault(); }
else if (!e.shiftKey && doc.activeElement===last)
{ first.focus(); e.preventDefault(); }
}
});
first.focus();
}
⏱ Esfuerzo: 3h · Impacto: bloquea navegación completa por teclado
Ubicación y evidencia
Al enviar formulario de alta con errores, se muestra un banner genérico "Hay errores en el formulario" sin indicar qué campo falló ni ofrecer corrección. Los mensajes de error no están vinculados programáticamente al input.
<!-- ❌ Incorrecto --> <div class="error-banner"> Hay errores en el formulario </div> <input type="email" class="error" /> <!-- el campo no sabe que tiene error -->
Remediación
<!-- ✅ Correcto --> <label for="email">Email corporativo</label> <input id="email" type="email" aria-invalid="true" aria-describedby="email-err" /> <span id="email-err" role="alert" class="field-error"> Introduce un email válido (ej: nombre@empresa.com) </span> /* Mover foco al primer campo con error */ document.querySelector('[aria-invalid]') ?.focus();
⏱ Esfuerzo: 3h · Impacto: usuarios cognitivos y lector de pantalla no pueden corregir
- ◉ aria-label en 12 botones de icono
- ◉ Labels visibles en formulario de empresa
- ◉ Focus trap en modal de configuración
- ◉ Errores vinculados con aria-describedby
- ◉ Alt text en gráficas canvas
- ◉ Corregir contraste verde #4CAF50 → #3d8b3d
- ◉ Añadir skip link en todas las páginas
- ◉ Títulos de página únicos por vista
- ◉ Restablecer estilos de foco (outline)
- ◉ prefers-reduced-motion en animaciones
- ◉ Datepicker accesible por teclado
- ◉ Tabla con th scope + caption
- ◉ Live regions para mensajes de estado
- ◉ Jerarquía de encabezados h1-h6
- ◉ Test con NVDA + VoiceOver real