Pular para conteúdo

Integrações e limites de responsabilidade

O core usa interfaces Python (Protocol) para depender de operações de negócio, enquanto os adapters traduzem essas operações para HTTP, SDK ou simulação. Uma factory é a função que escolhe e constrói a implementação com as configurações do ambiente. O ponto de composição principal é src/core/integrations/factory.py: build_dependencies() entrega um Dependencies para os casos de uso.

Esta referência descreve o código auditado em 14/09/2026, branch fix/ajustes, commit base 175a0bd986675a75ef760349b123595bf9fe714d. Implementação presente no repositório não comprova configuração, publicação ou funcionamento do serviço externo em produção.

Mapa de contratos

Fronteira Interface e operação Implementação real Simulação Consumidor
WhatsApp WhatsAppClient.send_message(to, text) WhatsAppCloudClient WhatsAppClientStub Webhook, orchestrator, painel e notificador
OpenAI LLMClient.chat(model, messages) OpenAIClient, Responses API Mocks injetados nos testes; sem stub selecionável por env AgentInvokerImpl, reparo de formato, resumo
CRM HubSpotClient.upsert_contact, create_deal, update_deal HubSpotHttpClient, base configurável HubSpotClientStub Confirmação cadastral e worker CRM
eGestor EGestorClient.upsert_customer(payload) EGestorHttpClient EGestorClientStub execute_confirm_action()
Handoff HandoffNotifier.notify(session) HandoffEmailNotifier ou HandoffNotifierWhatsApp HandoffNotifierStub Orquestração e renotificação agendada
Persistência Repositories de sessão, mensagens, demandas, receipts, configuração, autenticação Classes Dynamo* e CrmSyncRepository DynamoDB simulado por moto Toda a aplicação; veja DynamoDB

Os módulos de cada provider ficam em src/core/integrations/; LLMClient fica em openai_provider/client.py, e os demais contratos de provider em seus arquivos interface.py.

Respostas comuns

Apesar do nome histórico, StubResponse é usado também por implementações reais. O contrato em src/core/integrations/types.py é:

@dataclass(frozen=True)
class StubResponse:
    success: bool
    mock_id: str | None = None
    detail: str | None = None
    error_kind: Literal["transient", "permanent", "uncertain"] | None = None

mock_id contém o identificador real quando a chamada foi real. success=True nem sempre significa que existe ID: cada consumidor precisa exigir o ID quando necessário. CRM usa error_kind para separar falha temporária, bloqueio e resultado de criação desconhecido. WhatsApp/eGestor/handoff normalmente deixam esse campo vazio. A OpenAI retorna LLMCallResult e lança exceções próprias, em vez de usar este contrato.

Fluxo entre cadastro, CRM e ERP

sequenceDiagram
    participant O as Orchestrator
    participant C as ConfirmAction
    participant CRM as API CRM
    participant ERP as API eGestor
    participant DB as DynamoDB
    participant W as CrmSyncProcessor
    O->>C: Coleta s=4 e dados confirmados
    C->>C: Validar campos e gerar resumo
    C->>CRM: upsert_contact
    C->>ERP: upsert_customer
    C-->>O: HANDOFF_PENDING ou api_failure
    O->>DB: Sessão e intenção CRM na mesma transação
    Note over O,DB: Notificação humana após persistência
    W->>DB: Claim da projeção pendente
    W->>CRM: upsert_contact e POST ou PATCH do deal
    W->>DB: Confirmar revisão ou registrar falha

O worker é o único escritor de negócios/deals do fluxo da aplicação. Ainda existe upsert síncrono de contato CRM na confirmação, seguido do eGestor; não há transação distribuída nem rollback entre os providers. Se o contato falhar, o eGestor ainda é tentado. Ao substituir integrações, preserve a distinção entre sessão autoritativa no DynamoDB e projeção comercial eventual.

Configuração, segurança e diagnóstico

Os flags WHATSAPP_CLOUD_ENABLED, HUBSPOT_ENABLED, EGESTOR_ENABLED e HANDOFF_NOTIFIER_ENABLED selecionam adapters reais ou stubs. Desativar os três últimos não desativa a OpenAI: a factory cria OpenAIClient e load_settings() ainda exige chave. Os stubs retornam sucesso fictício, que permite avançar estados sem entregar nada externamente.

load_settings() é cacheado. Reinicie o processo após alterar env; loaders locais usam setdefault, portanto variáveis já presentes no processo prevalecem sobre .env. Valores diretos de OpenAI/CRM/eGestor prevalecem sobre SSM. Consulte a tabela completa de variáveis.

Logs passam por src/shared/logging.py, com JSON e scrubbing de chaves/expressões conhecidas. Isso não é anonimização universal: nomes de contato em firstname/lastname, chaves novas e exc_info não têm a mesma proteção que cadastral_data. Nunca adicione tokens aos logs nem copie payloads de clientes para exemplos. O e-mail operacional contém o telefone completo do cliente para uso da equipe; isso é diferente do log sanitizado.

Testar e substituir

tests/unit/integrations/test_factory.py verifica seleção, configuração incompleta e SSM; tests/unit/integrations/test_stubs.py verifica simulações. Os clients aceitam dependências de transporte (opener, sleeper, underlying ou ses_client), permitindo testar payload, erro e retry sem rede. Os smokes são mecanismos separados, capazes de produzir efeitos reais mediante flags.

Para uma migração, implemente o mesmo Protocol, mantenha os testes de contrato, substitua a construção na factory e transporte as configurações necessárias para settings, .env.example, SAM e deploy. O worker CRM tem outra construção de client em src/crm_sync/app.py; alterar apenas a factory principal não altera esse processo. Faça o mesmo para autenticação, payloads, IDs e classificação de erro: apenas mudar uma URL não torna APIs incompatíveis equivalentes.