prompt inicial do projeto

This commit is contained in:
2026-07-22 15:55:55 -03:00
commit 6fb920d333
19 changed files with 2937 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# FASE 5 — App do morador
## Objetivo
App Compose Multiplatform (Android + iOS) que **toca de verdade** com o app fechado, permite resolver entrega em um toque na notificação, e atende videochamada.
## Pré-requisitos
FASES 04 concluídas. Ler `docs/04-DESIGN-SYSTEM-UX.md` §4.
## O ponto que define esta fase
**No iOS, notificação comum não faz o telefone tocar como chamada.** Só PushKit + CallKit fazem. Sem isso o app não funciona como portaria — o morador vê a chamada quando abrir o celular, dez minutos depois, e o visitante já foi embora.
E a Apple **obriga**: ao receber um push do PushKit, o app precisa reportar a chamada ao CallKit imediatamente, na mesma execução. Não reportar faz o sistema matar o app e, com reincidência, revogar o direito de receber VoIP push. Não há meio-termo aqui.
## Tarefas
### 1. Estrutura
`apps/composeApp` com Compose Multiplatform, dependendo de `:shared`. Arquitetura MVVM com `ViewModel` compartilhado. Ktor Client para HTTP e WebSocket. Material 3 Expressive com o `Theme.kt` gerado dos tokens.
Um único app com dois perfis (morador e operador), decididos por `GET /me`.
### 2. Notificação de chamada — Android
FCM com prioridade `high`. Notificação `CallStyle` com full-screen intent para visita; notificação com ações para entrega.
Permissões necessárias: `POST_NOTIFICATIONS` (13+), `USE_FULL_SCREEN_INTENT` (14+), `FOREGROUND_SERVICE_MICROPHONE` e `FOREGROUND_SERVICE_CAMERA`.
**Otimização de bateria é o inimigo silencioso.** Xiaomi, Samsung e Huawei matam apps agressivamente e o push nunca chega. O app precisa detectar e orientar o usuário a isentá-lo — e o painel admin precisa mostrar quem está com push falhando (`05-INFRA-DOCKER.md` §6).
### 3. Notificação de chamada — iOS
**Visita** → PushKit VoIP push → `CXProvider.reportNewIncomingCall()` **imediatamente**, antes de qualquer chamada de rede. Só depois busque os detalhes da visita.
**Entrega** → notificação comum com `UNNotificationCategory` e ações. CallKit para uma entrega seria intrusivo e é risco de reprovação na App Review.
Entitlement de VoIP configurado. Chave APNs `.p8` no backend. Áudio configurado via `AVAudioSession` com categoria `playAndRecord`.
**Nota de App Review:** documente na submissão que o app é VoIP legítimo de controle de acesso. Descreva o fluxo. Apps que usam CallKit para notificação genérica são reprovados — este não é o caso, mas o revisor precisa entender por quê.
### 4. Ações de um toque
O morador resolve **sem abrir o app**:
- Android: `Notification.Action` com `PendingIntent` para um `BroadcastReceiver`
- iOS: `UNNotificationAction` tratada na extensão
Ambas chamam `POST /visits/{id}/resolve` com `Idempotency-Key` gerada no dispositivo. **Trate a falha de rede com retry**: a ação disparada em rede ruim que falha em silêncio é a pior falha possível deste app — o morador acha que autorizou e o entregador continua parado.
### 5. Telas
**Home** — pendências (recados não resolvidos) no topo, histórico abaixo, estado de conexão visível.
**Chamada** — vídeo do visitante em tela cheia, auto-preview no canto. Sobreposto: nome informado, unidade de destino, e o **selo de geofence** (`✓ Na entrada` verde / `⚠ A 340m da portaria` âmbar). Esse selo é a informação de segurança mais útil da tela — alguém acionando de longe é sinal claro. Ações: `Autorizar` (verde, primária), `Negar`, mudo, encerrar.
**Detalhe da visita** — foto, mapa do ponto, timeline de `visit_attempts`, player do recado.
**Configurações** — regra de entrega por unidade, ordem de toque, dispositivos ativos, notificações.
### 6. Registro de dispositivo
`POST /devices` no login e a cada renovação de token, enviando `push_token` e — no iOS — também `voip_token`. Renove sempre que o sistema emitir novo token; token expirado é a causa número um de "não recebi a chamada".
### 7. Concorrência entre moradores
Ao receber `409 VISIT_ALREADY_RESOLVED`, mostre **"Maria já autorizou"** com horário. Nunca um erro técnico — dois moradores respondendo juntos é situação normal, não falha.
### 8. Tempo real
WebSocket enquanto o app está em foreground, com fallback para push. Ao voltar do background, reconcilia estado via REST antes de confiar no WS.
## Critérios de aceite
- [ ] **iOS: chamada toca em tela cheia com o app fechado e o telefone bloqueado**
- [ ] **Android: full-screen intent aparece com o app fechado e a tela apagada**
- [ ] Entrega resolvida pela notificação, sem abrir o app, em ambas as plataformas
- [ ] Teste com o app morto por otimização de bateria (Xiaomi/Samsung)
- [ ] Videochamada funciona com o app vindo do background
- [ ] `409` mostra quem já resolveu
- [ ] Ação em rede ruim tem retry e não falha em silêncio
- [ ] Registro de dispositivo renova o token corretamente
- [ ] Testado em dispositivos físicos — simulador não recebe push
- [ ] Acessibilidade: TalkBack e VoiceOver navegam a tela de chamada
## Não faça nesta fase
- App do operador completo (FASE 7) — perfil de operador fica oculto
- WhatsApp (FASE 8)
- Autorizações recorrentes (v2)