# Agentes, Tiers e Pausas Agente.md secao 45-49 e 52. Completa o Call Center: depois desta fase, um usuário autenticado consegue logar como agente, entrar numa fila real, se pausar e voltar — tudo refletido de verdade no FreeSWITCH. ## Modelo - `agents` (tenant-scoped, RLS): liga `User` (login) → `Extension` (ramal SIP) → identidade de agente (`User`/`Agent`/`Extension` separados, agente.md secao 45). `state` espelha o estado real (secao 46), nunca escrito direto pela API — só via login/logout/pause/resume. - `tiers`: Queue↔Agent (secao 52) — level/position espelham 1:1 o mod_callcenter. - `agent_sessions`: uma linha por ciclo login→logout. - `agent_state_events`: histórico de transições de estado (auditoria). - `pause_reasons`/`agent_pause_events` (secao 48). ## Mecanismo: 100% dinâmico, sem arquivo Confirmado na fase anterior (Queues) via `help callcenter_config`: agentes e tiers não têm representação em XML — só comandos ESL diretos (`agent add/del/set status/set contact`, `tier add/del`). Isso muda o padrão de sincronização: em vez de "regenerar todos os arquivos" (Trunks/Queues), `apps/api` publica uma mensagem **por ação, com payload** (`{tenantId, agentId, action}` em `b2bcall:agents:sync`; `{tenantId, queueId, agentId, level, position, action}` em `b2bcall:tiers:sync`) — `b2bcall-fs-config` recebe e aplica o comando correspondente. ## Achados reais confirmados manualmente antes de codar Rodei cada comando contra o FreeSWITCH real antes de escrever qualquer lógica de sync (mesmo método usado nas fases anteriores): - `agent add` em cima de um agente que já existe: **erro** ("Agent already exist!") — não é idempotente. `tier add` duplicado: mesmo erro ("Tier already exist!"). Ambos capturados e ignorados no sync (`.catch(() => undefined)`), já que essa condição é esperada em qualquer resync. - `tier del`/`agent del` em algo que não existe: **não dá erro** (+OK) — seguro chamar sem checar existência antes. - `agent set status` só aceita 3 valores exatos: `Available`, `On Break`, `Logged Out` — qualquer outro string dá `-ERR Invalid Agent Status!` (testado deliberadamente). Os estados derivados de chamada do nosso enum (RESERVED/RINGING/IN_CALL/WRAP_UP) não têm status próprio no mod_callcenter — mapeiam pra "Available" (só o `state`, campo separado, muda sozinho conforme a chamada progride — não escrevemos isso). - **Achado de corrida real**, visto ao testar o fluxo completo: como um agente só passa a existir no FreeSWITCH no login (`agent add` roda ali, não na criação do registro `Agent`), atribuir um tier (`POST /queues/:id/agents`) **antes** do primeiro login do agente falha silenciosamente do lado do FreeSWITCH (`-ERR Agent not found!`, logado mas não propagado como erro HTTP). Por isso o login **sempre re-sincroniza todos os tiers do agente** depois de garantir que ele existe — a auto-correção aconteceu exatamente assim no teste real. ## Fluxo de login (agente.md secao 47) ``` POST /agents/me/login → valida usuário (JWT) e ramal (Agent.extensionId precisa existir) → cria agent_session → agent add (idempotente via catch) + set contact (dial-string do ramal) → set status Available → re-sincroniza todos os tiers do agente → Agent.state = AVAILABLE ``` `POST /agents/me/logout|pause|resume` seguem o mesmo padrão — sempre sobre o agente do **próprio usuário autenticado** (nunca um `agentId` arbitrário do client, mesmo princípio de nunca confiar em tenant_id/ids sensíveis vindo do frontend sem checar contra o JWT). ## Verificado ponta a ponta ``` POST /extensions {"number":"2000",...} POST /agents {userId, extensionId, name} POST /queues {"name":"Suporte"} POST /queues/:queueId/agents {agentId} → tier add falha (agente ainda nao existe) POST /agents/me/login → agent add + contact + status Available → tier re-sync (dessa vez funciona) callcenter_config agent list → status=Available, contact={ignore_early_media=true}user/2000@b2bcall.local ✓ callcenter_config tier list → queue|agent com state=Ready ✓ POST /agents/me/pause {pauseReasonId} → status=On Break ✓ POST /agents/me/resume → status=Available ✓ POST /agents/me/logout → status=Logged Out ✓ ``` Todos os 4 estados confirmados batendo entre o banco (`Agent.state`) e o FreeSWITCH (`agent list`). ## O que falta - Estados derivados de chamada (RINGING, IN_CALL, WRAP_UP, RESERVED) — dependem de `callcenter::info` (CUSTOM event), que **ainda não foi provado funcionando** nesta sessão (mesma lacuna de `sofia::gateway_state` documentada em docs/TRUNKS.md). Não implementado; precisa de uma chamada real passando pela fila pra testar. - Tela do agente (secao 49) — fase Frontend. - Monitoramento de filas/ramais em tempo real (secao 54-55) — depende de WebSocket multi-tenant. - Quota de agentes (`max_agents`) — depende de Plans/Entitlements. - `PauseReason.maxDuration` existe no modelo mas não é aplicado automaticamente ainda (ninguém força o fim da pausa ao expirar).