Todo banco do mundo opera sobre a mesma unidade primitiva: a transação. Um débito, um crédito, um carimbo de data e hora, uma referência. Bilhões desses lançamentos atravessam os sistemas financeiros diariamente, e todo o aparato regulatório, operacional e técnico do sistema bancário existe para garantir que eles fechem corretamente. A contabilidade por partidas dobradas é uma das grandes invenções da humanidade, e todo core bancário em operação hoje é, de uma forma ou de outra, uma variação dessa ideia.

Nós abrimos mão disso.

Não de maneira irresponsável — passamos meses refletindo sobre as implicações antes de escrever uma única linha de código. Mas a conclusão era inevitável: o modelo transacional responde à pergunta errada. Um razão contábil mostra o que aconteceu. Nós precisávamos de um sistema que representasse o que deveria acontecer, acompanhasse se isso de fato ocorreu e controlasse o que pode acontecer em seguida.

O abismo entre intenção e execução

Pense no que realmente acontece quando quatro estudantes dividem um apartamento.

O aluguel é de R$ 4.000 por mês. O contrato está no nome da Maria — foi ela quem assinou. Os outros três fazem um Pix de R$ 1.000 para ela, e a Maria paga o proprietário a partir da sua conta pessoal. A conta de luz está no nome do João. Todo mês ele paga a fatura, manda um print no grupo e divide o valor por quatro, enviando pedidos de Pix. A internet custa R$ 120 no cartão do Pedro — às vezes ele esquece de cobrar a parte de cada um e, como "são só R$ 30 por pessoa", ninguém acompanha isso com muito rigor. Eles compartilham uma conta da Netflix no cartão da Ana, um plano família do Spotify no cartão da Maria e um serviço de limpeza quinzenal em que cada vez uma pessoa diferente paga. As compras compartilhadas — café, papel higiênico, produtos de limpeza — vão sendo jogadas no grupo como fotos de recibos, para serem acertadas "depois".

Em qualquer momento, existem entre cinco e oito obrigações financeiras ativas entre essas quatro pessoas, com valores diferentes, vencimentos diferentes e responsáveis diferentes. O estado real de quem deve quanto para quem vira uma conta mental contínua com a qual ninguém concorda totalmente.

A infraestrutura atual para administrar isso costuma ser a seguinte: um grupo no WhatsApp chamado "🏠 Finanças do Apê", com centenas de mensagens não lidas; uma planilha no Google Sheets que foi atualizada religiosamente nos dois primeiros meses e depois abandonada; e uma noção coletiva e imprecisa de que, "no fim das contas, deve estar mais ou menos equilibrado".

Não existe, em nenhum sistema bancário, um objeto que represente "o apartamento". Não existe uma forma de enxergar o estado financeiro completo de uma convivência compartilhada. Não existe um mecanismo para garantir que "as contribuições do aluguel precisam ser pagas até o dia 5". Não existe uma trilha de auditoria que prove que a Maria recebeu e repassou R$ 3.000 todos os meses durante um ano. Não existe uma forma de a Maria demonstrar que não está ficando com parte do dinheiro — nem de os colegas verificarem que ela realmente está pagando o aluguel em dia. Todas essas obrigações existem completamente fora do sistema financeiro, espalhadas em mensagens, memória e confiança social.

E quando isso dá errado — alguém sai do apartamento no meio do mês e diz que pagou a mais, ou acusam a Maria de ter atrasado o aluguel — não existe uma fonte única da verdade. Só prints e discussão.

Isso não é um caso de borda. É a realidade financeira diária de milhões de pessoas que dividem despesas de moradia, organizam atividades em grupo, coordenam assinaturas compartilhadas ou administram caixas informais.

As finanças modernas não são mais apenas escrituração. Elas são orquestração. E o modelo transacional nunca foi concebido para orquestrar nada.

O que, de fato, há de errado com as transações

Nada, se tudo o que você precisa é de um registro histórico. O modelo débito-crédito é perfeitamente adequado para escrituração — ele serviu aos bancos dos Médici no século XV e continua sustentando todos os sistemas de core bancário atuais.

Os problemas aparecem quando se tenta representar algo além de fatos já consumados:

Ambiguidade de estado. Quando um cliente inicia uma transferência, a conta de origem é debitada imediatamente. Mas o dinheiro ainda não se liquidou. Ele está em trânsito. Se a transferência falhar ou retornar, o valor precisa ser creditado de volta. O razão central não tem um conceito nativo e de primeira classe para algo como "comprometido, mas ainda não resolvido". Esse estado só existe em flags na camada de aplicação, acrescentadas por cima de um modelo que só sabe lidar com lançamentos concluídos.

Carga de reconciliação. Como o core não consegue representar estados intermediários, entra em cena um sistema separado de reconciliação para casar débitos de saída com confirmações de entrada. Você roda processos em lote. Compara saldos esperados com saldos reais. Investiga divergências. Monta um time para isso — e é justamente aí que nasce boa parte dos erros operacionais do sistema bancário.

Lacunas de auditoria. O diário registra que R$ 500 foram debitados. Mas não registra por que isso aconteceu, qual resultado era esperado ou qual evidência confirmou esse resultado. Esse contexto fica espalhado por sistemas paralelos — quando chega a ser registrado em algum lugar.

Esses também não são casos excepcionais. São parte da realidade cotidiana de operar qualquer sistema financeiro, e o problema se agrava a cada novo produto construído em cima do mesmo razão.

Nossa unidade fundamental: a obrigação

No modelo da Sona, a unidade fundamental não é a transação. É a obrigação — uma unidade de valor submetida a uma restrição, com um ciclo de vida definido.

A obrigação é um objeto de primeira classe no nosso sistema. Ela tem identidade própria. Tem uma máquina de estados. Tem um conjunto definido de transições válidas. E carrega todo o contexto do seu ciclo de vida, da criação até a resolução.

Quando um cliente inicia uma transferência, o sistema não apenas registra um débito. Ele cria uma obrigação que declara o que deve acontecer, acompanha o que de fato aconteceu e controla o que pode acontecer na sequência:

Todo movimento de dinheiro na Sona — de um Pix simples a um pagamento complexo com múltiplas partes — é modelado como uma obrigação que percorre uma máquina de estados determinística:

OPEN → PENDING → SETTLED → FAILED → CANCELED → DISPUTED → RESOLVED

Um envio simples de Pix é uma obrigação com pré-condições triviais e um único caminho de cumprimento. Um pagamento em grupo é uma obrigação com pré-condições compostas (todas as contribuições coletadas) e múltiplos resultados de execução (pagamento ao estabelecimento mais distribuição de troco). O modelo é o mesmo. A máquina de estados é a mesma. O que muda é a complexidade.

A máquina de estados é o produto

Essa foi a parte que mais demoramos para internalizar: a máquina de estados das obrigações não é um detalhe de backend. Ela é o produto.

Em um sistema bancário tradicional, o cliente vê "Pendente" na lista de transações. Esse rótulo é cosmético — uma convenção de interface que encobre o fato de que o core, na prática, não possui um conceito real de pendência. O débito já foi lançado. "Pendente" significa apenas: "ainda não reconciliamos isso".

No nosso sistema, PENDING é um estado real. O valor está bloqueado e comprometido — ninguém pode gastá-lo até que a obrigação seja resolvida. O saldo do cliente reflete corretamente, e de forma estrutural, três categorias distintas: saldo disponível, saldo comprometido e saldo pendente. Isso não acontece porque mostramos etiquetas diferentes sobre o mesmo número, mas porque esses estados são genuinamente diferentes no modelo de dados subjacente.

Ponto-chave

Nossa arquitetura impõe essa consistência desde a camada de liquidação até o pixel na tela. A camada de domínio define entidades como objetos imutáveis que carregam todo o seu estado de ciclo de vida. A camada de repositório consegue consultar nativamente por status — "me mostre tudo que ainda está pendente" ou "me mostre todas as movimentações contestadas" são consultas de primeira classe, não filtros improvisados na camada de aplicação.

E a camada de apresentação espelha a mesma filosofia de máquina de estados:

sealed class AsyncState<T> { const AsyncState(); } final class AsyncIdle<T> extends AsyncState<T> {} final class AsyncLoading<T> extends AsyncState<T> {} final class AsyncSuccess<T> extends AsyncState<T> { final T data; } final class AsyncError<T> extends AsyncState<T> { final String message; }

Quando o modelo de dados é orientado por máquina de estados, a interface naturalmente passa a seguir a mesma lógica. Isso não é coincidência. É consistência arquitetural. E é isso que elimina toda uma classe de bugs em que a interface mostra uma coisa enquanto o backend sabe outra.

Liquidação orientada por evidências

Em vez de "assumir que o dinheiro saiu e reconciliar depois", nós adotamos "bloquear o valor, esperar a prova e então resolver".

O modelo de obrigação inverte o fluxo tradicional de liquidação. Quando ocorre um evento externo — uma instituição recebedora confirma um crédito, uma trilha de pagamento devolve um erro, uma contraparte reconhece a entrega — esse evento chega ao nosso sistema como uma peça de evidência. Essa evidência é normalizada, deduplicada e associada à obrigação pendente correspondente. Se ela confirma o resultado esperado, a obrigação é resolvida. Se contradiz a expectativa, a obrigação transita para um estado de exceção.

Cada etapa é determinística. A deduplicação de evidências acontece em múltiplas camadas para garantir que a mesma confirmação externa não seja processada duas vezes, independentemente de quantas vezes ela seja entregue. A resolução de identidade mapeia identificadores externos para contas internas por meio de um mecanismo determinístico e agnóstico à trilha de pagamento. E o casamento entre obrigação e evidência é preciso: se a evidência não corresponder exatamente a uma obrigação pendente, ela vira exceção — não um erro silencioso de reconciliação.

O ponto central é que as exceções são tratadas como cidadãs de primeira classe. Elas têm transições de estado próprias e caminhos de resolução próprios. Não são varridas para debaixo do tapete da reconciliação, nem empurradas para um sistema operacional separado. Um pagamento contestado vive na mesma máquina de estados que um pagamento bem-sucedido, com o mesmo peso arquitetural, as mesmas formas de consulta e os mesmos padrões de interface.

Agnosticismo em relação às trilhas, sem ignorar as trilhas

Uma das decisões arquiteturais em que mais confiamos é a separação rigorosa entre o mecanismo de obrigações e a camada das trilhas de pagamento.

O sistema central — a parte que cria obrigações, administra transições de estado, bloqueia valor e resolve liquidações — não sabe nada sobre Pix, ACH, SWIFT ou qualquer trilha específica. Ele opera sobre obrigações, eventos de evidência e chaves de identidade.

Todo o conhecimento específico de trilha fica encapsulado em adaptadores. Cada adaptador lida com as particularidades da sua trilha: como interpretar evidências recebidas, como construir chaves de identidade, como despachar pagamentos de saída. Quando adicionamos uma nova trilha de pagamento, escrevemos um novo adaptador. O sistema central não precisa mudar.

Por que isso importa

Isso não é apenas uma preocupação com "arquitetura limpa". É uma estratégia de sobrevivência. As trilhas de pagamento estão sempre mudando. O Pix ganha novos tipos de transação. O ACH atualiza códigos de retorno. O SWIFT migra para o ISO 20022. Se a lógica central estivesse cheia de ramificações específicas de cada trilha, cada mudança nessas redes seria, na prática, uma mudança no razão. Ao isolar a semântica das trilhas nos adaptadores, essas mudanças ficam contidas, testáveis e implantáveis de forma independente.

A partida dobrada continua valendo — mas de outra forma

Nós não abandonamos a contabilidade por partidas dobradas. Nós a reinterpretamos.

No nosso sistema, toda transferência produz obrigações que precisam fechar entre si. Os livros sempre fecham — cada unidade de valor está contabilizada dentro de uma obrigação em cada ponto do seu ciclo de vida. Mas, em vez de lançamentos contábeis que precisam ser reconciliados depois, temos obrigações que impõem sua própria consistência como uma invariante estrutural do modelo de dados.

A contabilidade por partidas dobradas deixa de ser uma checagem procedural feita depois do fato e passa a ser uma propriedade estrutural do próprio sistema. Essa é a diferença.


O que você ganha "de graça"

A reconciliação vira uma consulta

Em um razão tradicional, reconciliação é um processo — jobs em lote, comparação de saldos, investigação de divergências, equipe dedicada. Em um modelo baseado em obrigações, reconciliação vira uma consulta. Toda obrigação tem um resultado esperado e um resultado efetivo. Se os dois coincidem, ela está liquidada. Se não coincidem, temos uma exceção. Não há ambiguidade, não há processamento em lote, não há cron rodando às duas da manhã. O sistema está sempre reconciliado porque cada estado está explicitamente representado.

A auditabilidade passa a ser estrutural

Quando um regulador pergunta "mostre o ciclo de vida deste pagamento", não precisamos reconstruí-lo a partir de logs e snapshots de banco de dados. O ciclo de vida é o próprio modelo de dados. Cada transição de estado é um evento explícito e registrado. A trilha de auditoria não é algo que geramos depois — é algo que nós somos.

Isso é especialmente importante no nosso contexto regulatório. Como Instituição de Pagamento no Brasil, mantemos recursos em contas segregadas de salvaguarda. Cada real precisa estar devidamente explicado. Em um modelo baseado em obrigações, conseguimos provar, para qualquer instante no tempo, exatamente em que estado cada operação financeira estava e por quê.

A detecção de fraude vira análise de restrições

Em vez de procurar padrões suspeitos em sequências de transações depois do fato, conseguimos analisar anomalias de estado das obrigações em tempo real. Uma obrigação que permanece pendente por mais tempo do que a janela esperada já é um sinal. Uma obrigação que migra para um estado de exceção já é um sinal. A própria máquina de estados se torna um mecanismo de detecção.

Como novos recursos se compõem

É aqui que o modelo de obrigação entrega seu maior dividendo: extensibilidade.

Quando construímos transferências básicas, implementamos o ciclo de vida central da obrigação. OPEN → PENDING → SETTLED/FAILED. Simples.

Quando construímos pagamentos em grupo, não precisamos inventar um novo modelo. Precisamos apenas de um novo tipo de obrigação — uma obrigação com pré-condições compostas (as contribuições dos participantes) e múltiplos resultados de execução (pagamento ao lojista mais distribuição de troco). Mas a máquina de estados continua sendo a mesma. O padrão de repositório continua sendo o mesmo. O tratamento de estado na interface continua sendo o mesmo.

Todo módulo de funcionalidade no nosso código segue o mesmo padrão de Clean Architecture:

feature/ ├── domain/ │ ├── entities/ # Objetos imutáveis com estado de ciclo de vida │ └── repositories/ # Contratos abstratos ├── data/ │ ├── models/ # Camada de serialização │ ├── datasources/ # Acesso a dados │ └── repositories/ # Implementação └── presentation/ ├── providers/ # Gerenciamento de estado baseado em máquina de estados ├── pages/ # Telas completas └── widgets/ # Componentes reutilizáveis

Quando adicionamos o SafeSwap (trocas peer-to-peer protegidas), criamos um novo diretório de funcionalidade com a mesma estrutura. A obrigação terá novos estados — aguardando confirmação de cumprimento, evidência enviada, disputa em análise — mas continuam sendo estados dentro do mesmo tipo de máquina, geridos pelo mesmo tipo de provider e renderizados segundo o mesmo padrão de interface.

Quando adicionamos o Subscription Squad (pagamentos recorrentes coordenados), vale a mesma lógica. Quando adicionamos tesourarias de grupo transparentes, a mesma coisa. O custo marginal de criar um novo produto financeiro coordenado cai a cada nova funcionalidade, porque o modelo absorve a complexidade em vez de multiplicá-la.

Pense no que isso torna possível: um desembolso de crédito em que os recursos ficam bloqueados sob restrições e vão sendo liberados em parcelas conforme marcos verificados. Uma troca multimoeda em que as duas pontas são obrigações resolvidas atomicamente — ou não resolvidas de jeito nenhum. Um escrow programável em que as condições de liberação ficam codificadas nas próprias restrições da obrigação. Nada disso exige uma nova infraestrutura. São apenas configurações diferentes da mesma unidade fundamental.

O trade-off

Não somos ingênuos quanto ao custo dessa abordagem.

Finanças baseadas em intenção são mais difíceis de construir no início. Um razão tradicional é conceitualmente simples — débitos e créditos, saldos e extratos. Dá para colocar um aplicativo bancário básico de pé com relativa rapidez. O nosso modelo exige que você pense cuidadosamente sobre cada operação financeira antes de escrever uma única linha de código. Quais são as pré-condições? Quais são os critérios de sucesso? Quais são os caminhos de exceção? Como funciona uma reversão?

A máquina de estados precisa estar correta. Um bug nas transições de estado é um bug no próprio razão. Por isso investimos pesadamente em tornar as transições explícitas, auditáveis e impostas em múltiplas camadas.

Valor pendente é um conceito real, com implicações reais. Recursos bloqueados em obrigações criam restrições de liquidez e exigem uma gestão cuidadosa de políticas de timeout e expiração.

O pipeline de evidências é caminho crítico. Se a entrega de evidência falha ou atrasa, as obrigações permanecem pendentes. Deduplicação e idempotência estão presentes em todas as camadas, mas a confiabilidade desse pipeline continua sendo um investimento permanente de engenharia.

Esses custos são reais. Nós os aceitamos porque "difícil de construir no começo" é um custo pontual. Já "difícil de estender e operar" é um custo recorrente, que só cresce com o tempo. Preferimos pagar o preço inicial e construir sobre uma base que fique mais fácil de expandir à medida que evolui.

O que isso significa para o usuário

O usuário não se importa com a nossa arquitetura. E não deveria precisar se importar.

Mas ele vai sentir a diferença. Quando você gerenciar um apartamento compartilhado dentro da Sona, não vai precisar de um grupo no WhatsApp e de uma planilha abandonada. Você vai enxergar cada obrigação — aluguel, luz, internet, assinaturas, despesas compartilhadas — como um objeto coerente e vivo. Vai saber quem deve o quê, quem já pagou, o que está atrasado e onde está cada real a cada instante. A Maria não vai precisar provar que pagou o proprietário — o histórico de estados da obrigação faz isso por ela. Quando o João lançar a conta de luz, a divisão e a cobrança acontecem dentro do mesmo sistema que acompanha todo o resto. Quando alguém sair do apartamento, haverá uma liquidação real — e não uma negociação baseada em prints.

E se algo der errado, você verá exatamente o que deu errado e o que está sendo feito para resolver. Não uma mensagem vaga dizendo "transação falhou", mas um estado específico com um caminho específico de resolução.

O sistema sabe o que deveria ter acontecido, sabe o que efetivamente aconteceu e consegue explicar a diferença entre as duas coisas. Nenhuma outra arquitetura bancária faz isso, porque nenhuma outra arquitetura bancária modela a intenção como um conceito de primeira classe.