Capivara: hub pessoal contra a bagunça financeira
🐷 Capivara·

Capivara: hub pessoal contra a bagunça financeira

📖 8 min de leitura← Voltar para timeline

O problema

Eu tinha minhas finanças espalhadas em:

  • Nubank — conta principal, cartão de crédito
  • Inter — investimentos, segunda conta
  • PicPay — pagamentos diários, split de conta
  • Wise — recebimentos em dólar (freelas)
  • 3 planilhas Google Sheets — categorização manual
  • Caderno físico — sim, eu usava caderno

No fim do mês, olhar pra tudo isso e saber “quanto eu gastei de verdade” era impossível. Eu passava 2 horas por mês consolidando manualmente.

O Capivara começou como hub seguro (senhas, tokens, chaves), mas virou também meu centro financeiro quando percebi que o mesmo backend que guarda secrets também pode agregar transações.

A primeira versão: planilha exportada

# app/finance/legacy_import.py — primeira versão, tosca mas funcional
import csv
from pathlib import Path

def import_nubank_csv(path: Path) -> list[dict]:
    """Importa exportação CSV do Nubank."""
    transactions = []
    with open(path) as f:
        reader = csv.DictReader(f)
        for row in reader:
            transactions.append({
                "date": row["Data"],
                "description": row["Descrição"],
                "amount": float(row["Valor"].replace("R$", "").replace(",", ".")),
                "category": "não categorizado",
            })
    return transactions

Funcionava. Mas exigia: entrar no Nubank → exportar CSV → subir pro Capivara → categorizar manualmente. 15 minutos por banco.

A evolução: API integrada

Depois que conectei as APIs dos bancos (via plugins do Capivara), o processo ficou automático:

# app/finance/providers.py — sync automático
from datetime import datetime, timedelta
import httpx

class NubankProvider:
    """Sync automático de transações via API não-oficial."""
    
    BASE_URL = "https://prod.nubank.com.br/api"
    
    async def sync_transactions(self, token: str, days: int = 30) -> list[dict]:
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                f"{self.BASE_URL}/transactions",
                headers={"Authorization": f"Bearer {token}"},
                params={"since": (datetime.now() - timedelta(days=days)).isoformat()},
            )
            data = resp.json()
            return [self._normalize(t) for t in data["transactions"]]
    
    def _normalize(self, raw: dict) -> dict:
        """Normaliza transação Nubank pro schema unificado."""
        return {
            "id": raw["id"],
            "date": raw["post_date"],
            "description": raw["description"],
            "amount": abs(raw["amount"]),
            "type": "expense" if raw["amount"] < 0 else "income",
            "category": self._guess_category(raw["title"]),
            "provider": "nubank",
        }
    
    def _guess_category(self, title: str) -> str:
        """Categorização automática por palavra-chave."""
        rules = {
            "ifood": "alimentação",
            "uber": "transporte",
            "amazon": "compras",
            "netflix": "streaming",
            "spotify": "streaming",
            "gasolina": "transporte",
            "mercado": "alimentação",
            "farmacia": "saúde",
            "cinema": "lazer",
        }
        for keyword, category in rules.items():
            if keyword in title.lower():
                return category
        return "outros"

O dashboard financeiro

Com os dados centralizados, construí o dashboard financeiro — a página que mais uso no Capivara:

// frontend/src/components/finance/RevenueCard.tsx
interface RevenueStats {
  totalRevenue: number;
  monthlyRevenue: number;
  growth: number;
  byCategory: Record<string, number>;
  trend: 'up' | 'down' | 'stable';
}

function RevenueCard({ stats }: { stats: RevenueStats }) {
  return (
    <div className="grid grid-cols-2 gap-4 p-4">
      <MetricCard 
        label="Receita total" 
        value={formatBRL(stats.totalRevenue)}
        trend={stats.trend === 'up' ? 'positive' : 'negative'}
      />
      <MetricCard 
        label="Receita mensal" 
        value={formatBRL(stats.monthlyRevenue)} 
      />
      <CategoryBreakdown categories={stats.byCategory} />
      <GrowthIndicator 
        percentage={stats.growth} 
        period="últimos 30 dias"
      />
    </div>
  );
}

Categorias que uso hoje

Categoria % do orçamento Fonte de dados
Moradia 35% Nubank + Inter
Alimentação 18% Nubank + PicPay
Transporte 8% Nubank
Streaming/Apps 5% Nubank (cartão)
Saúde 6% Inter
Lazer 7% Split entre contas
Investimentos 15% Inter (automático)
Outros 6% Catch-all

Health checks + Finanças = visão completa

O Capivara não mostra só dinheiro — mostra saúde do ecossistema. Juntei health checks dos serviços com as métricas financeiras:

# app/finance/health_integration.py
async def financial_health_report() -> dict:
    """Relatório combinado: saúde financeira + serviços."""
    services = await check_all_services()
    revenue = await get_monthly_revenue()
    expenses = await get_monthly_expenses()
    
    return {
        "services": {
            "online": sum(1 for s in services if s["status"] == "ok"),
            "total": len(services),
            "degraded": [s["name"] for s in services if s["status"] != "ok"],
        },
        "financial": {
            "balance": revenue - expenses,
            "savings_rate": round((revenue - expenses) / revenue * 100, 1),
            "trend": "positive" if revenue > expenses else "negative",
        },
    }

Aprendizados

1. Categorização automática é 80% precisa

Com regex + palavra-chave, acerto ~80% das transações. Os 20% restantes eu reviso uma vez por mês. Muito melhor que 0% (planilha manual).

2. Dados de banco são bagunçados

Cada banco tem formato diferente de descrição:

  • Nubank: "IFD*Ifood 1234"
  • Inter: "Pagamento - Ifood - 12/03"
  • PicPay: "iFood Entrega R$ 45,90"

A normalização (tirar pontuação, lower case, match fuzzy) foi o maior trabalho.

3. Gráfico bonito < dado correto

Gastei mais tempo fazendo gráfico bonito do que validando dado. Depois que inverti a prioridade (dado certo primeiro, visual depois), o dashboard ficou útil de verdade.

4. Manter histórico é mais importante que precisão

No começo apagava transações duplicadas. Depois entendi que manter o raw data e marcar como duplicated: true é melhor — permite re-calcular sem perder informação.

Os números frios

Métrica Antes (planilhas) Depois (Capivara)
Tempo pra consolidar mês 2 horas 2 minutos
Precisão de categorização 100% (manual) ~80% (auto)
Contas integradas 0 4 (Nubank, Inter, PicPay, Wise)
Erros de lançamento ~5/mês 0
Visão consolidada 1x/mês tempo real
Gastos não identificados “muita coisa” ~5% do total

Comandos úteis

~/lifelog — bash
$cat about.txt
╔══════════════════════════════════════╗
║  Samuel Medeiros                    ║
║  Senior Software Engineer           ║
║  Stack: Python · TypeScript · Rust  ║
║  Projetos: Arachne, Dogwalk,        ║
║            Capivara, TatuEngine      ║
╚══════════════════════════════════════╝
      
$