5.3 KiB
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.
Visitas-sombra (unit_id nulo — busca cega, 03-FLUXOS-E-CONTRATOS.md §2) chegam à fila marcadas como "unidade não cadastrada", visível só para o operador. Ele trata como um porteiro trataria quem errou o número: pergunta, corrige a unidade (PATCH que resolve o unit_id e registra em audit_log) e redireciona — ou nega. Para o visitante, nada distingue esse atendimento de um normal.
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