# FASE 7 — Escalonamento, fila de operador e recado ## Objetivo Completar a retaguarda: escalonamento entre moradores, fila de operador humano com contexto, app do operador, e recado em vídeo. É o que transforma "chamada não atendida" em "visita resolvida". ## Pré-requisitos FASES 0–6 concluídas. Ler `docs/03-FLUXOS-E-CONTRATOS.md` §4 e `docs/00-VISAO-E-PRODUTO.md` §1. ## Por que esta fase existe A tese do produto é que o morador atende. A realidade é que ele nem sempre atende — gente em reunião, dirigindo, dormindo. É por isso que o mercado consolidado usa operador humano 24h. Esta fase é o que torna a aposta vendável: quando o morador não atende, o visitante **não fica sem saída**. E é o que sustenta comercialmente o plano Assistido. ## Tarefas ### 1. Escalonamento entre moradores `VISIT_RING_TIMEOUT` (20s) → `ESCALONADA`. Toca para os demais `unit_members` da unidade, **em paralelo**, respeitando `ring_order` para ordenar a exibição e `receives_calls`. O primeiro que atender vence; os demais recebem cancelamento da notificação — deixar notificação órfã tocando depois de resolvido é ruído que faz o morador desativar o app. ### 2. Fila de operador Ativa apenas com `tenant_features.operator_queue` habilitado. Sem o módulo, `ESCALONADA` vai direto para `RECADO_EM_VIDEO`. Fila ordenada por tempo de espera, com prioridade para `VISITA` sobre `ENTREGA`. `POST /operator/queue/claim` usa `FOR UPDATE SKIP LOCKED` — dois operadores nunca pegam a mesma visita. Posição na fila enviada ao visitante por `QUEUE_POSITION` no WebSocket. Esperar é tolerável; esperar sem saber quanto falta, não. `VISIT_QUEUE_TIMEOUT` (120s) → `RECADO_EM_VIDEO`. ### 3. App do operador Mesmo binário Compose Multiplatform, perfil decidido por `GET /me`. Layout mais denso que o do morador. **Fila** à esquerda: foto, tipo, unidade, tempo de espera e **por que escalonou** (ninguém atendeu / quiet hours / unidade inexistente). **Contexto** à direita, antes de assumir: histórico da unidade, visitas recentes, regra de entrega, nomes dos moradores, telefone. **O operador nunca atende sem contexto** — atender às cegas é o que faz a portaria remota parecer pior que a física. Presença: `DISPONIVEL` / `EM_ATENDIMENTO` / `OFFLINE`, com heartbeat. Operador que perde conexão volta para `OFFLINE` automaticamente e suas visitas retornam à fila. Ao resolver, o operador registra `resolution_reason` — obrigatório, e é o que alimenta a análise de por que o modelo autônomo falhou naquele caso. ### 4. Recado em vídeo `RECADO_EM_VIDEO`: o visitante grava até 30s. Salvo em `media_assets` com `kind = RECADO_VIDEO` e retenção de 30 dias. Notificação ao morador **sem urgência** — não é CallKit, não é full-screen intent. É pendência, não chamada. Aparece no topo da home do app até ser resolvida. O morador pode autorizar retroativamente (gerando `access_grant` com validade estendida), negar, ou apenas marcar como visto. ### 5. Quiet hours Dentro de `condominiums.quiet_hours`, visitas não pré-autorizadas **pulam o toque ao morador**: vão direto para `FILA_OPERADOR` (se o módulo estiver ativo) ou `RECADO_EM_VIDEO`. É a defesa contra tocar em todos os apartamentos de madrugada, e precisa ser óbvia para o visitante: "Fora do horário. Transferindo para a portaria." ### 6. Cancelamento de notificações Ao resolver a visita, cancele as notificações pendentes em todos os dispositivos que foram acionados. Android usa `notificationId` estável por visita; iOS usa `CXProvider.reportCall(with:endedAt:reason:)`. Uma chamada CallKit que continua tocando depois de resolvida é bug grave de percepção — o morador acha que o app está quebrado. ### 7. Métricas de escalonamento `portaria.escalation.ratio` (quantas visitas escalaram) · `portaria.queue.wait_time` · `portaria.queue.abandoned` · `portaria.message.recorded` · `portaria.message.unresolved_24h`. `escalation.ratio` é a métrica que decide se um condomínio fica no plano Autônomo ou precisa migrar para o Assistido. ## Critérios de aceite - [ ] Escalonamento toca para os demais moradores após 20s - [ ] Primeiro a atender vence; notificações dos outros são canceladas - [ ] Sem `operator_queue`, escalonada vai direto para recado - [ ] Dois operadores não pegam a mesma visita (teste concorrente) - [ ] Posição na fila chega ao visitante em tempo real - [ ] Operador que perde conexão devolve a visita à fila - [ ] Recado gravado, armazenado e notificado sem urgência - [ ] Recado aparece como pendência na home até resolver - [ ] Quiet hours desvia sem tocar em ninguém, com aviso ao visitante - [ ] CallKit não continua tocando após resolução - [ ] Fluxo completo sem morador algum: QR → escalonamento → fila → operador → autorizado ## Não faça nesta fase - Pré-autorização e recorrentes (v2) - WhatsApp (FASE 8) - Escalação para telefone/PSTN