Relatório Mensal (API)
End-to-end monthly financial closing workflow via REST API. Covers importing OFX bank files, categorizing transactions, balancing investment accounts, running verification checks, and sending the monthly report email. Use this skill at the beginning of each month to process the previous month's bank statements. Triggers on requests to "fechar o mês", "relatório mensal", "importar OFX", "categorizar transações", "monthly report", "monthly closing", or any mention of the monthly financial workflow.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install robertotcestari-financeiro-ratc-relatorio-mensal
Repository
Skill path: .claude/skills/relatorio-mensal
End-to-end monthly financial closing workflow via REST API. Covers importing OFX bank files, categorizing transactions, balancing investment accounts, running verification checks, and sending the monthly report email. Use this skill at the beginning of each month to process the previous month's bank statements. Triggers on requests to "fechar o mês", "relatório mensal", "importar OFX", "categorizar transações", "monthly report", "monthly closing", or any mention of the monthly financial workflow.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Backend, Testing.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: robertotcestari.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install Relatório Mensal (API) into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/robertotcestari/financeiro-ratc before adding Relatório Mensal (API) to shared team environments
- Use Relatório Mensal (API) for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
--- name: Relatório Mensal (API) description: End-to-end monthly financial closing workflow via REST API. Covers importing OFX bank files, categorizing transactions, balancing investment accounts, running verification checks, and sending the monthly report email. Use this skill at the beginning of each month to process the previous month's bank statements. Triggers on requests to "fechar o mês", "relatório mensal", "importar OFX", "categorizar transações", "monthly report", "monthly closing", or any mention of the monthly financial workflow. --- # Relatório Mensal — Workflow via API End-to-end workflow for monthly financial closing using the REST API. ## MANDATORY: Create Task List **At the very start of the workflow, after confirming the target month, use `TaskCreate` to create one task for each step below.** This tracks progress and keeps the user informed. Create these tasks (adjust month/year in subjects): 1. "Backup do banco de dados" — activeForm: "Fazendo backup do banco" 2. "Importar OFX: CC - Sicredi" — activeForm: "Importando OFX do Sicredi" 3. "Confirmar saldo: CC - Sicredi" — activeForm: "Confirmando saldo do Sicredi" 4. "Importar Imobzi: CC - PJBank" — activeForm: "Importando Imobzi do PJBank" 5. "Confirmar saldo: CC - PJBank" — activeForm: "Confirmando saldo do PJBank" 6. "Importar OFX: CC - BTG" — activeForm: "Importando OFX do BTG" 7. "Confirmar saldo: CC - BTG" — activeForm: "Confirmando saldo do BTG" 8. "Gerar e aplicar sugestões de categorização" — activeForm: "Aplicando sugestões automáticas" 9. "Categorizar transações restantes" — activeForm: "Categorizando transações" 10. "Balancear CI - SicrediInvest" — activeForm: "Balanceando SicrediInvest" 11. "Balancear CI - BTG" — activeForm: "Balanceando CI - BTG" 12. "Balancear CI - XP" — activeForm: "Balanceando CI - XP" 13. "Verificações finais (checks)" — activeForm: "Rodando verificações" 14. "Enviar relatório mensal por email" — activeForm: "Enviando relatório" **Rules:** - Use `TaskUpdate` to set status `in_progress` BEFORE starting each step - Use `TaskUpdate` to set status `completed` AFTER finishing each step - If a step is blocked (e.g., user hasn't provided OFX file), skip to the next unblocked task - If a step fails, keep it `in_progress` and explain the issue to the user - **NEVER mark transactions as "reviewed" (`isReviewed`).** Reviewing is a human-only step done manually after the workflow. Do not pass `markReviewed: true` when categorizing. - **NEVER create transfers or transfer adjustments.** Transfers already exist in the bank statements (OFX/Imobzi) — they are imported, not created. Do not use `create-transaction.sh` or `POST /transactions` to fabricate transfer transactions. Every "Transferência Entre Contas" entry must have a real counterpart in another account — the net of ALL transfer transactions for the month must be exactly R$0,00. - **CI-SicrediInvest: NEVER use a single lump-sum "ajuste".** Each APLICACAO FINANCEIRA in CC-Sicredi must have an individual mirror transaction (opposite sign) in CI-SicrediInvest, and each RESG.APLIC likewise. Do NOT create one aggregate "Ajuste transferencia" — create one transaction per APLIC/RESG entry. ## Prerequisites Set these environment variables before running scripts: ```bash export RATC_API_URL="https://financeiro.ratc.com.br/api/v1" export RATC_API_KEY="<your-api-key>" ``` For local development: ```bash export RATC_API_URL="http://localhost:3000/api/v1" export RATC_API_KEY="ratc-dev-api-key-2026" ``` Scripts require `curl` and `jq`. ## First: Determine Target Month **ALWAYS ask the user which month to process before starting.** Default is the previous month. Example: If today is February 19, 2026, the default target is January 2026 (`YEAR=2026, MONTH=1`). ```bash YEAR=2026 MONTH=1 ``` Confirm with the user: "Vou processar o mês **janeiro/2026**. Correto?" All scripts and commands below use `$YEAR` and `$MONTH`. ## Workflow Overview ``` 0. Ask month → Confirm with user 1. Backup → SSH to production 2. Import OFX → scripts/import-ofx.sh + confirm balance 3. Suggestions → scripts/suggestions.sh 4. Categorize → scripts/categorize.sh + heuristics 5. Investments → scripts/create-transaction.sh (ask user for balances) 6. Checks → scripts/checks.sh 7. Send Report → scripts/send-report.sh ``` ## Step 0: Database Backup (MANDATORY) ```bash ssh [email protected] "cd /opt/financeiro-ratc/current && npm run cli -- backup" ``` **DO NOT PROCEED without a successful backup.** ## Step 1: List Bank Accounts ```bash curl -s -H "Authorization: Bearer $RATC_API_KEY" "$RATC_API_URL/bank-accounts" | jq '.data[] | {id, name, bankName, balance}' ``` ### Account Types | Account | Type | Import Method | |---------|------|---------------| | CC - Sicredi | Checking | OFX file | | CC - PJBank | Checking | **Imobzi** (not OFX) — web UI `/importacao-imobzi` | | CC - BTG | Checking | **CSV file (not OFX)** — BTG exports CSV, must be imported manually via `POST /transactions` | | CI - SicrediInvest | Investment | Manual (Step 5) | | CI - BTG | Investment | Manual (Step 5) | | CI - XP | Investment | Manual (Step 5) | ## Step 2: Import OFX Files For each account with an OFX file: ```bash ./scripts/import-ofx.sh <ofx-file-path> <bank-account-id> ``` ### MANDATORY: Confirm Balance After Each Import After importing, check the account balance in the app: ```bash curl -s -H "Authorization: Bearer $RATC_API_KEY" "$RATC_API_URL/bank-accounts" \ | jq '.data[] | select(.name == "CC - Sicredi") | {name, balance, balanceDate}' ``` **ASK THE USER**: "O saldo da conta **CC - Sicredi** no app é **R$ X.XXX,XX**. Confere com o extrato bancário?" - If **YES** → proceed to next account - If **NO** → investigate: missing transactions, duplicates, or wrong import. DO NOT continue until resolved. **Repeat for each imported account.** **CC - PJBank**: Do NOT use OFX. Follow these steps in order: ### 2a. Reconcile PJBank PDF vs Imobzi **Ask the user to provide the PJBank PDF extract.** Then run the reconciliation: ```bash # 1. Authenticate with Imobzi TOKEN=$(curl -s -X POST \ "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyAFwmj0iszcf433EvcZ2bxs-XrK49ma4xA" \ -H "Content-Type: application/json" \ -d '{"email":"[email protected]","password":"Ratc12345","returnSecureToken":true}' \ | jq -r '.idToken') # 2. Fetch Imobzi transactions for the month curl -s "https://api.imobzi.app/v1/financial/transactions?start_at=$YEAR-$MONTH_PADDED-01&end_at=$YEAR-$MONTH_PADDED-31&periodType=this_month&order_by=due_date&sort_by=asc&page=1&account_id=6029003447074816" \ -H "Authorization: $TOKEN" \ | jq '[.transactions[] | {date: .paid_at, value: .total_value, type: .transaction_type, contact: .contact.name}]' ``` **Compare Imobzi totals per day against the PJBank PDF:** - Imobzi income − Imobzi expenses (fees) per day = PJBank net per day - The PJBank groups boletos by day; Imobzi has individual transactions per tenant - If totals match → all receipts are recorded in Imobzi ✅ ### 2b. Register PJBank Outgoing Transfers in Imobzi (MANDATORY) The PJBank PDF will show **Pix debits** (transfers to Sicredi). These are NOT automatically in Imobzi and must be created manually so the Imobzi balance matches PJBank. **For each Pix debit in the PDF, create a transfer in Imobzi:** ```bash TOKEN=$(curl -s -X POST \ "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyAFwmj0iszcf433EvcZ2bxs-XrK49ma4xA" \ -H "Content-Type: application/json" \ -d '{"email":"[email protected]","password":"Ratc12345","returnSecureToken":true}' \ | jq -r '.idToken') PJBANK_ACCOUNT='{"db_id":6029003447074816,"account_number":"3000013744-7","account_type":"digital_account","active":true,"agency":"0001","balance":0,"created_at":"2023-04-03T13:02:41.614662Z","default":false,"description":"Conta Digital PJBank","favorite":true,"name":"PJBank","has_transactions":true,"has_integration":true,"has_digital_account":true,"initial_value":0,"start_at":"2023-04-01","bank":{"code":"301","db_id":5762824273920000,"logo_url":null,"name":"BPP Instituição de Pagamentos S/A"}}' SICREDI_ACCOUNT='{"db_id":5253871883517952,"account_number":"44319-0","account_type":"others","active":true,"agency":"3003","balance":0,"created_at":"2023-03-23T12:03:11.825866Z","default":false,"description":"Sicredi","favorite":false,"name":"Sicredi","has_transactions":true,"has_integration":false,"has_digital_account":false,"initial_value":38994.74,"start_at":"2023-03-23","bank":{"code":"748","db_id":5759180434571264,"logo_url":null,"name":"Banco Cooperativo Sicredi S. A."}}' CONTACT='{"db_id":5074170158252032,"name":"Ratc Gerenciamento e Administração de Bens Ltda","type":"organization"}' curl -s -X POST "https://my.imobzi.com/v1/financial/transactions" \ -H "accept: application/json, text/plain, */*" \ -H "authorization: $TOKEN" \ -H "content-type: application/json" \ -H "origin: https://my.imobzi.com" \ -H "referer: https://my.imobzi.com/" \ --data-raw "{ \"transaction_type\": \"transference\", \"description\": \"<description from PDF>\", \"value\": <amount>, \"payment_method\": \"Pix\", \"paid\": true, \"paid_at\": \"<YYYY-MM-DD>\", \"account_to\": $SICREDI_ACCOUNT, \"account_from\": $PJBANK_ACCOUNT, \"account\": $PJBANK_ACCOUNT, \"tags\": [], \"attachments\": [], \"contact\": $CONTACT, \"pix_key_type\": \"cpf_cnpj\", \"pix_key\": \"13.292.738/0001-11\", \"account_credit\": null, \"bar_code\": null, \"financial_conciliation_transaction_id\": null }" | jq '{transaction_id, type: .transaction_type}' ``` **After creating, verify the transfers appear:** ```bash curl -s "https://api.imobzi.app/v1/financial/transactions?start_at=$YEAR-$MONTH_PADDED-01&end_at=$YEAR-$MONTH_PADDED-31&periodType=this_month&order_by=due_date&sort_by=asc&page=1&account_id=6029003447074816" \ -H "Authorization: $TOKEN" \ | jq '[.transactions[] | select(.transaction_type == "transference") | {date: .paid_at, value: .total_value, description}]' ``` **Final balance check** — Imobzi total must equal PJBank PDF ending balance: ```bash curl -s "https://api.imobzi.app/v1/financial/transactions?start_at=$YEAR-$MONTH_PADDED-01&end_at=$YEAR-$MONTH_PADDED-31&periodType=this_month&order_by=due_date&sort_by=asc&page=1&account_id=6029003447074816" \ -H "Authorization: $TOKEN" \ | jq '[.transactions[].total_value] | add' ``` Compare with the **Saldo final** shown in the PJBank PDF. If they match → proceed to import. ### 2c. Import via API (preferred) Once balances match, import directly via REST API — no browser needed: ```bash # Preview first (check duplicates) curl -s -X POST -H "Authorization: Bearer $RATC_API_KEY" -H "Content-Type: application/json" \ -d "{\"startDate\": \"$YEAR-$(printf '%02d' $MONTH)-01\", \"endDate\": \"$YEAR-$(printf '%02d' $MONTH)-31\", \"bankAccountId\": \"cmejz0tjr0001h2ywdhgddbe7\"}" \ "$RATC_API_URL/imobzi/preview" | jq '{total: .summary.total, new: .summary.new, duplicates: .summary.duplicates}' # Import (skips duplicates automatically) curl -s -X POST -H "Authorization: Bearer $RATC_API_KEY" -H "Content-Type: application/json" \ -d "{\"startDate\": \"$YEAR-$(printf '%02d' $MONTH)-01\", \"endDate\": \"$YEAR-$(printf '%02d' $MONTH)-31\", \"bankAccountId\": \"cmejz0tjr0001h2ywdhgddbe7\"}" \ "$RATC_API_URL/imobzi/import" | jq '{success, importBatchId, importedCount, skippedCount, failedCount}' ``` CC - PJBank account ID: `cmejz0tjr0001h2ywdhgddbe7` **IMPORTANT**: Always ask the user for the PJBank PDF before proceeding. Run reconciliation and create all missing transfers BEFORE importing into the app. ## Step 3: Generate and Apply Suggestions Use the suggestions script: ```bash ./scripts/suggestions.sh $YEAR $MONTH --auto-apply ``` This will: 1. Find all uncategorized transactions for the month 2. Generate rule-based suggestions 3. Show each suggestion with confidence level 4. Apply all suggestions automatically (with `--auto-apply`) Without `--auto-apply`, it shows suggestions without applying (for review first). ## Step 4: Categorize Remaining Transactions ### 4.1: List uncategorized ```bash curl -s -H "Authorization: Bearer $RATC_API_KEY" \ "$RATC_API_URL/transactions?year=$YEAR&month=$MONTH&hasCategory=false&limit=500" \ | jq '.data[] | {id, date, description, amount, bank: .bankAccount.name}' ``` ### 4.2: Categorize using heuristics For each uncategorized transaction, analyze the description and apply the correct category. See `references/categorization-guide.md` for pattern matching rules. **Decision flow:** 1. Is it a transfer between RATC accounts? → "Transferência Entre Contas" 2. Does the description match a known pattern? → Use the matching category (see guide) 3. Is it a recurring transaction seen in previous months? → Use same category 4. Can't determine? → **Ask the user** ```bash ./scripts/categorize.sh <transaction-id> <category-id> [property-id] ``` ### 4.3: When to ask the user Present unidentified transactions in this format: ``` Transação não identificada: - Data: 2025-12-15 - Descrição: PIX RECEBIDO - FULANO DE TAL - Valor: R$ 1.500,00 - Conta: CC - Sicredi Qual categoria? (se aplicável, qual imóvel?) ``` ## Step 5: Balance Investment Accounts Investment accounts (CI-*) don't have OFX. They need manual balancing with transfers and interest. ### 5.1: Identify Transfers from Checking Accounts Search for transfer-related transactions in CC accounts for the month: ```bash # Find APLICACAO FINANCEIRA and RESG.APLIC.FIN in CC - Sicredi curl -s -H "Authorization: Bearer $RATC_API_KEY" \ "$RATC_API_URL/transactions?year=$YEAR&month=$MONTH&bankAccountId=<cc-sicredi-id>&limit=500" \ | jq '[.data[] | select(.description | test("APLIC|RESG|APLICACAO|RESGATE"; "i"))] | .[] | {date, description, amount}' ``` Calculate the net: - **Aplicações** (negative in CC = money sent to CI): Sum absolute values - **Resgates** (positive in CC = money received from CI): Sum absolute values - **Net transfer** = Aplicações - Resgates ### 5.2: Create Individual Mirror Transactions on CI Account **DO NOT create a single lump-sum "ajuste".** For each APLIC/RESG in CC-Sicredi, create one mirror transaction in CI-SicrediInvest with the **opposite sign**: - CC-Sicredi `APLICACAO FINANCEIRA` (negative) → CI-SicrediInvest entry with **positive** amount (money entered investment) - CC-Sicredi `RESG.APLIC.FIN` (positive) → CI-SicrediInvest entry with **negative** amount (money left investment) ```python # Example: create mirror for each CC-Sicredi APLIC/RESG for each (date, amount, description) in cc_sicredi_aplic_resg: POST /transactions { bankAccountId: "<ci-sicrediinvest-id>", date: date, description: description, amount: -amount, # opposite sign categoryId: "cat-transferencia-entre-contas" } ``` After all mirrors are created, verify: sum of ALL "Transferência Entre Contas" for the month = R$0,00. ### 5.3: Ask User for Actual Bank Balance **THIS REQUIRES USER INPUT.** Ask: "Qual é o **saldo atual** da conta **CI - SicrediInvest** no extrato do banco/corretora no último dia do mês?" ### 5.4: Calculate and Add Interest ```bash # Check app balance after adding transfers APP_BALANCE=$(curl -s -H "Authorization: Bearer $RATC_API_KEY" "$RATC_API_URL/bank-accounts" \ | jq -r '.data[] | select(.name == "CI - SicrediInvest") | .balance') echo "Saldo no app: R$ $APP_BALANCE" echo "Saldo no banco: R$ <user-provided-balance>" echo "Rendimentos: R$ $(echo "<user-balance> - $APP_BALANCE" | bc)" ``` Create the interest transaction: ```bash ./scripts/create-transaction.sh \ --account-id "<ci-account-id>" \ --date "$YEAR-$(printf '%02d' $MONTH)-31" \ --description "Rendimentos $(date -j -f '%Y-%m-%d' "$YEAR-$(printf '%02d' $MONTH)-01" '+%b/%Y' 2>/dev/null || echo "$MONTH/$YEAR")" \ --amount <interest> \ --category-id "<rendimentos-category-id>" ``` ### 5.5: Confirm Balance Matches After adding transfers + interest, verify: ```bash curl -s -H "Authorization: Bearer $RATC_API_KEY" "$RATC_API_URL/bank-accounts" \ | jq '.data[] | select(.name == "CI - SicrediInvest") | {name, balance}' ``` **ASK THE USER**: "O saldo da CI - SicrediInvest agora é R$ X.XXX,XX. Confere com o extrato?" ### 5.6: Repeat for CI - BTG and CI - XP Apply the same process (5.1–5.5) for each investment account that had activity. ## Step 6: Verification Checks (MANDATORY) ```bash ./scripts/checks.sh $YEAR $MONTH ``` Verifies: 1. **Transfers sum to zero** — "Transferência Entre Contas" must net R$ 0,00 2. **All categorized** — No uncategorized transactions remain 3. **DRE summary** — Shows financial results for review **All checks MUST pass before proceeding.** ## Step 7: Send Monthly Report ```bash ./scripts/send-report.sh $YEAR $MONTH "[email protected],[email protected],[email protected]" ``` ## Checklist Resumo 1. [ ] Perguntar ao usuário: qual mês? 2. [ ] Backup do banco de dados 3. [ ] Importar OFX: CC - Sicredi → **confirmar saldo** 4. [ ] Importar Imobzi: CC - PJBank (web UI) → **confirmar saldo** 5. [ ] Importar OFX: CC - BTG → **confirmar saldo** 6. [ ] Gerar e aplicar sugestões (`scripts/suggestions.sh`) 7. [ ] Categorizar transações restantes (heurísticas + perguntar ao usuário) 8. [ ] Balancear CI - SicrediInvest → pedir saldo real → transferências + rendimentos → **confirmar saldo** 9. [ ] Balancear CI - BTG → mesmo processo 10. [ ] Balancear CI - XP → mesmo processo 11. [ ] CHECK: Transferências somam 0 12. [ ] CHECK: Nenhuma transação sem categoria 13. [ ] CHECK: DRE correto 14. [ ] Enviar relatório mensal por email ## Known Categorizations (do not guess these) | Descrição / CNPJ | Categoria | Observação | |---|---|---| | IMOBILIARIA PRO IMOVEIS (CNPJ 51840387000125) — boleto débito | `cat-despesas-pessoais-socios` | Aluguel do irmão do sócio pago pela empresa. NÃO é Aluguel Pago. | | PRO IMOVEIS LTDA (CNPJ 51840387000125) — TED crédito | `cat-aluguel` | Repasse de aluguéis recebidos via imobiliária | | DARF código 2089 | `cat-irpj` | IRPJ | | DARF código 2372 | `cat-csll` | CSLL | | PJBANK (tarifa imobizi) | `cat-ti` | | ## API: Notas Importantes - **`POST /transactions/bulk-delete`**: o campo `ids` deve conter o valor do campo **`id`** do registro retornado (processedTransaction id), NÃO o campo `transactionId`. Usar o `transactionId` retorna `deletedCount: 0`. ## References - **API Endpoints**: See `references/api-endpoints.md` for complete endpoint documentation - **Categories**: See `references/categories.md` for the full category hierarchy - **Categorization Guide**: See `references/categorization-guide.md` for description-to-category mapping patterns --- ## Referenced Files > The following files are referenced in this skill and included for context. ### scripts/import-ofx.sh ```bash #!/usr/bin/env bash # Import an OFX file via the REST API # Usage: ./import-ofx.sh <ofx-file-path> <bank-account-id> # # Steps: parse → preview (show duplicates) → confirm import set -euo pipefail OFX_FILE="${1:?Usage: $0 <ofx-file-path> <bank-account-id>}" BANK_ACCOUNT_ID="${2:?Usage: $0 <ofx-file-path> <bank-account-id>}" API="${RATC_API_URL:?Set RATC_API_URL env var}" KEY="${RATC_API_KEY:?Set RATC_API_KEY env var}" AUTH="Authorization: Bearer $KEY" CT="Content-Type: application/json" # Read file content if [ ! -f "$OFX_FILE" ]; then echo "ERROR: File not found: $OFX_FILE" >&2 exit 1 fi FILE_CONTENT=$(cat "$OFX_FILE") # Escape for JSON (handles newlines, quotes, backslashes) JSON_CONTENT=$(printf '%s' "$FILE_CONTENT" | jq -Rs .) echo "=== Step 1: Parsing OFX ===" PARSE_RESULT=$(curl -s -X POST -H "$AUTH" -H "$CT" \ -d "{\"fileContent\": $JSON_CONTENT}" \ "$API/ofx/parse") TX_COUNT=$(echo "$PARSE_RESULT" | jq '.transactions | length') echo "Transactions found: $TX_COUNT" if [ "$TX_COUNT" -eq 0 ]; then echo "No transactions found in OFX file." echo "$PARSE_RESULT" | jq '.errors' exit 1 fi echo "" echo "=== Step 2: Preview Import ===" PREVIEW_RESULT=$(curl -s -X POST -H "$AUTH" -H "$CT" \ -d "{\"fileContent\": $JSON_CONTENT, \"bankAccountId\": \"$BANK_ACCOUNT_ID\"}" \ "$API/ofx/preview") echo "$PREVIEW_RESULT" | jq '.summary' DUPS=$(echo "$PREVIEW_RESULT" | jq '.summary.duplicateTransactions // 0') UNIQUE=$(echo "$PREVIEW_RESULT" | jq '.summary.uniqueTransactions // 0') echo "" echo "Unique: $UNIQUE | Duplicates: $DUPS" if [ "$UNIQUE" -eq 0 ]; then echo "All transactions are duplicates. Nothing to import." exit 0 fi echo "" echo "=== Step 3: Importing $UNIQUE transactions ===" # Build transactionActions: import all non-duplicates ACTIONS=$(echo "$PREVIEW_RESULT" | jq '[.transactions[] | {key: .transactionId, value: (if .isDuplicate then "skip" else "import" end)}] | from_entries') IMPORT_RESULT=$(curl -s -X POST -H "$AUTH" -H "$CT" \ -d "{\"fileContent\": $JSON_CONTENT, \"bankAccountId\": \"$BANK_ACCOUNT_ID\", \"transactionActions\": $ACTIONS}" \ "$API/ofx/import") echo "$IMPORT_RESULT" | jq '{success, importBatchId, importedCount, skippedCount, failedCount}' SUCCESS=$(echo "$IMPORT_RESULT" | jq -r '.success // false') if [ "$SUCCESS" = "true" ]; then echo "" echo "Import completed successfully!" else echo "" echo "ERROR: Import failed" echo "$IMPORT_RESULT" | jq '.error // .message' exit 1 fi ``` ### scripts/suggestions.sh ```bash #!/usr/bin/env bash # Generate and apply rule-based categorization suggestions # Usage: ./suggestions.sh <year> <month> [--auto-apply] # # Without --auto-apply: shows summary and lists suggestions # With --auto-apply: automatically applies all generated suggestions set -euo pipefail YEAR="${1:?Usage: $0 <year> <month> [--auto-apply]}" MONTH="${2:?Usage: $0 <year> <month> [--auto-apply]}" AUTO_APPLY="${3:-}" API="${RATC_API_URL:?Set RATC_API_URL env var}" KEY="${RATC_API_KEY:?Set RATC_API_KEY env var}" AUTH="Authorization: Bearer $KEY" CT="Content-Type: application/json" echo "=== Step 1: Finding uncategorized transactions for $YEAR-$(printf '%02d' $MONTH) ===" # Get all uncategorized transaction IDs UNCAT_RESPONSE=$(curl -s -H "$AUTH" \ "$API/transactions?year=$YEAR&month=$MONTH&hasCategory=false&limit=500") UNCAT_COUNT=$(echo "$UNCAT_RESPONSE" | jq '.data | length') echo "Found $UNCAT_COUNT uncategorized transactions" if [ "$UNCAT_COUNT" -eq 0 ]; then echo "Nothing to do — all transactions are already categorized!" exit 0 fi # Extract IDs as JSON array IDS_JSON=$(echo "$UNCAT_RESPONSE" | jq '[.data[].id]') echo "" echo "=== Step 2: Generating suggestions ===" GEN_RESULT=$(curl -s -X POST -H "$AUTH" -H "$CT" \ -d "{\"transactionIds\": $IDS_JSON}" \ "$API/suggestions/generate") PROCESSED=$(echo "$GEN_RESULT" | jq '.processed // 0') SUGGESTED=$(echo "$GEN_RESULT" | jq '.suggested // 0') MATCHED=$(echo "$GEN_RESULT" | jq '.matched // 0') echo "Processed: $PROCESSED | Matched: $MATCHED | Suggested: $SUGGESTED" if [ "$SUGGESTED" -eq 0 ]; then echo "" echo "No suggestions generated. $UNCAT_COUNT transactions remain uncategorized." echo "These need manual categorization." exit 0 fi echo "" echo "=== Step 3: Listing suggestions ===" # Get suggestions for each transaction that had matches SUGGESTION_IDS="" SUGGESTION_COUNT=0 for TX_ID in $(echo "$UNCAT_RESPONSE" | jq -r '.data[].id'); do SUGS=$(curl -s -H "$AUTH" "$API/suggestions/$TX_ID") SUG_LIST=$(echo "$SUGS" | jq -r '.data // []') COUNT=$(echo "$SUG_LIST" | jq 'length') if [ "$COUNT" -gt 0 ]; then for i in $(seq 0 $((COUNT - 1))); do SUG_ID=$(echo "$SUG_LIST" | jq -r ".[$i].id") CONFIDENCE=$(echo "$SUG_LIST" | jq -r ".[$i].confidence // 0") CAT_NAME=$(echo "$SUG_LIST" | jq -r ".[$i].suggestedCategory.name // \"?\"") PROP_CODE=$(echo "$SUG_LIST" | jq -r ".[$i].suggestedProperty.code // \"-\"") # Get transaction description TX_DESC=$(echo "$UNCAT_RESPONSE" | jq -r ".data[] | select(.id == \"$TX_ID\") | .description // \"?\"") TX_AMOUNT=$(echo "$UNCAT_RESPONSE" | jq -r ".data[] | select(.id == \"$TX_ID\") | .amount // 0") echo " [$CONFIDENCE] $TX_DESC (R$ $TX_AMOUNT) → $CAT_NAME | $PROP_CODE" if [ -z "$SUGGESTION_IDS" ]; then SUGGESTION_IDS="\"$SUG_ID\"" else SUGGESTION_IDS="$SUGGESTION_IDS, \"$SUG_ID\"" fi SUGGESTION_COUNT=$((SUGGESTION_COUNT + 1)) done fi done echo "" echo "Total suggestions: $SUGGESTION_COUNT" if [ "$SUGGESTION_COUNT" -eq 0 ]; then echo "No applicable suggestions found." exit 0 fi # Apply suggestions if [ "$AUTO_APPLY" = "--auto-apply" ]; then echo "" echo "=== Step 4: Applying all $SUGGESTION_COUNT suggestions ===" APPLY_RESULT=$(curl -s -X POST -H "$AUTH" -H "$CT" \ -d "{\"suggestionIds\": [$SUGGESTION_IDS]}" \ "$API/suggestions/bulk-apply") SUCCESSFUL=$(echo "$APPLY_RESULT" | jq '.summary.successful // 0') FAILED=$(echo "$APPLY_RESULT" | jq '.summary.failed // 0') echo "Applied: $SUCCESSFUL | Failed: $FAILED" # Check remaining uncategorized REMAINING=$(curl -s -H "$AUTH" \ "$API/transactions?year=$YEAR&month=$MONTH&hasCategory=false&limit=1" \ | jq '.meta.total // 0') echo "" echo "Remaining uncategorized: $REMAINING" else echo "" echo "Run with --auto-apply to apply all suggestions:" echo " $0 $YEAR $MONTH --auto-apply" fi ``` ### scripts/categorize.sh ```bash #!/usr/bin/env bash # Categorize a single transaction via the REST API # Usage: ./categorize.sh <transaction-id> <category-id> [property-id] set -euo pipefail TX_ID="${1:?Usage: $0 <transaction-id> <category-id> [property-id]}" CATEGORY_ID="${2:?Usage: $0 <transaction-id> <category-id> [property-id]}" PROPERTY_ID="${3:-}" API="${RATC_API_URL:?Set RATC_API_URL env var}" KEY="${RATC_API_KEY:?Set RATC_API_KEY env var}" AUTH="Authorization: Bearer $KEY" CT="Content-Type: application/json" BODY="{\"categoryId\": \"$CATEGORY_ID\", \"markReviewed\": true" if [ -n "$PROPERTY_ID" ]; then BODY="$BODY, \"propertyId\": \"$PROPERTY_ID\"" fi BODY="$BODY}" RESULT=$(curl -s -X PUT -H "$AUTH" -H "$CT" \ -d "$BODY" \ "$API/transactions/$TX_ID/categorize") echo "$RESULT" | jq '{id: .data.id, description: .data.description, category: .data.category.name, property: .data.property.code}' ``` ### scripts/create-transaction.sh ```bash #!/usr/bin/env bash # Create a manual transaction via the REST API # Usage: ./create-transaction.sh --account-id <id> --date <YYYY-MM-DD> --description <desc> --amount <value> [--category-id <id>] [--property-id <id>] set -euo pipefail API="${RATC_API_URL:?Set RATC_API_URL env var}" KEY="${RATC_API_KEY:?Set RATC_API_KEY env var}" AUTH="Authorization: Bearer $KEY" CT="Content-Type: application/json" # Parse arguments ACCOUNT_ID="" DATE="" DESCRIPTION="" AMOUNT="" CATEGORY_ID="" PROPERTY_ID="" while [[ $# -gt 0 ]]; do case $1 in --account-id) ACCOUNT_ID="$2"; shift 2 ;; --date) DATE="$2"; shift 2 ;; --description) DESCRIPTION="$2"; shift 2 ;; --amount) AMOUNT="$2"; shift 2 ;; --category-id) CATEGORY_ID="$2"; shift 2 ;; --property-id) PROPERTY_ID="$2"; shift 2 ;; *) echo "Unknown option: $1" >&2; exit 1 ;; esac done if [ -z "$ACCOUNT_ID" ] || [ -z "$DATE" ] || [ -z "$DESCRIPTION" ] || [ -z "$AMOUNT" ]; then echo "Usage: $0 --account-id <id> --date <YYYY-MM-DD> --description <desc> --amount <value> [--category-id <id>] [--property-id <id>]" >&2 exit 1 fi # Build JSON body BODY=$(jq -n \ --arg accountId "$ACCOUNT_ID" \ --arg date "$DATE" \ --arg desc "$DESCRIPTION" \ --argjson amount "$AMOUNT" \ --arg catId "$CATEGORY_ID" \ --arg propId "$PROPERTY_ID" \ '{ bankAccountId: $accountId, date: $date, description: $desc, amount: $amount } + (if $catId != "" then {categoryId: $catId} else {} end) + (if $propId != "" then {propertyId: $propId} else {} end)') RESULT=$(curl -s -X POST -H "$AUTH" -H "$CT" \ -d "$BODY" \ "$API/transactions") STATUS=$(echo "$RESULT" | jq -r '.data.id // empty') if [ -n "$STATUS" ]; then echo "Transaction created successfully:" echo "$RESULT" | jq '{id: .data.id, date: .data.date, description: .data.description, amount: .data.amount, category: .data.category.name, bankAccount: .data.bankAccount.name}' else echo "ERROR: Failed to create transaction" >&2 echo "$RESULT" | jq . >&2 exit 1 fi ``` ### scripts/checks.sh ```bash #!/usr/bin/env bash # Run all monthly verification checks via the REST API # Usage: ./checks.sh <year> <month> set -euo pipefail YEAR="${1:?Usage: $0 <year> <month>}" MONTH="${2:?Usage: $0 <year> <month>}" API="${RATC_API_URL:?Set RATC_API_URL env var}" KEY="${RATC_API_KEY:?Set RATC_API_KEY env var}" AUTH="Authorization: Bearer $KEY" PASS=0 FAIL=0 echo "========================================" echo " Monthly Checks: $YEAR-$(printf '%02d' $MONTH)" echo "========================================" echo "" # --- Check 1: Transfers Sum to Zero --- echo "--- Check 1: Transfers Sum to Zero ---" # Get "Transferência Entre Contas" category ID TRANSFER_CAT_ID=$(curl -s -H "$AUTH" "$API/categories" \ | jq -r '.data[] | select(.name == "Transferência Entre Contas") | .id') if [ -z "$TRANSFER_CAT_ID" ]; then echo "WARNING: Could not find 'Transferência Entre Contas' category" FAIL=$((FAIL + 1)) else TRANSFER_TXS=$(curl -s -H "$AUTH" \ "$API/transactions?year=$YEAR&month=$MONTH&categoryId=$TRANSFER_CAT_ID&limit=500") TRANSFER_SUM=$(echo "$TRANSFER_TXS" | jq '[.data[].amount // 0] | add // 0') TRANSFER_COUNT=$(echo "$TRANSFER_TXS" | jq '.data | length') # Check if sum is zero (allow tiny floating point difference) IS_ZERO=$(echo "$TRANSFER_SUM" | awk '{if ($1 > -0.01 && $1 < 0.01) print "true"; else print "false"}') if [ "$IS_ZERO" = "true" ]; then echo "PASS: $TRANSFER_COUNT transfers, sum = R$ $(printf '%.2f' $TRANSFER_SUM)" PASS=$((PASS + 1)) else echo "FAIL: $TRANSFER_COUNT transfers, sum = R$ $(printf '%.2f' $TRANSFER_SUM) (expected 0.00)" FAIL=$((FAIL + 1)) echo "" echo "Transfer details:" echo "$TRANSFER_TXS" | jq -r '.data[] | " \(.date) | \(.bankAccount.name // "?") | \(.description // "?") | R$ \(.amount)"' fi fi echo "" # --- Check 2: All Transactions Categorized --- echo "--- Check 2: All Transactions Categorized ---" UNCATEGORIZED=$(curl -s -H "$AUTH" \ "$API/transactions?year=$YEAR&month=$MONTH&hasCategory=false&limit=500") UNCAT_COUNT=$(echo "$UNCATEGORIZED" | jq '.data | length') if [ "$UNCAT_COUNT" -eq 0 ]; then echo "PASS: All transactions categorized" PASS=$((PASS + 1)) else echo "FAIL: $UNCAT_COUNT uncategorized transactions" FAIL=$((FAIL + 1)) echo "" echo "Uncategorized transactions:" echo "$UNCATEGORIZED" | jq -r '.data[:10][] | " \(.date) | \(.bankAccount.name // "?") | \(.description // "?") | R$ \(.amount)"' if [ "$UNCAT_COUNT" -gt 10 ]; then echo " ... and $((UNCAT_COUNT - 10)) more" fi fi echo "" # --- Check 3: DRE Summary --- echo "--- Check 3: DRE Summary ---" DRE=$(curl -s -H "$AUTH" "$API/dre?year=$YEAR&month=$MONTH") DRE_OK=$(echo "$DRE" | jq '.data | length > 0') if [ "$DRE_OK" = "true" ]; then echo "PASS: DRE generated successfully" PASS=$((PASS + 1)) echo "" echo "DRE Summary:" echo "$DRE" | jq -r '.data[] | select(.lineType == "TOTAL" or .lineType == "SUBTOTAL") | " \(.name): R$ \(.amount)"' else echo "FAIL: Could not generate DRE" FAIL=$((FAIL + 1)) fi echo "" # --- Summary --- echo "========================================" echo " Results: $PASS passed, $FAIL failed" echo "========================================" if [ "$FAIL" -gt 0 ]; then echo "" echo "ACTION REQUIRED: Fix the failing checks before sending the monthly report." exit 1 else echo "" echo "All checks passed! Ready to send monthly report." exit 0 fi ``` ### scripts/send-report.sh ```bash #!/usr/bin/env bash # Send the monthly report email via the REST API # Usage: ./send-report.sh <year> <month> [recipients] # Recipients: comma-separated emails (default: [email protected],[email protected]) set -euo pipefail YEAR="${1:?Usage: $0 <year> <month> [recipients]}" MONTH="${2:?Usage: $0 <year> <month> [recipients]}" RECIPIENTS="${3:[email protected],[email protected]}" API="${RATC_API_URL:?Set RATC_API_URL env var}" KEY="${RATC_API_KEY:?Set RATC_API_KEY env var}" AUTH="Authorization: Bearer $KEY" CT="Content-Type: application/json" # Convert comma-separated to JSON array RECIPIENTS_JSON=$(echo "$RECIPIENTS" | jq -R 'split(",")') BODY=$(jq -n \ --argjson year "$YEAR" \ --argjson month "$MONTH" \ --argjson recipients "$RECIPIENTS_JSON" \ '{year: $year, month: $month, recipients: $recipients}') echo "Sending monthly report for $YEAR-$(printf '%02d' $MONTH)..." echo "Recipients: $RECIPIENTS" echo "" RESULT=$(curl -s -X POST -H "$AUTH" -H "$CT" \ -d "$BODY" \ "$API/reports/monthly/send") SUCCESS=$(echo "$RESULT" | jq -r '.success // false') if [ "$SUCCESS" = "true" ]; then MSG_ID=$(echo "$RESULT" | jq -r '.messageId // "N/A"') echo "Email sent successfully! Message ID: $MSG_ID" else ERROR=$(echo "$RESULT" | jq -r '.error // "Unknown error"') echo "ERROR: Failed to send email: $ERROR" >&2 exit 1 fi ``` ### references/categorization-guide.md ```markdown # Guia de Categorização Manual Após aplicar as sugestões automáticas (regras), as transações restantes precisam de categorização manual. Use os padrões abaixo para decidir a categoria. ## Padrões Comuns por Descrição ### Tarifas Bancárias - `TARIFA` / `TAR MANUT` / `TARIFA BANCÁRIA` - `PACOTE DE SERVIÇO` / `ANUIDADE` - `TAR EXTRATO` / `TAR DOC` / `TAR TED` ### Transferências Entre Contas - `TRANSF` / `TRANSFERENCIA` / `TEF` - `APLICACAO FINANCEIRA` / `APLIC.FIN` - `RESG.APLIC.FIN` / `RESGATE` - `PIX` entre contas próprias (verificar se origem/destino é conta da RATC) ### Aluguel (Receita — requer imóvel) - Créditos do PJBank/Imobzi com identificação de inquilino - `ALUGUEL` / `LOCAÇÃO` no description - Valor positivo recorrente mensal ### Condomínios (Despesa — requer imóvel) - `CONDOMINIO` / `COND.` / nome da administradora - Débito recorrente mensal ### IPTU (Despesa — requer imóvel) - `IPTU` / `PREFEITURA` / `TRIBUTO MUNICIPAL` ### Água (Despesa — requer imóvel) - `SABESP` / `COPASA` / `SEMAE` / nome da companhia de água - `AGUA` / `SANEAMENTO` ### Energia (Despesa — requer imóvel) - `CPFL` / `ENEL` / `ELEKTRO` / `ENERGISA` / nome da distribuidora - `ENERGIA` / `LUZ` ### Manutenção (Despesa — requer imóvel) - `MANUTENÇÃO` / `REFORMA` / `CONSERTO` - Pagamento a prestadores de serviço para imóveis ### Contabilidade - `CONTABILIDADE` / nome do escritório contábil ### Salários / FGTS / INSS - `FOLHA` / `SALARIO` / `RESCISAO` - `FGTS` / `CAIXA ECONOMICA` (para FGTS) - `INSS` / `GPS` / `DARF` (para INSS) ### Impostos e Taxas - `DARF` / `DAS` / `SIMPLES` - `IRPJ` / `CSLL` / `PIS` / `COFINS` - `IRPF` → DARF IRPF especificamente ### Rendimentos (Receita financeira) - `RENDIMENTO` / `JUROS` / `DIVIDENDO` - Créditos em contas de investimento (CI) ### IOF / Juros / Taxas Financeiras - `IOF` → IOF - `JUROS` em débito → Juros (despesa financeira) - `ENCARGO` / `MORA` → Taxas e Encargos ## Regras de Decisão 1. **Primeiro**: Verifique se a transação é uma transferência entre contas próprias (RATC) 2. **Segundo**: Procure palavras-chave na descrição (tabela acima) 3. **Terceiro**: Analise o valor e recorrência (ex: mesmo valor todo mês = aluguel/condomínio) 4. **Quarto**: Se não conseguir determinar, pergunte ao usuário ## Categorias que Requerem Imóvel Ao categorizar estas, SEMPRE associe o imóvel correspondente: - Aluguel, Aluguel de Terceiros - Condomínios, IPTU, Água, Energia, Manutenção Use `GET /properties` para listar imóveis disponíveis. O código do imóvel (ex: `CAT-01`, `SJP-02`) geralmente aparece na descrição ou pode ser inferido pelo endereço. ## Quando Perguntar ao Usuário Pergunte ao usuário quando: - A descrição é genérica (ex: `PIX RECEBIDO`, `TED ENVIADO`) - Não há padrão reconhecível - Valor incomum ou primeira ocorrência - Pode ser transferência OU despesa (ambíguo) Formato sugerido para perguntar: ``` Transação não identificada: - Data: 2025-12-15 - Descrição: PIX RECEBIDO - FULANO DE TAL - Valor: R$ 1.500,00 - Conta: CC - Sicredi Qual categoria? (se aplicável, qual imóvel?) ``` ``` ### references/api-endpoints.md ```markdown # API Endpoints Reference Base URL: `$RATC_API_URL` (e.g., `https://financeiro.ratc.com.br/api/v1`) **Interactive docs (Swagger UI):** https://financeiro.ratc.com.br/api/v1/doc All endpoints require: `Authorization: Bearer $RATC_API_KEY` ## Transactions ### List Transactions ``` GET /transactions?year=2025&month=12&hasCategory=false&limit=500 ``` Query params: `year`, `month`, `bankAccountId`, `categoryId`, `hasCategory` (true/false), `isReviewed` (true/false), `page`, `limit` (max 500) ### Get Transaction Detail ``` GET /transactions/{id} ``` ### Create Manual Transaction ``` POST /transactions Body: { bankAccountId, date: "YYYY-MM-DD", description, amount, categoryId?, propertyId?, details? } ``` ### Categorize Transaction ``` PUT /transactions/{id}/categorize Body: { categoryId?: string|null, propertyId?: string|null, markReviewed?: boolean } ``` ### Bulk Categorize ``` POST /transactions/bulk-categorize Body: { ids: string[], categoryId?, propertyId?, markReviewed? } ``` ### Mark Reviewed ``` PUT /transactions/{id}/review Body: { reviewed: boolean } ``` ### Update Details ``` PUT /transactions/{id}/details Body: { details: string|null } ``` ### Bulk Delete ``` POST /transactions/bulk-delete Body: { ids: string[] } ``` ## OFX Import ### Parse OFX ``` POST /ofx/parse Body: { fileContent: string } Returns: { success, version, format, accounts[], transactions[], errors[] } ``` ### Preview Import ``` POST /ofx/preview Body: { fileContent: string, bankAccountId: string } Returns: { success, transactions[], summary: { totalTransactions, validTransactions, duplicateTransactions, uniqueTransactions } } ``` ### Execute Import ``` POST /ofx/import Body: { fileContent: string, bankAccountId: string, transactionActions: Record<id, "import"|"skip"|"review">, transactionCategories?: Record<id, categoryId|null>, transactionProperties?: Record<id, propertyId|null> } Returns: { success, importBatchId, importedCount, skippedCount, failedCount } ``` ## Suggestions ### Generate Suggestions ``` POST /suggestions/generate Body: { transactionIds: string[], ruleIds?: string[] } Returns: { processed, suggested, matched } ``` ### Get Suggestions for Transaction ``` GET /suggestions/{transactionId} Returns: { data: Suggestion[] } ``` ### Apply Suggestion ``` POST /suggestions/{id}/apply ``` ### Bulk Apply ``` POST /suggestions/bulk-apply Body: { suggestionIds: string[] } ``` ### Dismiss Suggestion ``` DELETE /suggestions/{id} ``` ### Bulk Dismiss ``` POST /suggestions/bulk-dismiss Body: { suggestionIds: string[] } ``` ## Reports ### Send Monthly Report Email ``` POST /reports/monthly/send Body: { year: number, month: number, recipients: string[] } Returns: { success, messageId? } ``` ## Reference Data ### Categories ``` GET /categories Returns: { data: [{ id, name, level, orderIndex, parentId }] } ``` ### Bank Accounts ``` GET /bank-accounts Returns: { data: [{ id, name, bankName, accountType, isActive, balance, balanceDate }] } ``` ### Properties ``` GET /properties Returns: { data: [{ id, code, description, city, address }] } ``` ### DRE ``` GET /dre?year=2025&month=12 Returns: { data: [{ id, name, level, lineType, amount, isBold, showInReport }], period: { year, month } } ``` ## Rules ### List Rules ``` GET /rules?isActive=true&page=1&limit=50 ``` ### Create Rule ``` POST /rules Body: { name, description?, isActive, priority, criteria, categoryId?, propertyId? } ``` ### Apply Rule Retroactively ``` POST /rules/{id}/apply Body: { transactionIds: string[] } ``` ## Transfers ### Detect Potential Transfers ``` POST /transfers/detect Body: { startDate: "YYYY-MM-DD", endDate: "YYYY-MM-DD" } ``` ### Confirm Transfer ``` POST /transfers/confirm Body: { originTransactionId, destinationTransactionId } ``` ## Health Check (no auth) ``` GET /health Returns: { status: "ok", version: "1.0.0" } ``` ``` ### references/categories.md ```markdown # Category Hierarchy Use `GET /categories` to get current IDs. This is the standard structure: ## Income (Receitas) ``` Receitas Operacionais (L1) ├── Aluguel (L2) — own property rentals (requires property) ├── Aluguel de Terceiros (L2) — third-party rentals (requires property) └── Outras Receitas (L2) ``` ## Operating Expenses (Despesas) ``` Despesas Operacionais (L1) ├── Despesas Administrativas (L2) │ ├── Tarifas Bancárias (L3) │ ├── Escritórios e Postagens (L3) │ ├── Contabilidade (L3) │ ├── Salários (L3) │ ├── FGTS (L3) │ ├── INSS (L3) │ ├── TI (L3) │ └── Documentações e Jurídico (L3) ├── Despesas com Imóveis (L2) │ ├── Condomínios (L3) — requires property │ ├── IPTU (L3) — requires property │ ├── Água (L3) — requires property │ ├── Energia (L3) — requires property │ ├── Internet (L3) │ ├── Aluguel (L3) — requires property │ ├── Manutenção (L3) — requires property │ └── Seguro (L3) ├── Despesas com Vendas (L2) │ ├── Comissões (L3) │ └── Marketing (L3) └── Despesas Tributárias (L2) ├── IRPJ (L3) ├── Impostos e Taxas (L3) └── DARF IRPF (L3) ``` ## Financial ``` Despesas Financeiras (L1) ├── Juros (L2) ├── IOF (L2) └── Taxas e Encargos (L2) Receitas Financeiras (L1) └── Rendimentos (L2) — investment returns ``` ## Investments ``` Investimentos (L1) ├── Aplicações (L2) — money invested └── Resgates (L2) — money withdrawn ``` ## Transfers ``` Transferências (L1) └── Transferência Entre Contas (L2) — inter-account transfers (MUST sum to zero) ``` ## Categories Requiring Property When categorizing, these categories should have a property assigned: - Aluguel (own rentals) - Aluguel de Terceiros - Condomínios - IPTU - Água - Energia - Manutenção - Aluguel (under Despesas com Imóveis) ```