Files
Projeto-Portaria/prompts/FASE-7-escalonamento.md
2026-07-22 15:55:55 -03:00

92 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 06 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