# Prompt para o Atendro: gestão de ligações simultâneas Este texto pode ser enviado ao agente que trabalha no repositório `atendro-ai`. O contrato abaixo foi preparado no AtendroZAP, mas ainda não foi publicado na VPS 1. A implementação no consumidor pode avançar com fixtures; o teste real depende de `capabilities.calls_settings_webhook=true` no servidor escolhido. Nenhuma mudança de instâncias, webhooks de produção ou limite em produção é autorizada por este documento. ## Texto para implementar no atendro-ai Implemente a sincronização do limite de ligações simultâneas por instância AtendroZAP e a gestão das vagas no backend e na interface do Atendro. Inspecione primeiro os módulos atuais de provedor, conexões, webhooks, chamadas, ponte de mídia e permissões. Reutilize os mecanismos de inbox, assinatura e reconexão já existentes; preserve os demais provedores e os contratos em uso. O usuário precisa conseguir configurar uma instância para duas ligações, ver o limite atualizado quando ele mudar no painel do AtendroZAP e distribuir as duas vagas entre atendentes. Voz e vídeo compartilham o limite. Isso é uma configuração de admissão do provedor: mais de uma ligação simultânea no mesmo número ainda não foi homologada em aparelhos reais. Não apresente o número configurado como capacidade física comprovada ou como duas linhas WhatsApp independentes. ### Contrato do provedor Use a URL do servidor vinculado à conexão, nunca uma VPS fixa no código. Credenciais ficam no backend. As rotas de instância recebem header `token`; rotas administrativas usam `admintoken`, acessível somente a administradores autorizados. Não exponha esses segredos em bundle, navegador, URL, log ou erro. - `GET /v1/capabilities` anuncia `capabilities.calls`, `capabilities.calls_per_instance_limit` e `capabilities.calls_settings_webhook`; `calls_max_concurrent_range` contém `{min: 1, max: 8}`. `calls_max_concurrent` é o padrão do servidor, não o limite salvo da instância. Para vídeo, exija também `capabilities.calls_video`. - `GET /instance/status` retorna `instance.kind`, `instance.callsMaxConcurrent` e `instance.callsConfigVersion` para `kind: calls`. A versão é um inteiro de 64 bits, inclusive `0`; não confunda zero com ausência. O campo de data HTTP existente é `instance.updated`, não `updatedAt`. - `POST /instance/call-settings`, com header `token`, recebe `{"callsMaxConcurrent": 2}`. A variante administrativa é `POST /v1/instances/{id}/call-settings`. A resposta contém o objeto `instance` com limite e versão canônicos. Cada POST aceito incrementa a versão, mesmo repetindo o mesmo valor. Não existe comparação condicional de versão nesta rota: escritas concorrentes vencem pela última versão salva. - O limite de 1 a 8 conta chamadas recebidas e realizadas, de voz ou vídeo, em `ringing`, `connecting` e `active`, além das discagens que o serviço já reservou. Uma chamada de vídeo ocupa uma vaga, mesmo usando dois sockets. - Reduzir o limite preserva chamadas vivas. Se houver duas e o limite cair para um, nenhuma é encerrada; novas chamadas só entram quando o total ficar abaixo de um. Sem vaga, uma discagem recebe `409 line_busy`. A API não cria uma fila de discagem automática; não repita chamadas em loop ao liberar vaga. - A alteração pode ser salva desconectada. O worker local aplica sem reconectar; outro worker aplica na próxima renovação de lease e antes de cada nova discagem de saída. Uma confirmação de configuração não comprova que uma ligação de teste foi realizada. - `GET /call/active` retorna `{calls: [...]}` para a instância. A lista não mostra reservas de discagem que ainda não receberam `callId`. `GET /call/{id}` e `GET /call/history` permitem reconciliar chamadas e fins. ### Inscrição e processamento dos webhooks No receptor HMAC existente, acrescente `call_settings` à lista de eventos reconhecidos. Uma instância `calls` deve assinar `call`, `call_settings` e `connection`; `limits` é opcional conforme a integração. Ela não publica mensagens, recibos, edições, grupos ou `undecryptable`. `call_settings` não é um subevento `call` e não contém `callId`: nunca crie uma chamada a partir dele. Ao preparar a inscrição, consulte `GET /webhook` e mescle os nomes necessários com os eventos já salvos. Preserve URL, segredo, filtros, estado habilitado e demais opções. Para a mesma URL, omitir `secret` preserva o segredo existente; não envie segredo vazio ou inventado. O GET não devolve o segredo. Se o cadastro for herdado do global, preserve a herança: alteração global exige permissão administrativa e avaliação das demais instâncias. Não transforme silenciosamente um webhook herdado em exceção própria nem substitua configurações de clientes. Se a inscrição estiver desabilitada ou ausente, siga o fluxo autorizado de provisionamento do produto, sem ativá-la como efeito colateral de mudar o limite. O AtendroZAP grava a configuração e o evento atomicamente. Inscrição ausente não impede salvar o limite, mas não há entrega sem assinar `call_settings`. Registrar o webhook não produz snapshot inicial. Depois de preparar a inscrição, leia o status para obter o estado atual e resolver a corrida com eventos recebidos durante essa leitura. Exemplo sintético parcial do novo evento, no envelope assinado atual: ```json { "EventType": "call_settings", "event": "call_settings", "eventId": "00000000-0000-4000-8000-000000000002", "instanceId": "00000000-0000-4000-8000-000000000001", "seq": 42, "data": { "kind": "calls", "callsMaxConcurrent": 2, "callsConfigVersion": 3, "updatedAt": "2026-09-19T15:00:00.123456789Z" } } ``` `updatedAt` é UTC em RFC3339Nano. O evento `connection` mantém seu formato e acrescenta `instance.kind: calls`; quando a leitura do banco é possível, também traz `instance.callsMaxConcurrent` e `instance.callsConfigVersion`. Esses campos opcionais em `connection` não ficam em `data`. Se ausentes, preserve o limite conhecido e reconcilie pelo status. Valide a assinatura HMAC-SHA256 sobre `timestamp + "." + corpo bruto`, a janela de timestamp e o vínculo servidor/instância/empresa antes de aceitar. Deduplique por identidade do servidor, `instanceId` e `eventId`; confirme 2xx somente após persistência durável. Retries preservam o `eventId`. Use `seq` do envelope para ordenar eventos; a versão de configuração é um contador separado. Persista limite e versão juntos. Aplique snapshots de resposta POST, GET, `call_settings` e `connection` pela mesma regra transacional: versão maior substitui, menor é ignorada, igual é idempotente. Uma mesma versão com valor divergente exige reconciliação, sem escolher pelo horário. Campos ausentes em versões antigas da API não equivalem a zero. Não use `updatedAt`, horário de chegada ou ordem das promessas HTTP para sobrescrever uma versão maior. Use representação que preserve inteiros de 64 bits nos componentes que precisarem deles; não faça comparação textual lexicográfica de versões. Após um POST, aplique a resposta canônica e reconcilie com GET. Se houver timeout ou perda de resposta, leia o status antes de repetir a alteração. Notifique as sessões do Atendro vinculadas à conexão após o commit, usando o canal de atualização que o produto já possui. Uma mudança feita fora do Atendro deve aparecer sem exigir novo pareamento ou recarregar a página. ### Gestão de vagas e operação pelos atendentes Mantenha a identidade da chamada como `(servidor, instanceId, callId)`; no escopo de um servidor, `(instanceId, callId)`. Um mesmo `callId` pode existir em duas instâncias quando ambas as pontas estão pareadas. Conte recebidas e realizadas, incluindo as que estão tocando. `answered` confirma atendimento em `connecting`; não espere `active` para reservar a vaga. Eventos `video` atualizam a mesma chamada e não consomem vaga adicional. Eventos repetidos ou fora de ordem não podem incrementar o contador de novo. `ended` libera uma vez e conserva o primeiro motivo/origem de encerramento; eventos antigos não reabrem a chamada. `recording_ready` e `recording_failed` só atualizam a gravação: não ocupam nem liberam uma vaga por conta própria. Evite um contador isolado de incrementos/decrementos; derive a ocupação de registros idempotentes de chamadas e reservas válidas. Antes de discar, faça reserva atômica da vaga no backend compartilhado do Atendro. Duas abas, atendentes ou réplicas podem discar ao mesmo tempo; desabilitar o botão no navegador não impede essa disputa. Conte reservas pendentes ainda sem `callId`, atribua identidade à tentativa e reutilize sua `Idempotency-Key` ao repetir a mesma discagem. Ao receber `callId`, converta a reserva em chamada sem contar duas vezes. Correlacione também quando o webhook chegar antes da resposta HTTP. Em falha definitiva ou `line_busy`, libere a reserva e reconcilie. Em resultado ambíguo, preserve-a de forma conservadora até resolver pelo mecanismo de idempotência e consulta do provedor; o GET de chamadas ativas não mostra reservas internas ainda sem ID. Não libere uma reserva apenas porque o navegador fechou ou uma resposta demorou. O provedor continua sendo a autoridade final de admissão: ligações recebidas podem ocupar uma vaga entre a checagem local e a discagem. Trate `409 line_busy` como condição operacional, atualize a ocupação e informe o atendente. Quando sem vaga, mostre o limite e as chamadas que a ocupam. Se o Atendro implementar espera, ela deve ser explícita e cancelável; não iniciar uma ligação futura sem a intenção do atendente ainda válida. Mostre, por conexão, limite configurado, chamadas vivas e tentativas pendentes. Disponibilidade é no mínimo zero; ao reduzir abaixo da ocupação, mostre a situação excedente sem desligar ninguém. A permissão de alterar o limite deve ser validada no backend, respeitar empresa e conexão e aceitar somente inteiros de 1 a 8. Estado desconectado bloqueia discagem, mas permite configurar o limite. Cada chamada precisa de proprietário/atendente e ponte de mídia próprios, identificados por instância e `callId`. Não use um único socket ou estado global por número/conexão. Dois atendentes não devem tomar a mesma chamada por acidente; o AtendroZAP aceita substituir um attachment, o que não é um bloqueio de propriedade de atendimento. Preserve as regras existentes de transferência. Microfone, reprodução e câmera devem ser associados à chamada atendida e ao usuário correto, sem misturar áudio entre chamadas. Mantenha a janela existente de reconexão da ponte: o AtendroZAP tolera 30 s sem socket de áudio/vídeo e o Atendro retém a sessão por 25 s. Desligamento voluntário usa `/call/hangup`; câmera desligada usa `/call/video` com `action: stop` e não libera a vaga. Uma queda de socket não é, sozinha, prova de `ended`. Ao abrir/reconectar a tela, ao reconectar o receptor e após erros de admissão, reconcilie configuração e chamadas com `GET /instance/status` e `GET /call/active`. Consulte o estado/histórico das chamadas locais que desapareceram da lista para obter o encerramento real. Proteja essa leitura contra eventos mais novos recebidos em paralelo; preserve as reservas ainda em resolução. Faça reconciliação periódica moderada como recuperação de eventos perdidos, sem depender de polling rápido para a operação normal. ### Testes e entrega Cubra com testes significativos, usando mocks/fixtures e banco descartável: 1. Limite alterado no AtendroZAP de 1 para 2 chega via webhook e atualiza persistência, interface e admissão sem reconectar a instância. 2. Configuração via Atendro usa credencial no backend, valida permissões, aceita 1 a 8 e rejeita valores inválidos; falha remota não aparenta sucesso. 3. Snapshot inicial, versão zero, HTTP antigo após webhook novo, eventos duplicados/fora de ordem, assinatura inválida e instância de outra empresa. 4. Duas reservas concorrentes com limite 1 admitem apenas uma; com limite 2 admitem duas e recusam a terceira, inclusive entre réplicas do backend. 5. Webhook chega antes da resposta de discagem; timeout ambíguo não duplica chamada nem libera a vaga prematuramente; `409 line_busy` reconcilia. 6. Chamada recebida disputa vaga com saída; vídeo e gravação não contam duas vezes; duas instâncias com o mesmo `callId` continuam separadas. 7. Redução de 2 para 1 com duas chamadas vivas preserva ambas e bloqueia novas. 8. Reload, queda do receptor, reconexão dos sockets e atualização concorrente preservam propriedade, contagem e primeiro encerramento. 9. Inscrição mescla eventos sem perder URL, segredo, filtros ou herança; instância de mensagens e demais provedores conservam o comportamento atual. Entregue mudanças, migrações necessárias, testes executados e instruções de ativação. Não publique nem altere instâncias, limites, webhooks ou credenciais de produção apenas para executar testes. Separe o resultado dos testes locais da homologação pendente de duas chamadas WhatsApp reais no mesmo número. Referências: [Chamadas](https://wpp.atendro.cloud/docs/guias/chamadas-de-voz.md), [Eventos](https://wpp.atendro.cloud/docs/guias/eventos.md), [Receptor HMAC](https://wpp.atendro.cloud/docs/guias/receber-eventos.md) e [OpenAPI](https://wpp.atendro.cloud/openapi.json).