# Dossie de paridade profundo: Conciliacao + Expurgo + Tarefas Automaticas

Auditor: revisao senior de sistemas PIX. Metodo: READ-ONLY, prova por arquivo:linha
(decompilado .NET), tabela.coluna (SQL Server vivo) e codigo Elixir/Vue. Zero
inferencia: onde nao ha prova, o veredito e INCONCLUSIVO.

Legado relevante (workers .NET SPI):
- `SPI.Core.Worker.Conciliacao` (+ Application) e o motor `ConciliacaoUseCase`
  em `SPI.Core.Angular.Application`.
- `SPI.Core.Worker.Expurgo` (+ Application + Infrastructure).
- `SPI.Core.Worker.TarefasAutomaticas` (+ Application) e
  `SPI.Core.Worker.DownloadAutomatico` (+ Application).
- `Multiliquidacao.Core.Worker.Expurgo` (lado QR/multiliquidacao).

Nosso relevante (Elixir umbrella `pix/backend`):
- `SpiService.DailyReconciliation` + `SpiService.Workers.EodSnapshotWorker`.
- `SettlementService.Reconciliation` (conciliacao de sessao de netting).
- `Shared.Bacen.PixParticipants.{ImportWorker,DirectoryImporter,CSVParser}`.
- `Shared.Crypto.CertMaterializer` (certificados).
- Config Oban (`pix/backend/config/config.exs`) = unica fonte de crons/retencao.

Instituicao/param vivos citados sao do backup SQL Server (`docker exec
monetarie-bak-mssql ... crk_spi.dbo.SpiCadParam`).

---

## 1. LEGADO: comportamento com prova

### 1.1 Worker de Conciliacao (D0 camt052/060 x pacs)

Registro do worker (vivo): `crk_spi.dbo.SpiWorker` linha
`Id=1002, CodigoWorker=20, Descricao='Conciliacao', TempoPausa=5000`.

Loop do worker: `SPI.Core.Worker.Conciliacao.decompiled.cs:137-172`
(`WorkerConciliacao.ExecuteAsync`): laco continuo; pausa =
`SpiWorker.TempoPausa` do CodigoWorker 20 se >0, senao config
(min 60000 ms, `...Conciliacao.Application.decompiled.cs:64-77`). Cada ciclo monta
`ExecutarConciliacaoParametersDTO{ Ispb, EsEjecucionManual=false, DtMovtoDe=hoje
00:00, DtMovtoAte=hoje 23:59:59 }` e chama `ExecutaVerificacaoArquivo`
(linhas 146-160).

Motor `ConciliacaoUseCase.ExecutaVerificacaoArquivoInternal`
(`SPI.Core.Angular.Application.decompiled.cs:2549-2641`):

1. Lock global de execucao unica (`_lockExecucao` SemaphoreSlim(1,1), L2518/2551).
2. Se automatico, sobrescreve o periodo para o **dia anterior**
   (`dtDe = dtAte = DateTime.Today.AddDays(-1)`, L2574-2576). Prova: a conciliacao
   automatica concilia D-1, nao o dia corrente.
3. `ValidarHorarioEjecucion` (L2580; def L3899-3952): le o param `CONCILIACAO`
   (JSON), checa flags `AtivaConciliacaoAuto`/`AtivaConciliacaoManual` e a janela
   `HoraIniSinc{Auto,Manual}`..`HoraFimSinc{Auto,Manual}`; fora da janela ->
   retorna `Finalizado` com "Fora de horario" (L2584).
4. Maquina de status por `(ispb, dtRef, manual)` na tabela
   `SpiCadConciliacaoStatus` (colunas vivas: `Ispb, DtReferencia, StatusAtual,
   MessageId, UltimaInicio, UltimaFim, UltimoErro, AguardandoArquivo,
   ConciliacaoManual`): estados `Iniciando/Processando/AguardandoDownload/
   ProcessandoDiferencas/Finalizado/Error`. Se ja em execucao, ignora novo disparo
   (L2594-2600 retorna 98).
5. Busca arquivos baixados do periodo via `_logDownload.FindArquivo(1, dtDe, dtAte)`
   (L2606). Se nenhum, faz **polling** ate 2min (`AguardaArquivosPeriodoAsync`,
   L2617/2643-2669); se ainda nada, marca `Finalizado` com a instrucao "Por favor
   gerar novo CAMT.060 para o periodo, e solicitar conciliacao novamente"
   (L2619-2621).
6. `ProcesarConciliacion` -> `PagedConciliacaoList` (L2700/2756-2795):
   - Carrega SPI `PACS.008`/`PACS.004` (`_paymentUseCase.List`, L2780).
   - Carrega linhas CAMT.052 do extrato baixado (`ListarArquivosCAMT052`, L2784;
     CSV `RegistroCAMT052Map` L4391-4398).
   - Concilia `PACS.008` por **EndToEndId** e `PACS.004` por **RtrId**
     separadamente (`ExecutarConciliacaoPrincipal` L3112-3137 usa
     `s.EndToEndId`; `ExecutarConciliacaoDevolucao` L3139-3164 usa `s.RtrId`);
     status CAMT considerado apenas `BOOK`.
7. **Auto-reparo (mutacao):** `SincronizarOperacoesComDiferencas`
   (L3954-3987):
   - CAMT presente e SPI ausente -> `CriarOperacaoNoSPI` (cria a operacao SPI que
     faltava a partir do CAMT, L4138-...).
   - CAMT `BOOK` e SPI com status != `Efetivada` -> `AtualizarStatusSPI` forca
     `Efetivada` (`AnalisarPrecedenciaStatus` L4010-4028: "Status BOOK no CAMT
     prevalece"; `AtualizarStatusSPI` L4065-4102).
   - Conflito nao previsto -> `ManterAmbiguidade` (marca para analise manual,
     L3999-4006).
   - Persistencia em lote de 100 (`_batchSizePersistencia=100`, L2526).

Param vivo `CONCILIACAO` (`crk_spi.dbo.SpiCadParam` IdParam='CONCILIACAO'):
```
{"AtivaConciliacaoManual": true, "HoraIniSincManual": "11:00",
 "HoraFimSincManual": "15:20", "AtivaConciliacaoAuto": true,
 "HoraIniSincAuto": "01:00", "HoraFimSincAuto": "13:30",
 "AtualizaLegado": false}
```
Prova: conciliacao automatica roda D-1 na janela 01:00-13:30; manual 11:00-15:20;
ambas ativas; nao propaga ao legado.

Consulta manual de status: `ConciliacaoUseCase.ObterStatusAtual` (L2671-2698) le a
`SpiCadConciliacaoStatus` para a tela.

### 1.2 Conciliacao de SALDO (camt.060 auto)

`SaldoUseCase.Find` (`SPI.Core.Angular.Application.decompiled.cs:2229-2259`): ao ler
o saldo, se nao houver saldo CAMT nas ultimas N horas (param `ATUALIZACAOSALDOHORAS`,
default 24, L2234), **dispara automaticamente uma CAMT.060 de consulta de saldo**
(`CAMT060ApiSaldoAtualDTO`, L2237-2249). Prova: o legado mantem o saldo fresco
solicitando camt.060 sob demanda quando envelhece.

### 1.3 Worker de Expurgo (retencao/arquivamento por N dias)

Registro: `SpiWorker` `CodigoWorker=14, Descricao='Expurgo'`.

Sub-workers (`SPI.Core.Worker.Expurgo.decompiled.cs:145-149`):
`WorkerExpurgoSpi`, `WorkerExpurgoLog`, `WorkerExpurgoMensagem`,
`WorkerExpurgoSeed`, `WorkerExpurgoFilas` — cada um em laco com pausa
(min 60000 ms, `...Expurgo.Application.decompiled.cs:311-325`).

Gatilho e corte (`...Expurgo.Application.decompiled.cs:54-129`,
`VerificaExecucao`): le param `EXPURGO` (JSON `ParametroExpurgo`), so executa se
`Ativa` e dentro da janela `HoraInicio`..`HoraFim`; calcula
`dataCorte = hoje - QtdDiasManter dias` (L122) e `horaLimite = hoje + HoraFim`
(L112). Prova do param vivo (`SpiCadParam` IdParam='EXPURGO'):
```
{"Ativa": true, "QtdDiasManter": 2, "HoraInicio": "00:01:00", "HoraFim": "23:59:00"}
```
Prova: em producao o legado mantem apenas **2 dias** de dados no operacional; o
resto e movido/apagado.

Acoes:
- **ExpurgoSpi = MOVE PARA HISTORICO depois DELETE**
  (`...Expurgo.Infrastructure.decompiled.cs:498-578`, `LimparDadosSPI`):
  em lotes de 100, para `messages.DtMovto <= dataCorte`, carrega
  `payments/messageHistories/messageXMessageIds/messageXSystems/registroAlcadas/
  alcadaParaOperacoes/vistoAlcadas`; grava tudo no **contexto HIST**
  (`_histcontext.BulkDelete + BulkInsert`, L521-552) e so entao
  `DELETE FROM SpiMessage WHERE IdOperacao IN (...)` no SPI (L553-559). Loop ate
  esgotar ou passar de `horaLimite`. Base HIST confirmada viva:
  `CRK_SPIHIST.dbo.{SpiMessage, SpiPayment, SpiMessageHistory, SpiMessageIds,
  SpiMessageXSystem, SpiRegAlcada, SpiRegAlcadasOperacao, SpiRegVistoAlcada}`.
- **ExpurgoLog = HARD DELETE** (L580-612): 12 tabelas de log
  (`SpiDetLogIntegracao, SpiDownloadArquivos, SpiLogAlertaMsg,
  SpiLogAlertaMsgAlcada, SpiLogAlertaTela, SpiLogAtividade, SpiLogAutomacaoMsg,
  SpiLogEnvioBacen, SpiLogIntegracao, SpiLogRetornoBacen, SpiLogWorker,
  SpiWorkerAtivo`) em blocos `Delete Top(1000) ... <= dataCorte`.
- **ExpurgoMensagem = HARD DELETE** (L614-642): `SpiMessage` (DtMovto),
  `SpiXmlMessage` (DtHrMsg).
- **ExpurgoSeed = HARD DELETE** (L644-672): `SpiEndToEndIds`, `SpiMsgIds`,
  `SpiRecMsg`, `SpiReturnIdentification` (E2E ids, msg ids, recepcao, return ids).
- **ExpurgoFilas** (`...Expurgo.Application.decompiled.cs:203-255`, `LimparFilas`):
  limpa dead-letter/erro de IBM MQ / RabbitMQ / SQL Server Broker conforme
  `ConfiguracaoFila.tipoFila` (vivo: `{"tipoFila": null}` = desligado).

UI de config: `LegadoPIX/Pix/SPI/angular/.../parametrizacoes.component.ts:72-73,
119-121,286-288` (checkbox "Ativar Expurgo" + "Qtd dias a manter", valida > 1) que
grava o param `EXPURGO` (`IdParam="EXPURGO"`, L314).

### 1.4 Expurgo do lado Multiliquidacao (QR)

`Multiliquidacao.Core.Worker.Expurgo.decompiled.cs:124,163-...`: sub-workers
`WorkerExpurgoLog`/`WorkerExpurgoOperacao`, corte
`hoje - QtdDiasManterDados` (default **90**, L124/196/290), janela
`DtHrInicioExpurgo`..`DtHrFimExpurgo`, bulk-insert em historico
(`crk_multiliquidacao_hist` existe vivo). Prova: existe um segundo motor de
expurgo/arquivamento no dominio de QR/multiliquidacao.

### 1.5 Tarefas Automaticas + Download Automatico

`SPI.Core.Worker.TarefasAutomaticas.decompiled.cs:308-314` hospeda:
`WorkerDownloadAutomatico`, `WorkerEnvioAutomacaoMensagem`,
`WorkerEnvioConsultaOperacao`, `WorkerEnvioAlerta`,
`WorkerEnvioAlertaReprocessamento`, `WorkerEnvioAlertaAlcada`, `WorkerLog`.
Registros: `SpiWorker` `15=DownloadAutomatico, 16=EnvioAlerta,
17=EnvioAlertaAlcada, 18=EnvioAutomacaoMensagem, 19=EnvioConsultaOperacao`.

Download automatico (`SPI.Core.Worker.DownloadAutomatico.Application.decompiled.cs`):
`ExecutaVerificacaoEDownload` (L114-121) roda 3 downloads, cada um no maximo
1x/24h e gated por param DB:
- **DownloadCertificado** (L129-...): baixa zip de certificados do param
  `DownloadCertificados` e grava em disco. Vivo:
  `{"Ativa": false, "URL": "http://arq-h.pi.rsfn.net.br:1130/api/v1/download/pub/
  cert/certqrc-h.zip", "Diretorio": "C:/temp/CertificadoSSL"}` (desligado).
- **DownloadParticipante** (L183-...): apos 6h, 1x/24h, baixa
  `participantes-spi-AAAAMMDD.csv`, insere/atualiza `Participante`
  (ISPB, nome, `Tipo` Direto/Indireto por `DRCT`, L241/250). Vivo:
  `{"Ativa":true, "URL":"http://www.bcb.gov.br/content/estabilidadefinanceira/
  spi/participantes-spi-AAAAMMDD.csv"}`.
- **DownloadBanco/COMPE** (L275-...): apos 6h, 1x/24h, baixa CSV STR, insere/
  atualiza `Banco` com codigo **COMPE** (`CdBanco`, L338/344). Vivo:
  `{"Ativa":true, "URL":"http://www.bcb.gov.br/pom/spb/estatistica/port/
  ParticipantesSTRport.csv"}`.

Automacao de mensagens (`TarefasAutomaticas.Application.decompiled.cs:457-495`,
`IniciaProcessamento`/`Executa`): para cada `ParametroAutomacaoMensagem`
(`SpiCadParamAutomacaoMsg`), valida `DataDe/DataAte`, `HoraDe/HoraAte`,
`Periodicidade` e `UltimaExecucao`, e dispara CAMT.060 conforme
`TipoConsulta` (`SaldoAtual`, `SaldoData`, `ListLanctos`). Prova: consultas de
saldo/extrato BACEN sao agendadas e periodicas, configuraveis pela tela.

---

## 2. NOSSO: comportamento com prova

### 2.1 Conciliacao diaria EOD (3 vias)

`SpiService.DailyReconciliation` (`daily_reconciliation.ex`) + worker
`SpiService.Workers.EodSnapshotWorker` (`eod_snapshot_worker.ex`).

Agendamento: cron do Oban `{"30 3 * * *", SpiService.Workers.EodSnapshotWorker}`
(`config/config.exs:88`), 03:30 UTC = 00:30 BRT, concilia o **dia anterior UTC**
(`eod_snapshot_worker.ex:28-32`: `Date.add(utc_today, -1)`).

3 vias (`daily_reconciliation.ex:6-24, 147-179`):
- (a) nossos liquidados do dia via `CanonicalTransactions.list_statement_movements`
  filtrado a `settled?` (L286-293).
- (b) extrato BACEN: camt.053 inbound (`bacen_inbound.xml_content`,
  `Camt053Parser`, L306-342), fallback ao arquivo baixado camt.052
  (`statements.source="bacen_file"` + `statement_entries` BOOK, L383-400).
- (c) fechamento CLBD vs esperado (abertura + liquido proprio, L152-153,168).
Comparacao por **E2E agregado** (`entry_details`/`amounts_by_e2e`, L423-457);
status `conciliado`/`divergente`/`sem_extrato` (L172-179).
**Read-only:** grava 1 linha em `monetarie_spi.daily_reconciliations` (upsert por
`ispb,recon_date,kind`, L459-484) e emite `Logger.warning` + telemetry
`[:pix,:recon,:divergence]` (L203-215,488-494). **Nunca cria nem muta a operacao
divergente.** Idempotente. Migration:
`apps/shared/priv/repo/migrations/20260705120000_create_daily_recon_and_balance_snapshots.exs`.

Snapshot EOD de saldo: `snapshot_eod` (L111-138), tabela
`monetarie_spi.daily_balance_snapshots`, abertura = fechamento anterior,
fechamento = CLBD camt.053 (`camt.053_clbd`) ou agregado (`aggregate`).

Check de posicao confirmado-vs-local: `record_position_check` (L241-271), chamado
por `Balances.update_confirmed_balance` (`balances.ex:972-...`) quando chega o CLBD
do camt.053; grava kind "position" e alerta divergencia.

UI: `pix/frontend/admin/src/views/accounting/ReconciliationView.vue` (existe uma
tela de conciliacao).

### 2.2 Conciliacao de sessao de netting

`SettlementService.Reconciliation.reconcile_session/1` (`reconciliation.ex:47-...`):
concilia uma sessao de netting vs registros BACEN, gera `reconciliation_runs`,
`reconciliation_matches`, `discrepancies` (tipos `amount_mismatch`,
`missing_transaction`, `extra_transaction`, `status_mismatch`,
`timestamp_mismatch`, `participant_mismatch`, L23-30). Endpoints em
`settlement_service_web/router.ex:212-218` (`POST /reconciliation/runs` etc.).
Escopo = sessao de liquidacao, nao a conciliacao D0 camt052/pacs.

### 2.3 Download automatico de participantes

`Shared.Bacen.PixParticipants.ImportWorker` (`import_worker.ex`), cron
`{"30 9 * * *", Shared.Bacen.PixParticipants.ImportWorker}`
(`config/config.exs:199`), 09:30 UTC diario.
`DirectoryImporter.import_date/import_latest` (`directory_importer.ex`): baixa a
lista oficial de **adesao ao PIX** (`lista-participantes-instituicoes-em-adesao-
pix-YYYYMMDD.csv`, fallback PDF, L20-30), delete+insert por `source_date` em
`monetarie_spi_ref` `bacen_pix_participants` e upsert de `banks`
(`is_pix_participant`, L98-159). Parser: `csv_parser.ex` captura
`ispb, name, cnpj, institution_type, spi_participation_type,
pix_participation_type, ...` (L43-71) — **sem COMPE**.

### 2.4 Certificados

`Shared.Crypto.CertMaterializer` (`cert_materializer.ex:1-18`): hidrata os
certificados ICP-Brasil do **AWS Secrets Manager** no boot (CPIA/CPIC/PEERPIA),
gated por `CERT_MATERIALIZER_ENABLED` (default true). Nao ha download periodico do
BACEN/ARQ.

### 2.5 Retencao / Expurgo

Unica retencao no sistema: `Oban.Plugins.Pruner, max_age: 60*60*24*7` (7 dias)
nas 3 instancias Oban (`config/config.exs:74,145,183`) — poda a tabela
`public.oban_jobs` (registros de JOB), NAO dados de negocio.
Retencao passiva de eventos: streams NATS por config
(`MONETARIE_DLQ` 90d, `MONETARIE_SPI/DICT/SETTLEMENT` 7d; ver `pix/CLAUDE.md`
"NATS Streams"). Busca exaustiva por expurgo/arquivamento de dados de negocio
(`grep -rilE "expurgo|retention|purge|prune|delete_old"` + `delete_all` em
workers) nao encontrou NENHUM worker que apague/arquive `messages`, `payments`,
`statements`, `bacen_inbound`, `xml_messages`, logs, `end_to_end_ids`,
`return_identifications` por idade. Nenhuma tabela `*_hist` espelho no schema PIX.

### 2.6 Crons do nosso lado (inventario completo)

`config/config.exs`:
- `spi_service` Oban: `*/5` TimerEnforcer, `*/15` CautelarWorker,
  `30 3` EodSnapshotWorker (conciliacao+snapshot), `17,47 *` PixInOrphanReconciliation,
  `30 21` RemunerationSweepWorker.
- `settlement_service` Oban: `0 *` Reports.ScheduleWorker, `*/10` QrExpiryWorker.
- `shared` Oban: `30 9` PixParticipants.ImportWorker, `*/2`
  DictExternalReconcileWorker, `* * * * *` DictInboundPollWorker; Reindexer `30 7`.
Nao ha worker de expurgo, nem de automacao agendada de camt.060 (saldo/extrato),
nem de download STR/COMPE.

---

## 3. GAPS (legado x nosso)

Ver objeto estruturado. Sintese:

| id | titulo | tipo | risco |
|----|--------|------|-------|
| g1 | Expurgo/retencao de dados de negocio | ausente | alto |
| g2 | Conciliacao nao auto-repara operacoes divergentes | divergencia | medio |
| g3 | Conciliacao de devolucao (pacs.004) por RtrId | parcial | medio |
| g4 | Janela horaria + flags auto/manual + trigger manual da conciliacao | parcial | baixo |
| g5 | Maquina de status/lock/polling "gerar CAMT.060" | parcial | baixo |
| g6 | Download STR/COMPE (codigo COMPE dos bancos) | parcial | medio |
| g7 | Download participantes SPI direto/indireto (tabela de roteamento) | parcial | medio |
| g8 | Automacao agendada de CAMT.060 (SaldoAtual/SaldoData/ListLanctos) | parcial | medio |
| g9 | Auto-request de CAMT.060 quando saldo envelhece (>N horas) | ausente | baixo |
| g10| Expurgo de filas/DLQ persistidas | parcial | baixo |
| g11| Download automatico de certificados do BACEN | divergencia | info |
| c1 | Conciliacao diaria EOD 3 vias existe | coberto | info |
| c2 | Snapshot EOD de saldo por data | coberto | info |
| c3 | Download diario do diretorio de participantes | coberto | info |
| c4 | Conciliacao de sessao de netting | coberto | info |
| c5 | Alerta de divergencia desacoplado (telemetry) | coberto | info |

### Nota de risco sobre g1 (headline)
Com `QtdDiasManter=2` vivo no legado, o operacional guardava so 2 dias e arquivava
o resto em `CRK_SPIHIST`. Nosso PIX nao tem NENHUM expurgo/arquivamento: `messages`,
`payments`, `statements`, `bacen_inbound` (XML cru), `xml_messages`, seeds de E2E/
returnId e as tabelas de log/auditoria crescem sem teto. Impacto: custo/tamanho do
Aurora, degradacao de indices (o proprio comentario do Reindexer em config.exs cita
GIN bloat de oban_jobs), e ausencia de politica de retencao formal. NAO copiar
QtdDiasManter=2 as cegas (regulatorio exige 10 anos de ICOM / 2 anos de DICT, ver
`pix/CLAUDE.md` "Audit"): a paridade correta e um expurgo COM arquivamento em cold
storage (o legado ja fazia isso via HIST), nao delete cru.

### Nota sobre g2 (comportamento, nao so ausencia)
O legado auto-repara: cria a operacao SPI que faltava a partir do CAMT e forca
`Efetivada` quando o BACEN diz `BOOK`. Nos so REGISTRAMOS a divergencia (linha +
alerta). Isso e mais seguro (nao muta money-path automaticamente), mas significa
que uma operacao presente no extrato BACEN e ausente/nao-efetivada no nosso SPI
NAO e sanada por nenhum job — depende de acao humana a partir do alerta. Decisao de
produto: manter read-only e sanar via fluxo controlado, ou portar um reparo gated.
