Se você mantém um emissor de NF-e/NFC-e ou faz auditoria de XML para escritório contábil, um dos erros mais comuns no grupo IBSCBS não é o campo faltando: é a conta que não fecha. O valor do item não bate com base × alíquota, ou a soma dos itens não bate com o total em IBSCBSTot, quase sempre por arredondamento.
Este post mostra um script pequeno em Python, só com a biblioteca padrão, que faz essa conferência. Ele é um ponto de partida, não um validador completo.
Fonte das regras: NT 2025.002-RTC v1.52, no Portal da NF-e. Confira sempre a versão vigente da NT e o leiaute (XSD) antes de usar qualquer caminho de campo deste exemplo.
As contas que precisam fechar
Para cada item (det/imposto/IBSCBS/gIBSCBS):
-
vIBSUF=vBC×pIBSUF -
vIBSMun=vBC×pIBSMun -
vIBS=vIBSUF+vIBSMun -
vCBS=vBC×pCBS
E no total da nota (total/IBSCBSTot), cada valor deve bater com a soma dos itens. Em 2026 (ano-teste), as alíquotas esperadas são CBS 0,9%, IBS UF 0,1% e IBS Municipal 0% (LC 214/2025, arts. 343 e 346; regras UB18-10, UB37-10 e UB56-10). Por isso não deixe a alíquota fixa no código: ela muda em 2027.
Dois cuidados antes do código
-
Use
Decimal, nuncafloat.0.1 + 0.2em float já dá problema; em dinheiro, isso vira centavo de diferença no total. - Defina a tolerância. Arredondar por item e depois somar pode dar um resultado diferente de somar e depois arredondar. No exemplo usamos tolerância de R$ 0,01, que é um critério nosso de conferência, não uma regra da NT.
O script
import sys
from decimal import Decimal, ROUND_HALF_UP
import xml.etree.ElementTree as ET
NS = {"n": "http://www.portalfiscal.inf.br/nfe"}
CENT = Decimal("0.01")
TOL = Decimal("0.01")
def d(el, path):
x = el.find(path, NS)
return Decimal(x.text) if x is not None and x.text else None
def money(v):
return v.quantize(CENT, rounding=ROUND_HALF_UP)
def check(path):
root = ET.parse(path).getroot()
inf = root.find(".//n:infNFe", NS)
problems = []
soma = {"vIBSUF": Decimal(0), "vIBSMun": Decimal(0), "vIBS": Decimal(0), "vCBS": Decimal(0)}
for det in inf.findall("n:det", NS):
item = det.get("nItem")
g = det.find("n:imposto/n:IBSCBS/n:gIBSCBS", NS)
if det.find("n:imposto/n:IBSCBS", NS) is None:
problems.append(f"item {item}: grupo IBSCBS ausente (rejeição 1115)")
continue
if g is None:
continue # CST sem gIBSCBS (ex.: isenção); fora do escopo deste exemplo
vbc = d(g, "n:vBC")
pares = [("gIBSUF/n:pIBSUF", "gIBSUF/n:vIBSUF", "vIBSUF"),
("gIBSMun/n:pIBSMun", "gIBSMun/n:vIBSMun", "vIBSMun"),
("gCBS/n:pCBS", "gCBS/n:vCBS", "vCBS")]
for p_path, v_path, nome in pares:
p, v = d(g, "n:" + p_path), d(g, "n:" + v_path)
if p is None or v is None:
problems.append(f"item {item}: {nome} ausente")
continue
esperado = money(vbc * p / 100)
if abs(esperado - v) > TOL:
problems.append(f"item {item}: {nome} = {v}, esperado {esperado}")
soma[nome] += v
vibs = d(g, "n:vIBS")
if vibs is not None:
if vibs != d(g, "n:gIBSUF/n:vIBSUF") + d(g, "n:gIBSMun/n:vIBSMun"):
problems.append(f"item {item}: vIBS diferente de vIBSUF + vIBSMun")
soma["vIBS"] += vibs
tot = inf.find("n:total/n:IBSCBSTot", NS)
if tot is not None:
# Confira estes caminhos no leiaute da NT vigente
for nome, caminho in [("vIBSUF", "n:gIBS/n:gIBSUF/n:vIBSUF"),
("vIBSMun", "n:gIBS/n:gIBSMun/n:vIBSMun"),
("vIBS", "n:gIBS/n:vIBS"),
("vCBS", "n:gCBS/n:vCBS")]:
t = d(tot, caminho)
if t is not None and abs(t - soma[nome]) > TOL:
problems.append(f"total {nome} = {t}, soma dos itens {soma[nome]}")
return problems
for f in sys.argv[1:]:
probs = check(f)
print(f, "OK" if not probs else "")
for p in probs:
print(" -", p)
Rodando contra um XML de teste com dois itens (um com vCBS errado e outro sem o grupo IBSCBS):
$ python3 check.py nota.xml
nota.xml
- item 1: vCBS = 9.50, esperado 9.00
- item 2: grupo IBSCBS ausente (rejeição 1115)
- total vCBS = 9.00, soma dos itens 9.50
O que este script não faz
Para ser claro sobre os limites: ele não confere se CST e cClassTrib existem e são compatíveis, não trata redução de alíquota, diferimento nem os casos sem gIBSCBS, não confere vItem/vNFTot e não valida o XML contra o XSD nem a assinatura. São justamente as partes que dão mais trabalho de manter a cada versão da NT.
Se não quiser manter isso
Nós (AppVerix) fizemos o Checa Nota, um verificador grátis e sem cadastro que já faz essas contas e mais: presença do grupo IBSCBS, CST e cClassTrib, alíquotas de 2026, reduções e diferimento exigidos pelo CST, totais de IBSCBSTot, vItem/vNFTot e ordem e formato dos campos de IBS/CBS. Aceita XML avulso ou .zip com até 2.000 notas, roda inteiro no navegador (os XMLs não são enviados para servidor) e exporta o relatório em CSV.
Ele também tem limites: não cobre NFS-e nem CT-e, e não valida o XML inteiro contra o schema nem a assinatura. Passar nele não garante autorização na SEFAZ.
Se você já tratou o arredondamento de outro jeito no seu emissor (por item, por total, com ajuste no último item), conta nos comentários.
Top comments (0)