dfe 0.7.1

DFE - Documentos Fiscais Eletrônicos Brasileiros
Documentation
# dfe — API pública (uso)

Exemplos de uso dos builders da crate `dfe`. Referenciado pelo `dfe/CLAUDE.md`. Para
arquitetura, gotchas técnicos e convenções internas, ver o `CLAUDE.md`.

Re-exports de conveniência em `lib.rs`:
```rust
use dfe::{NFeBuilder, CancelarBuilder, SubstituicaoBuilder, CartaCorrecaoBuilder, DanfeBuilder, NFeService, DfeError};
```

## Features de compilação

Opt-out: por padrão tudo está ligado, então adicionar a crate sem configurar features dá acesso a
toda a API (comportamento recomendado para o consumidor completo).

| Feature | Liga | Deps extras |
|---|---|---|
| `danfe` (default) | DANFE em PDF (`DanfeBuilder`) | `printpdf`, `image`, `barcoders`, `qrcodegen` |
| `escpos` (default) | Impressão ESC/POS (`EscPosBuilder`, `EscPosNFCeBuilder`, `EscPosDanfeNFeBuilder`) | `font8x8`, `image`, `barcoders`, `qrcodegen` |
| `distribuicao` (default) | Distribuição do AN + manifestação (`ManifestacaoBuilder`) | `flate2` |

O **core** (emissão, cancelamento, substituição, CC-e, status, extração de XML, tipos) está **sempre
disponível**, independente de features. Para um build enxuto, desligue o que não usar:

```toml
# Só emissão/cancelamento + DANFE (sem ESC/POS nem distribuição):
dfe = { version = "…", default-features = false, features = ["danfe"] }
```

---

## Emissão de NF-e / NFC-e — `NFeBuilder`

```rust
use dfe::NFeBuilder;
use dfe::tipos::{Icms, Pis, Cofins, Det, Emit, Ide, Pag, Total, Transp};
use dfe::tipos::emissao::Dest;

let itens = vec![
    Det {
        c_prod: "001".to_string(),
        x_prod: "PRODUTO".to_string(),   // sobrescrito em homologação (ver abaixo)
        ncm: "22030000".to_string(),
        cfop: 5102,
        u_com: "UN".to_string(), q_com: 1.0, v_un_com: 10.0, v_prod: 10.0,
        u_trib: "UN".to_string(), q_trib: 1.0, v_un_trib: 10.0,
        icms: Icms::icms00(0, 3, 10.0, 12.0, 1.20),  // ou struct literal
        pis: Pis::Aliq { cst: "01".to_string(), v_bc: 10.0, p_pis: 0.65, v_pis: 0.07 },
        cofins: Cofins::Aliq { cst: "01".to_string(), v_bc: 10.0, p_cofins: 3.0, v_cofins: 0.30 },
        ..Default::default()
    },
];

let resposta = NFeBuilder::new()
    .cert("caminho.pfx", "senha")
    .ide(Ide { c_uf: 35, mod_: 55, serie: 1, n_nf: 1, tp_amb: 2, ..Default::default() })
    .emitente(emit)
    .destinatario(dest)              // opcional
    .itens(itens)                    // Vec<Det> — totais calculados automaticamente
    .total(Total::default())         // informar apenas frete, seguro, ST, FCP, etc.
    .transporte(transp)
    .pagamento(pag)
    .informacoes_adicionais(inf_adic) // opcional
    .id_csc("000001")                // NFC-e apenas — ID do CSC
    .csc("CODIGO_CSC")               // NFC-e apenas — código CSC
    .desconto_rateio(valor)          // opcional — desconto rateado nos itens (det/prod/vDesc)
    .outro_rateio(valor)             // opcional — acréscimo rateado nos itens (det/prod/vOutro)
    .emitir()
    .await?;

// resposta.protocolo.inf_prot.c_stat   → "100" = autorizada
// resposta.protocolo.inf_prot.x_motivo
// resposta.xml                          → XML do nfeProc autorizado
// resposta.send_xml                     → envelope SOAP enviado à SEFAZ (debug/auditoria)
// resposta.receive_xml                  → corpo cru da resposta da SEFAZ (debug/auditoria)
```

**NFC-e** (modelo 65): mesma API, com `mod_: 65`, `tp_imp: 4`, `.id_csc()` e `.csc()` obrigatórios. `dh_sai_ent` e QR Code tratados automaticamente.

### `Total` — campos informados pelo usuário

`v_bc`, `v_icms`, `v_prod`, `v_pis`, `v_cofins`, `v_nf`, `v_tot_trib`, `v_icms_deson` e `v_desc` são **calculados automaticamente** dos itens em `total_process`. Informar apenas:

| Campo | Descrição |
|---|---|
| `v_frete`, `v_seg`, `v_outro` | Despesas da NF (globais, não por item) |
| `v_ii`, `v_ipi`, `v_ipi_devol` | Impostos específicos |
| `v_bc_st`, `v_st` | ST (ICMS10/ICMS70 — não implementados ainda) |
| `v_fcp`, `v_fcpst`, `v_fcpst_ret` | Fundo de Combate à Pobreza |
| `v_fcpuf_dest`, `v_icms_uf_dest`, `v_icms_uf_remet` | Diferencial de alíquota UF destino |

Para uma venda simples sem frete/seguro: `Total::default()`.

---

## Enum `Icms` — variantes e construtores

| Variante | CST/CSOSN | Regime | Construtor |
|---|---|---|---|
| `Icms00 { orig, mod_bc, v_bc, p_icms, v_icms }` | 00 | Normal CRT=3 | `Icms::icms00(orig, mod_bc, v_bc, p_icms, v_icms)` |
| `Icms40 { orig, cst, v_icms_deson, mot_des_icms }` | 40/41/50 | Normal CRT=3 | `Icms::icms40(orig, cst)` |
| `Icms60 { orig, v_bcst_ret, p_st, v_icms_substituto, v_icmsst_ret }` | 60 | Normal CRT=3 | `Icms::icms60(orig)` |
| `Icms90 { orig }` | 90 | Normal CRT=3 | `Icms::icms90(orig)` |
| `Sn101 { orig, p_cred_sn, v_cred_icmssn }` | CSOSN 101 | Simples CRT=1 | `Icms::sn101(orig, p_cred_sn, v_cred_icmssn)` |
| `Sn102 { orig, csosn }` | CSOSN 102/103/300/400 | Simples CRT=1 | `Icms::sn102(orig, csosn)` |
| `Sn500 { orig, v_bcst_ret, v_icmsst_ret }` | CSOSN 500 | Simples CRT=1 | `Icms::sn500(orig)` |
| `Sn900 { orig, mod_bc, v_bc, … }` | CSOSN 900 | Simples CRT=1 | `Icms::sn900(orig)` |

Os construtores preenchem os campos obrigatórios e definem todos os `Option` como `None`. Para campos opcionais preenchidos (ex: ST em `Sn500`), usar o struct literal diretamente:
```rust
Icms::Sn500 { orig: 0, v_bcst_ret: Some(100.0), v_icmsst_ret: Some(12.0) }
```

## Enum `Pis` / `Cofins`

```rust
Pis::Aliq { cst, v_bc, p_pis, v_pis }            // CST 01/02 — alíquota
Pis::Outr                                          // CST 99 — outros (zeros automáticos)
Pis::Nt { cst }                                    // CST 04-09 — não tributado
Pis::Qtde { cst, q_bc_prod, v_aliq_prod, v_pis }  // CST 03 — por quantidade

Cofins::Aliq { cst, v_bc, p_cofins, v_cofins }
Cofins::Outr { cst }
Cofins::Nt { cst }
Cofins::Qtde { cst, q_bc_prod, v_aliq_prod, v_cofins }
```

---

## Cancelamento — `CancelarBuilder`

```rust
use dfe::CancelarBuilder;

let r = CancelarBuilder::new()
    .cert("caminho.pfx", "senha")
    .tp_amb(2)                   // 1 = Produção | 2 = Homologação
    .chave("35...")              // chave de acesso de 44 dígitos
    .protocolo("135...")         // protocolo de autorização
    .justificativa("Motivo do cancelamento aqui")  // mínimo 15 caracteres
    .mod_(55)                    // opcional — padrão 55; 65 para NFC-e
    .send()
    .await?;

// r.response.c_stat   → "135" = evento registrado
// r.response.x_motivo
// r.send_xml / r.receive_xml
```

---

## Cancelamento por substituição (NFC-e) — `SubstituicaoBuilder`

Evento `tpEvento` **110112** (NT 2018.004). **Só NFC-e (modelo 65)**, prazo 168h. Usado quando
duas NFC-e representam a mesma venda (ex.: uma normal + uma de contingência offline): cancela a
**duplicada** e mantém a **substituta** válida.

```rust
use dfe::SubstituicaoBuilder;

let r = SubstituicaoBuilder::new()
    .cert("caminho.pfx", "senha")
    .tp_amb(2)
    .chave("35...")             // NFC-e CANCELADA (44 dígitos, modelo 65)
    .protocolo("135...")        // protocolo da NFC-e cancelada
    .chave_substituta("35...")  // NFC-e que permanece VÁLIDA (chNFeRef)
    .ver_aplic("MeuPDV-1.0")    // nome/versão do software emissor (obrigatório)
    .justificativa("Falha na conexao com a internet no momento da venda")  // 15–255 caracteres
    .send()
    .await?;

// Retorna dfe::tipos::cancelar::Response (mesmo formato do cancelamento).
// r.response.c_stat / r.response.x_motivo / r.send_xml / r.receive_xml
```

⚠️ `chave`/`protocolo` = a NFC-e **cancelada**; `chave_substituta` = a que **fica válida**. Trocar
isso cancela a nota errada. O endpoint é resolvido pela UF real da chave (as 27 UFs).

---

## Carta de Correção (CC-e) — `CartaCorrecaoBuilder`

Evento `tpEvento` **110110**. Corrige dados **não** ligados a valores/impostos/destinatário. Vale
para NF-e e NFC-e. O `xCondUso` (texto legal fixo) é embutido; o `nSeqEvento` é **acumulativo**.

```rust
use dfe::CartaCorrecaoBuilder;

let r = CartaCorrecaoBuilder::new()
    .cert("caminho.pfx", "senha")
    .tp_amb(2)
    .chave("35...")                 // NF-e/NFC-e a corrigir (44 dígitos)
    .correcao("Onde se le X, leia-se Y no campo de observacoes")  // 15–1000 caracteres
    .n_seq_evento(1)                // opcional (padrão 1); incremente a cada nova CC-e da chave
    .send()
    .await?;

// Retorna dfe::tipos::cancelar::Response.
// r.response.c_stat / r.response.x_motivo / r.send_xml / r.receive_xml
```

`nSeqEvento` acumulativo: cada CC-e deve conter **todas** as correções anteriores (a última
substitui as demais). Quebras de linha no `correcao` são normalizadas para espaço (single-line).

---

## Manifestação do destinatário

```rust
use dfe::ManifestacaoBuilder;

// Um método terminal por tipo de evento; todos partem do mesmo builder.
let base = || ManifestacaoBuilder::new()
    .cert("caminho.pfx", "senha")
    .cnpj("11111111111111")
    .tp_amb(2)
    .chave("35...");

base().ciencia_operacao().await?;          // 210210
base().confirmacao_operacao().await?;      // 210200
base().desconhecimento_operacao().await?;  // 210220

base().operacao_nao_realizada("Motivo...").await?;  // 210240 (justificativa obrigatória)
```

Retorna `dfe::tipos::manifestacao::Response` (`.response` = `InfEvento`, `.send_xml`, `.receive_xml`).

Tipos de evento: `210210` Ciência · `210200` Confirmação · `210220` Desconhecimento · `210240` Operação Não Realizada (requer justificativa).

---

## Status do serviço SEFAZ

```rust
use dfe::NFeService;

let r = NFeService::new()
    .cert_path("caminho.pfx")
    .cert_pass("senha")
    .uf("SP")
    .environment(2)
    .send()
    .await?;
// r.c_stat, r.x_motivo, r.url
```

---

## Consulta de situação da NF-e/NFC-e — `ConsultaSituacaoBuilder`

`consSitNFe` — consulta **não assinada** (só mTLS, como o status do serviço) que devolve a
situação atual de uma nota pela chave de acesso: se está autorizada, se não consta na base da
SEFAZ, e eventos já registrados contra ela (ex.: cancelamento).

```rust
use dfe::ConsultaSituacaoBuilder;

let r = ConsultaSituacaoBuilder::new()
    .cert("caminho.pfx", "senha")
    .tp_amb(2)
    .chave("35...") // 44 dígitos
    .send()
    .await?;

r.response.c_stat;       // status da CONSULTA (ex.: "217" = não consta na base)
r.response.prot_nfe;     // Option<ProtNFe> — presente só se a nota consta (autorizada/cancelada)
r.response.proc_evento_nfe; // Vec<ProcEventoNFe> — eventos já registrados (ex.: cancelamento 110111)
```

Se `prot_nfe` estiver presente, `prot_nfe.inf_prot.c_stat` é o status da **nota** (`100` =
autorizada, `101` = cancelada) e `.n_prot`/`.dh_recbto` trazem o protocolo/data de autorização.

Uso previsto: recuperação de emissão órfã (app fechou/timeout antes da resposta chegar) —
consultar a chave 1x, respeitando o rate-limit da SEFAZ (10 consultas/hora por chave, NT
2014.002), para decidir se a nota foi autorizada antes de reenviar ou reemitir.

---

## Distribuição (Ambiente Nacional)

```rust
use dfe::distribuicao::Distribuicao;

let r = Distribuicao::new()
    .cert_path("caminho.pfx")
    .cert_pass("senha")
    .cnpj("11111111111111")
    .uf(35)
    .ambiente(2)
    .send()
    .await?;
```

Builders disponíveis: `Distribuicao`, `DistribuicaoNSU`, `DistribuicaoChaveAcesso`,
`CienciaOperacao`, `ConfirmacaoOperacao`, `DesconhecimentoOperacao`, `OperacaoNaoRealizada`.

A resposta pode conter `docZip` — base64 + GZIP, decodificado automaticamente.

---

## DANFE (PDF)

```rust
use dfe::DanfeBuilder;

// NF-e A4 — salvar em arquivo
let caminho = DanfeBuilder::new()
    .xml(xml_str)          // string XML do nfeProc ou caminho de arquivo (termina em ".xml")
    .paper_size("a4")      // padrão quando omitido
    .as_file("nota.pdf")
    .build()
    .await?;               // Ok(String) = caminho do arquivo gerado

// NF-e A4 — com logotipo do emitente
let b64 = DanfeBuilder::new()
    .xml(xml_str)
    .paper_size("a4")
    .logo("caminho/logo.png")   // caminho .png/.jpg, base64 puro ou data URI
    .as_base64()
    .build()
    .await?;

// NF-e 80mm — retornar como base64
let b64 = DanfeBuilder::new()
    .xml(xml_str)
    .paper_size("80mm")
    .as_base64()
    .build()
    .await?;               // Ok(String) = string base64 do PDF

// NFC-e 80mm — QR Code centralizado (padrão)
let b64 = DanfeBuilder::new()
    .xml(xml_str)
    .paper_size("80mm")    // modelo 65 detectado automaticamente pelo XML
    .as_base64()
    .build()
    .await?;

// NFC-e 80mm — QR Code lateral (à esquerda, ~33mm; chave e protocolo à direita)
let b64 = DanfeBuilder::new()
    .xml(xml_str)
    .paper_size("80mm")
    .qr_side()             // layout lateral — só faz efeito em NFC-e 80mm
    .as_base64()
    .build()
    .await?;
```

Retorno: `Result<String, String>` — `Ok` contém o caminho do arquivo (`.as_file`) ou a string base64 (`.as_base64`).

| Formato | Modelo 55 (NF-e) | Modelo 65 (NFC-e) |
|---|---|---|
| `"a4"` | ✅ (suporta `.logo()`) | ❌ não implementado |
| `"80mm"` || ✅ (suporta `.qr_side()`) |
| `"54mm"` | ❌ não implementado | ❌ não implementado |

O modelo é detectado automaticamente do campo `<mod>` no XML — não é necessário informar.

### Logotipo do emitente — `.logo(src)` (apenas A4)

| Formato de entrada | Exemplo |
|---|---|
| Caminho de arquivo | `"logo.png"` / `"logo.jpg"` |
| Base64 puro | `"iVBORw0KGgo..."` |
| Data URI | `"data:image/png;base64,iVBORw0KGgo..."` |

O logo é renderizado no topo da coluna do emitente, centralizado horizontalmente, com altura máxima de 18mm. A proporção original é sempre mantida; a imagem nunca é ampliada além do tamanho original. Formatos suportados: PNG e JPEG.

## DANFE Simplificado Tipo 2 da NF-e (ESC/POS) — `EscPosDanfeNFeBuilder`

DANFE 80 mm da **NF-e modelo 55** em impressora térmica, no leiaute do Ajuste SINIEF 13/2026 e
da **NT 2026.003** (nove divisões, na ordem da norma). Recusa modelo 65 — NFC-e é o
`EscPosNFCeBuilder`.

```rust
use dfe::EscPosDanfeNFeBuilder;

let bytes = EscPosDanfeNFeBuilder::new()
    .xml(xml_nfe_proc)   // string do nfeProc autorizado, ou caminho terminado em ".xml"
    .paper_width(80)     // 80 ou 58; abaixo de 56 mm o build() recusa (mínimo da NT)
    .columns(42)         // opcional: colunas do modelo da impressora
    .build()?;           // Vec<u8> pronto para job RAW
```

- **Fidelidade ao XML:** a NT proíbe imprimir o que não está no XML. O **QR Code** (Divisão V)
  só sai com `infNFeSupl/qrCode`, e o bloco **IBS/CBS/IS** (Divisão III-A) só com
  `IBSCBSTot`/`ISTot`. Sem `urlChave`, a consulta aponta para o portal nacional da NF-e. O cupom
  não leva o crédito "Gerado por dfe".
- **Avisos:** homologação (`tpAmb=2`) → "SEM VALOR FISCAL" na Divisão VIII; contingência em que
  o DANFE sai antes da autorização (`tpEmis` 2, 4, 5 ou 9) → aviso em dois locais. SVC (6/7) não
  leva aviso.
- **Limitação atual:** a emissão (`NFeBuilder`) só gera `infNFeSupl` para o modelo 65, então a
  NF-e 55 emitida pela crate ainda sai sem QR.