Saltar al contenido
BeryX
Volver a las respuestas para RFP

Documentación de BeryX

Este documento técnico está disponible en portugués.

Relatório de conformidade de acessibilidade (ACR): BeryX 1.0.0

ACR baseado no VPAT® 2.5 (WCAG edition), em versão simplificada: só a tabela WCAG 2.2 de nível A e AA, com a cláusula correspondente da EN 301 549. VPAT® é uma marca registada do Information Technology Industry Council (ITI).

É uma auto-avaliação da BeryxTech Não é uma auditoria externa nem uma certificação, e não afirma conformidade com a EN 301 549. Cada estado diz o que o código faz hoje e onde está a prova. Quando não verificámos, dizemo-lo.

Campo Valor
Produto BeryX (BeryxTech): aplicação web e site público
Versão 1.0.0
Data do relatório 2026-09-28
Próxima revisão 2027-09-28
Normas WCAG 2.2 (W3C), níveis A e AA; EN 301 549 V3.2.1, cláusula 9 (Web)
Método Auto-avaliação: revisão de código por critério e verificações automáticas no CI
Contacto suporte@praxis.lbostuff.tech

Âmbito

  • Aplicação web (app.praxis.lbostuff.tech). A SPA React de frontend/src, com as 40 rotas de frontend/src/App.tsx, nos temas escuro (por omissão) e claro e nas línguas pt, en e es. Inclui as páginas sem sessão: login e registo, definir password, partilha pública de dataset (/publico/:token), montra pública do data product (/p/dominios/:token), revisão de contrato, subprocessadores, DORA/NIS2 e o modo TV (/tv).
  • Site público (praxis.lbostuff.tech). O site Next.js (repositório praxis-site) em pt, en e es: páginas do produto, documentação, RFP, segurança, preços, legal, changelog, diagnóstico e Área de Cliente.

Fora do âmbito deste relatório. Não foram avaliados e não contam como «Not Applicable»:

  • os emails (digest por persona em backend/app/domain/insights/digest_email.py, verificação de conta, convites);
  • as exportações em PDF (backend/app/infra/pdf.py) e os ficheiros CSV, XLSX e JSON de relatórios;
  • os documentos descarregáveis do site (PDF e PPTX em /recursos);
  • a consola interna da BeryxTech (/consola, só para staff);
  • a API REST, o SDK, a CLI, o servidor MCP e as integrações, que não têm interface gráfica;
  • as páginas de terceiros para onde a app encaminha: o checkout do Stripe e o IdP de SSO do cliente.

Método de avaliação

Verificações automáticas, que correm em cada PR. As 1 a 4 estão no job «Frontend (tsc + build)» de .github/workflows/ci.yml, a 5 no job do backend e a 6 no job «E2E (Playwright)»:

  1. Lint estático eslint-plugin-jsx-a11y (frontend/eslint.config.js), com alt-text, aria-props, aria-role, click-events-have-key-events, no-static-element-interactions e label-has-associated-control como erro. A prova de que o lint chumba está em frontend/src/a11y-lint.test.ts::chumba <div onClick> sem teclado nem papel.

  2. axe-core 4.13 em vitest/jsdom (frontend/src/a11y.test.tsx), com as tags wcag2a, wcag2aa, wcag21a, wcag21aa e wcag22aa. Cobre Layout, Login e registo, Modal, ConfirmDialog, Atividades, Datasets, Regras, CatalogTreemap, QualityCalendar, DomainEditor, o erro de rota e os quatro separadores da Equipa. Tem dois limites:

    • color-contrast está desligada, porque o jsdom não calcula cores;
    • heading-order só avisa.

    Das 40 rotas, só Login, Atividades, Datasets, Regras e Equipa são renderizadas como página. As restantes não são cobertas, incluindo os dashboards com gráficos, Problemas e Notificações.

  3. Contraste dos tokens (frontend/scripts/check-contrast.mjs) nos dois temas: texto ≥4,5:1 e não-texto ≥3:1. Falha com um par abaixo do mínimo, com uma classe de cor sem par ou com um anel de foco de cor fixa. As falhas conhecidas ficam declaradas como followUp.

  4. Testes de comportamento: prisão de foco e Esc dos diálogos (frontend/src/components/Modal.test.tsx), skip link, foco e título por rota (frontend/src/components/Layout.test.tsx, frontend/src/lib/usePageTitle.test.tsx) e avisos temporizados (frontend/src/components/Toast.test.tsx).

  5. Guardas estáticas deste relatório (backend/tests/test_accessibility_conformance.py). Provam, no código da app, as ausências que o axe não vê: window.confirm, bloqueio de colar, bloqueio de orientação ou de zoom, sensores de movimento, multimédia e reordenação por CSS. No site, o equivalente é praxis-site/tests/accessibility.test.mjs, que corre no pnpm test do CI do site.

  6. axe-core no browser real, nos e2e dos 10 fluxos do guião (cartão #324, docs/GUIAO_TESTES.md): @axe-core/playwright com as mesmas tags, agora com color-contrast e target-size, em cada ecrã visitado por F1–F10 (diálogos abertos incluídos). Impacto serious ou critical fora da allowlist (frontend/e2e/a11y-allowlist.json, vazia; cada entrada exige cartão e razão) falha o job (frontend/e2e/support/a11y.ts::expectNoA11yViolations). O projeto mobile (Pixel 5, 393 px) corre F2 e F3 e falha com scroll horizontal; o projeto es percorre as páginas dos fluxos em ES e falha com chaves pedidas sem tradução (frontend/e2e/support/i18n.ts::expectNoMissingTranslations).

No CI do site correm também, desde o cartão #331, o contraste dos tokens (praxis-site/scripts/check_contrast.mjs, porta do script da app, no pnpm build) e o axe no browser (praxis-site/scripts/check_a11y_browser.mjs). O axe corre com as mesmas tags na home, em /datavault e em /diagnostico, nos dois temas e com «Reduzir movimento». O mesmo script percorre o menu móvel só com teclado a 390 px e confirma que nada se mexe com a preferência ligada.

Verificações pontuais, fora do CI:

  1. Browser real (Chromium com axe color-contrast e target-size) em Problemas, Regras e Equipa, nos dois temas, com as paragens de Tab medidas em Login, Conexões e Steward. Está em docs/verificacao/cartao-302/README.md; os rácios de contraste antes e depois estão em docs/verificacao/cartao-301/README.md. No site, o mesmo axe correu na página deste ACR e no RFP, em pt e en, nos dois temas, a 1280 px e a 320 px (docs/verificacao/cartao-313/README.md). Depois do cartão #331 repetiu-se nessas páginas, em /farol/energia e em /tese, e o Lighthouse (Accessibility) deu 100 na home, em /datavault e em /diagnostico, nos dois temas (docs/verificacao/cartao-331/README.md).
  2. Revisão de código por critério, feita para este relatório a 2026-09-28, na app e no site.

O que não foi feito:

  • auditoria externa;
  • testes com pessoas com deficiência;
  • sessões sistemáticas com leitores de ecrã. O frontend/README.md tem um roteiro manual de VoiceOver, mas não há registo de execução;
  • medição a 200 % e 400 % de zoom e a 320 px de largura. Na app não houve nenhuma; no site só se mediu a página deste ACR, o RFP e três docs;
  • axe em browser real fora dos ecrãs dos 10 fluxos do guião, e viewport móvel fora de F2 e F3;
  • axe no browser nas restantes páginas do site. O CI do site cobre a home, /datavault e /diagnostico (cartão #331).

Legenda

  • Supports: sem falhas conhecidas, e a nota indica a prova (teste, lint ou script do CI, guarda estática, ou a verificação feita para este relatório).
  • Partially Supports: há partes que não cumprem, ou a verificação não cobriu o âmbito todo. A nota diz qual das duas e onde.
  • Does Not Support: a maior parte da funcionalidade não cumpre.
  • Not Applicable: não existe esse tipo de conteúdo (por exemplo, multimédia).

Regra deste relatório: nada é «Supports» só por não termos procurado.

4.1.1 Parsing. Não entra na tabela. O WCAG 2.2 retirou-o por estar obsoleto e considera-o sempre cumprido em HTML. A EN 301 549 V3.2.1 ainda o lista (9.4.1.1). Os seis critérios novos do WCAG 2.2 (2.4.11, 2.5.7, 2.5.8, 3.2.6, 3.3.7 e 3.3.8) não têm cláusula nessa versão da norma e aparecem com «—».

Resumo

Estado App Site
Supports 23 21
Partially Supports 26 25
Does Not Support 0 0
Not Applicable 6 9

Nem a app nem o site cumprem ainda o WCAG 2.2 AA na íntegra. App: a base está no CI (estrutura, teclado, foco, títulos, diálogos, contraste dos tokens), mas faltam o score das folhas do treemap em texto, tooltips que se possam dispensar e a verificação no browser e em móvel fora dos 10 fluxos do guião (que o CI já cobre com axe, 393 px e ES desde o cartão #324). Site: tem skip link, lang por locale e tabelas marcadas. Desde o cartão #331, o contraste dos tokens e o axe no browser (home, /datavault e /diagnostico, nos dois temas) estão no CI do site. As animações param com «Reduzir movimento» e o menu móvel anuncia o estado. Faltam um controlo de pausa para quem não usa a preferência do sistema e o axe nas restantes páginas. As lacunas, com o plano de cada uma, estão em Lacunas conhecidas e plano.

Critérios WCAG 2.2 A e AA

Critério Nível EN 301 549 App Site Observações e evidência
1.1.1 Non-text Content A 9.1.1.1 Partially Supports Partially Supports App: os ícones são aria-hidden (frontend/src/lib/navigation.tsx::ICON_PROPS). O alt é obrigatório por lint (frontend/src/a11y-lint.test.ts::chumba <img> sem alt). O heatmap e o radar têm tabela equivalente (frontend/src/a11y.test.tsx::QualityCalendar: heatmap com 90 dias e tabela equivalente, frontend/src/components/AiReadinessRadar.tsx). Desde o cartão #330, os gráficos Recharts (tendência do score nos dashboards e no detalhe do dataset, barras e donut da Gestão, barras da Consola e tendência dos metadados) têm nome com o número de pontos e o intervalo, um resumo com o mínimo, o máximo e o último valor, e «Ver como tabela» com os mesmos dados (frontend/src/components/ChartFigure.tsx::ChartFigure, frontend/src/components/TrendChart.test.tsx::3 pontos: figure com nome e resumo, tabela com uma linha por ponto). As fatias do donut, que o Recharts desenha com role="img", têm nome (frontend/src/components/TrendChart.test.tsx::barras: figure com o intervalo de dias e tabela; donut: imagem com os números da legenda). No browser, o axe dá 0 violações nos gráficos do detalhe do dataset e dos três dashboards, nos dois temas (docs/verificacao/cartao-330/README.md). A Consola e a tendência dos metadados só foram verificadas em jsdom (frontend/src/pages/ConsolaGraficos.test.tsx). Falhas: o score das folhas do treemap só aparece no tooltip visual. O detalhe do frontend/src/components/TrustBadge.tsx só está no title. Site: o logótipo é aria-hidden com o texto «BeryxTech» ao lado, os SVG decorativos também são aria-hidden e as capturas têm alt com a legenda. Falhas: o sparkline de DoorPreviews e os ícones do formulário de diagnóstico, das FAQ e do quiz não estão escondidos dos leitores de ecrã.
1.2.1 Audio-only and Video-only (Prerecorded) A 9.1.2.1 Not Applicable Not Applicable App: não há áudio nem vídeo (backend/tests/test_accessibility_conformance.py::test_app_has_no_media). Site: o VideoEmbed de /recursos não tem nenhum vídeo publicado. Reavaliar quando houver.
1.2.2 Captions (Prerecorded) A 9.1.2.2 Not Applicable Not Applicable Não há vídeo, como em 1.2.1.
1.2.3 Audio Description or Media Alternative (Prerecorded) A 9.1.2.3 Not Applicable Not Applicable Não há vídeo, como em 1.2.1.
1.2.4 Captions (Live) AA 9.1.2.4 Not Applicable Not Applicable Não há multimédia em direto.
1.2.5 Audio Description (Prerecorded) AA 9.1.2.5 Not Applicable Not Applicable Não há vídeo, como em 1.2.1.
1.3.1 Info and Relationships A 9.1.3.1 Partially Supports Partially Supports App: os landmarks são <main id="main"> e a navegação num aside nomeado. As tabelas da matriz de acessos e do calendário têm scope e caption, e há fieldset nos grupos de opções. label-has-associated-control é erro no lint, e o axe passa nas superfícies cobertas. Os rótulos de «Novo canal» estão ligados aos campos desde o cartão #324 (frontend/e2e/f6-canal-notificacao.spec.ts::Rótulos ligados aos campos por htmlFor). Falhas: os filtros de frontend/src/pages/Problemas.tsx e os campos de convite e das integrações só têm nome acessível ou placeholder, sem rótulo visível. As páginas fora do Layout (login, partilha pública) não têm <main>. heading-order só avisa. Site: há um h1 por página, e as tabelas do RFP, da comparação e dos preços têm scope e caption. Falhas: as tabelas de Comparison e DataVaultPage não têm scope. Os links do menu móvel não estão num <nav>. Os erros do diagnóstico não estão ligados ao campo.
1.3.2 Meaningful Sequence A 9.1.3.2 Supports Supports App: a ordem do DOM é a ordem visual, sem reordenação por CSS (backend/tests/test_accessibility_conformance.py::test_app_has_no_css_reordering). Site: a única inversão (flex-row-reverse no ChatMock) troca o avatar e a bolha dentro da mesma mensagem, e a ordem das mensagens mantém-se (revisão).
1.3.3 Sensory Characteristics A 9.1.3.3 Partially Supports Partially Supports App: o estado dos passos das Rotinas também é dado por texto («Falhou»). Falha: a instrução diz «Escolhe a caixa a vermelho» (frontend/src/pages/Rotinas.tsx::Escolhe a caixa a vermelho). Site: a página /tese explica a comparação com «À esquerda / À direita», o que deixa de fazer sentido quando o layout empilha em móvel.
1.3.4 Orientation AA 9.1.3.4 Supports Supports Nenhum dos dois bloqueia a orientação (backend/tests/test_accessibility_conformance.py::test_app_does_not_lock_orientation_or_zoom, praxis-site/tests/accessibility.test.mjs). O modo TV foi desenhado para 16:9, mas não bloqueia.
1.3.5 Identify Input Purpose AA 9.1.3.5 Supports Supports App: nome, organização, email e password têm autocomplete no login e registo (frontend/src/pages/Login.tsx::autoComplete="email"), e a definição de password usa new-password (backend/tests/test_accessibility_conformance.py::test_app_login_fields_identify_their_purpose). Os campos com dados de outras pessoas (convites) não entram no critério. Site: o formulário de diagnóstico usa name, email, organization e organization-title, e a Área de Cliente usa email e current-password.
1.4.1 Use of Color A 9.1.4.1 Partially Supports Partially Supports App: score, severidade, estado do scan e prazo vencido levam sempre texto além da cor, e as legendas do treemap e do calendário são texto. A legenda do donut «Datasets por saúde» dá o número e a percentagem de cada fatia, e as séries das barras também estão na tabela equivalente (frontend/src/components/TrendChart.test.tsx::barras: figure com o intervalo de dias e tabela; donut: imagem com os números da legenda). Falhas: a persona ativa só se distingue pela cor (frontend/src/components/PersonaSwitcher.tsx, sem aria-pressed), e o score das folhas do treemap também. Site: os estados do RFP usam ícone e texto, e os links do texto dos docs são sublinhados. Os links no corpo das restantes páginas não foram verificados um a um.
1.4.2 Audio Control A 9.1.4.2 Not Applicable Not Applicable Não há áudio.
1.4.3 Contrast (Minimum) AA 9.1.4.3 Partially Supports Partially Supports App: os tokens de texto passam ≥4,5:1 nos dois temas e são verificados no CI (frontend/scripts/contrast.test.mjs::todos os pares passam nos dois temas, salvo os follow-ups declarados). O axe no browser deu 0 violações em Problemas, Regras e Equipa nos dois temas (docs/verificacao/cartao-302/README.md). Desde o cartão #324 os e2e dos 10 fluxos do guião correm o axe (WCAG 2.2 A/AA; serious e critical bloqueiam o CI) em cada ecrã visitado, em desktop, a 393 px e em ES, e na visão geral em tema claro (frontend/e2e/support/a11y.ts::expectNoA11yViolations); a allowlist está vazia (frontend/e2e/a11y-allowlist.json). Falhas declaradas (frontend/scripts/check-contrast.mjs::followUp): text-ink-600 usado como texto em Incidentes, Conformidade e PII; texto com opacidade no painel de insights; ink-500 sobre modais (4,0–4,4:1) — os diálogos dos fluxos já usam ink-400, os restantes não. Desde o cartão #329, os eixos, a legenda e o tooltip dos gráficos leem os tokens --chart-* do tema, medidos no CI (frontend/scripts/check-contrast.mjs::var:chart-axis). O texto sobre os blocos do treemap e as bolhas da constelação é preto ou branco, o que tiver mais contraste, e passa ≥4,5:1 nos dois temas (frontend/src/lib/chartTheme.test.ts::rótulos sobre blocos e bolhas (preto ou branco, ≥4,5:1)). Falhas encontradas no browser (cartão #330, detalhe do dataset em tema claro, docs/verificacao/cartao-330/README.md): o código inline do frontend/src/components/MarkdownView.tsx nas sugestões (ember sobre ink-800, 4,44:1) e o veredito «Com reservas» do caso de uso selecionado em frontend/src/components/AiReadinessRadar.tsx (âmbar sobre o fundo tingido, 4,38:1). Não verificado: as restantes cores inline dos SVG (Sankey, radar, Rotinas) e as páginas fora dos fluxos do guião. Site: desde o cartão #331, os tokens de texto passam ≥4,5:1 nos dois temas. São verificados no build e no CI do site com o método da app (praxis-site/tests/contrast.test.mjs::todos os pares passam nos dois temas, salvo os follow-ups declarados). No tema claro, o texto ember passa a #b33a0a: o .eyebrow estava a 2,2:1 e os links ember-300 a 1,6:1. O texto secundário usa os tons ink da app, e os campos do diagnóstico seguem o tema. O axe no browser dá 0 violações de contraste na home, em /datavault e em /diagnostico, nos dois temas (praxis-site/scripts/check_a11y_browser.mjs), e também na página deste ACR e no RFP (docs/verificacao/cartao-331/README.md). Falhas declaradas (praxis-site/scripts/check_contrast.mjs::followUp): numeração «01» e rótulos pequenos com opacidade, no tema escuro; ink-600 usado como texto; ink-500 sobre superfícies ink-900 sólidas. Falha conhecida: as ilustrações SVG usam cores rgb() fixas, escolhidas para o tema escuro, que o script não lê. Há legendas abaixo de 4,5:1 nos dois temas, por exemplo no diagrama das três camadas. O diagrama do hero da home passou aos tokens no cartão #331. Não verificado: as páginas fora das verificadas.
1.4.4 Resize Text AA 9.1.4.4 Partially Supports Partially Supports App: o zoom não está bloqueado (backend/tests/test_accessibility_conformance.py::test_app_does_not_lock_orientation_or_zoom) e as medidas são em rem. Não medido: o zoom a 200 %. O modo TV (h-screen overflow-hidden) corta o conteúdo por desenho. Site: o zoom não está bloqueado, mas não foi medido a 200 %.
1.4.5 Images of Text AA 9.1.4.5 Supports Supports App: não há imagens de texto. A única imagem é o badge de qualidade para incorporar noutros sites, e a página pública mostra o score em texto ao lado (frontend/src/pages/PublicoDataset.tsx::ScoreBadge). Site: as capturas da app ilustram o produto e têm legenda em texto. O logótipo está isento.
1.4.10 Reflow AA 9.1.4.10 Partially Supports Partially Supports App: abaixo de 1024 px a navegação passa a gaveta, e as tabelas largas fazem scroll no seu contentor (as tabelas de dados estão isentas). A 393 px (Pixel 5), as páginas dos fluxos F2 e F3 (catálogo, scan ao vivo, detalhe do dataset, Problemas, Regras) não fazem scroll horizontal e as tabelas mais largas rolam no seu contentor, verificado em cada corrida do CI (frontend/e2e/support/layout.ts::expectNoHorizontalScroll). O cabeçalho cabe numa linha: abaixo de 768 px ficam só o menu, o sino e a persona (num seletor), e o idioma e o tema passam para a gaveta do menu (cartões #324 e #329). Em cada um desses ecrãs, também a 393 px, o CI verifica que os controlos do cabeçalho ficam dentro dele e à mesma altura (frontend/e2e/support/layout.ts::expectHeaderFitsOneLine). A 375 px e nos dois temas, a visão geral (executivo e gestão), Datasets, Problemas e Relatórios não fazem scroll horizontal (docs/verificacao/cartao-329/README.md). Não verificado: os 320 px e as restantes páginas fora de F2 e F3. O modo TV é 16:9 por desenho. Site: a navegação é responsiva e as tabelas fazem scroll no contentor. A 320 px, a página deste ACR, o RFP e os docs de segurança, métodos e capacidades não fazem scroll horizontal (docs/verificacao/cartao-313/README.md). O resto do site não foi medido.
1.4.11 Non-text Contrast AA 9.1.4.11 Partially Supports Partially Supports App: o anel de foco, as bordas do .input e as séries dos gráficos passam ≥3:1 nos dois temas e são verificados no CI. Desde o cartão #329, as cores por tipo do grafo do conhecimento e da constelação têm um tom por tema e passam ≥3:1 sobre o fundo e o cartão (frontend/src/lib/chartTheme.test.ts::cores por tipo (não-texto, ≥3:1 sobre o fundo e o cartão)), e o anel de foco da constelação usa o token focus. Não verificado: os anéis de foco desenhados nas Rotinas, no treemap e no Sankey, e as restantes cores inline dos SVG. Site: desde o cartão #331, os anéis de foco usam o token focus (ember-500 no escuro, #c2410c no claro). As bordas dos campos do diagnóstico e da Área de Cliente passam a ink-500, ≥3:1 nos dois temas. O script falha com anéis de cor fixa (praxis-site/tests/contrast.test.mjs::nenhum anel de foco usa cor fixa (ring-ember-400 fica a 2,1:1 no claro)). Falha: a borda do .btn-secondary (ink-700) fica a 1,7:1 no escuro e 1,4:1 no claro. Não verificado: as cores inline dos SVG.
1.4.12 Text Spacing AA 9.1.4.12 Partially Supports Partially Supports Não testado nos dois. App: o texto dentro dos SVG é cortado por número de caracteres (Rotinas, treemap) e não se ajusta a mais espaçamento. O modo TV não quebra linhas por desenho.
1.4.13 Content on Hover or Focus AA 9.1.4.13 Partially Supports Partially Supports Falhas: os tooltips próprios não se podem dispensar com Esc nem ser percorridos com o ponteiro (pointer-events-none): frontend/src/components/CatalogTreemap.tsx, frontend/src/components/QualityCalendar.tsx e frontend/src/components/RadarChart.tsx. Os tooltips do Recharts também não se podem percorrer com o ponteiro, mas fecham com Esc e, desde o cartão #330, quando o foco sai do gráfico (frontend/src/components/TrendChart.test.tsx::Tab chega ao SVG (role application, nome e descrição) e as setas mostram o tooltip de cada ponto). O resto são title nativos. Site: as etiquetas da matriz de /programa-executivo (FitMatrix) só aparecem com o rato e não se podem dispensar.
2.1.1 Keyboard A 9.2.1.1 Partially Supports Partially Supports App: o lint recusa cliques sem teclado (frontend/eslint.config.js::jsx-a11y/click-events-have-key-events). As Rotinas navegam com setas, Home e End, e os separadores da Equipa também (frontend/src/a11y.test.tsx::Separadores: setas, Home e End mudam de separador e levam o foco). O treemap funciona com Tab e Enter. Desde o cartão #330, Tab leva o foco aos gráficos Recharts de eixo horizontal e as setas ← → mostram o tooltip de cada ponto, anunciado numa região status (frontend/src/components/TrendChart.test.tsx::Tab chega ao SVG (role application, nome e descrição) e as setas mostram o tooltip de cada ponto). Nas barras horizontais da Consola, que o Recharts não percorre com as setas, e no donut, os valores estão na tabela equivalente e na legenda. A tabela aberta faz scroll num contentor focável. Falha: as ligações do Sankey só respondem ao clique (os nós respondem ao teclado). Site: não há div clicáveis e os separadores funcionam com setas. As tabelas e os blocos de código da documentação fazem scroll num contentor focável (praxis-site/tests/accessibility.test.mjs). Falhas: as etiquetas da FitMatrix não se revelam com o teclado. A 320 px, a tabela de comparação da /tese faz scroll num contentor sem foco (praxis-site/components/Comparison.tsx; axe scrollable-region-focusable, docs/verificacao/cartao-331/README.md).
2.1.2 No Keyboard Trap A 9.2.1.2 Supports Supports App: os diálogos e a gaveta prendem o Tab de propósito (frontend/src/lib/useFocusTrap.ts::useFocusTrap), e o Esc fecha e devolve o foco (frontend/src/components/Modal.test.tsx::Esc fecha e o foco volta ao botão que abriu). Site: não tem diálogos nem outras prisões de foco (revisão).
2.1.4 Character Key Shortcuts A 9.2.1.4 Supports Supports App: não há atalhos globais de uma tecla. + e - só dão zoom com o foco no desenho das Rotinas, o que o critério permite (revisão). Site: só as setas nos separadores com foco.
2.2.1 Timing Adjustable A 9.2.2.1 Partially Supports Partially Supports App: os avisos duram ≥8 s e param com o ponteiro ou o foco (frontend/src/components/Toast.test.tsx::pausa com o foco lá dentro (teclado) e retoma ao sair). Falha: a sessão expira ao fim de 12 h (backend/app/config/settings.py::jwt_ttl_hours) sem aviso nem forma de prolongar, e o regresso ao login perde o que não estava gravado. Site: a Área de Cliente usa a mesma sessão.
2.2.2 Pause, Stop, Hide A 9.2.2.2 Partially Supports Partially Supports App: não há carrosséis, e as animações dos painéis ao vivo respeitam prefers-reduced-motion. Falhas: o modo TV atualiza a cada 60 s e o relógio a cada segundo, sem pausa (frontend/src/lib/tvWall.ts::TV_REFRESH_MS). Os painéis ao vivo (scan, tópicos) atualizam sozinhos sem pausa. O pulso do estado do scan não respeita prefers-reduced-motion. Site: desde o cartão #331, com «Reduzir movimento» ligado no sistema não há loops nem animações de scroll. As animações SMIL saem do DOM (praxis-site/components/MotionPreferences.tsx::MotionOnly). O motion/react para com MotionConfig e skipAnimations, e as animações CSS param com prefers-reduced-motion em globals.css. A prova no browser compara duas capturas seguidas da home, de /datavault e de /farol/energia (praxis-site/scripts/check_a11y_browser.mjs). Falha: sem a preferência do sistema, o logótipo do cabeçalho e as ilustrações continuam em ciclo infinito, e a página não tem um controlo de pausa.
2.3.1 Three Flashes or Below Threshold A 9.2.3.1 Supports Supports Nada pisca mais de três vezes por segundo: os ciclos mais rápidos são pulsos de ≥1 s na app e de ≥2,4 s no site (revisão).
2.4.1 Bypass Blocks A 9.2.4.1 Supports Supports App: «Saltar para o conteúdo» é o primeiro Tab (frontend/src/components/Layout.test.tsx::o primeiro Tab mostra «Saltar para o conteúdo» e Enter leva o foco ao <main>). Site: há skip link para #main-content em todas as páginas (praxis-site/tests/accessibility.test.mjs).
2.4.2 Page Titled A 9.2.4.2 Supports Supports App: cada rota tem título próprio (frontend/src/lib/usePageTitle.ts::usePageTitle, frontend/src/components/Layout.test.tsx::cada rota tem um título distinto no separador). Site: as páginas definem os metadados (praxis-site/tests/accessibility.test.mjs). A página inicial usa o título por omissão, que é descritivo, e em ES algumas páginas legais têm o título em inglês (ver 3.1.2).
2.4.3 Focus Order A 9.2.4.3 Partially Supports Partially Supports App: ao mudar de rota o foco vai para o conteúdo (frontend/src/components/Layout.test.tsx::navegar pela sidebar: foco no <main>, scroll ao topo, título e anúncio da página nova), e os diálogos abrem e devolvem o foco no sítio certo. Não verificado: a ordem nas restantes páginas, por exemplo depois de apagar uma linha. Site — falha: o quiz de maturidade troca o bloco da pergunta e o botão focado desaparece, sem que o foco passe para a pergunta seguinte.
2.4.4 Link Purpose (In Context) A 9.2.4.4 Partially Supports Supports App: o axe (link-name) passa nas superfícies cobertas. Links genéricos como «Abrir» em Relatórios, Rotinas e Steward dependem do contexto da linha, e não foram verificados um a um. Site: os links têm texto descritivo (revisão).
2.4.5 Multiple Ways AA 9.2.4.5 Supports Supports App: navegação lateral, pesquisa de datasets (incluindo a federada), migalhas no treemap e ⓘ para a Ajuda. Site: cabeçalho, rodapé e índice da documentação (revisão).
2.4.6 Headings and Labels AA 9.2.4.6 Partially Supports Partially Supports App: títulos e rótulos descritivos nas superfícies cobertas pelo axe, sem verificação sistemática nas restantes. Site: sem verificação automática.
2.4.7 Focus Visible AA 9.2.4.7 Supports Partially Supports App: há uma regra global *:focus-visible (frontend/src/index.css::*:focus-visible), e o script falha com anéis de cor fixa. As guardas por classe estão em frontend/src/components/Modal.test.tsx::Modal: o painel focável (tabIndex=-1) mostra o anel com teclado e frontend/src/a11y.test.tsx::Foco visível em ?tab=%s: nenhum focável fica sem anel. No browser, 100 % das paragens de Tab tinham indicador em Login, Conexões e Steward (docs/verificacao/cartao-302/README.md). Site: não há regra global. Os links do cabeçalho e do rodapé usam o contorno do browser. Os campos da Área de Cliente e do diagnóstico tiram o contorno (outline-none) e mostram o foco com a borda na cor do token focus e um halo de 3 px (cartão #331).
2.4.11 Focus Not Obscured (Minimum) AA — Partially Supports Partially Supports Não testado nos dois. O cabeçalho é fixo (sticky top-0) e não há scroll-padding-top. Na app, os avisos no canto inferior também podem tapar um controlo focado.
2.5.1 Pointer Gestures A 9.2.5.1 Supports Supports Não há gestos com vários dedos nem percursos. O zoom com a roda nas Rotinas tem os botões «Aproximar» e «Afastar» como alternativa (revisão).
2.5.2 Pointer Cancellation A 9.2.5.2 Partially Supports Supports Falha: o fundo dos diálogos fecha ao carregar no botão do rato, não ao largar (frontend/src/components/Modal.tsx::onMouseDown, frontend/src/components/ConfirmDialog.tsx::onMouseDown, e o mesmo no painel das Atividades e no sino). Nada se perde, porque fechar não grava. Site: as ações são click em botões e links nativos (revisão).
2.5.3 Label in Name A 9.2.5.3 Partially Supports Partially Supports App: nas amostras revistas (Equipa, paginação, ações de linha) o nome acessível contém o texto visível, mas não há verificação sistemática. Site: desde o cartão #331, o nome dos botões de língua começa pelo texto visível, como em «EN — English» (praxis-site/tests/accessibility.test.mjs). O Lighthouse não encontra desencontros na home, em /datavault e em /diagnostico. Não há verificação sistemática nas restantes páginas.
2.5.4 Motion Actuation A 9.2.5.4 Supports Supports Não usam sensores de movimento (backend/tests/test_accessibility_conformance.py::test_app_has_no_motion_actuation, praxis-site/tests/accessibility.test.mjs).
2.5.7 Dragging Movements AA — Supports Not Applicable App: arrastar um cartão nas Atividades tem alternativa com um só ponteiro: abre-se o cartão e muda-se o estado no painel (frontend/src/pages/Atividades.tsx::Sem rato: abre o cartão com Enter e muda o estado no painel.). Arrastar nós nas Rotinas e no grafo de conhecimento só muda a disposição, e o «Reenquadrar» e o teclado substituem o arrasto do fundo. Site: não há arrastar.
2.5.8 Target Size (Minimum) AA — Partially Supports Partially Supports App: os chips (.chip-btn) têm ≥24 px e os botões do cabeçalho também (frontend/src/components/HeaderToggles.test.tsx::alvo ≥24px: 16px de ícone + p-1.5 + borda). O axe target-size deu 0 violações em Problemas, Regras e Equipa, e corre nos ecrãs dos 10 fluxos dos e2e, também a 393 px (frontend/e2e/support/a11y.ts::wcag22aa). Abaixo de 24 px: o ⓘ InfoLink (16 px), os interruptores de Regras e Rotinas (20 px de altura) e alguns botões de texto pequenos em Equipas, Conhecimento e SSO. Não medimos se a exceção de espaçamento os cobre. Site: não medido.
3.1.1 Language of Page A 9.3.1.1 Supports Supports App: <html lang="pt-PT"> (frontend/index.html::lang="pt-PT") muda com a língua da UI (frontend/src/lib/i18n.tsx::document.documentElement.lang): pt-BR, en-GB, en-US ou es, com a guarda em backend/tests/test_accessibility_conformance.py::test_app_declares_page_language. Site: o lang é definido por locale no layout raiz (praxis-site/tests/accessibility.test.mjs).
3.1.2 Language of Parts AA 9.3.1.2 Partially Supports Partially Supports App: o catálogo das Rotinas em EN leva lang (frontend/src/lib/i18n.tsx::apiContentLang). O texto de um relatório de IA leva a língua em que foi escrito (pt, en ou es) quando é diferente da da UI (frontend/src/lib/i18n.tsx::reportContentLang, frontend/src/pages/Relatorios.dom.test.tsx::UI em %s, relatório em %s). Num relatório em en ou es, os títulos das secções que os prompts fixam em pt e o rodapé de rotulagem do art. 50.º levam pt-PT (frontend/src/lib/i18n.tsx::reportSectionLang, frontend/src/pages/Relatorios.dom.test.tsx::títulos e rodapé). Um ## que o LLM junte fica na língua do relatório (frontend/src/lib/reportSections.ts::isReportSectionTitle, frontend/src/pages/Relatorios.dom.test.tsx::que o LLM juntou). O export HTML/PDF põe a língua do relatório no <html lang>. Num relatório em en ou es, marca pt-PT no texto fixo do BeryX, nos títulos das secções que os prompts fixam e no rodapé de rotulagem (backend/tests/test_content_language.py::test_english_export_marks_the_portuguese_parts). Não verificado: outro texto da API numa língua diferente da UI. Falha: o README, as sugestões e os resumos da KB saem na língua do workspace mas não a guardam, por isso ficam sem lang quando a pessoa usa a UI noutra língua. Site: a documentação leva lang="pt", e a versão inglesa de SEGURANCA, CONECTORES, CAPACIDADES, WEBHOOKS e MCP_SERVER leva lang="en" (praxis-site/tests/product-docs-en.test.mjs). Os botões do seletor de língua levam lang. Falha: em ES, os Termos, a Privacidade, a Segurança e o changelog mostram texto em inglês sem lang="en".
3.2.1 On Focus A 9.3.2.1 Supports Supports O foco não muda o contexto. Na app, o onFocus só destaca um elemento, mostra um tooltip ou seleciona o texto de um campo só de leitura. O site não tem onFocus (revisão).
3.2.2 On Input A 9.3.2.2 Supports Supports App: nenhum <select> navega nem submete um formulário. Mudar a língua troca o texto no mesmo sítio. Guardar ao escolher um responsável muda o conteúdo, não o contexto (revisão). Site: a língua muda por botão, não por campo.
3.2.3 Consistent Navigation AA 9.3.2.3 Supports Supports App: todas as rotas com sessão usam o mesmo frontend/src/components/Layout.tsx. Site: o cabeçalho e o rodapé vêm do mesmo layout por locale.
3.2.4 Consistent Identification AA 9.3.2.4 Supports Supports App: as mesmas funções usam os mesmos componentes e nomes: Modal com «Fechar», ConfirmDialog, paginação, badges de score e severidade (revisão). Site: os botões e os links partilham as mesmas classes e textos.
3.2.6 Consistent Help A — Supports Supports App: o botão «Pedir ajuda» (frontend/src/components/HelpRequestButton.tsx::Pedir ajuda, na barra lateral: frontend/src/components/Layout.tsx::<HelpRequestButton) e «Ajuda» estão no mesmo sítio da navegação em todas as páginas com sessão, e o «?» do cabeçalho (frontend/src/components/Layout.tsx::Ajuda sobre esta página) abre a ajuda da página atual. Site: o suporte@praxis.lbostuff.tech está no rodapé de todas as páginas (revisão).
3.3.1 Error Identification A 9.3.3.1 Partially Supports Partially Supports App: os erros aparecem em texto num role="alert" e dizem o que falta («Preenche o email e a password.»). Falha: o erro raramente fica ligado ao campo (aria-invalid só em 4 sítios). Site: os erros do diagnóstico aparecem em texto, dentro do rótulo, sem aria-invalid.
3.3.2 Labels or Instructions A 9.3.3.2 Partially Supports Partially Supports Falhas na app: os filtros de Problemas têm nome acessível mas não rótulo visível, e alguns formulários (convites da Equipa, integrações Git e de tickets, registo de sistemas em Conformidade) só têm placeholder. Site: os campos obrigatórios do diagnóstico não estão indicados.
3.3.3 Error Suggestion AA 9.3.3.3 Partially Supports Partially Supports App: as mensagens de validação sugerem a correção na maior parte dos formulários, sem verificação sistemática. Site: desde o cartão #497, os erros do diagnóstico vêm na língua da página (pt, en, es) e dizem o que falta («Mín. 2 caracteres», «Escolhe uma opção»). Antes, quatro campos caíam nas mensagens por omissão do zod, em inglês. Prova: praxis-site/tests/diagnostico-page.test.mjs::DiagnosticoForm: nenhuma verificação do zod sem mensagem nem com texto escrito à mão.
3.3.4 Error Prevention (Legal, Financial, Data) AA 9.3.3.4 Supports Not Applicable App: as ações destrutivas pedem confirmação, sem window.confirm (backend/tests/test_accessibility_conformance.py::test_app_never_uses_window_confirm). Revogar partilhas exige escrever a confirmação (frontend/src/components/Modal.test.tsx::typeToConfirm: a ação fica bloqueada até o texto coincidir), e apagar o workspace exige escrever o nome. Os Termos aceitam-se por checkbox (frontend/src/a11y.test.tsx::Login em modo registo: aceitação dos Termos por checkbox com nome (cartão #309)). O pagamento é revisto no checkout do Stripe. Site: não tem compromissos legais, financeiros nem de dados.
3.3.7 Redundant Entry A — Supports Not Applicable App: o assistente de conexão guarda os dados entre passos, voltar ao login mantém o email, e a revisão de contrato lembra o nome (revisão). Site: não tem processos de vários passos que peçam dados.
3.3.8 Accessible Authentication (Minimum) AA — Supports Supports App: o login funciona com gestores de passwords (autocomplete), deixa colar (backend/tests/test_accessibility_conformance.py::test_app_allows_paste) e não tem captcha. O código TOTP usa one-time-code (frontend/src/components/MfaChallenge.tsx::one-time-code) e aceita colar. Há SSO OIDC e SAML e códigos de recuperação. Site: o login da Área de Cliente usa email e current-password, deixa colar e não tem captcha.
4.1.2 Name, Role, Value A 9.4.1.2 Partially Supports Partially Supports App: os separadores, interruptores (role="switch"), rádios e disclosures expõem o estado (aria-selected, aria-checked, aria-pressed, aria-expanded), e o axe passa nas superfícies cobertas. Falhas: a persona ativa não expõe estado nos botões de ecrãs largos (frontend/src/components/PersonaSwitcher.tsx), e o TrustBadge põe um aria-label num span sem papel. Site: as FAQ e os filtros do RFP expõem o estado. Desde o cartão #331, o botão do menu móvel também o expõe (aria-expanded, aria-controls), com o nome «Abrir menu» ou «Fechar menu». A prova com teclado, a 390 px e nos dois temas, está em praxis-site/scripts/check_a11y_browser.mjs. Os restantes componentes do site não foram verificados de forma sistemática.
4.1.3 Status Messages AA 9.4.1.3 Partially Supports Partially Supports App: os avisos usam role="status" ou role="alert" (frontend/src/components/Toast.test.tsx::markup: role=status (alert no erro), ação e fecho com nome), e os esqueletos das listas anunciam o carregamento (frontend/src/components/Skeleton.tsx::role="status"). A mudança de página é anunciada, e a paginação é anunciada em aria-live (frontend/src/a11y.test.tsx::Regras: 120 regras paginadas, «1–50 de 120» e navegação). Falhas: o frontend/src/components/Spinner.tsx não anuncia o carregamento, e alguns erros (Equipa, definir password) não têm role. Site: o número de resultados dos filtros do RFP é anunciado. Falha: o sucesso e o erro do diagnóstico e o erro de login da Área de Cliente não são anunciados.

Lacunas conhecidas e plano

Cada lacuna abaixo tem de ter cartão próprio antes de o estado mudar. Não se corrige no ACR. A data só aparece quando está agendada.

Lacuna Critérios Onde Plano
axe no browser real e viewport móvel fora dos 10 fluxos do guião (os fluxos estão cobertos desde o cartão #324, docs/GUIAO_TESTES.md) 1.4.3, 1.4.10, 1.4.11, 2.4.11, 2.5.8 app cartão a abrir
Tooltips que se dispensam com Esc e se podem percorrer 1.4.13 app, site cartão a abrir
Rótulos visíveis nos campos só com nome acessível ou placeholder (filtros de Problemas, convites, integrações) 1.3.1, 3.3.2 app cartão a abrir
Aviso antes de a sessão expirar 2.2.1 app cartão a abrir
Pausa no modo TV e nos painéis ao vivo 2.2.2 app cartão a abrir
Fechar diálogos ao largar o botão do rato 2.5.2 app cartão a abrir
Alvos abaixo de 24 px (ⓘ, interruptores) 2.5.8 app cartão a abrir
Estado da persona ativa e do TrustBadge 1.4.1, 4.1.2 app cartão a abrir
Contraste no site: cores rgb() fixas das ilustrações SVG, numeração com opacidade no tema escuro, ink-600 como texto e a borda do .btn-secondary (o tema claro, os anéis e as bordas dos campos ficaram no cartão #331) 1.4.3, 1.4.11 site cartão a abrir
Controlo de pausa das animações do site para quem não usa «Reduzir movimento» (a preferência do sistema é respeitada desde o cartão #331) 2.2.2 site cartão a abrir
Foco no quiz de maturidade (o menu móvel e o nome do seletor de língua ficaram no cartão #331) 2.4.3 site cartão a abrir
Tabela de comparação da /tese num contentor de scroll focável, como as dos docs 2.1.1 site cartão a abrir
Mensagens de estado e erros ligados aos campos nos formulários do site 3.3.1, 4.1.3 site cartão a abrir
lang="en" no texto legal e no changelog em ES 3.1.2 site cartão a abrir
axe no browser nas restantes páginas do site (a home, /datavault e /diagnostico estão no CI desde o cartão #331) todos site cartão a abrir

Contacto e revisão

  • Feedback e formatos alternativos. Para reportar uma barreira de acessibilidade ou pedir um formato alternativo, envie um email para suporte@praxis.lbostuff.tech com o assunto «Acessibilidade». Respondemos por email. Os clientes com contrato têm os prazos de suporte do contrato.
  • Revisão. Este relatório revê-se todos os anos: a próxima revisão é até 2027-09-28. Também se revê antes disso quando um critério muda de estado, quando sai uma versão major ou quando a cobertura dos e2e com axe muda — a revisão de 2026-09-28 (cartão #324) registou a entrada do axe no browser, do viewport móvel e do locale ES no CI. A de 2026-09-29 (cartão #331) passou o 2.2.2 do site a «Partially Supports» e registou a entrada do contraste e do axe no CI do site. A versão e o commit em produção estão em GET /api/v1/version.
  • Declaração de acessibilidade. Este ACR é a peça para procurement. A Diretiva (UE) 2016/2102 obriga cada organismo público a tornar acessíveis os seus sites e aplicações e a publicar a sua própria declaração. Quando compra software, o que pede ao fornecedor é evidência face à EN 301 549, e é esse o papel deste relatório. Uma declaração pública da BeryxTech no modelo da Decisão de Execução (UE) 2018/1523 fica para quando os critérios em falta puderem passar a «Supports», para não publicarmos uma declaração que envelhece mal.