Pular para conteúdo

16 — Arquitetura e diretórios

Mapa do código: onde cada coisa mora, o que cada subsistema faz e como eles se encaixam. É o documento de entrada para quem vai mexer no pacote (para a visão em camadas e as ADRs, ver 01-arquitetura.md).

Fluxos

Fluxos da PyCobrança: dados → banco → boleto → PDF; e emissão → remessa CNAB → banco → retorno CNAB/OFX → conciliação

Emissão do boleto. Os dados do título (valor, data_vencimento, sacado, cedente) entram numa subclasse de BancoBase. O banco monta as 25 posições do campo livre com as regras do seu manual; montar_codigo_barras prefixa banco/moeda/DV geral/fator/valor e fecha as 44 posições; linha_digitavel rearranja tudo nos 5 campos do IPTE (47 dígitos); render desenha o PDF a partir de contexto_render().

Ciclo CNAB/OFX. Os títulos emitidos viram Pagamento e são serializados numa remessa CNAB 400 ou 240, enviada ao banco. O banco devolve o retorno CNAB (liquidações, baixas, rejeições) e o extrato OFX; ambos são lidos e casados com os boletos emitidos pelo nosso número (extrair_nosso_numero + concilia).

Árvore do repositório

pyCobranca/
├── pycobranca/           pacote da biblioteca (detalhado abaixo)
├── tests/                suíte pytest + fixtures congeladas (.rem, .RET, .ofx)
├── docs/                 esta documentação
│   ├── bancos/           um .md por banco + README (índice) + fontes-oficiais.md
│   └── images/           SVGs (banner, arquitetura, ciclo, diretórios) e screenshots
├── .github/workflows/    pipelines de CI e verificação
├── pyproject.toml        build PEP 517, deps, ruff (line-length 100) e pytest
├── README.md · CHANGELOG.md · CONTRIBUTING.md · LICENSE

Árvore do pacote

pycobranca/
├── __init__.py                 __version__, banco_info(), BANCOS (lazy, derivado do REGISTRO)
├── exceptions.py               PyCobrancaError, BoletoInvalido (.erros), BancoNaoRegistrado,
│                               OFXInvalido, RetornoInvalido
├── core/                       núcleo utilitário — sem dependência de nenhuma outra camada
│   ├── __init__.py
│   ├── dv.py                   modulo10, modulo11_resto, modulo11_codigo_barras,
│   │                           modulo11_flex, duplo_digito
│   ├── datas.py                fator_vencimento, data_do_fator, BASE_FATOR, ROLLOVER_FATOR
│   └── documentos.py           so_digitos, validar_cpf/cnpj, formatar_cpf/cnpj
├── boleto/                     composição do título (independente de banco)
│   ├── __init__.py
│   ├── codigo_barras.py        montar_codigo_barras() — 44 posições, campo livre de 25
│   └── linha_digitavel.py      linha_digitavel() — IPTE, 47 dígitos, 3 DVs módulo 10
├── bancos/                     regras por banco + registro
│   ├── __init__.py             Bancos.todos/find/com_pix + exports das 18 classes
│   ├── base.py                 BancoBase (dataclass + ClassVars + validar) e REGISTRO
│   ├── ailos.py                085 — Ailos
│   ├── banco_do_brasil.py      001 — Banco do Brasil
│   ├── banco_nordeste.py       004 — Banco do Nordeste
│   ├── banestes.py             021 — Banestes
│   ├── banrisul.py             041 — Banrisul
│   ├── bradesco.py             237 — Bradesco
│   ├── brb.py                  070 — BRB (Banco de Brasília)
│   ├── c6.py                   336 — C6 Bank
│   ├── caixa.py                104 — Caixa Econômica Federal
│   ├── citibank.py             745 — Citibank
│   ├── credisis.py             097 — CrediSIS
│   ├── hsbc.py                 399 — HSBC (legado)
│   ├── itau.py                 341 — Itaú
│   ├── safra.py                422 — Safra
│   ├── santander.py            033 — Santander
│   ├── sicoob.py               756 — Sicoob
│   ├── sicredi.py              748 — Sicredi
│   └── unicred.py              136 — Unicred
├── cnab/                       remessa e retorno FEBRABAN
│   ├── __init__.py             reexporta Pagamento e todas as classes Remessa*
│   ├── pagamento.py            Pagamento / PagamentoPix — título a registrar + formatadores
│   ├── formatacao.py           remover_acentos, format_size, format_valor
│   ├── cnab400/
│   │   ├── base.py             RemessaCnab400Base — header/detalhe/trailer, gera_arquivo()
│   │   ├── pix.py              PixMixinCnab400 (registro tipo 8) + Remessa*400Pix
│   │   └── <banco>.py          12 bancos: itau, bradesco, banco_brasil, santander, sicoob,
│   │                           unicred, banrisul, banco_nordeste, banco_brasilia, citibank,
│   │                           credisis, banco_c6
│   ├── cnab240/
│   │   ├── base.py             RemessaCnab240Base — header arq/lote, segmentos P/Q/R, trailers
│   │   ├── pix.py              segmento Y-03 + Remessa*240Pix
│   │   └── <banco>.py          7 bancos: ailos, banco_brasil, caixa, santander, sicoob,
│   │                           sicredi, unicred
│   └── retorno/
│       ├── __init__.py         Retorno.ler/ler_linhas — autodetecta layout e banco
│       ├── base.py             RegistroRetorno, extrai_campo, transforma_motivo, ATRIBUTOS
│       ├── cnab400.py          parse_cnab400, banco_do_arquivo_400 (código nas pos. 76–78)
│       ├── cnab240.py          parse_cnab240, banco_do_arquivo_240 (segmentos T + U)
│       └── ocorrencias.py      descreve_ocorrencia — rótulos legíveis por banco/layout
├── pix/
│   ├── __init__.py
│   ├── payload.py              PixPayload (BR Code EMV), crc16_ccitt, PixInvalido
│   └── qr.py                   qr_matrix (matriz de módulos), qr_svg
├── ofx/
│   ├── __init__.py
│   ├── parser.py               Extrato.ler (OFX v1 SGML e v2 XML), Transacao
│   ├── nosso_numero.py         extrair_nosso_numero — heurística por memo/instituição
│   └── conciliacao.py          concilia(), Conciliacao (conciliadas × pendentes)
├── contracts/
│   ├── __init__.py
│   └── contrato_rest.py        CONTRATO (OpenAPI 3.0), SLUG_POR_CODIGO, *_para_api,
│                               valida_contrato, ErroDeContrato
└── render/                     PDF via ReportLab — estrutura alvo
    ├── __init__.py             API pública (render_boleto_pdf, render_carne_pdf, barcode, logos)
    ├── comum.py                constantes + primitivas de desenho (canvas, texto, célula,
    │                           barcode, QR, logo)
    ├── dados.py                DadosBoleto / extrai_dados — normaliza o contexto para os
    │                           renderizadores
    ├── boleto.py               render_boleto_pdf
    ├── carne.py                render_carne_pdf
    ├── barcode.py              Interleaved 2 of 5 (interleaved_2of5_svg / sequencia_i2of5)
    ├── marcas.py               logos empacotados dos bancos (logo_do_banco, bancos_com_logo)
    └── logos/                  logos/*.png (um por código FEBRABAN) + NOTICE.md

render/ em reorganização. A árvore acima é a estrutura alvo do pacote de renderização: o desenho em ReportLab, hoje concentrado num módulo único, passa a ser dividido em primitivas (comum.py), normalização do contexto (dados.py) e os dois renderizadores (boleto.py, carne.py). A API pública exportada por render/__init__.py não muda.

Subsistemas

core/ — núcleo utilitário

Funções puras, sem estado e sem dependência de nenhuma outra camada. É a base de tudo: os DVs de dv.py são usados pelo campo livre de cada banco, pelo DV geral do código de barras e pelos três DVs de campo da linha digitável; datas.fator_vencimento implementa a regra FEBRABAN de reinício do fator (a contagem voltou a 1000 em 22/02/2025); documentos valida CPF/CNPJ e fornece o so_digitos que normaliza toda entrada com máscara.

boleto/ — código de barras e linha digitável

montar_codigo_barras(banco, fator, valor_centavos, campo_livre) monta as 44 posições e calcula o DV geral (módulo 11 com a regra de colapsar 0/10/11 em 1). Rejeita campo livre que não tenha exatamente 25 dígitos — é a fronteira que garante a corretude de todo banco novo. linha_digitavel(codigo_barras) faz o rearranjo IPTE e devolve a string formatada de 47 dígitos. Nenhum dos dois conhece banco algum.

bancos/ — registro + 18 bancos

BancoBase é uma dataclass com os campos do título e a composição pronta: as propriedades codigo_barras e linha_digitavel chamam validar(), campo_livre() e as funções de boleto/. Cada banco herda, declara os ClassVars (codigo, nome, digito_banco, carteiras, suporta_pix, regras_campos) e implementa campo_livre(). O auto-registro acontece em __init_subclass__, que insere a classe em REGISTRO[codigo]; a fachada Bancos (todos()/find()/com_pix()) consulta esse dicionário — e pycobranca.BANCOS deriva dele, de modo que a lista de bancos suportados nunca sai de sincronia. contexto_render() produz o dicionário consumido pelo render/, incluindo o bloco pix (payload EMV + matriz do QR) quando há chave PIX. Como adicionar um banco: 15-novo-banco.md.

cnab/ — remessa 400/240 e retorno

Pagamento (e PagamentoPix) descreve o título a registrar e concentra os formatadores posicionais (formata_valor, formata_valor_mora, formata_data_desconto, …), além de validar(), que confere obrigatórios e a coerência dos encargos. RemessaCnab400Base e RemessaCnab240Base implementam a estrutura comum do layout e deixam ganchos por banco; o gera_arquivo() de ambas valida os pagamentos, confere o tamanho de cada registro (tamanho_registro, desligável para layouts proprietários), remove acentos, aplica upper() e grava com CRLF. O retorno/ faz o caminho inverso: Retorno.ler detecta o layout pelo tamanho do primeiro registro e o banco pelo header, e devolve RegistroRetorno com os valores crus do arquivo. Detalhes em 06-cnab.md.

pix/ — Bolepix

PixPayload.br_code() monta o payload EMV (BR Code copia-e-cola) com CRC16-CCITT; qr_matrix devolve a matriz de módulos que o renderizador desenha vetorialmente no PDF e qr_svg gera uma pré-visualização. O subsistema é acionado por BancoBase._contexto_pix() — só para bancos com suporta_pix=True — e pelos mixins PIX da remessa (registro tipo 8 no 400, segmento Y-03 no 240). Ver 07-pix.md.

ofx/ — extrato e conciliação

Extrato.ler aceita OFX v1 (SGML) e v2 (XML) em Latin-1 ou UTF-8 e devolve Transacaos com data, valor, memo e o nosso_numero_extraido. concilia(extrato, nossos_numeros) casa as transações com os títulos emitidos e devolve uma Conciliacao (conciliadas × pendentes). É o fecho do ciclo, complementar ao retorno CNAB. Ver 13-ofx.md.

contracts/ — contrato REST

Serializadores dos artefatos para JSON no formato do contrato OpenAPI 3.0 (boleto_para_api, pagamento_para_api, remessa_para_api, retorno_item_para_api) e valida_contrato, um validador leve que levanta ErroDeContrato. A PyCobrança não expõe HTTP: ela apenas produz o payload que uma camada REST publica. Ver 04-api-rest.md.

render/ — PDF

Backend único ReportLab, Python puro, sem dependências de sistema. Entrada: o dicionário de contexto_render(). Saída: bytes de PDF — render_boleto_pdf(ctx, modelo=...) nos modelos classico e moderno (com Bolepix e tema) e render_carne_pdf (3 parcelas por A4). O código de barras é desenhado a partir da sequência Interleaved 2 of 5 gerada em barcode.py, e marcas.py expõe os logos empacotados por código FEBRABAN. Ver 11-renderizacao.md.

exceptions.py — contrato de erros

Toda falha de domínio herda de PyCobrancaError. A mais relevante para um consumidor é BoletoInvalido, que carrega .erros como lista (um item por violação), permitindo mapear cada problema individualmente. Ver 14-validacao-campos.md.

Dependências entre subsistemas

As setas apontam sempre para dentro: nada em core/ conhece bancos, e nada em bancos/ conhece CNAB, OFX ou render.

Notação: A ◀── B significa "B importa A".

exceptions ......... transversal — toda camada levanta PyCobrancaError

core ◀── boleto ◀── bancos ──▶ pix          (Bolepix no boleto)
  ▲                    │
  │                    └──▶ contexto_render() (dict) ──▶ render ──▶ PDF
  └── cnab ──▶ pix                          (registro tipo 8 / segmento Y-03)

ofx ......... parser + conciliação pelo nosso número (não importa core/bancos/cnab)
contracts ... serializa boleto · pagamento · remessa · retorno em JSON
Subsistema Depende de Não depende de
core todo o resto
boleto core bancos, cnab, render
bancos core, boleto, exceptions, pix (import tardio) cnab, ofx, render
cnab core, exceptions bancos, render
pix exceptions, qrcode (import tardio) core, bancos, cnab
ofx exceptions core, bancos, cnab
render reportlab (+ contexto em dicionário) bancos, cnab
contracts cnab.retorno.ocorrencias (import tardio) HTTP/framework web

O acoplamento entre bancos e render é por dicionário, não por import: o banco produz contexto_render() e o renderizador consome esse contrato. Por isso os dois evoluem independentemente — e por isso a reorganização de render/ não toca em nenhum banco.