Panel de gestión de pedidos B2B — React SPA — Mercado ES/LATAM
| Severidad | Componente / Descripción | Lectores afectados | WCAG |
|---|---|---|---|
| 🔴 Crítico |
Modal de Confirmación — Sin trampa de foco
Al abrir el modal de confirmación de pedido, el foco no queda atrapado dentro. Con Tab el usuario puede salir del modal y seguir navegando el contenido de fondo, perdiendo contexto.
|
NVDAVOJAWS |
2.1.2 Sin trampa de teclado |
| 🔴 Crítico |
Tabla de Inventario — Encabezados sin scope
Las celdas
<th> de la tabla de inventario no tienen atributo scope ni id asociado. NVDA y JAWS no anuncian el encabezado al navegar por celda con Ctrl+Alt+Flechas. |
NVDAJAWS |
1.3.1 Info y relaciones |
| 🔴 Crítico |
Botones de acción — Solo icono SVG, sin label
Los botones "Editar pedido", "Eliminar" y "Ver detalle" contienen únicamente SVG sin
aria-label ni texto alternativo. Todos los lectores anuncian solo "botón" sin propósito. |
NVDAVOJAWSTB |
4.1.2 Nombre, rol, valor |
| 🔴 Crítico |
Modal — No se anuncia como diálogo al abrir
El contenedor del modal usa
<div class="modal"> sin role="dialog" ni aria-modal="true". Los lectores no anuncian que se ha abierto un diálogo y no saben que el contenido de fondo está bloqueado. |
NVDAVOJAWS |
4.1.2 Nombre, rol, valor |
| 🟡 Medio |
Formulario — Errores no vinculados al campo
Los mensajes de error de validación están visualmente próximos al campo pero sin
aria-describedby ni aria-invalid. VoiceOver y NVDA no los anuncian al recibir el foco el campo. |
NVDAVO |
3.3.1 Identificación de errores |
| 🟡 Medio |
Formulario Multi-paso — Pasos sin anuncio de progreso
Al avanzar de paso (1/3 → 2/3 → 3/3) el lector no anuncia en qué paso se encuentra el usuario. Falta un live region o un h2 enfocable que informe del contexto actual.
|
NVDAVOTB |
3.2.3 Navegación consistente |
| 🟡 Medio |
Filtros de tabla — Combo sin
aria-expandedLos desplegables de filtro ("Estado", "Fecha", "Distribuidor") no actualizan
aria-expanded al abrirse/cerrarse. NVDA no anuncia si el menú está abierto o cerrado. |
NVDAJAWS |
4.1.2 Nombre, rol, valor |
| 🟡 Medio |
Dashboard — Sin landmark
mainLa página carece de landmark
<main> o role="main". Los usuarios no pueden saltar al contenido principal con la tecla D (NVDA) o el rotor (VoiceOver). |
NVDAVO |
1.3.6 Identificar el propósito |
| 🟡 Medio |
Modal — Foco no regresa al disparador al cerrar
Al cerrar el modal (botón Cancelar o Escape), el foco va al inicio del documento en vez de volver al botón que abrió el modal. Desorientador para usuarios de teclado y lector.
|
NVDAVOJAWS |
2.4.3 Orden de foco |
| 🔵 Bajo |
Notificaciones —
aria-live="assertive" demasiado agresivoLas notificaciones de actualización de estado del pedido interrumpen la lectura en curso. Deberían usar
aria-live="polite" salvo para alertas críticas. |
NVDAVO |
Buenas prácticas ARIA |
| 🔵 Bajo |
Links de paginación sin contexto
Los links de paginación dicen "Siguiente" y "Anterior" sin contexto de página. Mejor: "Página siguiente (3 de 8)" con
aria-label. |
NVDAVOJAWS |
2.4.6 Encabezados y etiquetas |
| ✅ OK |
Pestañas Catálogo/Historial/Analítica
Implementación correcta de
role="tablist", role="tab" y role="tabpanel" con navegación de flechas. Todos los lectores anuncian correctamente el tab activo y el panel asociado. |
NVDAVOJAWSTB |
ARIA 1.1 — Tabs Pattern |
aria-label
<button class="action-btn">
<svg width="20" height="20">...</svg>
</button> <!-- Anuncia: "botón" — inútil --><button class="action-btn" aria-label="Editar pedido #FH-2241">
<svg aria-hidden="true" width="20" height="20">...</svg>
</button> <!-- Anuncia: "Editar pedido #FH-2241, botón" --><div class="modal">
<h2>Confirmar pedido</h2>
<button>Cancelar</button>
<button>Confirmar</button>
</div><div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-desc"
>
<h2 id="modal-title">Confirmar pedido #FH-2241</h2>
<p id="modal-desc">Se enviará el pedido de 200 rosas. Esta acción no se puede deshacer.</p>
<button id="btn-cancel">Cancelar</button>
<button id="btn-confirm">Confirmar pedido</button>
</div>
// JS — Trampa de foco + retorno al disparador
function openModal(modal, trigger) {
lastFocus = trigger;
modal.querySelector('h2').focus();
modal.addEventListener('keydown', trapFocus);
}
function closeModal(modal) {
modal.removeEventListener('keydown', trapFocus);
lastFocus.focus(); // Retorna al botón disparador
}
function trapFocus(e) {
const focusable = modal.querySelectorAll(
'button, [href], input, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0], last = focusable[focusable.length - 1];
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) { last.focus(); e.preventDefault(); }
else if (!e.shiftKey && document.activeElement === last) { first.focus(); e.preventDefault(); }
}
if (e.key === 'Escape') closeModal(modal);
}<input type="email" name="email" />
<span class="error">Email no válido</span><label for="email">Email de contacto</label>
<input
id="email"
type="email"
name="email"
aria-invalid="true"
aria-describedby="email-error"
/>
<span id="email-error" role="alert">
Email no válido. Ej: pedidos@floreria.es
</span>
<!-- NVDA/VO anuncian el error al recibir foco el campo -->scope
<th>Referencia</th><th>Flor</th><th>Stock</th><th scope="col">Referencia</th>
<th scope="col">Variedad</th>
<th scope="col">Stock disponible</th>
<!-- NVDA anuncia: "Referencia, Variedad, columna 2" al navegar -->| Acción | Tecla |
|---|---|
| Siguiente encabezado | H |
| Leer todo desde cursor | Ins+↓ |
| Siguiente campo de formulario | F |
| Siguiente botón | B |
| Siguiente landmark | D |
| Lista de elementos | Ins+F7 |
| Navegar celdas de tabla | Ctrl+Alt+←→↑↓ |
| Cambiar modo Browse/Focus | Ins+Space |
| Acción | Tecla (VO = Ctrl+Opt) |
|---|---|
| Activar VoiceOver | Cmd+F5 |
| Leer todo | VO+A |
| Abrir Rotor | VO+U |
| Siguiente encabezado | VO+Cmd+H |
| Siguiente enlace | VO+Cmd+L |
| Activar elemento | VO+Space |
| Entrar en grupo | VO+Shift+↓ |
| Parar lectura | Ctrl |