brans-nfe 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ docs oficiais/
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ *.egg
21
+ .installed.cfg
22
+
23
+ .pytest_cache/
24
+ .coverage
25
+ .coverage.*
26
+ htmlcov/
27
+ .tox/
28
+ .nox/
29
+ .mypy_cache/
30
+ .ruff_cache/
31
+
32
+ .venv/
33
+ venv/
34
+ env/
35
+ ENV/
36
+
37
+ .idea/
38
+ .vscode/
39
+ *.swp
40
+ *.swo
41
+ .DS_Store
42
+ Thumbs.db
43
+
44
+ *.pfx
45
+ *.p12
46
+ certs/
47
+ .env
48
+ .env.*
49
+ !.env.example
50
+
51
+ .claude/
52
+ .cursor/
53
+ CLAUDE.md
54
+ .aider*
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Raphael Brans
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: brans-nfe
3
+ Version: 0.1.0
4
+ Summary: Cliente Python para a NFS-e Nacional do Brasil (SEFIN) — assinatura, transmissão, consulta, cancelamento, DFe e DANFSe.
5
+ Project-URL: Homepage, https://github.com/badbrans/brans-nfe
6
+ Project-URL: Repository, https://github.com/badbrans/brans-nfe
7
+ Project-URL: Issues, https://github.com/badbrans/brans-nfe/issues
8
+ Author-email: Raphael Brans <raphael_brans@hotmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: brasil,fiscal,icp-brasil,nfs-e,nfse,nota fiscal,sefin
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Natural Language :: Portuguese (Brazilian)
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Office/Business :: Financial
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: cryptography>=42.0
24
+ Requires-Dist: lxml>=5.0
25
+ Requires-Dist: nfelib>=2.5.2
26
+ Requires-Dist: pydantic>=2.5
27
+ Requires-Dist: requests>=2.31
28
+ Requires-Dist: xsdata>=24.0
29
+ Provides-Extra: danfse
30
+ Requires-Dist: reportlab>=4.0; extra == 'danfse'
31
+ Provides-Extra: dev
32
+ Requires-Dist: black>=24.0; extra == 'dev'
33
+ Requires-Dist: isort>=5.13; extra == 'dev'
34
+ Requires-Dist: mypy>=1.10; extra == 'dev'
35
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
36
+ Requires-Dist: pytest>=8.0; extra == 'dev'
37
+ Requires-Dist: reportlab>=4.0; extra == 'dev'
38
+ Requires-Dist: responses>=0.25; extra == 'dev'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # brans-nfe
42
+
43
+ [![PyPI version](https://img.shields.io/pypi/v/brans-nfe.svg)](https://pypi.org/project/brans-nfe/)
44
+ [![Python versions](https://img.shields.io/pypi/pyversions/brans-nfe.svg)](https://pypi.org/project/brans-nfe/)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
46
+ [![Tests](https://github.com/badbrans/brans-nfe/actions/workflows/tests.yml/badge.svg)](https://github.com/badbrans/brans-nfe/actions/workflows/tests.yml)
47
+
48
+ Cliente Python para a **NFS-e Nacional do Brasil** (SEFIN/Receita Federal) — emissão, consulta, cancelamento, distribuição de DFe e DANFSe, com assinatura XMLDSIG, mTLS com certificado A1 ICP-Brasil e payloads compactados em gzip+base64.
49
+
50
+ A `nfelib` fornece os bindings do XSD oficial. A `brans-nfe` foca na conversa com o SEFIN: PKCS#12, cadeia ICP-Brasil, assinatura XMLDSIG, transporte mTLS, paginação de DFe, mapeamentos de enums e modelagem de input.
51
+
52
+ ## Recursos
53
+
54
+ - ✅ Emissão de NFS-e a partir de DPS (`POST /nfse`)
55
+ - ✅ Consulta de NFS-e por chave de acesso (`GET /nfse/{chave}`)
56
+ - ✅ Consulta/verificação de DPS por Id (`GET /dps/{id}`, `HEAD /dps/{id}`)
57
+ - ✅ Cancelamento com evento e101101 assinado (`POST /nfse/{chave}/eventos`)
58
+ - ✅ Consulta de evento específico (`GET /nfse/{chave}/eventos/{tipo}/{seq}`)
59
+ - ✅ Download do DANFSe oficial (`GET /danfse/{chave}` no ADN)
60
+ - ✅ Sincronização DFe (`GET /contribuintes/DFe/{NSU}?cnpjConsulta=...`)
61
+ - ✅ Listagem de eventos de uma NFS-e (`GET /contribuintes/NFSe/{chave}/Eventos`)
62
+ - ✅ Carregamento de certificado A1 (.pfx/.p12) com extração de CNPJ do subject **ou** SAN ICP-Brasil
63
+ - ✅ Resolução automática da cadeia ICP-Brasil a partir de bundle PEM
64
+ - ✅ Modelos Pydantic v2 com validação (CEP, CNPJ, codigos IBGE, codigos LC 116)
65
+ - ✅ Sanitização Latin-1 do `xInfComp` (en-dash, aspas curvas, emojis)
66
+ - ✅ Patch automático do enum `TstipoRetPiscofins` da nfelib 2.5.2
67
+ - ✅ Tipagem estrita (mypy-friendly), erros tipados
68
+
69
+ ## Instalação
70
+
71
+ ```bash
72
+ pip install brans-nfe
73
+ ```
74
+
75
+ Para gerar DANFSe não-oficial em PDF (auxiliar, quando o portal SEFIN está fora):
76
+
77
+ ```bash
78
+ pip install "brans-nfe[danfse]"
79
+ ```
80
+
81
+ ## Quick start
82
+
83
+ ### Emitir NFS-e
84
+
85
+ ```python
86
+ from datetime import date
87
+ from decimal import Decimal
88
+ from brans_nfe import (
89
+ Ambiente, Certificado, NfseClient, NotaServico,
90
+ Prestador, Tomador, Servico, Valores, Endereco,
91
+ RegimeTributario, TributacaoIss,
92
+ )
93
+
94
+ certificado = Certificado.from_pfx_path("certificado.pfx", "senha-do-pfx")
95
+
96
+ client = NfseClient(
97
+ certificado=certificado,
98
+ ambiente=Ambiente.HOMOLOGACAO,
99
+ )
100
+
101
+ nota = NotaServico(
102
+ serie_rps="1",
103
+ numero_rps="42",
104
+ data_competencia=date(2026, 5, 18),
105
+ prestador=Prestador(
106
+ cnpj=certificado.cnpj,
107
+ razao_social=certificado.razao_social,
108
+ regime_tributario=RegimeTributario.SIMPLES_NACIONAL,
109
+ endereco=Endereco(
110
+ codigo_municipio_ibge="3304557",
111
+ cep="20010000",
112
+ logradouro="Rua do Prestador",
113
+ numero="100",
114
+ bairro="Centro",
115
+ ),
116
+ ),
117
+ tomador=Tomador(
118
+ cpf_cnpj="98.765.432/0001-10",
119
+ razao_social="CLIENTE TESTE SA",
120
+ endereco=Endereco(
121
+ codigo_municipio_ibge="3304557",
122
+ cep="20010001",
123
+ logradouro="Av. do Tomador",
124
+ numero="200",
125
+ bairro="Centro",
126
+ ),
127
+ email="financeiro@cliente.com",
128
+ ),
129
+ servico=Servico(
130
+ codigo_tributacao_nacional="010101",
131
+ codigo_municipio_prestacao="3304557",
132
+ discriminacao="Servico de consultoria em TI",
133
+ ),
134
+ valores=Valores(
135
+ valor_bruto=Decimal("1000.00"),
136
+ valor_liquido=Decimal("950.00"),
137
+ ),
138
+ iss=TributacaoIss(
139
+ aliquota=Decimal("5.00"),
140
+ valor=Decimal("50.00"),
141
+ base_calculo=Decimal("1000.00"),
142
+ ),
143
+ )
144
+
145
+ resp = client.transmitir(nota)
146
+ print(resp.chave_acesso)
147
+ print(resp.id_dps)
148
+ print(resp.xml_nfse_retorno)
149
+ ```
150
+
151
+ ### Cancelar NFS-e
152
+
153
+ ```python
154
+ from brans_nfe import MotivoCancelamento
155
+
156
+ resp = client.cancelar(
157
+ chave_acesso=resp.chave_acesso,
158
+ motivo="Erro no preenchimento dos valores tributados",
159
+ motivo_codigo=MotivoCancelamento.ERRO_EMISSAO,
160
+ )
161
+ ```
162
+
163
+ ### Consultar e baixar DANFSe oficial
164
+
165
+ ```python
166
+ nfse = client.consultar(chave_acesso="33...50_digitos")
167
+ pdf_bytes = client.baixar_danfse(chave_acesso="33...50_digitos")
168
+
169
+ with open("danfse.pdf", "wb") as f:
170
+ f.write(pdf_bytes)
171
+ ```
172
+
173
+ ### Sincronizar DFe (notas recebidas)
174
+
175
+ ```python
176
+ resp = client.sincronizar_dfe(ultimo_nsu=0)
177
+ for item in resp.itens:
178
+ print(item.nsu, item.chave_acesso, item.tipo_documento)
179
+ print("Proximo NSU:", resp.ultimo_nsu)
180
+ ```
181
+
182
+ ### Recuperar de timeout: consultar DPS por Id
183
+
184
+ Se a transmissão deu timeout mas você quer descobrir se a NFS-e foi gerada:
185
+
186
+ ```python
187
+ if client.existe_dps(id_dps="DPS3304557..."):
188
+ resp = client.consultar_dps(id_dps="DPS3304557...")
189
+ print("Foi emitida:", resp.chave_acesso)
190
+ ```
191
+
192
+ ### Construir DPS sem transmitir
193
+
194
+ Útil para preview, auditoria ou armazenamento prévio:
195
+
196
+ ```python
197
+ from brans_nfe import construir_dps, serializar_dps, assinar_xml
198
+
199
+ dps = construir_dps(nota, Ambiente.HOMOLOGACAO)
200
+ xml = serializar_dps(dps)
201
+ xml_assinado = assinar_xml(xml, certificado)
202
+ ```
203
+
204
+ ## API
205
+
206
+ ### Modelos (Pydantic v2)
207
+
208
+ | Modelo | Descrição |
209
+ |---|---|
210
+ | `NotaServico` | DPS completa: prestador, tomador, serviço, valores, ISS, PIS/COFINS, retenções |
211
+ | `Prestador` | CNPJ + razão social + regime tributário + endereço |
212
+ | `Tomador` | CPF ou CNPJ + razão social + endereço (opcional) + contato |
213
+ | `Servico` | Código LC 116 (6 dígitos) + município prestação + discriminação |
214
+ | `Endereco` | IBGE 7 dígitos + CEP 8 dígitos + logradouro + bairro |
215
+ | `Valores` | Valor bruto, líquido, descontos condicional/incondicional |
216
+ | `TributacaoIss` | Alíquota, valor, base, retenção (prestador/tomador/intermediário) |
217
+ | `TributacaoPisCofins` | CST, alíquotas, valores, retidos |
218
+ | `Retencoes` | IRRF, INSS, CSLL |
219
+
220
+ ### Cliente
221
+
222
+ ```python
223
+ NfseClient(
224
+ certificado: Certificado,
225
+ ambiente: Ambiente = Ambiente.HOMOLOGACAO,
226
+ ca_bundle: str | Path | None = None,
227
+ chain_bundle: str | Path | None = None,
228
+ timeout: int = 120,
229
+ versao_aplicativo: str = "brans-nfe-0.1.0",
230
+ )
231
+ ```
232
+
233
+ | Método | Endpoint | Retorno |
234
+ |---|---|---|
235
+ | `transmitir(nota)` | `POST /nfse` | `RespostaTransmissao` |
236
+ | `consultar(chave)` | `GET /nfse/{chave}` | `dict` |
237
+ | `consultar_dps(id)` | `GET /dps/{id}` | `RespostaConsultaDps` |
238
+ | `existe_dps(id)` | `HEAD /dps/{id}` | `bool` |
239
+ | `cancelar(chave, motivo)` | `POST /nfse/{chave}/eventos` | `RespostaEvento` |
240
+ | `consultar_evento(chave, codigo, seq)` | `GET /nfse/{chave}/eventos/{tipo}/{seq}` | `RespostaEvento` |
241
+ | `baixar_danfse(chave)` | `GET /danfse/{chave}` | `bytes` |
242
+ | `sincronizar_dfe(ultimo_nsu)` | `GET /contribuintes/DFe/{NSU}` | `RespostaDfe` |
243
+ | `listar_eventos_nfse(chave)` | `GET /contribuintes/NFSe/{chave}/Eventos` | `RespostaDfe` |
244
+
245
+ ### Erros
246
+
247
+ Todos derivam de `BransNfeError`:
248
+
249
+ - `CertificadoError`, `CertificadoExpiradoError`, `CertificadoSenhaInvalidaError`
250
+ - `ValidacaoDpsError`
251
+ - `AssinaturaXmlError`
252
+ - `TransmissaoError`, `ConsultaError`, `CancelamentoError`, `DanfseIndisponivelError`, `SincronizacaoDfeError` (todos com `status_code` e `corpo`)
253
+
254
+ ## Ambientes oficiais
255
+
256
+ | Serviço | Homologação | Produção |
257
+ |---|---|---|
258
+ | SEFIN | `https://sefin.producaorestrita.nfse.gov.br/SefinNacional` | `https://sefin.nfse.gov.br/SefinNacional` |
259
+ | ADN | `https://adn.producaorestrita.nfse.gov.br` | `https://adn.nfse.gov.br` |
260
+
261
+ O `NfseClient` resolve automaticamente com base no `Ambiente`.
262
+
263
+ ## Por que outra lib?
264
+
265
+ A `nfelib` é excelente — fornece os bindings gerados a partir do XSD oficial — mas é apenas o **modelo de dados**. Pra emitir realmente uma NFS-e Nacional você precisa de:
266
+
267
+ | Etapa | nfelib | brans-nfe |
268
+ |---|---|---|
269
+ | Dataclasses do XSD (`Dps`, `Tcserv`, enums) | ✅ | reutiliza |
270
+ | Serializar dataclass → XML | ✅ (via xsdata) | reutiliza |
271
+ | Carregar PKCS#12 + extrair CNPJ ICP-Brasil | ❌ | ✅ |
272
+ | Resolver cadeia ICP-Brasil | ❌ | ✅ |
273
+ | Assinar XML-DSig (C14N + SHA1 + KeyInfo) | ❌ | ✅ |
274
+ | Gzip + base64 do payload | ❌ | ✅ |
275
+ | POST mTLS ao SEFIN | ❌ | ✅ |
276
+ | Consultar / Cancelar / Sincronizar DFe | ❌ | ✅ |
277
+ | Baixar DANFSe oficial | ❌ | ✅ |
278
+ | Patch de enum bugado da nfelib 2.5.2 | ❌ | ✅ |
279
+ | Sanitização Latin-1 do `xInfComp` | ❌ | ✅ |
280
+ | Modelagem Pydantic pronta pra usar | ❌ | ✅ |
281
+
282
+ ## Desenvolvimento
283
+
284
+ ```bash
285
+ git clone https://github.com/badbrans/brans-nfe.git
286
+ cd brans-nfe
287
+
288
+ python -m venv venv
289
+ .\venv\Scripts\Activate.ps1 # Windows
290
+ source venv/bin/activate # Linux/Mac
291
+
292
+ pip install -e ".[dev]"
293
+
294
+ pytest # 89 testes
295
+ pytest --cov=brans_nfe # com coverage
296
+ black src tests
297
+ isort src tests
298
+ mypy src
299
+ ```
300
+
301
+ ## Licença
302
+
303
+ [MIT](LICENSE) — Copyright (c) 2026 Raphael Brans
304
+
305
+ ## Disclaimer
306
+
307
+ Esta biblioteca é um projeto independente e **não tem vínculo com a Receita Federal, SEFIN, ICP-Brasil ou Comitê Gestor da NFS-e Nacional**. Use em produção por sua conta e risco, em conformidade com a legislação vigente.
@@ -0,0 +1,267 @@
1
+ # brans-nfe
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/brans-nfe.svg)](https://pypi.org/project/brans-nfe/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/brans-nfe.svg)](https://pypi.org/project/brans-nfe/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Tests](https://github.com/badbrans/brans-nfe/actions/workflows/tests.yml/badge.svg)](https://github.com/badbrans/brans-nfe/actions/workflows/tests.yml)
7
+
8
+ Cliente Python para a **NFS-e Nacional do Brasil** (SEFIN/Receita Federal) — emissão, consulta, cancelamento, distribuição de DFe e DANFSe, com assinatura XMLDSIG, mTLS com certificado A1 ICP-Brasil e payloads compactados em gzip+base64.
9
+
10
+ A `nfelib` fornece os bindings do XSD oficial. A `brans-nfe` foca na conversa com o SEFIN: PKCS#12, cadeia ICP-Brasil, assinatura XMLDSIG, transporte mTLS, paginação de DFe, mapeamentos de enums e modelagem de input.
11
+
12
+ ## Recursos
13
+
14
+ - ✅ Emissão de NFS-e a partir de DPS (`POST /nfse`)
15
+ - ✅ Consulta de NFS-e por chave de acesso (`GET /nfse/{chave}`)
16
+ - ✅ Consulta/verificação de DPS por Id (`GET /dps/{id}`, `HEAD /dps/{id}`)
17
+ - ✅ Cancelamento com evento e101101 assinado (`POST /nfse/{chave}/eventos`)
18
+ - ✅ Consulta de evento específico (`GET /nfse/{chave}/eventos/{tipo}/{seq}`)
19
+ - ✅ Download do DANFSe oficial (`GET /danfse/{chave}` no ADN)
20
+ - ✅ Sincronização DFe (`GET /contribuintes/DFe/{NSU}?cnpjConsulta=...`)
21
+ - ✅ Listagem de eventos de uma NFS-e (`GET /contribuintes/NFSe/{chave}/Eventos`)
22
+ - ✅ Carregamento de certificado A1 (.pfx/.p12) com extração de CNPJ do subject **ou** SAN ICP-Brasil
23
+ - ✅ Resolução automática da cadeia ICP-Brasil a partir de bundle PEM
24
+ - ✅ Modelos Pydantic v2 com validação (CEP, CNPJ, codigos IBGE, codigos LC 116)
25
+ - ✅ Sanitização Latin-1 do `xInfComp` (en-dash, aspas curvas, emojis)
26
+ - ✅ Patch automático do enum `TstipoRetPiscofins` da nfelib 2.5.2
27
+ - ✅ Tipagem estrita (mypy-friendly), erros tipados
28
+
29
+ ## Instalação
30
+
31
+ ```bash
32
+ pip install brans-nfe
33
+ ```
34
+
35
+ Para gerar DANFSe não-oficial em PDF (auxiliar, quando o portal SEFIN está fora):
36
+
37
+ ```bash
38
+ pip install "brans-nfe[danfse]"
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ ### Emitir NFS-e
44
+
45
+ ```python
46
+ from datetime import date
47
+ from decimal import Decimal
48
+ from brans_nfe import (
49
+ Ambiente, Certificado, NfseClient, NotaServico,
50
+ Prestador, Tomador, Servico, Valores, Endereco,
51
+ RegimeTributario, TributacaoIss,
52
+ )
53
+
54
+ certificado = Certificado.from_pfx_path("certificado.pfx", "senha-do-pfx")
55
+
56
+ client = NfseClient(
57
+ certificado=certificado,
58
+ ambiente=Ambiente.HOMOLOGACAO,
59
+ )
60
+
61
+ nota = NotaServico(
62
+ serie_rps="1",
63
+ numero_rps="42",
64
+ data_competencia=date(2026, 5, 18),
65
+ prestador=Prestador(
66
+ cnpj=certificado.cnpj,
67
+ razao_social=certificado.razao_social,
68
+ regime_tributario=RegimeTributario.SIMPLES_NACIONAL,
69
+ endereco=Endereco(
70
+ codigo_municipio_ibge="3304557",
71
+ cep="20010000",
72
+ logradouro="Rua do Prestador",
73
+ numero="100",
74
+ bairro="Centro",
75
+ ),
76
+ ),
77
+ tomador=Tomador(
78
+ cpf_cnpj="98.765.432/0001-10",
79
+ razao_social="CLIENTE TESTE SA",
80
+ endereco=Endereco(
81
+ codigo_municipio_ibge="3304557",
82
+ cep="20010001",
83
+ logradouro="Av. do Tomador",
84
+ numero="200",
85
+ bairro="Centro",
86
+ ),
87
+ email="financeiro@cliente.com",
88
+ ),
89
+ servico=Servico(
90
+ codigo_tributacao_nacional="010101",
91
+ codigo_municipio_prestacao="3304557",
92
+ discriminacao="Servico de consultoria em TI",
93
+ ),
94
+ valores=Valores(
95
+ valor_bruto=Decimal("1000.00"),
96
+ valor_liquido=Decimal("950.00"),
97
+ ),
98
+ iss=TributacaoIss(
99
+ aliquota=Decimal("5.00"),
100
+ valor=Decimal("50.00"),
101
+ base_calculo=Decimal("1000.00"),
102
+ ),
103
+ )
104
+
105
+ resp = client.transmitir(nota)
106
+ print(resp.chave_acesso)
107
+ print(resp.id_dps)
108
+ print(resp.xml_nfse_retorno)
109
+ ```
110
+
111
+ ### Cancelar NFS-e
112
+
113
+ ```python
114
+ from brans_nfe import MotivoCancelamento
115
+
116
+ resp = client.cancelar(
117
+ chave_acesso=resp.chave_acesso,
118
+ motivo="Erro no preenchimento dos valores tributados",
119
+ motivo_codigo=MotivoCancelamento.ERRO_EMISSAO,
120
+ )
121
+ ```
122
+
123
+ ### Consultar e baixar DANFSe oficial
124
+
125
+ ```python
126
+ nfse = client.consultar(chave_acesso="33...50_digitos")
127
+ pdf_bytes = client.baixar_danfse(chave_acesso="33...50_digitos")
128
+
129
+ with open("danfse.pdf", "wb") as f:
130
+ f.write(pdf_bytes)
131
+ ```
132
+
133
+ ### Sincronizar DFe (notas recebidas)
134
+
135
+ ```python
136
+ resp = client.sincronizar_dfe(ultimo_nsu=0)
137
+ for item in resp.itens:
138
+ print(item.nsu, item.chave_acesso, item.tipo_documento)
139
+ print("Proximo NSU:", resp.ultimo_nsu)
140
+ ```
141
+
142
+ ### Recuperar de timeout: consultar DPS por Id
143
+
144
+ Se a transmissão deu timeout mas você quer descobrir se a NFS-e foi gerada:
145
+
146
+ ```python
147
+ if client.existe_dps(id_dps="DPS3304557..."):
148
+ resp = client.consultar_dps(id_dps="DPS3304557...")
149
+ print("Foi emitida:", resp.chave_acesso)
150
+ ```
151
+
152
+ ### Construir DPS sem transmitir
153
+
154
+ Útil para preview, auditoria ou armazenamento prévio:
155
+
156
+ ```python
157
+ from brans_nfe import construir_dps, serializar_dps, assinar_xml
158
+
159
+ dps = construir_dps(nota, Ambiente.HOMOLOGACAO)
160
+ xml = serializar_dps(dps)
161
+ xml_assinado = assinar_xml(xml, certificado)
162
+ ```
163
+
164
+ ## API
165
+
166
+ ### Modelos (Pydantic v2)
167
+
168
+ | Modelo | Descrição |
169
+ |---|---|
170
+ | `NotaServico` | DPS completa: prestador, tomador, serviço, valores, ISS, PIS/COFINS, retenções |
171
+ | `Prestador` | CNPJ + razão social + regime tributário + endereço |
172
+ | `Tomador` | CPF ou CNPJ + razão social + endereço (opcional) + contato |
173
+ | `Servico` | Código LC 116 (6 dígitos) + município prestação + discriminação |
174
+ | `Endereco` | IBGE 7 dígitos + CEP 8 dígitos + logradouro + bairro |
175
+ | `Valores` | Valor bruto, líquido, descontos condicional/incondicional |
176
+ | `TributacaoIss` | Alíquota, valor, base, retenção (prestador/tomador/intermediário) |
177
+ | `TributacaoPisCofins` | CST, alíquotas, valores, retidos |
178
+ | `Retencoes` | IRRF, INSS, CSLL |
179
+
180
+ ### Cliente
181
+
182
+ ```python
183
+ NfseClient(
184
+ certificado: Certificado,
185
+ ambiente: Ambiente = Ambiente.HOMOLOGACAO,
186
+ ca_bundle: str | Path | None = None,
187
+ chain_bundle: str | Path | None = None,
188
+ timeout: int = 120,
189
+ versao_aplicativo: str = "brans-nfe-0.1.0",
190
+ )
191
+ ```
192
+
193
+ | Método | Endpoint | Retorno |
194
+ |---|---|---|
195
+ | `transmitir(nota)` | `POST /nfse` | `RespostaTransmissao` |
196
+ | `consultar(chave)` | `GET /nfse/{chave}` | `dict` |
197
+ | `consultar_dps(id)` | `GET /dps/{id}` | `RespostaConsultaDps` |
198
+ | `existe_dps(id)` | `HEAD /dps/{id}` | `bool` |
199
+ | `cancelar(chave, motivo)` | `POST /nfse/{chave}/eventos` | `RespostaEvento` |
200
+ | `consultar_evento(chave, codigo, seq)` | `GET /nfse/{chave}/eventos/{tipo}/{seq}` | `RespostaEvento` |
201
+ | `baixar_danfse(chave)` | `GET /danfse/{chave}` | `bytes` |
202
+ | `sincronizar_dfe(ultimo_nsu)` | `GET /contribuintes/DFe/{NSU}` | `RespostaDfe` |
203
+ | `listar_eventos_nfse(chave)` | `GET /contribuintes/NFSe/{chave}/Eventos` | `RespostaDfe` |
204
+
205
+ ### Erros
206
+
207
+ Todos derivam de `BransNfeError`:
208
+
209
+ - `CertificadoError`, `CertificadoExpiradoError`, `CertificadoSenhaInvalidaError`
210
+ - `ValidacaoDpsError`
211
+ - `AssinaturaXmlError`
212
+ - `TransmissaoError`, `ConsultaError`, `CancelamentoError`, `DanfseIndisponivelError`, `SincronizacaoDfeError` (todos com `status_code` e `corpo`)
213
+
214
+ ## Ambientes oficiais
215
+
216
+ | Serviço | Homologação | Produção |
217
+ |---|---|---|
218
+ | SEFIN | `https://sefin.producaorestrita.nfse.gov.br/SefinNacional` | `https://sefin.nfse.gov.br/SefinNacional` |
219
+ | ADN | `https://adn.producaorestrita.nfse.gov.br` | `https://adn.nfse.gov.br` |
220
+
221
+ O `NfseClient` resolve automaticamente com base no `Ambiente`.
222
+
223
+ ## Por que outra lib?
224
+
225
+ A `nfelib` é excelente — fornece os bindings gerados a partir do XSD oficial — mas é apenas o **modelo de dados**. Pra emitir realmente uma NFS-e Nacional você precisa de:
226
+
227
+ | Etapa | nfelib | brans-nfe |
228
+ |---|---|---|
229
+ | Dataclasses do XSD (`Dps`, `Tcserv`, enums) | ✅ | reutiliza |
230
+ | Serializar dataclass → XML | ✅ (via xsdata) | reutiliza |
231
+ | Carregar PKCS#12 + extrair CNPJ ICP-Brasil | ❌ | ✅ |
232
+ | Resolver cadeia ICP-Brasil | ❌ | ✅ |
233
+ | Assinar XML-DSig (C14N + SHA1 + KeyInfo) | ❌ | ✅ |
234
+ | Gzip + base64 do payload | ❌ | ✅ |
235
+ | POST mTLS ao SEFIN | ❌ | ✅ |
236
+ | Consultar / Cancelar / Sincronizar DFe | ❌ | ✅ |
237
+ | Baixar DANFSe oficial | ❌ | ✅ |
238
+ | Patch de enum bugado da nfelib 2.5.2 | ❌ | ✅ |
239
+ | Sanitização Latin-1 do `xInfComp` | ❌ | ✅ |
240
+ | Modelagem Pydantic pronta pra usar | ❌ | ✅ |
241
+
242
+ ## Desenvolvimento
243
+
244
+ ```bash
245
+ git clone https://github.com/badbrans/brans-nfe.git
246
+ cd brans-nfe
247
+
248
+ python -m venv venv
249
+ .\venv\Scripts\Activate.ps1 # Windows
250
+ source venv/bin/activate # Linux/Mac
251
+
252
+ pip install -e ".[dev]"
253
+
254
+ pytest # 89 testes
255
+ pytest --cov=brans_nfe # com coverage
256
+ black src tests
257
+ isort src tests
258
+ mypy src
259
+ ```
260
+
261
+ ## Licença
262
+
263
+ [MIT](LICENSE) — Copyright (c) 2026 Raphael Brans
264
+
265
+ ## Disclaimer
266
+
267
+ Esta biblioteca é um projeto independente e **não tem vínculo com a Receita Federal, SEFIN, ICP-Brasil ou Comitê Gestor da NFS-e Nacional**. Use em produção por sua conta e risco, em conformidade com a legislação vigente.