Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.pixdopix.com.br/llms.txt

Use this file to discover all available pages before exploring further.

Sessão de 30/04. Investimentos saíram do modelo “1 linha = posição atual” pra modelo aporte (1 linha = 1 compra; posição é derivada agregando por nome). Spec completo em docs/superpowers/specs/2026-04-30-investimentos-aportes-design.md.

Resumo executivo

3 commits no main, 1 migration Supabase em produção.
BlocoCommitStatus
Fix silencioso anterior (sheet fechava sem mostrar erro)b90eafd✅ produção
Feat: modelo aporte completo (schema + form + display + helper)b9f7078✅ produção
Fix: toggleTag com setForm funcional (descoberto no smoke test)56fae90✅ produção

1. Bug raiz: investimentos não cadastravam (1ª parte da sessão)

Sintoma: usuário relatou que cadastros não estavam indo pro banco. Investigação:
  • DB tinha 3 registros, todos deletado=true desde 2026-04-29 22:54
  • API logs nas últimas 24h: zero POST /rest/v1/investimentos
  • Mesmo bug-pattern que foi consertado em salarios no commit 81a007e
Causa raiz: handleSubmit chamava .mutate(...) (fire-and-forget) e em seguida onOpenChange(false) síncrono. Se a mutation falhasse, o sheet fechava antes do toast renderizar — o erro era engolido e o usuário pensava que tinha cadastrado. Fix (b90eafd):
  • Hook: createInvestimento/updateInvestimento viram mutateAsync
  • Sheet: handleSubmit vira async, com try/await/catch — fecha só em sucesso

2. Modelo aporte (parte principal)

Decisão arquitetural

Cada linha de investimentos passa a representar uma compra individual, não a posição agregada. A posição (qty total + preço médio) é derivada agregando todas as linhas com o mesmo nome (case-insensitive) num mesmo workspace. Por quê: preserva histórico de compras, calcula preço médio sem dor, mantém a tabela append-only. Padrão de home-broker.

Migração investimentos_aporte_qty_unit_tags_alocacoes

ALTER TABLE public.investimentos
  ADD COLUMN IF NOT EXISTS quantidade numeric,
  ADD COLUMN IF NOT EXISTS valor_unitario numeric,
  ADD COLUMN IF NOT EXISTS tags text[],
  ADD COLUMN IF NOT EXISTS alocacoes jsonb;

CREATE INDEX idx_investimentos_workspace_nome
  ON public.investimentos (workspace_id, lower(nome))
  WHERE deletado = false;

CREATE INDEX idx_investimentos_tags
  ON public.investimentos USING gin (tags)
  WHERE deletado = false;
Tudo nullable, retrocompatível. Aportes simples (CDB, poupança, alternativos) seguem só com valor.

Taxonomia (lib/investment-taxonomy.{ts,json})

Adicionada flag temUnidades?: boolean na interface SubcategoriaInvestimento. Marcado em:
  • Renda Fixa → Tesouro Direto
  • Renda Variável → todas
  • Internacional → todas
  • Criptoativos → todas
Subcategorias sem unidade discreta (Bancários, Crédito Privado, Fundos RF, Poupança, Alternativos, Caixa) ficam apenas com valor.

Form (components/investimento-sheet.tsx)

Comportamento condicional baseado na subcategoria:
  • temUnidades: mostra Quantidade + Valor unitário (R$); Valor total é read-only e auto-calcula (qty × unit)
  • tags declaradas (Ações): chips multi-select opcional
  • alocacoes declaradas (Cripto principais/altcoins/stablecoins): split Em spot (R$) + Em staking (R$) com validação de soma vs valor total
  • Cabeçalho mudou pra “Novo Aporte” + hint sobre agregação por nome

Helper de agregação (lib/investment-aggregate.ts)

Novo módulo. agruparPorAtivo(investimentos: Investimento[]): AtivoAgregado[] retorna:
interface AtivoAgregado {
  nomeKey: string                    // lower(trim(nome))
  nomeDisplay: string                // primeiro nome encontrado
  aportes: Investimento[]            // ordenados por data desc
  quantidadeTotal: number | null
  custoTotal: number                 // SUM(valor)
  precoMedio: number | null          // custoTotal / quantidadeTotal
  tagsConsolidadas: string[]         // união
  algumAtivo: boolean
}

Display (app/investimentos/[categoria]/page.tsx)

Agora dentro de cada subcategoria os ativos são agregados por nome. Cada card mostra:
  • Posição (qty + preço médio + custo total)
  • Tags consolidadas (se houver)
  • Botão “Ver aportes” → drilldown com aportes individuais (edit/delete por aporte)

3. Smoke test E2E

Testado via Playwright durante a sessão:
CenárioResultado
HGLG11 (FII): 2 aportes (10×130 + 8×145)✅ 18 un., custo R2.460,prec\comeˊdioR 2.460, **preço médio R 136,67** (= 2460/18)
Bitcoin: 0,5 × R$ 350k com split 100k spot + 75k staking✅ alocações persistidas como JSONB; validação verde
PETR4: 100 × R$ 35 com tags [blue chip, energia]✅ tags como text[]; chips visíveis no card
Drilldown “Ver aportes”✅ aportes individuais com qty × unit; edit/delete
Bug detectado e corrigido durante o teste (56fae90): toggleTag usava setForm({...form, tags: ...}) lendo do closure stale. Migrado pra setForm((prev) => ({...prev, tags: ...})) — consistente com cliques rápidos em sequência.

4. Fora de escopo (fase 2)

  • Cotação atual via API externa (B3 / Alpha Vantage / CoinGecko)
  • Rendimento da carteira = preço atual × qty − custo médio × qty
  • Cruzamentos por tag em relatórios (ex: “patrimônio em blue chip vs growth”)
  • Filtros por tag na própria página de classe
A base de preço médio que esta fase entregou é o pré-requisito de tudo isso.

5. Bug-pattern análogo em outros sheets

O fix de b90eafd (await mutation antes de fechar sheet) também estava ausente em outros forms. Worth fazer uma sweep:
  • components/gasto-sheet.tsx
  • components/receita-sheet.tsx
  • components/meta-sheet.tsx
  • components/cartao-sheet.tsx
  • components/conta-fixa-sheet.tsx
  • components/parcela-sheet.tsx
  • components/gasolina-sheet.tsx
(Já corrigidos no histórico: salarios em 81a007e, investimentos em b90eafd.)

Referências

  • Spec de design: docs/superpowers/specs/2026-04-30-investimentos-aportes-design.md
  • Doc de usuário: docs/recursos/investimentos.mdx (atualizada nesta sessão)
  • Commit do fix análogo em salarios: 81a007e