# Design — Produto de empréstimo: contas contábeis com partida e contrapartida (Fase 1: configuração)

**Data:** 2026-07-07
**Tela:** `core/apps/admin/src/views/credit/LoanProductsView.vue` — Panel "Contas Contábeis"
**Status:** design aprovado (seções 1–4), aguardando revisão do spec.

## Contexto e problema

Hoje cada conta contábil do produto de empréstimo (`loan_products_historic`: `liberation`, `fees`,
`liquidation`, `fine`, provisões, etc.) é **uma única conta COSIF por campo**. Mas um lançamento é uma
**dupla-entrada** — precisa de uma conta a **débito** e uma a **crédito**. Ex.: os juros lançam débito
numa conta `1.6` (empréstimo) e crédito numa conta `7.1` (receita). Sem os dois lados configuráveis, a
contabilização do empréstimo fica incompleta (raiz do bug em que o desembolso de produto normal não gera
JE COSIF — ver `loan-principal-account-field` / diagnóstico 2026-07-07).

Já foi criado o campo **`principal_account`** ("Conta Principal do Empréstimo", ativo `1.6`), obrigatório
no cadastro. Este design estrutura **cada configuração como um par débito/crédito** e redesenha a tela para
configurá-los.

## Escopo

**Fase 1 (esta entrega) — só a configuração:**
- Modelo de dados: par débito/crédito por config em `loan_products_historic`.
- Backend: `ProductAccounts` validando os pares (autoridade do 422) + definição declarativa das configs.
- Front: listagem read-only por config + subtela (modal) de edição por config, com validação espelhada.
- Persistência: mecanismo genérico existente (`split_historic`/`upsert_historic!` + `@account_fields`).

**Fora do escopo — Fase 2 (só depois desta pronta e testada):**
- Religar os lançamentos (desembolso, juros, liquidação, multa, provisão, renegociação) para **usar** os
  pares configurados.
- Remover o `Enum.reject(&(&1 == :principal_account))` de `ProductAccounts.missing_for_product` → trava de
  liberação fail-closed real.
- Backfill dos contratos `2026000002`–`005` (liberados sem JE).
- Aposentar os campos antigos do `historic` (reaproveitados como um lado na fase 1).

## Semântica contábil

Regra geral: **partida = débito, contrapartida = crédito**, ambos configuráveis.

Exceção — configs cujo pivô é a **Conta Principal** (`1.6`, `principal_account`). O lado da Conta Principal
**não é armazenado**: deriva de `principal_account` (fica sincronizado se a Conta Principal mudar) e aparece
**travado** na UI, apenas visível:

| Config | Débito (partida) | Crédito (contrapartida) |
|---|---|---|
| **Liberação** | **Conta Principal** (deriva) | configurável (conta de saída) |
| **Liquidação** | configurável | **Conta Principal** (deriva) |
| **Desconto** | configurável | **Conta Principal** (deriva) |
| Juros, Multa, Juros de Atraso, "a apropriar", Encargos, Provisões | configurável | configurável |

> Ponto a validar no review: o conjunto de configs com lado-Conta-Principal são as **3 citadas**
> (Liberação, Liquidação, Desconto). As demais têm os dois lados livres. O mapeamento contábil específico
> de cada config (em especial as 4 de **provisão** da Res. 4.966) é preenchido pelo operador na tela — o
> modelo suporta qualquer par; a política contábil é do usuário.

## Modelo de dados

Alternativa escolhida: **colunas pareadas no `loan_products_historic`** (não tabela filha nem JSON —
consistente com o `principal_account` e com a persistência genérica que já está de pé).

- Configs com os **dois lados livres**: par de colunas `<config>_debit` + `<config>_credit`.
- Configs com lado-Conta-Principal: **só o lado livre** vira coluna (`liberation_credit`,
  `liquidation_debit`, `discount_debit`); o lado-CP deriva de `principal_account`.
- Adicionamos colunas **explícitas** `<config>_debit` / `<config>_credit` (nomenclatura consistente,
  independente de qual lado o campo antigo representava). Os campos atuais (`liberation`, `fees`, …) são
  **mantidos** na fase 1 — os JEs ainda os consomem — e **aposentados na fase 2** (daí a duplicação
  temporária da nota 3). Migração best-effort pode popular os novos a partir dos atuais; como o operador
  reconfigura na tela (correção definitiva), o mapeamento inicial não é crítico.
- Novas colunas entram em `LoanProductsHistoric.@account_fields` (schema) e em `HISTORIC_FIELDS` (front).
- Migração: `add_if_not_exists` para cada coluna (idempotente). Valores iniciais podem nascer vazios — o
  operador reconfigura na tela (é a correção definitiva); migração best-effort dos valores atuais é opcional.

## UI — listagem + subtela

**Tela principal (Panel "Contas Contábeis"):**
- `principal_account` segue como campo único no topo (é uma conta só, não um par).
- Cada configuração vira uma **linha read-only**: `Nome | D: <conta> | C: <conta> | [✏️]`.
- Lados fixos exibidos como `"1.6.x (Conta Principal)"`. Campo vazio exibe `"—"`.
- Sai o `AutoComplete` inline por conta — a edição migra 100% para a subtela.

**Subtela (modal), um lápis por configuração:**
- Título: `"Configurar conta — <label>"`.
- Dois `AutoComplete` COSIF: **Débito (partida)** e **Crédito (contrapartida)** — reusa o componente atual
  com o overlay largo já ajustado (`.p-autocomplete-overlay`, `appendTo="self"`).
- Nas configs de Liberação/Liquidação/Desconto, o lado da Conta Principal vem **preenchido e travado** (🔒),
  rotulado como Conta Principal.
- **"Aplicar"** grava no estado local (`historicSelected`) e atualiza a linha; **"Cancelar"** descarta. A
  persistência real ocorre no **"Salvar Produto"** (fluxo atual).

## Estrutura declarativa (organiza listagem + subtela + validação)

Uma definição única por config, no front e espelhada no back, dirige as três coisas:

```
{ key: 'fees', label: 'Juros', grupo: 'operacional',
  debito:  { campo: 'fees_debit' },
  credito: { campo: 'fees_credit' } }

{ key: 'liberation', label: 'Liberação', grupo: 'operacional',
  debito:  { fixo: 'conta_principal' },      // deriva de principal_account
  credito: { campo: 'liberation_credit' } }
```

Adicionar/ajustar uma config = mexer num lugar só.

## Validação + persistência

**Validação** (front espelha o back, como hoje):
- Cada config **obrigatória** (matriz `@required_by_model` por `model_type`) exige os **lados livres**
  preenchidos. O lado fixo na Conta Principal **não** é validado (deriva).
  - Ex.: Juros obrigatório → exige débito **e** crédito. Liberação obrigatória → exige só o crédito.
- Mensagem específica: *"Informe as contas de: Juros (crédito), Multa (débito e crédito)"*.
- `principal_account` segue obrigatória (já implementado).
- `ProductAccounts` continua a autoridade → **422**; o front bloqueia o "Salvar" espelhando a mesma regra.
- A trava de **liberação** (`missing_for_product`) **não** muda nesta fase (segue o `reject` do
  `principal_account`); os pares só passam a ser exigidos/consumidos na fase 2.

**Persistência** (sem infra nova):
- `split_historic` (pega o mapa `historic` inteiro) + `upsert_historic!` (changeset com `@account_fields`)
  gravam as novas colunas automaticamente — mesmo caminho validado hoje com `principal_account`.
- `historicSelected` no front passa a chavear pelos campos de lado (`fees_debit`, `fees_credit`, …); a
  subtela e a listagem leem/gravam por eles.

## Riscos / notas

1. **Fase 1 não afeta lançamento.** As contas configuradas na nova tela **só ficam gravadas**; os JEs
   continuam usando o modelo antigo até a fase 2. Comunicar ao operador (mesma nota do `principal_account`).
2. **Produtos existentes travam na edição** até reconfigurar débito+crédito (a validação exige) — atinge
   todos os produtos, já que os pares são novos. Seeds não travam (validação só na fronteira da API).
3. **Duplicação temporária** de contas (campos antigos + novos lados) durante a fase 1→2; consolidada na
   fase 2. Não há descasamento: cada conjunto serve seu propósito na sua fase.

## Verificação (planejada)

- Migration aplicada; colunas presentes (`information_schema`).
- Changeset casta os novos campos (`rpc`).
- `missing_on_create`/`missing_on_update` exigem os lados livres das configs obrigatórias; `missing_for_product`
  inalterado (`rpc`).
- `docker build` core-api + admin exit 0; ambos no ar.
- Visual: linha read-only + subtela (débito/crédito, lado-CP travado), bloqueio no save, persistência —
  validação do dono.
