Pular para o conteúdo
Todos os projetos

Produto independente · Mobile & Web

Kairo

App de finanças pessoais offline-first construído com Expo, React Native e TypeScript. Todos os dados financeiros ficam no dispositivo, em SQLite, o que o torna rápido, privado e independente de conexão.

Período
2026 · Em desenvolvimento
Papel
Arquitetura, Design System e implementação
  1. UITelas do Expo Router compõem componentes do Design System e consomem hooks, sem SQL e sem regra de negócio.
  2. HooksChamam services e fazem cache com React Query, expondo estados de carregamento e erro.
  3. ServicesÚnico lugar com regra de negócio; TypeScript puro e com testes unitários.
  4. RepositoriesAcesso a dados de uma entidade, mapeando linhas para entidades de domínio tipadas; dependem apenas de um DatabaseAdapter.
  5. SQLiteÚnica fonte de verdade dos dados financeiros, com migrations versionadas.
O Design System consome apenas tokens do tema e recebe dados exclusivamente por props, podendo ser extraído como pacote independente.

Contexto

Um app financeiro precisa ser confiável antes de qualquer outra coisa: os saldos têm que fechar sempre, os dados precisam sobreviver sem conexão e o código precisa continuar manutenível à medida que cartões, parcelamentos, recorrências e sincronização chegam. A arquitetura do Kairo foi planejada em torno dessas restrições e documentada antes da implementação.

Arquitetura

Regras de negócio ficam nos services, acesso a dados nos repositories e o React apenas orquestra. Cada camada tem uma única responsabilidade e uma lista do que não pode fazer.

SQLite → Repositories → Services → Hooks → UI

Decisões principais

  • Dinheiro em centavos inteiros

    Aritmética de ponto flutuante gera erros de arredondamento inaceitáveis em um app financeiro (0.1 + 0.2 ≠ 0.3).

    Decisão: Todo valor é armazenado como INTEGER em centavos e convertido apenas para exibição, na camada de formatação.

  • Saldo derivado

    Uma coluna de saldo armazenada dessincroniza quando transações são editadas, excluídas ou importadas.

    Decisão: A conta guarda apenas o saldo inicial; o saldo atual é sempre calculado a partir das transações e mantido em cache pelo React Query, invalidado a cada mutação.

  • O banco como servidor local

    Duplicar dados do banco em stores do cliente cria duas fontes de verdade.

    Decisão: O React Query é dono de tudo que vem do SQLite; o Zustand guarda apenas estado de UI. Tratar o SQLite como "servidor local" deixa a futura sincronização quase transparente.

  • Pronto para sincronizar, sem implementar a sincronização

    IDs auto-incrementais colidem entre dispositivos, e exclusões definitivas quebram o histórico.

    Decisão: IDs UUID v4 gerados no cliente, datas ISO-8601 em UTC e soft delete com timestamps de auditoria em todas as tabelas.

  • Adapters em vez de remover funcionalidades

    O MMKV, usado para preferências no nativo, não existe na web.

    Decisão: Uma interface PreferencesStorage com implementações em MMKV e localStorage, resolvidas por plataforma (.ts / .web.ts).

  • Migrations versionadas

    Chamadas avulsas de "CREATE TABLE IF NOT EXISTS" espalhadas pelo código tornam mudanças de schema imprevisíveis.

    Decisão: Migrations sequenciais controladas por PRAGMA user_version e executadas dentro de uma transação na inicialização.

Qualidade

  • TypeScript em modo strict, com validação Zod nas bordas (formulários e importação de backup).
  • Hierarquia única de erros tipados (AppError → DomainError, ValidationError, NotFoundError, DatabaseError), mapeada por código na UI.
  • Testes com Jest e Testing Library para services, runners de migrations e seeds, utilitários e componentes do Design System.

Status

Implementado

  • Dashboard
  • Contas
  • Categorias
  • Receitas e despesas
  • Transferências
  • Visualização de fluxo de caixa

Planejado

  • Orçamentos
  • Cartões de crédito e parcelamentos
  • Transações recorrentes
  • Exportação de dados
  • Backup em nuvem e sincronização entre dispositivos

Stack

  • Expo
  • React Native
  • Expo Router
  • TypeScript
  • SQLite
  • React Query
  • Zustand
  • Zod
  • NativeWind
  • Jest
Próximo estudo de casoBatata com Granulado