frontend — pantallas que funcionan para cada usuario, cada estado y se mantienen fáciles de cambiar
Sigue primero el CLAUDE.md, docs/conventions.md y docs/design-system.md del repositorio; esta skill
rellena vacíos y nunca los anula. El trabajo visual (tokens, layout, color, CSS) pertenece a
css-design; ejecutar la app como lo hará el usuario y el veredicto final pertenecen a verify-app.
Elige el stack. Vue 3 y React son ambos de primera clase y siguen las mismas reglas. Usa el
framework que ya tenga el repositorio; para una app nueva usa el frontend por defecto del perfil de entrega
(ver el README del repo), si no, pregunta una vez en el triage. Vue: TypeScript + Vite (npm create vue@latest -- --ts --router --pinia --vitest --playwright --eslint --prettier) con references/vue-patterns.md.
React: TypeScript + Vite (o el meta-framework del repo) con references/react-patterns.md. Nunca
mezcles frameworks en una misma app. ¿Necesitas SSR o SSG para SEO? Registra Nuxt o Next.js en un ADR (architect);
esta skill cubre las partes del lado del cliente de todas formas.
Flujo de trabajo
- Carga las convenciones e inventario del repositorio. Lee
CLAUDE.md,docs/conventions.md,docs/design-system.md; lista las partes reutilizables (ls src/components src/features src/composables src/hooks src/stores). Detecta la capa de datos, router, formulario y bibliotecas de i18n (Vue:grep -E '"(@tanstack/vue-query|pinia|vue-router|vee-validate|vue-i18n)"' package.json; React:grep -E '"(@tanstack/react-query|@tanstack/react-router|react-router|react-hook-form|i18next|react-intl)"' package.json). Nunca agregues una segunda capa de datos, router, store, formulario o biblioteca de i18n. Evidencia: las notas de la historia listan los componentes y composables/hooks reutilizados y cualquier nuevo, con la razón. - Escribe la tabla de estados antes del código. Para cada vista: cada pieza de datos y dónde vive —
servidor (query), URL (parámetros de ruta / búsqueda), cliente compartido (store de Pinia, o contexto de React o el
store del repo), local (
ref/useState), derivado (computed/ una constante en render), externo (un composable ouseSyncExternalStoreque envuelve la API del navegador). Luego lista los cuatro estados (cargando, vacío, error, éxito) y las claves de copia para cada uno. Evidencia: la tabla en la historia. - Escribe las pruebas fallidas primero. Vitest +
@testing-library/vueo@testing-library/react,userEvent.setup(), consultas por rol y nombre accesible, un comportamiento por prueba, red falsificada en el límite del servicio (MSW o el cliente falso del repositorio ensrc/test-utils). Cubre cada estado, la ruta de teclado y un caso negativo. Evidencia: la ejecución muestra rojo por la razón correcta. - Construye dentro de la carpeta de feature.
src/features/<nombre>/services(llamadas API tipadas, sin framework) →composables/hooks(useXenvolviendouseQuery/useMutationy una fábrica de claves) →components(Name.vuecon<script setup lang="ts">, oName.tsx) → la vista de ruta que los compone. Props y eventos tipados, sinany, sin!no nulo, imports con@/. Evidencia:git diff --statse mantiene dentro de la feature más cambios compartidos deliberados. - Copia y formato. Cada cadena visible proviene de los archivos de locale (vue-i18n
useI18n(), o la biblioteca i18n del repositorio React o módulo de copia) con las mismas claves en cada locale; una prueba verifica paridad. Números y fechas a través del formateador i18n oIntl. Evidencia: prueba de paridad verde; sin copia hardcodeada, forzado por lint (@intlify/vue-i18n/no-raw-text; React:react/jsx-no-literalso la regla del plugin i18n) — cita el código de salida del lint. - Estiliza con
css-design. Solo tokens, BEM bloque = nombre del componente, cada estado estilizado; estilos.vuelintados a través depostcss-html. Evidencia: Stylelint verde. - Stories (bibliotecas de componentes). Una story por estado, ambos temas; Storybook axe pasa. Evidencia: la prueba de stories y la ejecución de axe.
- Gates, encadenados por código de salida.
npm run lint && npm run type-check && npm run test:unit -- --run(Vue:vue-tsc --build; React:tsc -b), omake ci; nunca juzgues desde| tail. Evidencia: código de salida 0 y cobertura en o por encima del umbral del repositorio. - Recorrido en un navegador real contra el build de producción.
npm run buildy sirve la salida (vite preview, o el servidor estático que use el repositorio). Agrega o extiende una especificación de Playwright para la ruta principal con@axe-core/playwright(etiquetaswcag2a wcag2aa wcag21aa wcag22aa, serios y críticos = 0). Capturadocs/delivery/<slug>/evidence/<vista>-390.pngy-1280.png, más el tema oscuro si la app lo tiene. Abre cada captura de pantalla antes de citarla. Evidencia: especificación verde, PNGs. - Prueba de teclado y lector de pantalla. Tabula a través de la nueva vista: orden, foco visible, Escape
cierra superposiciones y el foco vuelve al disparador; después de la navegación, el foco aterriza en el nuevo encabezado
y el título cambió. Verifica nombres y anuncios puntualmente con un lector de pantalla (VoiceOver
Cmd+F5, o NVDA). Evidencia: una nota de tres líneas en la sección de QA de la historia. - Entrega. Ejecuta
verify-apppara la afirmación de extremo a extremo; dentro dedeliver-loopla revisión independiente califica el ítem. No repitas sus pasos aquí.
Lista de verificación central
- Nada almacenado que se pueda derivar: usa
computed(React: una constante en render); ningún watcher o efecto cuyo único trabajo sea establecer otro estado. - Watchers y efectos solo sincronizan con el exterior (APIs del DOM, suscripciones, temporizadores, widgets
de terceros) y limpian (
onWatcherCleanup/onUnmounted; React: el retorno del efecto). El trabajo causado por una acción del usuario se ejecuta en el manejador de eventos. - Los datos del servidor nunca se copian en estado local o un store; las claves de query contienen cada entrada que el fetch utiliza (en Vue como refs o getters, para que un cambio refetchee).
- Selección, filtros, pestañas y paginación que un usuario compartiría o recargaría viven en la URL query.
- Cada vista de datos renderiza estados de carga, vacío, error (con reintento) y éxito; las pruebas cubren los cuatro.
- Cada entrada tiene un
<label for>visible (ids deuseId()en ambos frameworks); los errores son texto vinculado conaria-describedby, la entrada se conserva, el foco se mueve al primer campo inválido al enviar. - Elementos nativos primero (
button,a href/router link,dialog,details,input type); sindiv @click/div onClick. - Widgets personalizados siguen el patrón WAI-ARIA APG correspondiente, teclado incluido.
- Resultados asíncronos que no mueven el foco se anuncian mediante una región
role="status"/role="alert"renderizada una vez y vacía (Vue: teletransportada abody; React: un portal, si debe sobrevivir a la vista). - Cambio de ruta:
document.titleactualizado y foco movido alh1omainde la página (Vue: unrouter.afterEach+ ref de plantilla; React: el equivalente del router). - Los errores se capturan por ruta y por widget riesgoso (Vue: un componente de límite
onErrorCaptured; React: un límite de error) y muestran un reintento, nunca una página en blanco. - Sin cadenas de visualización hardcodeadas; cada locale tiene las mismas claves.
- Las pruebas consultan por rol/label/texto, interactúan con
user-event, y nunca afirman sobre nombres de clase, internos de componentes (wrapper.vm, valores de estado), o llamadas a composables/hooks. - Sin
v-html/dangerouslySetInnerHTMLa menos que se haya sanitizado con DOMPurify justo antes de usar; URLs de datos están en lista blanca parahttps:/mailto:/relativas. shallowRef/markRaw/v-memo/KeepAlive(React:memo/useMemo/useCallback) solo con una medición de perfilador o una necesidad documentada.- Gráficos y visualizaciones de datos tienen un nombre accesible y resumen, una alternativa en tabla de datos (o texto), una paleta segura para daltonismo con series también diferenciadas por etiqueta, forma o patrón, y acceso por teclado a cualquier interacción — sea cual sea la biblioteca de gráficos que use el repositorio.
Anti-patrones
Vue:
watch(items, v => { filtered.value = v.filter(...) })— usacomputed.const { count } = reactive(state)oconst { name } = store— reactividad perdida; usatoRefs/storeToRefs, o prefiereref.watch(props.id, ...)o pasar una prop desestructurada a un composable — pasa un getter() => id.- Mutar una prop o datos de
useQuery; vincular datos de query directamente av-model— copia a un formulario. v-ifyv-foren el mismo elemento;:key="index"en una lista reordenable.awaitantes deonWatcherCleanupo antes de registrar hooks de ciclo de vida en un composable.defineAsyncComponentpara un componente de ruta (las rutas toman() => import(...)directamente).next()en un guardia de navegación — devuelvefalse, una ubicación, o nada.- Pruebas que acceden a
wrapper.vmo llaman asetData.
React:
useEffect(() => setFiltered(items.filter(...)), [items])— calcula en render.- Resetear un formulario en un efecto cuando un id cambia — pasa
key={id}. - Hooks llamados dentro de condiciones o bucles; estado mutado en lugar de reemplazado.
key={index}en una lista reordenable; un nuevo objeto o función prop en cada render pasado a un hijo memoizado que fue memoizado por una razón medida.
Ambos:
- Fetch al montar sin cancelación, caché, reintento o rama de error — usa la capa de datos.
- Spinner sin rama vacía o de error; error tragado en
console.log. staleTimedejado en 0 para datos que cambian diariamente, por lo que cada focus refetchea.- Actualización optimista sin snapshot, rollback e invalidación al settled.
- Placeholder usado como label; solo color marcando un error;
role="button"sin teclas. getByTestIdpor todas partes; snapshots de página completa; probar detalles de implementación.- Un archivo barrel de feature que empuja todo al primer chunk de ruta.
:href="user.url"/href={user.url}sin verificar el esquema.
Evidencia antes de "hecho"
- Pruebas unitarias que fallan y luego pasan para cada estado y la ruta de teclado.
- Lint,
vue-tscotsc, pruebas encadenadas, salida 0; cobertura en o por encima del umbral. - Recorrido de Playwright contra el build de producción, axe serios/críticos = 0.
- Capturas de pantalla a 390 y 1280 px (y tema oscuro) en
docs/delivery/<slug>/evidence/, cada una abierta. - Nota de teclado y lector de pantalla en la sección de QA de la historia.
- Luego
verify-app.
Referencias
references/vue-patterns.md— la lista de verificación de Vue: SFC y API de componentes, trampas de reactividad, computed vs watch, composables, Pinia, TanStack Query para Vue, Vue Router (estado de URL, guardias, foco), formularios, i18n, accesibilidad específica, rendimiento, seguridad, herramientas; cada ítem con fuente.references/react-patterns.md— la lista de verificación de React: los mismos temas, más una variante de exportación estática (Next.js / Nuxt generate).references/a11y-and-testing.md— ítems WCAG 2.2 AA para código de UI, ARIA/APG, teclado y regiones vivas, gráficos, Testing Library para Vue y React, MSW, Playwright + axe, qué no probar.