Calidad del Frontend
Estándar de calidad para cualquier interfaz web dirigida al usuario. Requisitos estrictos (accesibilidad, rendimiento, mobile-first), no preferencias estilísticas. Las revisiones detectan errores que importan a los usuarios reales: páginas lentas, navegación por teclado rota, layouts que se desplazan al cargar.
Enrutamiento del modelo
Usa la preferencia de frontend del operador cuando un modelo necesite criticar o reescribir UI, UX, jerarquía visual, texto de accesibilidad o detalles de interacción:
source "$WALTER_OS_HOME/scripts/walter/lib/model-router.sh"
frontend_model=""
walter_model_resolve frontend frontend_model
La preferencia por defecto es Claude. El operador puede redirigir el alias a otra ruta de LiteLLM en ~/.config/walter-os/overlay/personal.env.
Esta habilidad se activa automáticamente en diffs que tocan el frontend. Para una landing page desde cero, usa landing-page-fast (que incluye estas reglas pero proporciona andamiaje).
Por qué esto es importante específicamente para este operador
- [Proyecto A] los usuarios son proveedores PyME argentinos — muchos en teléfonos, a menudo en 3G/4G con conexión intermitente. Sitio lento = usuarios se van. WCAG importa porque algunos usuarios tienen baja alfabetización técnica + visión reducida.
- [Proyecto B] los usuarios incluyen pacientes con discapacidades. WCAG no es "bonito de tener" — es un requisito de usabilidad.
- [Empresa] docs es leído por desarrolladores sofisticados que instantáneamente abandonan sitios lentos o con teclado roto.
Accesibilidad (WCAG 2.2 AA — requerido)
Teclado
- Cada elemento interactivo accesible con Tab. Botones, enlaces, campos de formulario, controles personalizados. Ejecuta una navegación manual con Tab; si algún control se salta, BLOQUEANTE.
- Foco visible. El contorno
:focus-visibledebe ser obvio. No pongasoutline: nonesin una alternativa. - Orden de tabulación lógico. El orden de lectura coincide con el orden del DOM; evita valores
tabindex>0 (rompen el flujo natural). - Enlace de salto. El primer elemento enfocable en cada página es "Saltar al contenido principal".
- Escape cierra modales. Atrapa el foco dentro del modal hasta que se cierre.
HTML semántico
<button>para acciones,<a>para navegación. Nunca uses<div onClick>para ninguno.- Encabezados jerárquicos: un
<h1>por página;<h2>...<h6>no saltan niveles. - Hitos:
<header>,<nav>,<main>,<footer>. Roles ARIA solo cuando ningún elemento semántico encaja. - Las listas son
<ul>/<ol>. No simules listas con<div>. - Los controles de formulario tienen
<label>. El atributoforcoincide conid.
Color y contraste
- Contraste mínimo WCAG AA: 4.5:1 para texto del cuerpo, 3:1 para texto grande (18pt+ o 14pt+ negrita), 3:1 para componentes de UI y gráficos.
- El color no es el único indicador: los estados de error muestran icono + texto + color, no solo un borde rojo.
- Probado con daltonismo simulado. Usa Stark, axe DevTools o los simuladores de visión cromática de las herramientas de desarrollo del navegador.
Formularios
- Cada entrada tiene una etiqueta visible. El marcador de posición NO es una etiqueta.
- Los mensajes de error están asociados programáticamente mediante
aria-describedby. - Los campos obligatorios están marcados. Tanto visualmente (
*o "obligatorio") COMO programáticamente (aria-required="true"orequired). - Los errores de validación no desaparecen al enfocar. Persisten hasta que se corrigen o se descartan.
- Los estados de éxito se confirman. Después de enviar, los usuarios de lectores de pantalla saben que funcionó.
Imágenes y medios
- Atributo
alten cada<img>. Vacío (alt="") para decorativas; descriptivo para contenido. Imágenes generadas por IA: describe lo que realmente está en la imagen, no la instrucción. - Subtítulos/transcripciones para video/audio.
- Sin reproducción automática con sonido.
- Las animaciones respetan
prefers-reduced-motion:@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }
Herramientas
@axe-core/reacten modo desarrollo — registra problemas de accesibilidad en la consola.eslint-plugin-jsx-a11y— detecta en tiempo de compilación.- Prueba manual con lector de pantalla antes del lanzamiento: VoiceOver (Mac), NVDA (Windows), TalkBack (Android).
- Puntuación de accesibilidad de Lighthouse ≥ 95 en CI.
Rendimiento — Core Web Vitals
Las 3 métricas en las que Google clasifica. PASA LAS TRES para estar en "Bueno":
LCP (Largest Contentful Paint) — velocidad de carga
Objetivo: < 2,5 s en 3G. En el mundo real, para [Proyecto A] es más agresivo ya que las redes móviles argentinas son más lentas que la mediana.
Cómo:
- Precarga la imagen LCP:
<link rel="preload" as="image" href="...">. - Sin recursos que bloqueen el renderizado: aplaza JS no crítico, incluye CSS crítico en línea.
- Fuentes auto-alojadas con
font-display: swapo usa la pila de fuentes del sistema. - CDN para activos estáticos: Vercel, Cloudflare R2, Bunny.net.
- HTTP/2 o HTTP/3: Vercel/Cloudflare por defecto.
- Comprime imágenes: WebP/AVIF, dimensionadas para el viewport.
- Optimiza la imagen principal específicamente: casi siempre es LCP.
INP (Interaction to Next Paint) — capacidad de respuesta
Objetivo: < 200 ms p75. Reemplazó a FID en 2024.
Cómo:
- Sin tareas largas > 50 ms en el hilo principal durante la interacción.
- Aplaza la hidratación (islas de Astro, streaming de Next.js, qwik).
- Web Workers para cálculos pesados.
- Debounce/throttle en los manejadores de entrada.
- Evita
dangerouslySetInnerHTMLcon cadenas grandes — el análisis bloquea. useMemo/memosolo donde se mide, no de forma preventiva.
CLS (Cumulative Layout Shift) — estabilidad visual
Objetivo: < 0,1.
Cómo:
- Las etiquetas de imagen y video tienen
width+height(el navegador reserva espacio). - Reserva espacio para anuncios/embebidos con min-height.
- No insertes contenido encima del existente: los banners de notificación van debajo del pliegue o empujan hacia abajo mediante transform, no insertándolos en el DOM arriba.
- Fuentes web: usa
font-display: swap+size-adjustpara coincidir con las métricas, Ofont-display: optionalsi puedes. - Pantallas de esqueleto dimensionadas para coincidir con el contenido real.
Tamaño del bundle
- Presupuesto inicial de JS: < 150 KB comprimido (gzip) para landing pages, < 250 KB para shells de aplicación.
- División de código por ruta (Next.js, Astro, SvelteKit lo hacen por defecto).
- Importaciones dinámicas para dependencias pesadas no críticas (gráficos, editores).
- Audita dependencias:
bundle-analyzeronext-bundle-analyzer. Elimina la biblioteca de UI de 10 MB de la que importaste un solo componente. - Tree-shaking funcionando: importa solo lo que necesitas (
import { thing } from 'lib'noimport lib from 'lib').
Patrones de carga
- Preconexión a orígenes requeridos:
<link rel="preconnect" href="...">. - Prelectura de páginas probables siguientes al pasar el ratón/enfocar (Next.js lo hace).
- Carga diferida de imágenes debajo del pliegue:
loading="lazy". - Carga diferida de iframes:
loading="lazy". - Service Worker para modo offline-first (donde tenga sentido — [Proyecto A] se beneficia, las páginas de marketing no).
Estados — cada componente maneja los cinco
Para cualquier UI basada en datos, BLOQUEANTE si algún estado no se maneja:
- Carga — esqueleto o spinner. NO pantalla en blanco. NO cambio de layout.
- Vacío — estado sin datos por primera vez. Con CTA para llenar.
- Error — mensaje claro, botón de reintentar, sin traza de pila al usuario. Registrado en Sentry.
- Parcial / desactualizado — cuando los datos provienen de caché o de una obtención parcial.
- Éxito — el camino feliz.
Ejemplo para una lista:
if (isLoading) return <ListSkeleton />
if (error) return <ErrorState onRetry={refetch} />
if (data.length === 0) return <EmptyState cta={...} />
return <List items={data} />
Si tu componente tiene solo el estado de éxito mostrado en el diff, pregúntate: ¿dónde viven los otros 4?
Mobile-first
- Diseña primero para 360 px de ancho, escala hacia arriba.
- Objetivos táctiles ≥ 44×44 px (Apple HIG) o 48×48 px (Material).
- Sin interacciones solo hover: cualquier cosa disponible en hover también tiene una ruta de foco/toque.
- Prueba en un dispositivo real o con chrome devtools limitado: red 3G, ralentización de CPU 4×.
- Los formularios funcionan sin zoom: input
font-size: 16px+(de lo contrario iOS hace zoom al enfocar).
Referencia de herramientas
CI / pre-fusión:
- Lighthouse CI con presupuestos por métrica.
@axe-core/clio subconjunto de accesibilidad de Lighthouse.bundlewatchpara fallar PRs que aumenten el bundle más allá del presupuesto.- Regresión visual: Chromatic (Storybook), Percy o capturas de pantalla de Playwright verificadas.
Local:
- Chrome DevTools paneles de Rendimiento + Lighthouse.
unlighthousepara auditoría de sitio completo.@unjs/check-vitalspara una verificación rápida de Web Vitals.
Formato de salida
**[BLOQUEANTE|ADVERTENCIA|NIT] [FE-N] <una línea>**
Donde: `ruta/hacia/Componente.tsx:42-58`
Categoría: <a11y | perf | estados | mobile | semántica>
Impacto: <qué usuario / escenario / métrica>
Solución: <cambio específico, código si cabe>
Verificar: <cómo confirmar — ejecución de Lighthouse / prueba manual / etc.>
Gravedad:
- BLOQUEANTE: violación de WCAG AA, flujo principal roto, Web Vitals fallando en rango "Pobre". Debe arreglarse.
- ADVERTENCIA: brecha de mejores prácticas, regresión de rendimiento que aún no es visible para el usuario.
- NIT: oportunidad de mejora (bundle más pequeño, mejor esqueleto).
Reglas estrictas
- Sin
<div onClick>para botones. Siempre. - Sin imágenes sin
alt. Siempre. - Sin cambio de layout por contenido real. Siempre reserva espacio.
- Sin reproducción automática con sonido. Siempre.
prefers-reduced-motionrespetado. Siempre.
Qué NO cubre esta habilidad
- Backend (usa
web-security-baseline). - Esquema / consultas de DB (usa
data-migration-safety). - Identidad de marca / diseño visual (usa
brand-creation). - Construcción de nuevas landing pages (usa
landing-page-fastque proporciona andamiaje).
Referencias
- Referencia rápida de WCAG 2.2
- Documentación de Core Web Vitals en web.dev
- Ruta de aprendizaje de rendimiento en web.dev
- Componentes Inclusivos por Heydon Pickering
- Lista de verificación del Proyecto A11y