Evolução transversal do Cadencia — ciclo dev externo 2026-06/07¶
Consolidação das entregas de CRM/scoring, email por tenant, onboarding atômico, UX mobile e confiabilidade que acompanharam Lara, agenda e cadências.
Por que foi construído assim¶
As mudanças parecem independentes na interface, mas compartilham o mesmo objetivo técnico: consolidar o CRM Cadencia como fonte única e impedir perda de estado em fluxos assíncronos e multi-tenant. Configuração passou a usar merges atômicos; email ganhou domínio e identidade por tenant; eventos carregam a origem do conteúdo; a UI passou a refletir estado real em desktop e mobile.
O caminho de email foi desenhado para tolerar callbacks duplicados e respostas incertas. O webhook responde cedo ao Svix, mas usa idempotência e processamento durável. O provider só é habilitado após verificação do domínio, e writers concorrentes não substituem o objeto inteiro de configuração.
Stack¶
| Domínio | Stack |
|---|---|
| CRM e UI | Next.js 15, React 19, Supabase, Tailwind |
| Configuração | PostgreSQL RPC, tenant_config.config JSONB |
| Resend, Cloudflare DNS, List-Unsubscribe | |
| Scoring | Webhook Svix, Python, Supabase scoring_events |
| Integrações | CRM Cadencia, Resend/Svix, DataStone, OpenAI-compatible SDK |
Como funciona¶
flowchart TD
classDef core fill:#FEE2E2,stroke:#EF4444,color:#111
classDef external fill:#FEF3C7,stroke:#F59E0B,color:#111
classDef warning fill:#FEF9C3,stroke:#EAB308,color:#111
classDef decision fill:#EDE9FE,stroke:#8B5CF6,color:#111
classDef flow fill:#DBEAFE,stroke:#3B82F6,color:#111
classDef component fill:#D1FAE5,stroke:#10B981,color:#111
subgraph SG_component["Componentes"]
doc[("Projetos/Cadencia/Docs/evolucao-transversal-2026-07.md")]
app["cadencia-app"]
growth["cadencia-growth"]
end
class doc,app,growth component
subgraph SG_flow["Fluxo do processo"]
configure["Tenant configura identidade e email"]
merge["RPC faz merge atômico sem clobber"]
send["Resend envia com tags, compliance e rate limit"]
event["Svix entrega evento idempotente com post_id"]
crm["CRM atualiza score, temperatura e timeline"]
end
class configure,merge,send,event,crm flow
subgraph SG_decision["Decisões"]
verified["Domínio está verificado?"]
duplicate["svix-id já foi processado?"]
tag["post_id veio na tag?"]
provider["Seleciona provider próprio do canal"]
end
class verified,duplicate,tag,provider decision
title["Evolução transversal do Cadência"]
class title core
data["Supabase + Resend"]
class data external
risk["Falha/gap"]
class risk warning
configure --> merge
merge --> send
send --> event
event --> crm
merge --> verified
event --> duplicate
event --> tag
send --> provider
verified -->|"não"| risk
duplicate -->|"sim"| risk No CRM, score e temperatura ganharam representação visual e acessível; a timeline usa post_id para atribuir eventos ao email correto. O board de oportunidades pagina explicitamente com .range(). No onboarding, merge_tenant_config e merge_tenant_config_email preservam mudanças concorrentes.
No runtime de growth, o envio carrega tags, respeita rate limit e deduplica retries. O webhook normaliza eventos, deduplica por svix-id, recupera tags ausentes pela API do Resend e drena processamento no shutdown.
Decisões técnicas¶
config.emailé canônico; chaves flat são apenas espelho de transição.- RPCs de merge são
service_roleonly e substituem read-modify-write de JSONB. - Resend só vira provider após domínio verificado.
post_idatravessa sender, webhook escoring_eventspara atribuição por conteúdo.- Responder 200 cedo ao Svix exige idempotência e processamento recuperável.
- Cada canal valida somente a credencial de seu provider atual.
- A base mobile usa safe areas,
dvh, overflow guard e ações contextuais.
Gotchas & armadilhas¶
- Conceder as RPCs a
anon/authenticatedquebra a fronteira de segurança. - Escrever somente nas chaves flat pode recriar drift entre formatos.
- Sem
post_id, o scoring precisa recorrer ao fallback/emails/{id}. range()do PostgREST é inclusivo; lotes consecutivos não podem repetir o limite.- Resposta 200 sem dedupe perde a capacidade de retry seguro.
- Domínio com DKIM/SPF mas sem tracking DNS ainda não fecha o ciclo de clique.
- Safe area deve ser validada junto com TabBar, modais e toasts.
Como operar¶
- Verifique
config.emaile o estado do domínio antes de habilitar Resend. - Execute o preflight de email e valide envio, unsubscribe, open e click.
- Confirme
post_idno envio e emscoring_eventsapós o webhook. - Monitore duplicatas por
svix-ide o fallback de tags. - Em alterações de onboarding/config, use as RPCs de merge e nunca grave o JSON inteiro.
- Teste CRM e shell mobile em viewport com safe area e base grande o suficiente para paginar.
FAQ¶
Por que existem chaves flat e config.email ao mesmo tempo? As flat mantêm leitores legados durante a migração. O objeto aninhado é a fonte canônica.
Responder 200 antes de processar não perde eventos? Não quando o worker mantém processamento durável e idempotência por svix-id; sem esses dois requisitos, perderia.
Quais providers atendem os canais? Email usa Resend, WhatsApp usa Lara/Evolution e os demais canais usam suas integrações próprias.
Como um evento é ligado ao email que o originou? Pela tag post_id, persistida também em scoring_events, com fallback para a API do Resend.