# Vínculos, listas restritivas e processos

<https://unifokal.com/docs/modulos/compliance-vinculos>

## Vínculos, listas restritivas e processos

Seis módulos de **compliance** aprofundam o screening além do titular: `pep_parentes` (parentes de pessoa exposta politicamente), `impedidos_vinculos` (vínculo familiar com impedido de apostar, a fase 2 da Lei 14.790/2023, Art. 26), `pep_lista_restritiva` (listas restritivas), `antecedentes_estaduais`, `processos_judiciais` e `scr_bacen` (retrato de endividamento no SCR). Todos partem do **CPF lido do documento**, e exigem Documento, Face Match e Liveness no mesmo flow pela mesma razão do `pep_sancoes`: nada aqui é consultado a partir de um número digitado. A consulta à fonte é **pelo CPF**; o **nome** só entra no `impedidos_vinculos`, e ainda assim como nome do **parente**, dentro do nosso motor local de vedação.

**Venda pausada hoje, os seis.** Eles aparecem na tabela de preços e no `GET /v1/capabilities` com preço e status `coming_soon`, e não podem ser ligados num flow enquanto a credencial do fornecedor não existir na conta. O estado vem do catálogo vivo, e esta página acompanha. O payload abaixo é o contrato que já está implementado e que passa a valer no dia da abertura.

! **Nunca leia só `flagged` nestes módulos.** Quando a fonte não é chamada (credencial ausente, portão de identidade fechado, consulta adiada), o check sai `pending` **e ainda assim traz `data`**, preenchido com os valores neutros: `flagged: false`, `matched_by: "none"`, `aggregates: null` e o bloco da fonte `null`. Isso é "não perguntamos", e é indistinguível de "nada consta" se você olhar só a flag. Ramifique por `outcome` primeiro, sempre.

**Outro campo que promete mais do que entrega:** nestes módulos `matched_by: "document"` é **derivado de `flagged`**, não é a prova de que o casamento se deu por CPF. Ele diz "houve resultado", e não "casou pelo documento". A única exceção é o `impedidos_vinculos`, onde ele é medido de verdade e distingue `document`, `document_partial` e `name`. E ele **nem existe** em dois dos seis: `antecedentes_estaduais` e `scr_bacen` não emitem a chave. Onde ela aparece fora do `impedidos_vinculos`, trate-a como sinônimo de `flagged`.

```
// pep_parentes: parente PEP encontrado. "parentes" é o dossiê da fonte passando por nós.
{ "module": "pep_parentes", "passed": false, "outcome": "failed", "score": 40,
  "data": { "flagged": true,
            "matched_by": "document",   // derivado de flagged, NÃO é prova de match por CPF
            "reason": null,
            "parentes": { "parentescosPEP": [
              { "nome": "MARIA SILVA", "cpf": "***456789**", "grauParentesco": "MAE",
                "pep": { "nome": "JOSE SILVA", "funcao": "DEPUTADO FEDERAL",
                         "orgao": "CAMARA MOCK", "nivel": "FEDERAL" } } ] } } }

// pep_lista_restritiva: o bloco "listas" é a resposta da fonte, como ela manda.
{ "module": "pep_lista_restritiva", "passed": false, "outcome": "failed", "score": 40,
  "data": { "flagged": true, "matched_by": "document",
            "listas": { "listas": [ { "lista": "LISTA RESTRITIVA MOCK", "nome": "JOAO SILVA",
                                      "origem": "MOCK", "dataInclusao": "2025-03-01" } ] } } }

// antecedentes_estaduais: repare no tri-estado de "nada_consta" e na cobertura FIXA de UFs.
{ "module": "antecedentes_estaduais", "passed": false, "outcome": "failed", "score": 40,
  "data": { "nada_consta": false,      // true | false | null. null = a fonte NÃO afirmou nada.
            "flagged": true,
            "cobertura_uf": ["CE", "MG", "MT", "RS"],   // as UFs cobertas hoje, e só elas
            "antecedentes": { "nadaConsta": false,
                              "ocorrencias": [ { "uf": "MG", "tribunal": "TJMG",
                                                 "classe": "Acao Penal", "ano": 2024 } ] } } }

// processos_judiciais: "encontrados" é contado por nós sobre a resposta, não é campo da fonte.
{ "module": "processos_judiciais", "passed": false, "outcome": "failed", "score": 40,
  "data": { "flagged": true, "matched_by": "document", "encontrados": 2,
            "processos": { "totalProcessos": 2,
                           "processos": [ { "numero": "0001234-56.2024.8.13.0000",
                                            "tribunal": "TJMG", "classe": "Execucao de Titulo",
                                            "polo": "passivo", "status": "ativo" } ] } } }

// scr_bacen: INFORMACIONAL. O dossiê é o produto, e "flagged" é sempre false.
{ "module": "scr_bacen", "passed": true, "outcome": "approved", "score": 100,
  "data": { "flagged": false,
            "scr": { "dataBase": "2026-07", "quantidadeInstituicoes": 1,
                     "quantidadeOperacoes": 2,
                     "carteira": { "vencido": 0, "aVencer": 8765.43 } } } }
```

Duas leituras que evitam conclusão errada. `nada_consta`, no `antecedentes_estaduais`, tem **três** estados e não dois: `false` quando há ocorrência, `true` só quando a fonte **afirma** que nada consta, e `null` quando não houve ocorrência e a fonte também não afirmou nada. Campo ausente na resposta **nunca** vira atestado nosso. E `scr_bacen.flagged` é **sempre `false`**, por construção: o módulo é informacional, o produto dele é o retrato de endividamento, e não existe caminho no código que o marque. Não escreva alerta em cima dessa flag.

O `impedidos_vinculos` é o mais denso dos seis, porque ele precisa provar **duas** coberturas ao mesmo tempo: de onde veio o **grafo familiar** (`graph_coverage`, hoje sempre `"pep_relatives"`, ou seja a fonte de parentesco de PEP e mais nada) e quais **bases de vedação** sustentaram a triagem de cada parente (`coverage` e `dataset_versions`, as mesmas do módulo [Impedidos de apostar](https://unifokal.com/docs/modulos/impedidos-apostar#modulo-impedidos-apostar)). E ele conta os parentes de forma auditável: `relatives_total` é quanto a fonte devolveu, `relatives_screened` é quanto o motor de fato respondeu, e `relatives_unscreened` agrupa **por motivo** quem ficou de fora. Essa lista tem vocabulário fechado de seis valores: `degree_unknown`, `degree_out_of_scope`, `document_missing`, `name_missing`, `screening_indeterminate` e `over_cap`. Repare que `relatives_total` menos `relatives_screened` **não** é o tamanho dessa lista: ela é agrupada, e cada item traz o próprio `count`.

```
// impedidos_vinculos: parente de 1o grau na base de impedidos -> review COM evidência
{ "module": "impedidos_vinculos", "passed": false, "outcome": "failed", "score": 40,
  "data": { "is_restricted": true, "flagged": true,
            "matched_by": "name",            // aqui ele é MEDIDO: document | document_partial | name
            "reason": "family_link_restricted",
            "restrictions": [ { "type": "vinculo_familiar_1g", "link_level": 1, "sport": null,
                                "entity": null, "source": "ptransp_servidores_reg",
                                "matched_by": "name",
                                "similarity": 91,       // 0..100, NÃO 0..1
                                "listed_at": null, "left_at": null,
                                "birth_date_mismatch": false,
                                "entry_ref": "ptransp_servidores_reg:e_a8954835" } ],
            "restrictions_total": 1, "restrictions_truncated": false,
            "aggregates": { "is_restricted": true,
                            "restriction_types": ["vinculo_familiar_1g"],
                            "strongest_match": "name" },
            "graph_coverage": "pep_relatives",
            "relatives_total": 2, "relatives_screened": 2, "relatives_unscreened": [],
            "coverage": ["ptransp_servidores_reg"], "coverage_degraded": [],
            "dataset_versions": { "ptransp_servidores_reg": { "ingested_at": "2026-09-01T03:00:00Z",
                                                              "age_hours": 4, "stale": false,
                                                              "disabled": false },
                                  "cbf_bid":      { "ingested_at": null, "age_hours": null,
                                                    "stale": false, "disabled": true },
                                  "cbf_arbitros": { "ingested_at": null, "age_hours": null,
                                                    "stale": false, "disabled": true } },
            "name_source": "ocr" } }

// o caminho que MAIS importa: o parente existe e NÃO foi rastreável.
// Não é "nada consta": é "não deu para perguntar", e a contagem diz por quê.
{ "module": "impedidos_vinculos", "passed": null, "outcome": "pending", "score": 0,
  "data": { "is_restricted": false, "flagged": false, "matched_by": "none",
            "reason": "relatives_unscreenable",
            "restrictions": [], "restrictions_total": 0, "restrictions_truncated": false,
            "aggregates": null,
            "graph_coverage": "pep_relatives",
            "relatives_total": 1, "relatives_screened": 0,
            "relatives_unscreened": [ { "reason": "degree_unknown", "count": 1 } ],
            // vazios porque o motor local NEM FOI CHAMADO: sem grau de parentesco não há o
            // que triar, então não há cobertura a declarar. Não confunda com base vencida.
            "coverage": [], "coverage_degraded": [], "dataset_versions": null,
            "name_source": null } }   // null porque nem chegamos a usar um nome
```

**Nenhum dos seis reprova sozinho**, pela mesma regra do `pep_sancoes`: um resultado é candidato, vai para `review` com a evidência no webhook, e a sua análise decide. E vale a mesma minimização: o que sai do `impedidos_vinculos` sobre o parente é **contagem e restrição minimizada**, nunca nome, CPF ou datas de terceiro. O nome e o documento do parente entram na consulta e morrem lá.

! **No sandbox, o desfecho destes módulos não acompanha o resultado, e isso é deliberado do ambiente de testes.** Os sufixos `88` (hit) e `77` (indeterminado, só no `impedidos_vinculos`) escolhem o **dado**, mas o `outcome` continua vindo da tabela universal do sandbox, onde só `00`, `01` e `02` mudam o desfecho. Ou seja: em sandbox você vê `outcome: "approved"` com `flagged: true`. Em produção o mesmo hit sai `failed` e a verificação vai a revisão. Use os sufixos para exercitar o **parser**, e `02` para exercitar o desfecho.
