# Design — Datas das parcelas mês a mês (mesmo dia do vencimento inicial)

> Data: **2026-07-13**. Branch: `feat/loan-product-required-cosif-accounts`.
> Status: **aprovado no brainstorming**; pendente plano de implementação.
> Área: crédito (`loans.ex` `apply_periodicity` + `LoanEngine` `advance_months` + `SimulationDialog.vue` mirror).

## Problema

Hoje a data de cada parcela é `first_due + (i-1) * periodicidade * 30 dias`, que **drifta**: com
`first_due = 12/08`, +30 dias = **11/09** (perde o dia 12). O correto é manter o **mesmo dia** do
vencimento inicial no **mês seguinte**: 12/08, 12/09, 12/10... (periodicidade 1); com periodicidade N,
+N meses mantendo o dia.

## Achado

O `LoanEngine.price_schedule`/`sac_schedule` **já** calculam as datas com `advance_months` (mesmo dia,
+meses, com clamp de fim de mês). Quem introduz o drift é o **`apply_periodicity/4` (loans.ex)**, que
**sobrescreve** as datas com o `Date.add(..., ×30)`. O juro é **mensal-fixo** (`saldo × taxa_mensal`
composta na periodicidade), independente dos dias.

## Objetivo

Corrigir a data das parcelas para **mesmo dia, +N meses** (via `advance_months`), mantendo o ajuste de
dia útil já existente. Base do juro: **mês fixo** (decisão do dono) → **o valor das parcelas NÃO muda**,
só as datas.

## Comportamento

- Parcela `i` (1-based): `advance_months(first_due, (i-1) * periodicidade)`.
- **Fim de mês:** se o dia não existe no mês destino, cai pro **último dia do mês** (o `advance_months`
  já faz `min(dia, dias_do_mês)`): 31/01 → 28/02 (ou 29 em bissexto), 31/03 → 30/04.
- **Dia útil:** se `business_day_adjust`, aplica `next_business_day` **depois** da data mês-a-mês.
- **Valor das parcelas: inalterado.** O juro é por mês (não por dias); IOF e capital líquido usam
  `term_months`, não as datas. A revisão do "capital liberado da parcela" conclui: **nada no valor muda**.

## Backend

### `LoanEngine.advance_months/2`
Hoje é `defp`. Expor como `def` (ou um wrapper público) pra o `loans.ex` reusar a MESMA lógica (sem
duplicar o clamp de fim de mês).

### `apply_periodicity/4` (loans.ex)
```
# antes: due = Date.add(first_due, (inst.number-1) * periodicity * 30)
# depois: nominal = LoanEngine.advance_months(first_due, (inst.number-1) * periodicity)
#         due = if business_day_adjust, do: next_business_day(nominal), else: nominal
```
O `period_rate` e o `price_schedule/sac_schedule` (valores) ficam **inalterados**.

## Frontend (`SimulationDialog.vue`)

O `schedule` (preview) hoje usa `addDays(baseFirstDue, (i-1)*periodicity*30)`. Passa a usar um
`advanceMonths(date, meses)` em JS espelhando `LoanEngine.advance_months` (mesmo dia + clamp de fim de
mês), e aí aplica `nextBusinessDay` (mirror já existente). Os valores/parcelas do preview não mudam.

Nota JS: montar a data por componentes (`new Date(ano, mes+meses, min(dia, dias_do_mês))`) pra evitar o
overflow do `Date` (ex.: 31 em fevereiro viraria março).

## Testes

- **Backend** (`loan_lifecycle_test.exs`): `first_due=12/08/2026` → parcelas 12/08, 12/09, 12/10 (mesmo dia,
  não 11/09); `first_due=31/01/2026` → 28/02 (clamp fim de mês); periodicidade 2 → +2 meses; dia útil ainda
  empurra; **valores (payment/interest/principal) idênticos** ao schedule de hoje (só as datas diferem).
- **Front:** type-check; validação viva (preview mostra o mesmo dia todo mês).

## Fora de escopo (YAGNI)
- Juro por dias corridos (pro-rata) — descartado (base mês fixo).
- Pro-rata do 1º período (desembolso → 1º vencimento) — mantém como hoje (mensal).

## Arquivos afetados (previsão)
| Arquivo | Mudança |
|---|---|
| `loan_engine.ex` | `advance_months/2` de `defp` → `def` (expor) |
| `loans.ex` | `apply_periodicity/4` usa `advance_months` (+ dia útil depois) |
| `SimulationDialog.vue` | `advanceMonths` JS no `schedule` + `nextBusinessDay` |
| `loan_lifecycle_test.exs` | testes de data mês-a-mês + fim de mês + valores inalterados |
