diff --git a/scripts/gen_goldens.py b/scripts/gen_goldens.py
new file mode 100644
index 0000000..eed24fb
--- /dev/null
+++ b/scripts/gen_goldens.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+"""Regenera `src/sowai_fiscal/goldens/caso_*.expected.xml` a partir dos
+`caso_*.input.json` correspondentes. Uso ÚNICO hoje (fixar o corpus golden,
+Task 2 do plano F1) e sempre que uma NT mudar o leiaute DE PROPÓSITO --
+nunca para "corrigir" um teste vermelho sem entender por que o XML mudou.
+
+ uv run python scripts/gen_goldens.py
+"""
+from pathlib import Path
+
+from sowai_fiscal.golden_helpers import dados_from_json, serialize_infnfe
+
+_GOLDENS_DIR = Path(__file__).resolve().parent.parent / "src" / "sowai_fiscal" / "goldens"
+
+
+def main() -> None:
+ inputs = sorted(_GOLDENS_DIR.glob("caso_*.input.json"))
+ if not inputs:
+ raise SystemExit(f"nenhum caso_*.input.json encontrado em {_GOLDENS_DIR}")
+ for input_path in inputs:
+ expected_path = input_path.with_name(input_path.name.replace(".input.json", ".expected.xml"))
+ dados = dados_from_json(input_path)
+ xml_bytes = serialize_infnfe(dados)
+ expected_path.write_bytes(xml_bytes)
+ print(f"{input_path.name} -> {expected_path.name} ({len(xml_bytes)} bytes)")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/sowai_fiscal/golden_helpers.py b/src/sowai_fiscal/golden_helpers.py
new file mode 100644
index 0000000..26c7249
--- /dev/null
+++ b/src/sowai_fiscal/golden_helpers.py
@@ -0,0 +1,173 @@
+"""Helpers do corpus golden (`sowai_fiscal.goldens/`): carregam um
+`DadosEmissao` a partir de um JSON determinístico e serializam o `Nfe`
+resultante do `build_nfe` na MESMA configuração canônica que o auto usa em
+`fiscal/emissao.py::_serialize_nfe` antes de assinar (`SerializerConfig
+(xml_declaration=False, indent=None)`, `ns_map={None: NFE_NAMESPACE}`).
+
+Módulo do PACOTE (não de `tests/`) de propósito: tanto o teste de goldens
+desta lib quanto o teste de paridade do produto consumidor (auto, Task 4 do
+plano F1) importam `sowai_fiscal.golden_helpers` para ler exatamente os
+mesmos arquivos via `importlib.resources` -- zero cópia que possa divergir.
+
+O JSON de input espelha 1:1 os dataclasses de `sowai_fiscal.xml_builder`
+(`DadosEmissao`, `EmitenteData`, `DestinatarioData`, `ItemData`,
+`PagamentoData`) e `sowai_fiscal.resolver` (`FiscalResult`, `TributoLinha`)
+-- é o `DadosEmissao` JÁ RESOLVIDO (o motor de regras não roda aqui; cada
+item já traz seu `fiscal_result`), exatamente o formato que o orquestrador
+de emissão do auto monta antes de chamar `build_nfe`.
+"""
+import json
+from decimal import Decimal
+from pathlib import Path
+from typing import Any
+
+from sowai_fiscal.resolver import FiscalResult, TributoLinha
+from sowai_fiscal.xml_builder import (
+ DadosEmissao,
+ DestinatarioData,
+ EmitenteData,
+ ItemData,
+ PagamentoData,
+ build_nfe,
+)
+from xsdata.formats.dataclass.serializers import XmlSerializer
+from xsdata.formats.dataclass.serializers.config import SerializerConfig
+
+# A MESMA config/namespace que `app/modules/fiscal/emissao.py::_serialize_nfe`
+# usa no auto -- conferida lá antes de fixar aqui (ver docstring do módulo).
+NFE_NAMESPACE = "http://www.portalfiscal.inf.br/nfe"
+
+
+def _dec(value: Any) -> Decimal | None:
+ if value is None:
+ return None
+ return Decimal(str(value))
+
+
+def _tributo_from_dict(data: dict) -> TributoLinha:
+ return TributoLinha(
+ tax_domain=data["tax_domain"],
+ cst=data.get("cst"),
+ csosn=data.get("csosn"),
+ base_calc=_dec(data["base_calc"]),
+ base_calc_percent=_dec(data["base_calc_percent"]),
+ aliquota=_dec(data.get("aliquota")),
+ valor=_dec(data["valor"]),
+ mva=_dec(data.get("mva")),
+ aliquota_st=_dec(data.get("aliquota_st")),
+ fcp_percent=_dec(data.get("fcp_percent")),
+ codigo_beneficio=data.get("codigo_beneficio"),
+ rule_id=data["rule_id"],
+ )
+
+
+def _fiscal_result_from_dict(data: dict) -> FiscalResult:
+ return FiscalResult(
+ cfop=data["cfop"],
+ cst=data.get("cst"),
+ csosn=data.get("csosn"),
+ origem=data.get("origem"),
+ consumidor_final=data["consumidor_final"],
+ indicador_ie=data["indicador_ie"],
+ tributos=[_tributo_from_dict(t) for t in data["tributos"]],
+ )
+
+
+def _item_from_dict(data: dict) -> ItemData:
+ return ItemData(
+ codigo=data["codigo"],
+ descricao=data["descricao"],
+ ncm=data["ncm"],
+ cfop=data["cfop"],
+ unidade_comercial=data["unidade_comercial"],
+ unidade_tributavel=data["unidade_tributavel"],
+ quantidade=_dec(data["quantidade"]),
+ valor_unitario=_dec(data["valor_unitario"]),
+ fiscal_result=_fiscal_result_from_dict(data["fiscal_result"]),
+ gtin=data.get("gtin"),
+ cest=data.get("cest"),
+ peso_liquido_kg=_dec(data.get("peso_liquido_kg")),
+ peso_bruto_kg=_dec(data.get("peso_bruto_kg")),
+ )
+
+
+def _emitente_from_dict(data: dict) -> EmitenteData:
+ return EmitenteData(
+ cnpj=data["cnpj"],
+ razao_social=data["razao_social"],
+ nome_fantasia=data.get("nome_fantasia"),
+ ie=data["ie"],
+ crt=data["crt"],
+ address_street=data["address_street"],
+ address_number=data["address_number"],
+ address_complement=data.get("address_complement"),
+ address_district=data["address_district"],
+ address_city=data["address_city"],
+ address_state=data["address_state"],
+ address_zip=data["address_zip"],
+ address_city_ibge_code=data["address_city_ibge_code"],
+ fone=data.get("fone"),
+ )
+
+
+def _destinatario_from_dict(data: dict | None) -> DestinatarioData | None:
+ if data is None:
+ return None
+ return DestinatarioData(
+ nome=data["nome"],
+ cnpj=data.get("cnpj"),
+ cpf=data.get("cpf"),
+ indicador_ie=data["indicador_ie"],
+ ie=data.get("ie"),
+ address_street=data.get("address_street"),
+ address_number=data.get("address_number"),
+ address_complement=data.get("address_complement"),
+ address_district=data.get("address_district"),
+ address_city=data.get("address_city"),
+ address_state=data.get("address_state"),
+ address_zip=data.get("address_zip"),
+ address_city_ibge_code=data.get("address_city_ibge_code"),
+ email=data.get("email"),
+ )
+
+
+def _pagamento_from_dict(data: dict) -> PagamentoData:
+ return PagamentoData(
+ tpag=data["tpag"],
+ valor=_dec(data["valor"]),
+ indpag=data.get("indpag", "0"),
+ )
+
+
+def dados_from_json(path: Path) -> DadosEmissao:
+ """Carrega um `DadosEmissao` completo (já resolvido) a partir do JSON
+ golden em `path`. Campos opcionais do dataclass que o JSON omite caem
+ no default do próprio `DadosEmissao`."""
+ raw = json.loads(Path(path).read_text(encoding="utf-8"))
+ kwargs: dict[str, Any] = dict(
+ emitente=_emitente_from_dict(raw["emitente"]),
+ itens=[_item_from_dict(i) for i in raw["itens"]],
+ pagamento=_pagamento_from_dict(raw["pagamento"]),
+ ambiente=raw["ambiente"],
+ chave_acesso=raw["chave_acesso"],
+ numero=raw["numero"],
+ serie=raw["serie"],
+ cnf=raw["cnf"],
+ dh_emi=raw["dh_emi"],
+ uf_destino_tipo=raw["uf_destino_tipo"],
+ destinatario=_destinatario_from_dict(raw.get("destinatario")),
+ )
+ for optional in ("nat_op", "tp_emis", "ind_final", "ind_pres", "fin_nfe", "ver_proc"):
+ if optional in raw:
+ kwargs[optional] = raw[optional]
+ return DadosEmissao(**kwargs)
+
+
+def serialize_infnfe(dados: DadosEmissao) -> bytes:
+ """`DadosEmissao` -> XML pré-assinatura, bytes UTF-8. Config e namespace
+ IDÊNTICOS a `emissao.py::_serialize_nfe` no auto -- é o que torna este
+ XML comparável byte a byte com o gerado pelo produto consumidor."""
+ nfe = build_nfe(dados)
+ config = SerializerConfig(xml_declaration=False, indent=None)
+ xml_str = XmlSerializer(config=config).render(nfe, ns_map={None: NFE_NAMESPACE})
+ return xml_str.encode("utf-8")
diff --git a/src/sowai_fiscal/goldens/caso_com_ub.expected.xml b/src/sowai_fiscal/goldens/caso_com_ub.expected.xml
new file mode 100644
index 0000000..a4b1952
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_com_ub.expected.xml
@@ -0,0 +1 @@
+4110000005Venda55110052026-07-16T10:20:00-03:0011410690211021110sowai-auto/1b.112345678000190AUTOPECAS THIAGO LTDAThiago Auto CenterRua das Autopecas1500Centro4106902CuritibaPR800000001058Brasil41333344441234567890199887766000155NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCALAv Cliente50Bairro4106902CuritibaPR800001001058Brasil9PC001SEM GTINFiltro de oleo842123005102UN1100.00100.00SEM GTINUN1100.00101020808000000001100.000.100010.000.900090.000.000.000.000.000.000.000.000.00100.000.000.000.000.000.000.000.000.000.00100.009001100.00
\ No newline at end of file
diff --git a/src/sowai_fiscal/goldens/caso_com_ub.input.json b/src/sowai_fiscal/goldens/caso_com_ub.input.json
new file mode 100644
index 0000000..3aa1aa4
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_com_ub.input.json
@@ -0,0 +1,128 @@
+{
+ "emitente": {
+ "cnpj": "12345678000190",
+ "razao_social": "AUTOPECAS THIAGO LTDA",
+ "nome_fantasia": "Thiago Auto Center",
+ "ie": "1234567890",
+ "crt": "1",
+ "address_street": "Rua das Autopecas",
+ "address_number": "1500",
+ "address_complement": null,
+ "address_district": "Centro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000000",
+ "address_city_ibge_code": "4106902",
+ "fone": "4133334444"
+ },
+ "destinatario": {
+ "nome": "CLIENTE BALCAO LTDA",
+ "cnpj": "99887766000155",
+ "cpf": null,
+ "indicador_ie": "9",
+ "ie": null,
+ "address_street": "Av Cliente",
+ "address_number": "50",
+ "address_complement": null,
+ "address_district": "Bairro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000100",
+ "address_city_ibge_code": "4106902",
+ "email": null
+ },
+ "itens": [
+ {
+ "codigo": "PC001",
+ "descricao": "Filtro de oleo",
+ "ncm": "84212300",
+ "cfop": "5102",
+ "unidade_comercial": "UN",
+ "unidade_tributavel": "UN",
+ "quantidade": "1",
+ "valor_unitario": "100.00",
+ "gtin": null,
+ "cest": null,
+ "peso_liquido_kg": null,
+ "peso_bruto_kg": null,
+ "fiscal_result": {
+ "cfop": "5102",
+ "cst": null,
+ "csosn": "102",
+ "origem": "0",
+ "consumidor_final": true,
+ "indicador_ie": "9",
+ "tributos": [
+ {
+ "tax_domain": "pis",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "100.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "7867351e-f79e-59c5-9e83-882563853fdd"
+ },
+ {
+ "tax_domain": "cofins",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "100.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "050677c1-38dc-5246-a41c-d1b58017b0d0"
+ },
+ {
+ "tax_domain": "ibs",
+ "cst": null,
+ "csosn": null,
+ "base_calc": "100.00",
+ "base_calc_percent": "100",
+ "aliquota": "0.1000",
+ "valor": "10.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "f926eaa3-87a6-5acc-91dd-cbb9d52567f8"
+ },
+ {
+ "tax_domain": "cbs",
+ "cst": null,
+ "csosn": null,
+ "base_calc": "100.00",
+ "base_calc_percent": "100",
+ "aliquota": "0.9000",
+ "valor": "90.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "20f2bed4-07b3-5c41-b2ca-b659e0ae76a8"
+ }
+ ]
+ }
+ }
+ ],
+ "pagamento": {
+ "tpag": "01",
+ "valor": "100.00",
+ "indpag": "0"
+ },
+ "ambiente": "homologacao",
+ "chave_acesso": "41260712345678000190550010000010051100000050",
+ "numero": 1005,
+ "serie": 1,
+ "cnf": "10000005",
+ "dh_emi": "2026-07-16T10:20:00-03:00",
+ "uf_destino_tipo": "interna"
+}
diff --git a/src/sowai_fiscal/goldens/caso_devolucao_intra.expected.xml b/src/sowai_fiscal/goldens/caso_devolucao_intra.expected.xml
new file mode 100644
index 0000000..4b1e5fd
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_devolucao_intra.expected.xml
@@ -0,0 +1 @@
+4110000004Devolucao de venda55110042026-07-16T10:15:00-03:0011410690211521110sowai-auto/1b.112345678000190AUTOPECAS THIAGO LTDAThiago Auto CenterRua das Autopecas1500Centro4106902CuritibaPR800000001058Brasil41333344441234567890199887766000155NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCALAv Cliente50Bairro4106902CuritibaPR800001001058Brasil9PC001SEM GTINFiltro de oleo842123001202UN175.0075.00SEM GTINUN175.0010102080875.000.000.000.000.000.000.000.0075.000.000.000.000.000.000.000.000.000.0075.00909075.00
\ No newline at end of file
diff --git a/src/sowai_fiscal/goldens/caso_devolucao_intra.input.json b/src/sowai_fiscal/goldens/caso_devolucao_intra.input.json
new file mode 100644
index 0000000..d1e65fe
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_devolucao_intra.input.json
@@ -0,0 +1,115 @@
+{
+ "emitente": {
+ "cnpj": "12345678000190",
+ "razao_social": "AUTOPECAS THIAGO LTDA",
+ "nome_fantasia": "Thiago Auto Center",
+ "ie": "1234567890",
+ "crt": "1",
+ "address_street": "Rua das Autopecas",
+ "address_number": "1500",
+ "address_complement": null,
+ "address_district": "Centro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000000",
+ "address_city_ibge_code": "4106902",
+ "fone": "4133334444"
+ },
+ "destinatario": {
+ "nome": "CLIENTE BALCAO LTDA",
+ "cnpj": "99887766000155",
+ "cpf": null,
+ "indicador_ie": "9",
+ "ie": null,
+ "address_street": "Av Cliente",
+ "address_number": "50",
+ "address_complement": null,
+ "address_district": "Bairro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000100",
+ "address_city_ibge_code": "4106902",
+ "email": null
+ },
+ "itens": [
+ {
+ "codigo": "PC001",
+ "descricao": "Filtro de oleo",
+ "ncm": "84212300",
+ "cfop": "1202",
+ "unidade_comercial": "UN",
+ "unidade_tributavel": "UN",
+ "quantidade": "1",
+ "valor_unitario": "75.00",
+ "gtin": null,
+ "cest": null,
+ "peso_liquido_kg": null,
+ "peso_bruto_kg": null,
+ "fiscal_result": {
+ "cfop": "1202",
+ "cst": null,
+ "csosn": "102",
+ "origem": "0",
+ "consumidor_final": true,
+ "indicador_ie": "9",
+ "tributos": [
+ {
+ "tax_domain": "icms",
+ "cst": null,
+ "csosn": "102",
+ "base_calc": "75.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "38192a7c-594c-52c0-a911-29ab0385d848"
+ },
+ {
+ "tax_domain": "pis",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "75.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "48265596-d65d-5ce0-a201-06bfe961a4ef"
+ },
+ {
+ "tax_domain": "cofins",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "75.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "94781077-adee-5dbd-aef4-b4aca3803a8a"
+ }
+ ]
+ }
+ }
+ ],
+ "pagamento": {
+ "tpag": "90",
+ "valor": "75.00",
+ "indpag": "0"
+ },
+ "ambiente": "homologacao",
+ "chave_acesso": "41260712345678000190550010000010041100000045",
+ "numero": 1004,
+ "serie": 1,
+ "cnf": "10000004",
+ "dh_emi": "2026-07-16T10:15:00-03:00",
+ "uf_destino_tipo": "interna",
+ "nat_op": "Devolucao de venda"
+}
diff --git a/src/sowai_fiscal/goldens/caso_fracao_centavo.expected.xml b/src/sowai_fiscal/goldens/caso_fracao_centavo.expected.xml
new file mode 100644
index 0000000..39f3b99
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_fracao_centavo.expected.xml
@@ -0,0 +1 @@
+4110000006Venda55110062026-07-16T10:25:00-03:0011410690211621110sowai-auto/1b.112345678000190AUTOPECAS THIAGO LTDAThiago Auto CenterRua das Autopecas1500Centro4106902CuritibaPR800000001058Brasil41333344441234567890199887766000155NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCALAv Cliente50Bairro4106902CuritibaPR800001001058Brasil9PC900SEM GTINArruela avulsa731819005102UN1.50.010.02SEM GTINUN1.50.01101020808PC901SEM GTINArruela avulsa 2731819005102UN1.50.010.02SEM GTINUN1.50.011010208080.040.000.000.000.000.000.000.000.040.000.000.000.000.000.000.000.000.000.0490010.04
\ No newline at end of file
diff --git a/src/sowai_fiscal/goldens/caso_fracao_centavo.input.json b/src/sowai_fiscal/goldens/caso_fracao_centavo.input.json
new file mode 100644
index 0000000..709b80a
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_fracao_centavo.input.json
@@ -0,0 +1,180 @@
+{
+ "emitente": {
+ "cnpj": "12345678000190",
+ "razao_social": "AUTOPECAS THIAGO LTDA",
+ "nome_fantasia": "Thiago Auto Center",
+ "ie": "1234567890",
+ "crt": "1",
+ "address_street": "Rua das Autopecas",
+ "address_number": "1500",
+ "address_complement": null,
+ "address_district": "Centro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000000",
+ "address_city_ibge_code": "4106902",
+ "fone": "4133334444"
+ },
+ "destinatario": {
+ "nome": "CLIENTE BALCAO LTDA",
+ "cnpj": "99887766000155",
+ "cpf": null,
+ "indicador_ie": "9",
+ "ie": null,
+ "address_street": "Av Cliente",
+ "address_number": "50",
+ "address_complement": null,
+ "address_district": "Bairro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000100",
+ "address_city_ibge_code": "4106902",
+ "email": null
+ },
+ "itens": [
+ {
+ "codigo": "PC900",
+ "descricao": "Arruela avulsa",
+ "ncm": "73181900",
+ "cfop": "5102",
+ "unidade_comercial": "UN",
+ "unidade_tributavel": "UN",
+ "quantidade": "1.5",
+ "valor_unitario": "0.01",
+ "gtin": null,
+ "cest": null,
+ "peso_liquido_kg": null,
+ "peso_bruto_kg": null,
+ "fiscal_result": {
+ "cfop": "5102",
+ "cst": null,
+ "csosn": "102",
+ "origem": "0",
+ "consumidor_final": true,
+ "indicador_ie": "9",
+ "tributos": [
+ {
+ "tax_domain": "icms",
+ "cst": null,
+ "csosn": "102",
+ "base_calc": "0.02",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "0662ed57-174b-599c-9568-02b524b66edf"
+ },
+ {
+ "tax_domain": "pis",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "0.02",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "25880960-e53b-584a-b446-d0fe3fb79761"
+ },
+ {
+ "tax_domain": "cofins",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "0.02",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "431b87af-2fa6-5413-a8b5-056a92035429"
+ }
+ ]
+ }
+ },
+ {
+ "codigo": "PC901",
+ "descricao": "Arruela avulsa 2",
+ "ncm": "73181900",
+ "cfop": "5102",
+ "unidade_comercial": "UN",
+ "unidade_tributavel": "UN",
+ "quantidade": "1.5",
+ "valor_unitario": "0.01",
+ "gtin": null,
+ "cest": null,
+ "peso_liquido_kg": null,
+ "peso_bruto_kg": null,
+ "fiscal_result": {
+ "cfop": "5102",
+ "cst": null,
+ "csosn": "102",
+ "origem": "0",
+ "consumidor_final": true,
+ "indicador_ie": "9",
+ "tributos": [
+ {
+ "tax_domain": "icms",
+ "cst": null,
+ "csosn": "102",
+ "base_calc": "0.02",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "863b3e8d-ab7f-5cab-b699-c443668a2d41"
+ },
+ {
+ "tax_domain": "pis",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "0.02",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "5e787f8f-1968-569b-8e2d-c7126a8d1e73"
+ },
+ {
+ "tax_domain": "cofins",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "0.02",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "2c094bb0-fc8c-598a-9af8-3202fb24bba1"
+ }
+ ]
+ }
+ }
+ ],
+ "pagamento": {
+ "tpag": "01",
+ "valor": "0.04",
+ "indpag": "0"
+ },
+ "ambiente": "homologacao",
+ "chave_acesso": "41260712345678000190550010000010061100000066",
+ "numero": 1006,
+ "serie": 1,
+ "cnf": "10000006",
+ "dh_emi": "2026-07-16T10:25:00-03:00",
+ "uf_destino_tipo": "interna"
+}
diff --git a/src/sowai_fiscal/goldens/caso_padrao_inter.expected.xml b/src/sowai_fiscal/goldens/caso_padrao_inter.expected.xml
new file mode 100644
index 0000000..960174b
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_padrao_inter.expected.xml
@@ -0,0 +1 @@
+4110000002Venda55110022026-07-16T10:05:00-03:0012410690211421110sowai-auto/1b.112345678000190AUTOPECAS THIAGO LTDAThiago Auto CenterRua das Autopecas1500Centro4106902CuritibaPR800000001058Brasil41333344441234567890111222333000144NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCALAv Paulista1000Sala 10Bela Vista3550308Sao PauloSP013100001058Brasil11122334455compras@clienteinter.com.brPC001SEM GTINFiltro de oleo842123006102UN1075.00750.00SEM GTINUN1075.00101020808750.000.000.000.000.000.000.000.00750.000.000.000.000.000.000.000.000.000.00750.009017750.00
\ No newline at end of file
diff --git a/src/sowai_fiscal/goldens/caso_padrao_inter.input.json b/src/sowai_fiscal/goldens/caso_padrao_inter.input.json
new file mode 100644
index 0000000..69af378
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_padrao_inter.input.json
@@ -0,0 +1,115 @@
+{
+ "emitente": {
+ "cnpj": "12345678000190",
+ "razao_social": "AUTOPECAS THIAGO LTDA",
+ "nome_fantasia": "Thiago Auto Center",
+ "ie": "1234567890",
+ "crt": "1",
+ "address_street": "Rua das Autopecas",
+ "address_number": "1500",
+ "address_complement": null,
+ "address_district": "Centro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000000",
+ "address_city_ibge_code": "4106902",
+ "fone": "4133334444"
+ },
+ "destinatario": {
+ "nome": "CLIENTE INTERESTADUAL LTDA",
+ "cnpj": "11222333000144",
+ "cpf": null,
+ "indicador_ie": "1",
+ "ie": "1122334455",
+ "address_street": "Av Paulista",
+ "address_number": "1000",
+ "address_complement": "Sala 10",
+ "address_district": "Bela Vista",
+ "address_city": "Sao Paulo",
+ "address_state": "SP",
+ "address_zip": "01310000",
+ "address_city_ibge_code": "3550308",
+ "email": "compras@clienteinter.com.br"
+ },
+ "itens": [
+ {
+ "codigo": "PC001",
+ "descricao": "Filtro de oleo",
+ "ncm": "84212300",
+ "cfop": "6102",
+ "unidade_comercial": "UN",
+ "unidade_tributavel": "UN",
+ "quantidade": "10",
+ "valor_unitario": "75.00",
+ "gtin": null,
+ "cest": null,
+ "peso_liquido_kg": null,
+ "peso_bruto_kg": null,
+ "fiscal_result": {
+ "cfop": "6102",
+ "cst": null,
+ "csosn": "102",
+ "origem": "0",
+ "consumidor_final": true,
+ "indicador_ie": "1",
+ "tributos": [
+ {
+ "tax_domain": "icms",
+ "cst": null,
+ "csosn": "102",
+ "base_calc": "750.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "033f9888-3e26-5b16-bf6f-3d8573d02fb1"
+ },
+ {
+ "tax_domain": "pis",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "750.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "2028ef78-a3cd-59ed-b2b9-09f04a3e433f"
+ },
+ {
+ "tax_domain": "cofins",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "750.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "a0c97f3b-7938-5168-94c1-f43011671136"
+ }
+ ]
+ }
+ }
+ ],
+ "pagamento": {
+ "tpag": "17",
+ "valor": "750.00",
+ "indpag": "0"
+ },
+ "ambiente": "homologacao",
+ "chave_acesso": "41260712345678000190550010000010021100000024",
+ "numero": 1002,
+ "serie": 1,
+ "cnf": "10000002",
+ "dh_emi": "2026-07-16T10:05:00-03:00",
+ "uf_destino_tipo": "interestadual",
+ "ind_final": "1"
+}
diff --git a/src/sowai_fiscal/goldens/caso_padrao_intra.expected.xml b/src/sowai_fiscal/goldens/caso_padrao_intra.expected.xml
new file mode 100644
index 0000000..31e77ea
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_padrao_intra.expected.xml
@@ -0,0 +1 @@
+4110000001Venda55110012026-07-16T10:00:00-03:0011410690211921110sowai-auto/1b.112345678000190AUTOPECAS THIAGO LTDAThiago Auto CenterRua das Autopecas1500Centro4106902CuritibaPR800000001058Brasil41333344441234567890199887766000155NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCALAv Cliente50Bairro4106902CuritibaPR800001001058Brasil9PC001SEM GTINFiltro de oleo842123005102UN275.00150.00SEM GTINUN275.00101020808PC002SEM GTINPastilha de freio870830905102PC445.50182.00SEM GTINPC445.50101020808332.000.000.000.000.000.000.000.00332.000.000.000.000.000.000.000.000.000.00332.009001332.00
\ No newline at end of file
diff --git a/src/sowai_fiscal/goldens/caso_padrao_intra.input.json b/src/sowai_fiscal/goldens/caso_padrao_intra.input.json
new file mode 100644
index 0000000..5a724db
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_padrao_intra.input.json
@@ -0,0 +1,180 @@
+{
+ "emitente": {
+ "cnpj": "12345678000190",
+ "razao_social": "AUTOPECAS THIAGO LTDA",
+ "nome_fantasia": "Thiago Auto Center",
+ "ie": "1234567890",
+ "crt": "1",
+ "address_street": "Rua das Autopecas",
+ "address_number": "1500",
+ "address_complement": null,
+ "address_district": "Centro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000000",
+ "address_city_ibge_code": "4106902",
+ "fone": "4133334444"
+ },
+ "destinatario": {
+ "nome": "CLIENTE BALCAO LTDA",
+ "cnpj": "99887766000155",
+ "cpf": null,
+ "indicador_ie": "9",
+ "ie": null,
+ "address_street": "Av Cliente",
+ "address_number": "50",
+ "address_complement": null,
+ "address_district": "Bairro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000100",
+ "address_city_ibge_code": "4106902",
+ "email": null
+ },
+ "itens": [
+ {
+ "codigo": "PC001",
+ "descricao": "Filtro de oleo",
+ "ncm": "84212300",
+ "cfop": "5102",
+ "unidade_comercial": "UN",
+ "unidade_tributavel": "UN",
+ "quantidade": "2",
+ "valor_unitario": "75.00",
+ "gtin": null,
+ "cest": null,
+ "peso_liquido_kg": null,
+ "peso_bruto_kg": null,
+ "fiscal_result": {
+ "cfop": "5102",
+ "cst": null,
+ "csosn": "102",
+ "origem": "0",
+ "consumidor_final": true,
+ "indicador_ie": "9",
+ "tributos": [
+ {
+ "tax_domain": "icms",
+ "cst": null,
+ "csosn": "102",
+ "base_calc": "150.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "388a102f-f935-5f9f-ba70-42db4062f171"
+ },
+ {
+ "tax_domain": "pis",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "150.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "a4471177-52bf-5b5f-a69d-53b8af65bcef"
+ },
+ {
+ "tax_domain": "cofins",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "150.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "07604b6a-287d-5314-9fbe-c68495166181"
+ }
+ ]
+ }
+ },
+ {
+ "codigo": "PC002",
+ "descricao": "Pastilha de freio",
+ "ncm": "87083090",
+ "cfop": "5102",
+ "unidade_comercial": "PC",
+ "unidade_tributavel": "PC",
+ "quantidade": "4",
+ "valor_unitario": "45.50",
+ "gtin": null,
+ "cest": null,
+ "peso_liquido_kg": null,
+ "peso_bruto_kg": null,
+ "fiscal_result": {
+ "cfop": "5102",
+ "cst": null,
+ "csosn": "102",
+ "origem": "0",
+ "consumidor_final": true,
+ "indicador_ie": "9",
+ "tributos": [
+ {
+ "tax_domain": "icms",
+ "cst": null,
+ "csosn": "102",
+ "base_calc": "182.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "51ff4cd8-651d-5b26-a068-3996791a3af9"
+ },
+ {
+ "tax_domain": "pis",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "182.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "893ccc32-9cb6-5dbc-b5e3-427461de4cb7"
+ },
+ {
+ "tax_domain": "cofins",
+ "cst": "08",
+ "csosn": null,
+ "base_calc": "182.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "9c2c1b35-3f67-56bb-a109-e78ab1a6f7f1"
+ }
+ ]
+ }
+ }
+ ],
+ "pagamento": {
+ "tpag": "01",
+ "valor": "332.00",
+ "indpag": "0"
+ },
+ "ambiente": "homologacao",
+ "chave_acesso": "41260712345678000190550010000010011100000019",
+ "numero": 1001,
+ "serie": 1,
+ "cnf": "10000001",
+ "dh_emi": "2026-07-16T10:00:00-03:00",
+ "uf_destino_tipo": "interna"
+}
diff --git a/src/sowai_fiscal/goldens/caso_st_intra.expected.xml b/src/sowai_fiscal/goldens/caso_st_intra.expected.xml
new file mode 100644
index 0000000..e190465
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_st_intra.expected.xml
@@ -0,0 +1 @@
+4110000003Venda55110032026-07-16T10:10:00-03:0011410690211021110sowai-auto/1b.112345678000190AUTOPECAS THIAGO LTDAThiago Auto CenterRua das Autopecas1500Centro4106902CuritibaPR800000001058Brasil41333344441234567890112345678909NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCALRua Oficina22Industrial4106902CuritibaPR800002001058Brasil9OL500SEM GTINOleo lubrificante 15W40 1L2710125906001005405UN1100.00100.00SEM GTINUN1100.0010500140.0018.000013.2004040.000.000.000.00140.0013.200.000.00100.000.000.000.000.000.000.000.000.000.00100.009001100.00
\ No newline at end of file
diff --git a/src/sowai_fiscal/goldens/caso_st_intra.input.json b/src/sowai_fiscal/goldens/caso_st_intra.input.json
new file mode 100644
index 0000000..badeddb
--- /dev/null
+++ b/src/sowai_fiscal/goldens/caso_st_intra.input.json
@@ -0,0 +1,114 @@
+{
+ "emitente": {
+ "cnpj": "12345678000190",
+ "razao_social": "AUTOPECAS THIAGO LTDA",
+ "nome_fantasia": "Thiago Auto Center",
+ "ie": "1234567890",
+ "crt": "1",
+ "address_street": "Rua das Autopecas",
+ "address_number": "1500",
+ "address_complement": null,
+ "address_district": "Centro",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000000",
+ "address_city_ibge_code": "4106902",
+ "fone": "4133334444"
+ },
+ "destinatario": {
+ "nome": "CLIENTE BALCAO LTDA",
+ "cnpj": null,
+ "cpf": "12345678909",
+ "indicador_ie": "9",
+ "ie": null,
+ "address_street": "Rua Oficina",
+ "address_number": "22",
+ "address_complement": null,
+ "address_district": "Industrial",
+ "address_city": "Curitiba",
+ "address_state": "PR",
+ "address_zip": "80000200",
+ "address_city_ibge_code": "4106902",
+ "email": null
+ },
+ "itens": [
+ {
+ "codigo": "OL500",
+ "descricao": "Oleo lubrificante 15W40 1L",
+ "ncm": "27101259",
+ "cfop": "5405",
+ "unidade_comercial": "UN",
+ "unidade_tributavel": "UN",
+ "quantidade": "1",
+ "valor_unitario": "100.00",
+ "gtin": null,
+ "cest": "0600100",
+ "peso_liquido_kg": null,
+ "peso_bruto_kg": null,
+ "fiscal_result": {
+ "cfop": "5405",
+ "cst": null,
+ "csosn": "500",
+ "origem": "0",
+ "consumidor_final": true,
+ "indicador_ie": "9",
+ "tributos": [
+ {
+ "tax_domain": "icmsst",
+ "cst": null,
+ "csosn": "500",
+ "base_calc": "140.00",
+ "base_calc_percent": "100",
+ "aliquota": "18",
+ "valor": "13.20",
+ "mva": "40",
+ "aliquota_st": "18",
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "dd66fbca-c7d0-5d3e-a231-1a2afd3c1c74"
+ },
+ {
+ "tax_domain": "pis",
+ "cst": "04",
+ "csosn": null,
+ "base_calc": "100.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "ac7c5793-b5a8-58b5-b060-af95db499e78"
+ },
+ {
+ "tax_domain": "cofins",
+ "cst": "04",
+ "csosn": null,
+ "base_calc": "100.00",
+ "base_calc_percent": "100",
+ "aliquota": "0",
+ "valor": "0.00",
+ "mva": null,
+ "aliquota_st": null,
+ "fcp_percent": null,
+ "codigo_beneficio": null,
+ "rule_id": "5b0b52d8-50e1-57b9-a473-0911572b040b"
+ }
+ ]
+ }
+ }
+ ],
+ "pagamento": {
+ "tpag": "01",
+ "valor": "100.00",
+ "indpag": "0"
+ },
+ "ambiente": "homologacao",
+ "chave_acesso": "41260712345678000190550010000010031100000030",
+ "numero": 1003,
+ "serie": 1,
+ "cnf": "10000003",
+ "dh_emi": "2026-07-16T10:10:00-03:00",
+ "uf_destino_tipo": "interna"
+}
diff --git a/tests/test_goldens.py b/tests/test_goldens.py
new file mode 100644
index 0000000..31d9d0f
--- /dev/null
+++ b/tests/test_goldens.py
@@ -0,0 +1,56 @@
+"""Corpus golden: para cada `caso_*.input.json` em `sowai_fiscal.goldens`,
+carrega o `DadosEmissao`, monta+serializa o XML pré-assinatura e compara
+BYTE A BYTE com o `caso_*.expected.xml` irmão. Estes dados são PACOTE
+(`importlib.resources`), não fixtures de `tests/` -- o produto consumidor
+(auto) e o futuro serviço leem os MESMOS arquivos da lib instalada.
+
+Regenerar os `.expected.xml` (nunca à mão) com `scripts/gen_goldens.py` --
+só quando uma NT mudar o leiaute DE PROPÓSITO."""
+import importlib.resources
+
+import pytest
+
+from sowai_fiscal.golden_helpers import dados_from_json, serialize_infnfe
+
+_GOLDENS = importlib.resources.files("sowai_fiscal") / "goldens"
+
+
+def _casos() -> list[str]:
+ with importlib.resources.as_file(_GOLDENS) as goldens_dir:
+ return sorted(
+ p.name.removesuffix(".input.json")
+ for p in goldens_dir.glob("caso_*.input.json")
+ )
+
+
+_CASOS = _casos()
+
+
+def test_at_least_six_golden_cases_exist():
+ assert len(_CASOS) >= 6, f"esperado >=6 casos golden, achei {len(_CASOS)}: {_CASOS}"
+
+
+@pytest.mark.parametrize("caso", _CASOS)
+def test_golden_case_matches_expected_xml_byte_a_byte(caso):
+ with importlib.resources.as_file(_GOLDENS) as goldens_dir:
+ input_path = goldens_dir / f"{caso}.input.json"
+ expected_path = goldens_dir / f"{caso}.expected.xml"
+
+ dados = dados_from_json(input_path)
+ actual = serialize_infnfe(dados)
+ expected = expected_path.read_bytes()
+
+ if actual != expected:
+ # Diff legível: aponta o primeiro byte divergente em vez de despejar
+ # os dois XMLs inteiros no assert.
+ first_diff = next(
+ (i for i, (a, e) in enumerate(zip(actual, expected)) if a != e),
+ min(len(actual), len(expected)),
+ )
+ window = 60
+ start = max(0, first_diff - window)
+ assert actual == expected, (
+ f"caso {caso!r} diverge no byte {first_diff}:\n"
+ f" actual: ...{actual[start:first_diff + window]!r}...\n"
+ f" expected: ...{expected[start:first_diff + window]!r}..."
+ )