wandres.dev
ACCESIBILIDAD · apps para todos

Auditar: inspector, navegación a ciegas y proceso

Una app no es accesible porque alguien lo afirme: lo es porque se ha comprobado. Esta lección articula las tres capas de verificación que se refuerzan entre sí —la auditoría automática del inspector y de `performAccessibilityAudit`, la navegación manual con la pantalla apagada, y la prueba con personas que usan tecnologías de asistencia a diario— y explica cómo convertirlas en un control permanente del proceso en lugar de un ritual anual.

⏱ 18 min

Toda disciplina de calidad acaba enfrentándose a la misma pregunta incómoda: cómo se sabe. En accesibilidad la respuesta suele ser insatisfactoria porque el equipo confunde tres actividades distintas: ejecutar una herramienta, recorrer la app a ciegas y observar a alguien usarla de verdad. Las tres son verificación, pero detectan clases de defectos disjuntas y ninguna sustituye a las otras. La herramienta encuentra lo mecánico y lo encuentra sin cansancio; el recorrido manual encuentra lo estructural, que ninguna heurística puede juzgar; y la prueba con personas encuentra lo que ni siquiera sabías que era un problema. Una estrategia seria no elige entre ellas: las ordena por coste y las coloca en el punto del proceso donde cada una rinde más.

🎯 Al terminar esta lección sabrás
  • Operar el Accessibility Inspector y delimitar con precisión qué familia de defectos detecta.
  • Automatizar auditorías en la suite de pruebas con performAccessibilityAudit y gestionar los falsos positivos.
  • Ejecutar una navegación manual completa con la pantalla apagada y saber qué buscar.
  • Integrar la verificación en la definición de terminado, la revisión de código y la integración continua.

La capa automática

El Accessibility Inspector se abre desde el menú de herramientas de desarrollo de Xcode y ofrece tres modos que conviene distinguir. El de inspección muestra, para el elemento bajo el puntero, todos los campos del nodo: etiqueta, valor, sugerencia, rasgos, identificador y marco; es la forma más rápida de comprobar si lo que creíste declarar llegó al árbol. El de auditoría ejecuta un conjunto de comprobaciones sobre la pantalla activa y devuelve una lista de hallazgos con enlace al elemento culpable. Y el de ajustes permite alterar en caliente el tamaño de texto, el contraste, la transparencia y la inversión de color sobre la app en ejecución, incluso en dispositivo físico.

Una nota sobre expectativas antes de continuar: la literatura de accesibilidad web lleva años estimando que las herramientas automáticas detectan alrededor de un tercio de los problemas reales, y no hay motivo para suponer que en aplicaciones nativas la proporción sea mejor. Ese número no descalifica la automatización, la sitúa: es el filtro barato que elimina el ruido para que la atención humana se dedique a lo que solo un humano puede juzgar.

Las comprobaciones automáticas cubren, con nombres que varían poco entre versiones, seis familias: contraste insuficiente, elemento sin descripción, descripción insuficiente o redundante, área táctil menor que el mínimo, texto recortado y rasgos mal asignados o ausentes. Es exactamente el tipo de defecto que una persona detecta mal y una máquina detecta bien, porque son propiedades locales, medibles y sin contexto.

Un detalle de operación que ahorra frustración: el inspector se conecta a un proceso concreto, de modo que hay que elegir la app objetivo antes de auditar, y funciona igual contra el simulador que contra un dispositivo conectado por cable. La diferencia entre ambos no es menor. El simulador miente en todo lo que depende del renderizado real —materiales, sombras, gamma de pantalla— y por tanto los hallazgos de contraste conviene confirmarlos siempre en hardware.

Desde Xcode 15 esa misma maquinaria está disponible dentro de XCTest, lo que cambia la naturaleza del control: deja de ser una inspección puntual y pasa a ser una prueba de regresión que corre en cada integración.

func testAccesibilidadDelPanel() throws {
    let app = XCUIApplication()
    app.launch()
    app.buttons["boton.panel"].tap()

    try app.performAccessibilityAudit(for: [.contrast, .hitRegion, .dynamicType]) { issue in
        // Devolver true marca el hallazgo como conocido y no falla el test
        issue.element?.identifier == "grafica.decorativa"
    }
}

El manejador de incidencias merece atención porque es donde se decide si la herramienta será útil o se convertirá en ruido. Suprimir un hallazgo es legítimo cuando se ha analizado y se ha concluido que es un falso positivo; es corrosivo cuando se usa para silenciar lo que no apetece arreglar. La disciplina que funciona consiste en tratar cada supresión como una excepción documentada, con un comentario que explique por qué, del mismo modo que se documenta un aviso del compilador desactivado.

⚠️
Lo que ninguna herramienta puede encontrar

Un auditor automático verifica que exista una etiqueta, jamás que sea buena. No sabe que Botón es un nombre inútil, ni que la etiqueta describe el icono en lugar de la acción, ni que dos controles distintos se llaman igual. Tampoco puede juzgar el orden de lectura, porque el orden correcto depende del significado; ni la agrupación, porque saber qué elementos forman una unidad exige entender el dominio. Un informe automático limpio significa que no hay defectos mecánicos, no que la app se pueda usar.

La capa manual

La segunda capa es la que produce las revelaciones y la que casi nadie ejecuta con rigor. El protocolo es simple de enunciar y exigente de cumplir: activar VoiceOver, encender la cortina de pantalla con un triple toque de tres dedos para eliminar la tentación de mirar, y completar el recorrido principal de la app de principio a fin usando solo gestos de deslizamiento y el rotor. No se trata de comprobar elementos sueltos, se trata de ejecutar una tarea con un objetivo.

🕶️

Cortina de pantalla

Apaga la imagen y deja el sistema operativo funcionando. Sin ella se hace trampa sin darse cuenta: la vista periférica sigue orientando y el resultado del ensayo miente.

🎛️

El rotor

Girar dos dedos cambia el modo de navegación: por cabeceras, por enlaces, por controles, por acciones. Si tu app no ofrece cabeceras, el rotor lo delata de inmediato.

↩️

Gestos del sistema

El frotado con dos dedos para retroceder, el toque mágico para la acción principal y el rotor de acciones. Comprueba que tu interfaz no los bloquea ni los deja sin respuesta.

El resultado no debe medirse solo en defectos encontrados sino en tiempo. Cronometrar la tarea con lector de pantalla y compararla con el tiempo visual da un número comparable entre versiones y honesto: si completar una compra exige cuatro veces más tiempo, la app cumple la norma y sigue siendo mala. Ese cociente es la métrica que mejor resume la calidad real de la experiencia auditiva.

Durante ese recorrido hay que buscar cinco síntomas concretos, y conviene anotarlos en lugar de confiar en la memoria. Primero, paradas que no dicen nada: elementos anunciados como imagen o botón sin más. Segundo, exceso de paradas: tarjetas que exigen ocho deslizamientos para una información que a la vista se capta de golpe. Tercero, saltos de orden: el cursor va a un sitio que no corresponde a la lógica de lectura. Cuarto, callejones sin salida: hojas y diálogos de los que el cursor no sale o de los que se escapa al fondo. Y quinto, funciones inalcanzables: cualquier cosa que solo se logre con un gesto que no está expuesto como acción.

💡
El recorrido no acaba en VoiceOver

Repite la tarea principal con Voice Control diciendo mostrar nombres, y verás si tus controles tienen nombres pronunciables o si el sistema tiene que inventar números. Repítela con Full Keyboard Access en un iPad con teclado y comprobarás el orden de tabulación y la visibilidad del foco. Cada tecnología de asistencia consume el mismo árbol desde un ángulo distinto y expone defectos que las otras ocultan.

Hay un detalle metodológico que decide si el ensayo sirve o no: quien lo ejecuta no debe ser quien escribió la pantalla. El autor conoce el orden de los elementos, sabe dónde está cada botón y reconstruye el modelo mental sin darse cuenta, de modo que atraviesa sin fricción una interfaz que a otra persona le resultaría opaca. Cruzar los recorridos dentro del equipo cuesta media hora y multiplica el valor del ejercicio.

La tercera capa, la que ningún equipo puede simular, es la prueba con personas que usan estas tecnologías a diario. Un desarrollador con VoiceOver recién activado navega a la velocidad de un principiante y comete errores de principiante; una persona experta lo usa a una velocidad de habla que resulta ininteligible para quien no está entrenado y detecta en segundos fricciones que un ensayo interno no revela nunca. Presupuestar dos sesiones al año con usuarios reales rinde más que cualquier herramienta.

Meterlo en el proceso

Todo lo anterior se degrada si depende de la voluntad individual. La accesibilidad se comporta como cualquier propiedad transversal del software —seguridad, rendimiento, internacionalización—: si no está en el proceso, se pierde en la primera semana con prisa. La arquitectura de control que funciona reparte la verificación en cuatro puntos con costes crecientes y frecuencias decrecientes.

flowchart LR
a[Diseno: contraste y objetivos tactiles revisados] --> b[Pull request: checklist corto y obligatorio]
b --> c[Integracion continua: auditoria automatica por pantalla]
c --> d[Antes de publicar: recorrido manual con cortina]
d --> e[Cada semestre: sesion con personas usuarias reales]
e --> a

El primero de esos puntos, el diseño, es el que más se olvida y el único donde algunos defectos son gratis de evitar. Un par de colores que no cumple el umbral se detecta en la herramienta de diseño antes de existir en código; un objetivo táctil de veinticuatro puntos se corrige moviendo una guía; una pantalla que codifica el estado solo con color se arregla añadiendo un icono a la maqueta. Llevar tres comprobaciones al momento de la revisión de diseño elimina de raíz los defectos que después cuestan una tarde cada uno.

El elemento con mejor relación entre coste y efecto es el más humilde: una lista corta en la plantilla de la solicitud de cambios. Corta de verdad, cinco puntos como máximo, porque una lista de veinte se marca sin leer. Debe preguntar si los controles nuevos tienen etiqueta, si lo decorativo está oculto, si el color no es el único canal, si la vista se ha visto en el tamaño de texto máximo y si el recorrido con lector sigue siendo razonable. La revisión de código, además, es el único momento en que el coste de arreglar sigue siendo una línea.

// Prueba de humo por pantalla, barata y con enorme cobertura
func testAuditoriaDePantallasPrincipales() throws {
    let app = XCUIApplication()
    app.launch()
    for ruta in RutaPrincipal.allCases {
        navegar(app, a: ruta)
        try app.performAccessibilityAudit()
    }
}

La colocación de esa auditoría automática en el pipeline importa tanto como su existencia. Si corre en cada solicitud de cambios y falla, se convierte en una barrera que el equipo aprende a rodear desactivándola; si corre solo en la rama principal, los defectos entran y alguien los descubre tarde. El equilibrio que funciona en la mayoría de equipos es ejecutarla en cada cambio pero fallando únicamente sobre las pantallas ya saneadas, con el resto en modo informativo, y ampliar el conjunto bloqueante a medida que la deuda se reduce. Es el mismo patrón con que se introduce un comprobador estático en un proyecto viejo sin detener el trabajo.

Queda la dimensión documental, que en cuanto la app entra en ventas corporativas o en compra pública deja de ser opcional. La conformidad se declara por escrito contra un estándar concreto —normalmente las WCAG doble A a través de EN 301 549 en Europa, o la Sección 508 en Estados Unidos— en un informe de conformidad de accesibilidad. Ese documento no se improvisa en una semana: se construye con las evidencias que el proceso anterior va generando, y esa es una razón adicional para que las auditorías dejen rastro versionado en lugar de vivir en la cabeza de alguien.

Que no vuelva a romperse

Una app accesible no es un estado que se alcanza, es un estado que se mantiene, y el enemigo es la regresión silenciosa. El patrón típico se repite con precisión: alguien refactoriza una vista, sustituye un Button por un contenedor con gesto para lograr un efecto visual, y la pantalla deja de ser navegable sin que ninguna prueba funcional se entere, porque a efectos de lógica todo sigue funcionando. La única defensa realista es hacer que las propiedades de accesibilidad sean observables por el sistema de pruebas y no solo por una persona atenta.

Las pruebas de instantánea ayudan más de lo que parece si se configuran con criterio. Capturar cada vista crítica en dos escenarios —el predeterminado y el tamaño de texto máximo con contraste aumentado— convierte cualquier recorte, solapamiento o desaparición de texto en un cambio de imagen que el revisor ve en el propio diff. No detecta problemas semánticos, pero cubre por completo la familia de defectos de layout, que es la más frecuente y la más aburrida de encontrar a mano.

// Contrato explicito: si alguien rompe la semantica, el test lo dice
func testLaFilaEsUnSoloElementoAccionable() throws {
    let app = XCUIApplication()
    app.launch()
    let fila = app.buttons["fila.tarea"]
    XCTAssertTrue(fila.exists)
    XCTAssertEqual(fila.label, "Comprar pan, pendiente")
    XCTAssertTrue(fila.isHittable)
}

Ese tipo de aserción merece un comentario, porque es donde muchos equipos se equivocan de nivel. Afirmar sobre la etiqueta exacta de cada elemento produce pruebas frágiles que fallan con cada cambio de redacción y acaban borrándose. Afirmar sobre las propiedades estructurales —que existe un único elemento accionable por fila, que el elemento tiene rasgo de botón, que es alcanzable, que el contenedor modal atrapa el foco— produce pruebas estables que capturan justo las regresiones que importan. La regla es la de siempre: prueba la forma, no el contenido.

Conviene cerrar con la métrica, porque lo que no se mide se abandona. Tres indicadores bastan y todos son baratos de obtener: número de hallazgos automáticos abiertos por pantalla, porcentaje de pantallas cubiertas por la auditoría en integración continua, y tiempo que tarda una persona experta en completar la tarea principal con lector de pantalla comparado con el que tarda visualmente. El tercero es el más revelador de los tres, porque no mide conformidad sino experiencia: una app puede aprobar todas las comprobaciones y seguir exigiendo el triple de tiempo para hacer lo mismo.

La accesibilidad no se audita al final por la misma razón por la que la seguridad no se audita al final

Existe una analogía exacta y vale la pena llevarla hasta el fondo, porque explica por qué los programas de accesibilidad basados en auditorías anuales fracasan con tanta regularidad. Nadie que entienda de seguridad propone escribir una aplicación durante un año y contratar después una revisión que la vuelva segura, y no por falta de presupuesto: porque la seguridad no es una capa que se añada, es una propiedad emergente de miles de decisiones locales —dónde se valida la entrada, quién posee un secreto, qué frontera confía en qué— que una revisión final solo puede inventariar, nunca cambiar sin reescribir. La accesibilidad tiene exactamente esa estructura. Una etiqueta no es un adorno, es la consecuencia de haber decidido qué significa un control; el orden de lectura es la consecuencia de una jerarquía; el comportamiento con texto grande es la consecuencia de cómo se formularon las restricciones de layout. Una auditoría al final produce entonces un documento largo cuyas conclusiones son en su mayoría irreparables a coste razonable, y el equipo aprende la lección equivocada: que la accesibilidad es cara. Lo caro no era la accesibilidad, era el momento. El desplazamiento mental que hay que hacer es el mismo que la industria ya hizo con las pruebas y con la seguridad: dejar de tratarla como una fase y empezar a tratarla como un invariante, algo que se afirma de cada cambio y se verifica de forma continua, con herramientas automáticas para lo mecánico, revisión humana para lo semántico y contacto real con usuarios para lo que ninguna de las dos ve. Un equipo que interioriza el invariante deja de necesitar campañas de accesibilidad, del mismo modo que un equipo con pruebas deja de necesitar semanas de estabilización.

📝
Lo esencial

Tres capas complementarias: automática para lo mecánico —contraste, área táctil, descripciones ausentes, texto recortado—, manual para lo estructural —orden, agrupación, callejones sin salida, funciones inalcanzables— y con usuarios reales para lo que no imaginas. El Accessibility Inspector inspecciona, audita y altera ajustes en caliente; performAccessibilityAudit lleva esas mismas comprobaciones a XCTest y a integración continua. Ninguna herramienta juzga si una etiqueta es buena. Y todo se degrada si no está en la definición de terminado, en la plantilla de revisión y en el pipeline.

⚔️ Montar el control permanente
  1. Ejecuta la auditoría del Accessibility Inspector sobre las cinco pantallas más usadas y clasifica los hallazgos en mecánicos, semánticos y falsos positivos antes de arreglar nada.
  2. Añade una prueba de interfaz que recorra esas cinco pantallas invocando performAccessibilityAudit y documenta por escrito cada supresión que introduzcas.
  3. Completa la tarea principal de tu app con la cortina de pantalla activada y cronométralo; repítelo una semana después de los arreglos y compara los tiempos.
  4. Redacta una lista de cinco comprobaciones para la plantilla de solicitudes de cambio y aplícala durante dos semanas midiendo cuántos defectos captura antes de fusionar.
  5. Repite el recorrido principal con Voice Control y con teclado completo, y anota qué defectos aparecen que VoiceOver no había revelado.