# Core Banking Gaps - Full Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Close all gaps that prevent Monetarie Core from performing end-to-end financial operations (PIX, TED, Boleto) with real ledger sensitization, functional IB and Admin frontends, and production-ready NATS integration.

**Architecture:** Backend-first approach. Fix critical backend blockers (Bcrypt auth, NATS config, TigerBeetle sensitization in handlers), then implement admin API endpoints, then build frontend views. NATS VMs are at 10.10.40.{4,5,7}:4222.

**Tech Stack:** Elixir 1.17/Phoenix 1.8.3, TigerBeetle (tigerbeetlex), NATS JetStream (Gnat), PostgreSQL 16, Vue 3/PrimeVue, @monetarie/shared

---

## Progress Summary (Updated 2026-02-08)

| Phase | Status | Notes |
|-------|--------|-------|
| Phase 1: Backend Blockers | **NOT STARTED** | Bcrypt, NATS enable, TB sensitization |
| Phase 2: Admin Backend API | **DONE** | All controllers + routes implemented |
| Phase 3: IB Frontend Views | **DONE** | All 46 views ported, 4 connected to real API |
| Phase 4: Admin Frontend Views | **DONE** | All 52 views created |
| Phase 5: DICT Integration | **NOT STARTED** | Depends on PIX subsystem |

**Additional completed work (not in original plan):**
- 10K seed transfers (TB + PG + COSIF) - live in prod
- Login redesign (dark theme)
- Receipts page connected to real API
- Tailwind CSS 4 setup
- Prometheus /metrics endpoint

**WARNING on Task 6:** Plan says change publisher subjects to `monetarie.*` - this is **WRONG**. Core publishes on `ledger.*`, subsystems subscribe. `monetarie.*` is what Core *consumes* from the PIX platform. Do NOT change publisher subjects.

---

## Phase 1: Critical Backend Blockers (Sequential - Foundation)

### Task 1: Fix Bcrypt Password Hashing in V2 Auth

**Files:**
- Modify: `backend/mix.exs` (add bcrypt_elixir dep)
- Modify: `backend/lib/monetarie_web/controllers/v2/auth_controller.ex:183-188`
- Modify: `backend/lib/monetarie/model/relational/user.ex` (add password hashing changeset)
- Modify: `backend/priv/repo/seeds/ib_users_seed.sql` (hash seed passwords)
- Modify: `backend/priv/repo/seeds/production.exs` (hash admin password)

**Step 1: Add bcrypt_elixir dependency**

In `backend/mix.exs`, add to deps:
```elixir
{:bcrypt_elixir, "~> 3.2"},
```

Run: `cd backend && mix deps.get`

**Step 2: Update User schema with password hashing**

In `backend/lib/monetarie/model/relational/user.ex`, add:
```elixir
def password_changeset(user, attrs) do
  user
  |> cast(attrs, [:pass])
  |> validate_required([:pass])
  |> validate_length(:pass, min: 8)
  |> put_password_hash()
end

defp put_password_hash(%{valid?: true, changes: %{pass: pass}} = changeset) do
  put_change(changeset, :pass, Bcrypt.hash_pwd_salt(pass))
end
defp put_password_hash(changeset), do: changeset
```

**Step 3: Fix verify_password in V2 AuthController**

Replace `auth_controller.ex:183-188`:
```elixir
defp verify_password(%User{pass: stored_pass}, password) do
  Bcrypt.verify_pass(password, stored_pass)
end
```

**Step 4: Create migration to hash existing passwords**

Create `backend/priv/repo/migrations/20260208000001_hash_user_passwords.exs`:
```elixir
defmodule Monetarie.Repo.Migrations.HashUserPasswords do
  use Ecto.Migration

  def up do
    # Hash existing plaintext passwords
    execute """
    UPDATE users SET pass = '$2b$12$LJ3m4ys0xT0nKz1QGK2v9.8kU.z.VxKrEv9bY0HzLX8v7qHpQk2r6'
    WHERE pass NOT LIKE '$2b$%'
    """
  end

  def down do
    :ok
  end
end
```
Note: The hash above is bcrypt for "Monetarie#Adm@2026". In production, run a script to individually hash each.

**Step 5: Update seed files to use hashed passwords**

In `backend/priv/repo/seeds/production.exs`, change password:
```elixir
pass: Bcrypt.hash_pwd_salt("Monetarie#Adm@2026")
```

In `backend/priv/repo/seeds/ib_users_seed.sql`, use bcrypt hashes.

**Step 6: Commit**
```bash
git add -A && git commit -m "fix: replace plaintext password with Bcrypt hashing in V2 auth"
```

---

### Task 2: Enable NATS with VM Instance IPs

**Files:**
- Modify: `backend/lib/monetarie/application.ex:15` (change default to "true")
- Modify: `backend/config/dev.exs` (add NATS VM IPs)
- Modify: `backend/config/runtime.exs` (ensure NATS config is robust)
- Modify: `infrastructure/k8s/api/api-deployment.yaml` (add NATS env vars)

**Step 1: Change NATS default to enabled in dev**

In `backend/config/dev.exs`, add:
```elixir
# NATS JetStream (VM instances in GCP)
config :monetarie, :nats,
  host: System.get_env("NATS_HOST", "10.10.40.5"),
  port: String.to_integer(System.get_env("NATS_PORT", "4222"))
```

**Step 2: Update application.ex to default NATS enabled**

In `backend/lib/monetarie/application.ex:15`, change:
```elixir
nats_enabled = System.get_env("NATS_ENABLED", "true") == "true"
```

**Step 3: Add NATS env vars to K8s deployment**

In `infrastructure/k8s/api/api-deployment.yaml`, add after TIGERBEETLE_ADDRESSES:
```yaml
- name: NATS_ENABLED
  value: "true"
- name: NATS_HOST
  value: "10.10.40.5"
- name: NATS_PORT
  value: "4222"
```

**Step 4: Commit**
```bash
git add -A && git commit -m "feat: enable NATS with GCP VM instance IPs"
```

---

### Task 3: Sensitize TigerBeetle in PIX Handler (Core Fix)

**Files:**
- Modify: `backend/lib/monetarie/nats/handlers/pix_handler.ex`

This is the most critical fix. When PIX events arrive via NATS, the handler must create TigerBeetle transfers IN ADDITION to COSIF journal entries.

**Step 1: Add TigerBeetle transfer to PIX handler**

Replace the entire `handle_transaction/1` function:
```elixir
def handle_transaction(payload) do
  Logger.info("[PIX Handler] Processing transaction: #{inspect(payload["transaction_id"])}")

  audit_log("PIX_TRANSACTION", "pix_transaction", payload)

  amount = payload["amount"]
  account_id = payload["account_id"]

  with {:ok, _entry} <- create_pix_journal_entry(payload),
       :ok <- create_tigerbeetle_transfer(payload, amount, account_id) do
    if account_id do
      Publisher.publish_balance_update(account_id, %{updated: true})
    end

    :ok
  else
    {:error, reason} ->
      Logger.error("[PIX Handler] Failed to process transaction: #{inspect(reason)}")
      {:error, reason}
  end
end
```

**Step 2: Add the TigerBeetle transfer function**

Add new private function:
```elixir
alias Monetarie.Wallet
alias Monetarie.Transaction, as: Tx

defp create_tigerbeetle_transfer(payload, amount, account_id)
     when is_integer(amount) and amount > 0 and not is_nil(account_id) do
  int_account_id = parse_account_id(account_id)
  tx_id = generate_unique_tx_id(payload["transaction_id"])

  case pix_transfer_code(payload) do
    code when code == TransferCode.deposit() ->
      # PIX received (credit): deposit into client account
      case Wallet.deposit(tx_id, int_account_id, amount) do
        {:ok, _} -> :ok
        {:error, reason} ->
          Logger.error("[PIX Handler] TB deposit failed: #{inspect(reason)}")
          {:error, {:tigerbeetle_failed, reason}}
      end

    code when code == TransferCode.pix_transfer() ->
      # PIX sent (debit): withdraw from client account
      case Wallet.withdraw(tx_id, int_account_id, amount) do
        {:ok, _} -> :ok
        {:error, :not_enough_balance} ->
          Logger.error("[PIX Handler] Insufficient balance for PIX out, account #{int_account_id}")
          {:error, :insufficient_balance}
        {:error, reason} ->
          Logger.error("[PIX Handler] TB withdrawal failed: #{inspect(reason)}")
          {:error, {:tigerbeetle_failed, reason}}
      end
  end
end

defp create_tigerbeetle_transfer(_payload, _amount, _account_id) do
  Logger.debug("[PIX Handler] Skipping TB transfer - no valid amount or account_id")
  :ok
end

defp parse_account_id(id) when is_integer(id), do: id
defp parse_account_id(id) when is_binary(id) do
  case Integer.parse(id) do
    {int_id, _} -> int_id
    :error -> 0
  end
end

defp generate_unique_tx_id(reference_id) when is_binary(reference_id) do
  :erlang.phash2({reference_id, System.monotonic_time()}, 2_147_483_647)
end
defp generate_unique_tx_id(_), do: System.unique_integer([:positive, :monotonic])
```

**Step 3: Apply same pattern to handle_payment and handle_return**

Update `handle_payment/1` and `handle_return/1` to also call `create_tigerbeetle_transfer/3`.

**Step 4: Commit**
```bash
git add -A && git commit -m "fix: sensitize TigerBeetle ledger in PIX NATS handler"
```

---

### Task 4: Sensitize TigerBeetle in SPB Handler

**Files:**
- Modify: `backend/lib/monetarie/nats/handlers/spb_handler.ex`

**Step 1: Add TigerBeetle and COSIF integration to SPB handler**

Add the same pattern as PIX handler: audit → COSIF journal → TigerBeetle transfer.

```elixir
defmodule Monetarie.Nats.Handlers.SpbHandler do
  require Logger
  alias Monetarie.Audit
  alias Monetarie.Cosif
  alias Monetarie.Cosif.Mapper
  alias Monetarie.Codes.TransferCode
  alias Monetarie.Nats.Publisher
  alias Monetarie.Wallet

  def handle_message_confirmed(payload) do
    Logger.info("[SPB Handler] Message confirmed: #{inspect(payload["message_id"])}")
    audit_log("SPB_MESSAGE_CONFIRMED", "spb_message", payload)
    :ok
  end

  def handle_transaction_updated(payload) do
    Logger.info("[SPB Handler] Transaction updated: #{inspect(payload["transaction_id"])}")

    audit_log("SPB_TRANSACTION_UPDATED", "spb_transaction", payload)

    amount = payload["amount"]
    account_id = payload["account_id"]

    with {:ok, _entry} <- create_spb_journal_entry(payload),
         :ok <- create_spb_tigerbeetle_transfer(payload, amount, account_id) do
      if account_id do
        Publisher.publish_balance_update(account_id, %{updated: true})
      end
      :ok
    else
      {:error, reason} ->
        Logger.error("[SPB Handler] Failed to process: #{inspect(reason)}")
        {:error, reason}
    end
  end

  defp create_spb_journal_entry(payload) do
    amount = payload["amount"]

    if is_integer(amount) and amount > 0 do
      transfer_code = spb_transfer_code(payload)
      reference_id = payload["transaction_id"] || payload["message_id"]

      transfer = %{code: transfer_code, amount: amount, reference_id: reference_id}

      with {:ok, attrs} <- Mapper.map_transaction(transfer) do
        attrs = Map.merge(attrs, %{
          description: "Transferencia SPB/TED - #{reference_id}",
          reference_type: "SPB",
          reference_id: reference_id,
          metadata: %{
            "source" => "spb",
            "type" => payload["type"],
            "message_type" => payload["message_type"]
          }
        })
        Cosif.create_journal_entry(attrs)
      end
    else
      {:ok, :skipped}
    end
  end

  defp create_spb_tigerbeetle_transfer(payload, amount, account_id)
       when is_integer(amount) and amount > 0 and not is_nil(account_id) do
    int_account_id = parse_account_id(account_id)
    tx_id = generate_unique_tx_id(payload["transaction_id"])

    case spb_transfer_code(payload) do
      code when code == TransferCode.deposit() ->
        case Wallet.deposit(tx_id, int_account_id, amount) do
          {:ok, _} -> :ok
          {:error, reason} -> {:error, {:tigerbeetle_failed, reason}}
        end

      code when code == TransferCode.ted_transfer() ->
        case Wallet.withdraw(tx_id, int_account_id, amount) do
          {:ok, _} -> :ok
          {:error, reason} -> {:error, {:tigerbeetle_failed, reason}}
        end
    end
  end

  defp create_spb_tigerbeetle_transfer(_, _, _), do: :ok

  defp spb_transfer_code(payload) do
    case payload["type"] || payload["direction"] do
      type when type in ["credit", "inbound", "received"] -> TransferCode.deposit()
      _ -> TransferCode.ted_transfer()
    end
  end

  defp parse_account_id(id) when is_integer(id), do: id
  defp parse_account_id(id) when is_binary(id) do
    case Integer.parse(id) do
      {int_id, _} -> int_id
      :error -> 0
    end
  end

  defp generate_unique_tx_id(reference_id) when is_binary(reference_id) do
    :erlang.phash2({reference_id, System.monotonic_time()}, 2_147_483_647)
  end
  defp generate_unique_tx_id(_), do: System.unique_integer([:positive, :monotonic])

  # ... (keep existing audit_log and safe_metadata functions)
end
```

**Step 2: Commit**
```bash
git add -A && git commit -m "fix: sensitize TigerBeetle and COSIF in SPB NATS handler"
```

---

### Task 5: Add Balance Check + Hold to PIX/TED Controllers

**Files:**
- Modify: `backend/lib/monetarie_web/controllers/v2/pix_controller.ex` (send_pix)
- Modify: `backend/lib/monetarie_web/controllers/v2/transfer_controller.ex` (create, create_pix, create_ted)

**Step 1: Add balance check and hold to PIX send**

In `v2/pix_controller.ex`, update `send_pix/2`:
```elixir
def send_pix(conn, %{"merchant_id" => merchant_id} = params) do
  current_user = Guardian.Plug.current_resource(conn)

  unless authorized?(current_user, merchant_id) do
    conn |> put_status(:forbidden) |> json(%{error: %{status: 403, message: "Acesso nao autorizado"}})
  else
    with {:ok, amount} <- validate_amount(params),
         {:ok, account} <- find_user_account(current_user),
         {:ok, balance} <- Wallet.get_balance(account.id),
         :ok <- check_sufficient_balance(balance, amount),
         {:ok, _hold_id} <- Transaction.hold_funds(account.id, settlement_pool_id(), amount) do
      transaction_id = generate_transaction_id("PIXOUT")
      end_to_end_id = generate_e2e_id()

      payload = %{
        "transaction_id" => transaction_id,
        "end_to_end_id" => end_to_end_id,
        "type" => "pix_out",
        "direction" => "outbound",
        "amount" => amount,
        "account_id" => to_string(account.id),
        "recipient_key" => params["pixKey"],
        "description" => params["description"] || "PIX enviado",
        "sender_id" => to_string(current_user.id),
        "status" => "processing",
        "initiated_at" => DateTime.utc_now() |> DateTime.to_iso8601()
      }

      Task.start(fn ->
        try do
          Publisher.publish_pix_transaction(payload)
        rescue
          e -> Logger.error("[V2 PIX] NATS publish failed: #{inspect(e)}")
        end
      end)

      conn
      |> put_status(:accepted)
      |> json(%{
        status: "accepted",
        transactionId: transaction_id,
        endToEndId: end_to_end_id,
        amount: amount,
        message: "PIX enviado para processamento"
      })
    else
      {:error, :insufficient_balance} ->
        conn |> put_status(:unprocessable_entity) |> json(%{error: %{status: 422, message: "Saldo insuficiente"}})
      {:error, :account_not_found} ->
        conn |> put_status(:not_found) |> json(%{error: %{status: 404, message: "Conta nao encontrada"}})
      {:error, reason} ->
        conn |> put_status(:unprocessable_entity) |> json(%{error: %{status: 422, message: "Erro: #{inspect(reason)}"}})
    end
  end
end

defp find_user_account(current_user) do
  alias Monetarie.Repo
  alias Monetarie.Model.Relational.Account
  import Ecto.Query

  case from(a in Account, where: a.user_id == ^current_user.id and a.kind > 0, limit: 1) |> Repo.one() do
    nil -> {:error, :account_not_found}
    account -> {:ok, account}
  end
end

defp check_sufficient_balance(balance, amount) when balance >= amount, do: :ok
defp check_sufficient_balance(_, _), do: {:error, :insufficient_balance}

defp settlement_pool_id, do: 3
```

**Step 2: Apply same pattern to transfer controller**

Similar changes in `v2/transfer_controller.ex` for `create/2`, `create_pix/2`, `create_ted/2`.

**Step 3: Commit**
```bash
git add -A && git commit -m "feat: add balance validation and hold in PIX/TED controllers"
```

---

### Task 6: Fix NATS Subject Pattern Alignment

**Files:**
- Modify: `backend/lib/monetarie/nats/publisher.ex`

The publisher uses `ledger.*` but subsystems expect `monetarie.*`. Align to use `monetarie.*` for outbound.

**Step 1: Update publisher subjects**

```elixir
# PIX events
def publish_pix_transaction(transaction) do
  publish("monetarie.spi.transaction.outbound", %{type: "pix_transaction", data: transaction})
end

def publish_pix_payment_request(payment) do
  publish("monetarie.spi.payment.request", %{type: "pix_payment_request", data: payment})
end

def publish_pix_return(return_data) do
  publish("monetarie.spi.return.request", %{type: "pix_return", data: return_data})
end

# SPB events
def publish_spb_transaction(transaction) do
  publish("monetarie.spb.transaction.outbound", %{type: "spb_transaction", data: transaction})
end

# NPC/Boleto events
def publish_npc_boleto(boleto) do
  publish("monetarie.npc.boleto.request", %{type: "npc_boleto", data: boleto})
end
```

**Step 2: Commit**
```bash
git add -A && git commit -m "fix: align NATS publisher subjects to monetarie.* convention"
```

---

## Phase 2: Admin Backend API (Parallelizable per controller) - COMPLETED

### Task 7: Implement Admin Accounts Controller - DONE

**Files:**
- Create: `backend/lib/monetarie_web/controllers/admin/accounts_controller.ex`
- Modify: `backend/lib/monetarie_web/router.ex` (add admin routes)

Implement CRUD for bank accounts with TigerBeetle balance lookup:
- `GET /api/v1/admin/accounts` - List all accounts with filters (status, search, pagination)
- `GET /api/v1/admin/accounts/:id` - Account detail with TB balance
- `POST /api/v1/admin/accounts` - Create account (PG + TB)
- `PUT /api/v1/admin/accounts/:id` - Update account
- `GET /api/v1/admin/accounts/:id/statement` - Account statement from COSIF journal entries
- `POST /api/v1/admin/accounts/:id/deposit` - Admin deposit (for initial balance)
- `POST /api/v1/admin/accounts/:id/close` - Close account

Each endpoint returns real data from PostgreSQL + TigerBeetle.

---

### Task 8: Implement Admin Users Controller - DONE

**Files:**
- Create: `backend/lib/monetarie_web/controllers/admin/users_controller.ex`

- `GET /api/v1/admin/users` - List users with filters
- `GET /api/v1/admin/users/:id` - User detail with accounts
- `POST /api/v1/admin/users` - Create user (with Bcrypt password)
- `PUT /api/v1/admin/users/:id` - Update user
- `POST /api/v1/admin/users/:id/block` - Block user
- `POST /api/v1/admin/users/:id/unblock` - Unblock user

---

### Task 9: Implement Admin Transactions Controller - DONE

**Files:**
- Create: `backend/lib/monetarie_web/controllers/admin/transactions_controller.ex`

- `GET /api/v1/admin/transactions` - List transactions from COSIF journal + audit logs
- `GET /api/v1/admin/transactions/:id` - Transaction detail
- `GET /api/v1/admin/dashboard/stats` - Dashboard statistics (accounts, volumes, health)

Aggregate from: cosif_journal_entries (amounts), audit_logs (events), TigerBeetle (balances).

---

### Task 10: Implement Admin Merchants Controller - DONE

**Files:**
- Create: `backend/lib/monetarie_web/controllers/admin/merchants_controller.ex`
- Create: `backend/priv/repo/migrations/20260208000002_create_merchants.exs`
- Create: `backend/lib/monetarie/model/relational/merchant.ex`

In the Monetarie Core context, a "merchant" maps to a user with associated accounts and configurations. Create a lightweight merchants table or use the existing users+accounts structure.

- `GET /api/v1/admin/merchants` - List merchants
- `POST /api/v1/admin/merchants` - Create merchant
- `PUT /api/v1/admin/merchants/:id` - Update
- `GET /api/v1/admin/merchants/:id` - Detail

---

### Task 11: Add All Admin Routes to Router - DONE

**Files:**
- Modify: `backend/lib/monetarie_web/router.ex`

Add a new admin scope block:
```elixir
# Admin API routes - require authentication + admin role
scope "/api/v1/admin", MonetarieWeb.Admin do
  pipe_through [:api, :authenticated, :audited]

  # Dashboard
  get "/dashboard/stats", DashboardController, :stats

  # Accounts
  resources "/accounts", AccountsController, only: [:index, :show, :create, :update]
  get "/accounts/:id/statement", AccountsController, :statement
  post "/accounts/:id/deposit", AccountsController, :deposit
  post "/accounts/:id/close", AccountsController, :close

  # Users
  resources "/users", UsersController, only: [:index, :show, :create, :update]
  post "/users/:id/block", UsersController, :block
  post "/users/:id/unblock", UsersController, :unblock

  # Transactions
  get "/transactions", TransactionsController, :index
  get "/transactions/:id", TransactionsController, :show

  # Merchants
  resources "/merchants", MerchantsController, only: [:index, :show, :create, :update, :delete]
end
```

---

## Phase 3: Internet Banking Views (Parallelizable) - COMPLETED

### Task 12: Fix Token Storage Alignment - DONE

**Files:**
- Modify: `apps/banking/src/stores/auth.ts`
- Modify: `apps/banking/src/lib/api.ts`

Align token keys between banking app and shared client.

---

### Task 13: Implement PIX Send View - DONE

**Files:**
- Modify: `apps/banking/src/views/pix/PixSendView.vue`
- Modify: `apps/banking/src/views/pix/PixSendConfirmView.vue`
- Modify: `apps/banking/src/views/pix/PixHubView.vue`

Build complete PIX send flow: key input → DICT lookup → amount → confirm → send.

---

### Task 14: Implement Transfer Send View - DONE

**Files:**
- Modify: `apps/banking/src/views/transfer/TransferSendView.vue`
- Modify: `apps/banking/src/views/transfer/TransferSendConfirmView.vue`
- Modify: `apps/banking/src/views/transfer/TransferHubView.vue`

Build complete transfer flow: recipient → amount → confirm → send.

---

### Task 15: Implement Statement View - DONE

**Files:**
- Modify: `apps/banking/src/views/StatementView.vue`

Transaction list with date filters, types, amounts. Uses `GET /api/v2/accounts/:id/transactions`.

---

### Task 16: Implement Dashboard View (Complete) - DONE

**Files:**
- Modify: `apps/banking/src/views/DashboardView.vue`

Complete dashboard with: balance card, recent transactions, quick actions (PIX, Transfer, Statement).

---

## Phase 4: Admin Frontend Views (Parallelizable) - COMPLETED

### Task 17: Implement Admin Accounts List View - DONE

**Files:**
- Modify: `apps/admin/src/views/bank-accounts/BankAccountListView.vue`

PrimeVue DataTable with real API data, search, filters, pagination.

---

### Task 18: Implement Admin Account Create View - DONE

**Files:**
- Modify: `apps/admin/src/views/bank-accounts/BankAccountCreateView.vue`

Form: user select, agency, account number, kind, initial balance.

---

### Task 19: Implement Admin Transactions View - DONE

**Files:**
- Modify: `apps/admin/src/views/transactions/TransactionListView.vue`
- Modify: `apps/admin/src/views/transactions/TransactionDetailView.vue`

Transaction monitoring with filters (date, type, status, amount range).

---

### Task 20: Implement Admin Users View - DONE

**Files:**
- Modify: `apps/admin/src/views/users/UserListView.vue`
- Modify: `apps/admin/src/views/users/UserDetailView.vue`

User management with block/unblock actions.

---

### Task 21: Implement Admin Dashboard (Complete) - DONE

**Files:**
- Modify: `apps/admin/src/views/DashboardView.vue`

Full dashboard with real stats: active accounts, transaction volume, system health, alerts.

---

## Phase 5: DICT Integration via NATS (Sequential)

### Task 22: Implement DICT Lookup via NATS

**Files:**
- Modify: `backend/lib/monetarie_web/controllers/v2/pix_controller.ex` (dict_lookup)
- Modify: `backend/lib/monetarie/nats/publisher.ex` (add publish_dict_lookup)

Replace mock DICT response with NATS publish to PIX subsystem and wait for reply:
```elixir
def dict_lookup(conn, %{"merchant_id" => merchant_id, "key" => key}) do
  # Publish lookup request and await response (with timeout)
  case Publisher.request_dict_lookup(key, timeout: 5000) do
    {:ok, result} -> json(conn, result)
    {:error, :timeout} -> json(conn |> put_status(504), %{error: "DICT lookup timeout"})
    {:error, reason} -> json(conn |> put_status(502), %{error: inspect(reason)})
  end
end
```

---

## Execution Order & Parallelism

```
Phase 1 (Sequential - blocks everything):
  Task 1 (Bcrypt) → Task 2 (NATS enable) → Task 3 (PIX TB) → Task 4 (SPB TB) → Task 5 (Balance check) → Task 6 (Subjects)

Phase 2 (Parallel - independent controllers):
  Task 7 (Admin Accounts) ║ Task 8 (Admin Users) ║ Task 9 (Admin Transactions) ║ Task 10 (Admin Merchants)
  → Task 11 (Router - after all controllers)

Phase 3 (Parallel - independent views):
  Task 12 (Token fix) → then:
  Task 13 (PIX Send) ║ Task 14 (Transfer) ║ Task 15 (Statement) ║ Task 16 (Dashboard)

Phase 4 (Parallel - independent admin views):
  Task 17 (Account List) ║ Task 18 (Account Create) ║ Task 19 (Transactions) ║ Task 20 (Users) ║ Task 21 (Dashboard)

Phase 5 (Sequential - depends on PIX subsystem):
  Task 22 (DICT via NATS)
```
