Back to skills
SkillHub ClubShip Full StackFull StackBackendTesting

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.

Stars
0
Hot score
74
Updated
March 20, 2026
Overall rating
C0.0
Composite score
0.0
Best-practice grade
D41.1

Install command

npx @skill-hub/cli install robertotcestari-financeiro-ratc-relatorio-mensal

Repository

robertotcestari/financeiro-ratc

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 repository

Best 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

Claude CodeCodex CLIGemini CLIOpenCode

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)

```