4.5 KiB
FASE 3 — Web do visitante
Objetivo
PWA do visitante completa: QR → aviso → permissões → captura → destino → espera narrada → resultado. Ambos os funis (visita e entrega). Sem videochamada ainda.
Pré-requisitos
FASES 0–2 concluídas. Ler docs/04-DESIGN-SYSTEM-UX.md §3 e docs/03-FLUXOS-E-CONTRATOS.md §2 e §3.
Contexto que determina cada decisão
O visitante está em pé na calçada, no sol, com pressa, em 4G, e não vai instalar nada. Tudo aqui deriva disso: bundle mínimo, uma ação por tela, tipografia grande, contraste alto.
Tarefas
1. Base
Vite + React 19 + TS em web/visitor. Roteamento por /v/{gateId}?s={sig}. Tokens de shared-ui. PWA com manifest, sem service worker de cache agressivo — o visitante usa uma vez e o cache velho causaria mais problema que benefício.
Orçamento: 200KB gzip, com gate em CI. O SDK do LiveKit entra por import() dinâmico apenas na FASE 4, na tela de chamada — ele sozinho tem o tamanho de todo o resto do app.
2. Telas
Cinco telas conforme 04-DESIGN-SYSTEM-UX.md §3, uma ação cada.
Tela 1 — Aviso de tratamento. Não é modal de consentimento com checkbox. A base legal é legítimo interesse; o que se faz é informar com clareza. Texto de 06-LGPD-E-SEGURANCA.md §1, com link "Saiba mais". O botão diz "Continuar", não "Aceito".
Tela 2 — Escolha do funil. 📦 Entrega primeiro, porque é a maioria do volume. 👤 Visita abaixo.
Tela 3 — Permissões e captura. Solicita câmera e localização, com explicação antes do prompt nativo — pedir permissão sem contexto é a principal causa de negativa. Captura foto do rosto (visita) ou do pacote (entrega). Comprime no cliente para no máximo 1600px no lado maior.
Permissão negada: localização é bloqueante (403 LOCATION_REQUIRED), com instrução de como reabilitar. Câmera negada em entrega degrada para "sem foto"; em visita, é bloqueante.
Tela 4 — Destino. Bloco e unidade, campos livres. Nenhum autocomplete, nenhuma sugestão, nenhuma validação de existência — é a busca cega, e um autocomplete a destruiria. Nome do visitante obrigatório; documento e telefone opcionais.
Tela 5 — Espera narrada. A tela onde o produto se ganha ou se perde:
TOCANDO "Chamando o morador..." barra 0→20s
ESCALONADA "Tentando outros moradores..." barra 0→15s
FILA_OPERADOR "Transferindo para a portaria" + posição na fila
RECADO "Grave um recado em vídeo" → gravação
Nunca um spinner mudo. Um spinner de 35 segundos é indistinguível de um app quebrado.
Tela 6 — Resultado. Autorizado: PIN de 6 dígitos em display, QR grande, contagem regressiva de validade. Negado: mensagem clara e sem tom acusatório. Expirado: instrução de contingência (botão físico da portaria).
3. Estado e conexão
Estado da visita via WebSocket com fallback automático para polling de 3s se o WS cair ou não conectar em 5s. O visitante em 4G instável é o caso comum, não a exceção.
Reconexão com backoff exponencial e teto de 10s. Estado da sessão em sessionStorage, para que refresh acidental não perca a visita.
4. Gravação de recado
MediaRecorder, 30s máximo, preview antes de enviar, upload com barra de progresso e retry.
5. Erros
Todo erro exibido vem do detail do problem+json, em português e acionável: "Aproxime-se da entrada e tente novamente", nunca OUTSIDE_GEOFENCE.
6. Acessibilidade e legibilidade em campo
- Alvos de toque ≥ 48×48px, sem exceção
- Corpo de texto ≥ 16px
- Contraste ≥ 4.5:1, verificado em CI
- Apenas extremos da escala neutra — sol direto derruba tons médios
prefers-reduced-motionrespeitadolang="pt-BR", textos externalizados para i18n futura- Funciona em Safari iOS 15+ e Chrome Android 100+
Critérios de aceite
- Bundle ≤ 200KB gzip (gate de CI)
- TTI < 2s em 4G simulado (Lighthouse throttling)
- Lighthouse: Performance ≥ 90, Acessibilidade = 100
- Fluxo de entrega completo em ≤ 4 toques
- Tela 4 não tem autocomplete nem validação de unidade
- WS derrubado à força → polling assume e o fluxo continua
- Localização negada → mensagem acionável, não erro técnico
- Testado em iPhone e Android reais, sob sol
- Todos os textos de erro em português, sem código técnico
Não faça nesta fase
- Videochamada (FASE 4)
- Service worker com cache de assets versionados
- Qualquer tela de admin