Skip to content

vilsonneto/tributos-br

Repository files navigation

tributos-br f(x)

Correto, testado, sem surpresas.
Motor de cálculo tributário brasileiro com precisão decimal arbitrária. Zero dependências.

npm version CI License: MIT TypeScript strict Zero Dependencies Coverage

tributos-br.vercel.app


Por que tributos-br?

  1. Audit trail em cada cálculo — quando a SEFAZ rejeita, você sabe exatamente onde o arredondamento causou a divergência. Cada função retorna um array de steps com fórmula e valor de cada etapa.

  2. DIFAL com 3 modos — base única, base dupla (LC 190/2022) e base reduzida (CST 20 com benefício fiscal). A maioria dos ERPs menores calcula DIFAL apenas com base única. tributos-br seleciona o modo via parâmetro.

  3. FECOP na MVA Ajustada — ALQ Intra Efetiva = ALQ Interna + FECOP. Ignorar FECOP gera diferença de ~4pp na MVA. Estados: RJ, MG, CE, PE, BA, GO, MT, PI.

  4. ST unificada — uma única calcSt() cobre todos os 5 cenários de Substituição Tributária via parâmetros opcionais. Sem nomenclatura ST-01 a ST-05.


O problema

JavaScript usa IEEE 754 (ponto flutuante) para números. Isso causa erros silenciosos em cálculos fiscais:

1.064 * 39680
// Esperado: 42219.52
// IEEE 754: 42219.520000000004

A SEFAZ rejeita NF-e com erros de validação 629/630 quando vProd != vUnCom x qCom. Esse drift de centavos é causado por arredondamento IEEE 754.

O tributos-br resolve isso com aritmética em strings de precisão arbitrária. Toda operação é feita sobre dígitos decimais puros, sem conversão para number em nenhum ponto intermediário.


Quem precisa disso

Qualquer sistema que calcula preço unitário por divisão de embalagem e usa IEEE 754 está sujeito a rejeição 629. O problema afeta múltiplos mercados:

Mercado Exemplo de cálculo Resultado
Combustível R$ 5,799/L × 15L toFixed: "86.98", correto: "86.99"
Distribuidora Cx R$ 6,99 / 6un × 9un toFixed: "10.48", correto: "10.49"
Grãos Saca R$ 50,10 / 60kg × 1kg toFixed: "0.83", correto: "0.84"
Farmacêutico Cx R$ 15,30 / 12un × 1un toFixed: "1.27", correto: "1.28"
Autopeças Kit R$ 20,30 / 4un × 7un toFixed: "35.52", correto: "35.53"
Bebidas Cx R$ 20,10 / 12un × 7un toFixed: "11.72", correto: "11.73"
Construção Barra R$ 11,10 / 12m × 19m toFixed: "17.57", correto: "17.58"
Químico Bombona R$ 50,10 / 20L × 1L toFixed: "2.50", correto: "2.51"

ERPs enterprise já documentam o problema: SAP aumentou o campo vUnCom de 4 pra 10 casas decimais, Senior criou parâmetro para recalcular perda de precisão, TOTVS avisa sobre perda em cálculos encadeados.


Instalação

npm install tributos-br
yarn add tributos-br
pnpm add tributos-br

Uso rápido

import { calcSt } from 'tributos-br'

const resultado = calcSt({
  baseIcms: '1000',
  aliquotaIcms: '0.12',
  mva: '0.40',
  aliquotaSt: '0.18',
  valorIpi: '100',
})

resultado.icmsSt.toFixed(2) // '157.20'
resultado.baseSt.toFixed(2) // '1540.00'

// Audit trail — cada etapa do cálculo
resultado.audit
// [
//   { step: "Base ICMS",            formula: "1000",                        value: "1000.00" },
//   { step: "ICMS Próprio",         formula: "1000.00 x 0.1200",            value: "120.00"  },
//   { step: "Base ST (MVA + IPI)",  formula: "(1000.00 + 100.00) x 1.4000", value: "1540.00" },
//   { step: "ICMS-ST",             formula: "1540.00 x 0.1800 - 120.00",   value: "157.20"  },
// ]

Todas as funções recebem e retornam valores como Decimal, nunca number. Use .toFixed(2) para formatar e .toNumber() apenas para interoperabilidade com APIs externas.


Funções disponíveis

Função Descrição
calcIcms() ICMS próprio (por dentro / por fora)
calcIpi() IPI sobre produto
calcMvaAjustada() MVA ajustada interestadual (com FECOP)
calcSt() ICMS-ST unificada (5 cenários via parâmetros)
calcDifal() DIFAL base única + dupla + reduzida (CST 20)
calcPis() PIS (cumulativo e não cumulativo)
calcCofins() COFINS (cumulativo e não cumulativo)
calcCbs() CBS (reforma tributária, LC 214/2025)
calcIbs() IBS (reforma tributária, LC 214/2025)

Todas as funções são puras, recebem parâmetros (nunca hardcode de alíquotas), retornam objeto com breakdown dos cálculos + audit trail, e usam Decimal internamente com arredondamento HALF_UP (padrão SEFAZ).


Interoperabilidade com outras libs

Se o seu projeto já usa decimal.js, big.js ou bignumber.js, o tributos-br aceita os valores diretamente. Internamente, o parse converte via .toString(), preservando a precisão:

import DecimalJS from 'decimal.js'
import { Decimal } from 'tributos-br/precision'

const preco = new DecimalJS('1.064')
const resultado = Decimal.from(preco.toString()).mul('39680')

resultado.toFixed(2) // '42219.52' — exato

Não é necessário abandonar libs existentes para usar tributos-br. A camada de precisão é independente e aceita string, number ou qualquer objeto com .toString().


Roadmap: Reforma Tributária (CBS/IBS)

A reforma tributária do consumo (LC 214/2025) substitui PIS/COFINS por CBS e ICMS/ISS por IBS entre 2026 e 2033. O tributos-br já suporta calcCbs() e calcIbs() desde a v1.0.0.

Fase Período O que muda Status tributos-br
Teste 2026 Campos CBS/IBS opcionais na NF-e (NT 2025.002). Alíquotas teste: CBS 0,9%, IBS 0,1% Suportado. calcCbs + calcIbs implementados. NF-e #1 já tem ground truth CBS/IBS.
Transição 2027-2028 CBS/IBS obrigatórios. PIS/COFINS e ICMS coexistem com alíquotas reduzidas. Parcial. Calculadoras existem, mas calcTransicao() com ponderação entre regimes ainda não (issue #66).
Dual 2029-2032 Regime dual. Alíquotas CBS/IBS sobem, PIS/COFINS e ICMS descem proporcionalmente. Planejado. Depende de calcTransicao() + alíquotas definitivas da regulamentação.
Pleno 2033 CBS/IBS pleno. PIS/COFINS e ICMS extintos. Planejado. calcCbs + calcIbs já prontos. Falta remover calculadoras legadas.

A lib recebe alíquotas como parâmetro, então qualquer mudança de alíquota pela regulamentação funciona sem atualização de código. A feature pendente é calcTransicao() (issue #66), que pondera resultado antigo x novo conforme fator de transição do ano-calendário.


Escopo e limitações

O tributos-br cobre cálculos tributários com precisão validada contra NF-e reais. É importante saber o que está e o que não está coberto.

O que a lib faz bem:

  • Cálculo de ICMS, IPI, ST, DIFAL, MVA, PIS, COFINS, CBS e IBS com precisão arbitrária
  • Audit trail de cada etapa intermediária
  • Ground truth validado contra 9 NF-e reais aceitas pela SEFAZ (cStat 100)
  • Cobertura focada em e-commerce B2C (operações interestaduais para consumidor final)

O que a lib NÃO faz (ainda):

  • Roteamento fiscal por CST/CSOSN (qual tributo aplicar em cada operação)
  • PIS/COFINS não cumulativo com créditos (a lib calcula o débito, não o crédito)
  • ST com pauta fiscal estadual (sem ground truth B2B)
  • DIFAL contribuinte (sem NF-e B2B para validar)
  • Monofasia, alíquota zero, isenção (cenários de exceção por NCM/CEST)
  • ISS (municipal, regras por município)
  • Tabelas de alíquota (a lib recebe como parâmetro, não mantém tabelas)
  • Geração de XML de NF-e

Cobertura estimada: a lib calcula corretamente os tributos para os cenários validados pelo ground truth. Para cenários fora do ground truth (ST com pauta, DIFAL contribuinte, operações B2B), os cálculos seguem a legislação mas não foram confrontados com NF-e reais. Valide com seu contador.


Exemplos

ICMS próprio

import { calcIcms } from 'tributos-br'

// ICMS por fora (padrão, operações interestaduais)
const icms = calcIcms({
  valorProduto: '1000',
  aliquota: '0.18', // 18%
})
// icms.base    → 1000
// icms.imposto → 180

// ICMS por dentro (operações internas, imposto embutido no preço)
const icmsDentro = calcIcms({
  valorProduto: '1000',
  aliquota: '0.18',
  incluirImpostoNaBase: true,
})
// icmsDentro.base    → ~1219.51 (1000 / (1 - 0.18))
// icmsDentro.imposto → ~219.51

IPI

import { calcIpi } from 'tributos-br'

const ipi = calcIpi({
  valorProduto: '1000',
  aliquota: '0.10', // 10%
})
// ipi.base    → 1000
// ipi.imposto → 100

MVA ajustada

Corrige a MVA original para operações interestaduais, neutralizando o diferencial de alíquota (EC 87/2015).

import { calcMvaAjustada } from 'tributos-br'

const mva = calcMvaAjustada({
  mvaOriginal: '0.40', // 40%
  aliquotaInterestadual: '0.12', // 12%
  aliquotaInterna: '0.18', // 18%
})
// mva.mvaOriginal → 0.40
// mva.mvaAjustada → ~0.5024 (50,24%)

// Com FECOP (2%)
const mvaFecop = calcMvaAjustada({
  mvaOriginal: '0.40',
  aliquotaInterestadual: '0.12',
  aliquotaInterna: '0.18',
  fecop: '0.02',
})
// mvaFecop.mvaAjustada → 0.54 (54%)

ICMS-ST (Substituição Tributária)

Uma única função que cobre todos os cenários via parâmetros opcionais:

import { calcSt } from 'tributos-br'

// Cenário básico: operação interna com MVA original
const st = calcSt({
  baseIcms: '1000',
  aliquotaIcms: '0.18', // 18%
  mva: '0.40', // 40%
  aliquotaSt: '0.18', // 18%
})
// st.baseIcms    → 1000
// st.icmsProprio → 180
// st.baseSt      → 1400 (1000 x 1.40)
// st.icmsSt      → 72   (1400 x 0.18 - 180)

// Cenário interestadual com IPI compondo base ST
const stInter = calcSt({
  baseIcms: '1000',
  aliquotaIcms: '0.12',
  mva: '0.5024',
  aliquotaSt: '0.18',
  valorIpi: '100',
})
// stInter.baseSt → 1652.64 ((1000 + 100) x 1.5024)
// stInter.icmsSt → 177.47

// Cenário com pauta fiscal (SEFAZ define valor mínimo)
const stPauta = calcSt({
  baseIcms: '500',
  aliquotaIcms: '0.12',
  mva: '0.40',
  aliquotaSt: '0.18',
  pautaFiscal: '15', // R$ 15 por unidade
  quantidade: '100', // 100 unidades
})
// Se pauta (15 x 100 = 1500) > base MVA (500 x 1.40 = 700), usa pauta
// stPauta.baseSt → 1500

// Cenário com redução de base
const stReduzida = calcSt({
  baseIcms: '1000',
  aliquotaIcms: '0.18',
  mva: '0.40',
  aliquotaSt: '0.18',
  reducaoBase: '0.10', // reduz 10% da base ICMS
  reducaoBaseSt: '0.10', // reduz 10% da base ST
})
// stReduzida.baseIcms → 900 (1000 x 0.90)
// stReduzida.baseSt   → 1400 (base ST antes da redução)
// icmsSt calculado sobre base ST efetiva (1400 x 0.90 = 1260)

// Cenário com FECOP
const stFecop = calcSt({
  baseIcms: '1000',
  aliquotaIcms: '0.12',
  mva: '0.40',
  aliquotaSt: '0.18',
  fecop: '0.02', // +2% sobre alíquota ST
})
// Alíquota ST efetiva = 0.18 + 0.02 = 0.20

DIFAL (Diferencial de Alíquota)

import { calcDifal } from 'tributos-br'

// Base única (não-contribuinte, consumidor final)
const difalSimples = calcDifal({
  valorOperacao: '1000',
  aliquotaInterestadual: '0.12',
  aliquotaInternaDestino: '0.18',
  destinatarioContribuinte: false,
})
// difalSimples.icmsOrigem  → 120
// difalSimples.icmsDestino → 180
// difalSimples.difal       → 60
// difalSimples.baseDifal   → 1000

// Base dupla (contribuinte, LC 190/2022)
const difalDupla = calcDifal({
  valorOperacao: '1000',
  aliquotaInterestadual: '0.12',
  aliquotaInternaDestino: '0.18',
  destinatarioContribuinte: true,
})
// difalDupla.baseDifal   → ~1073.17 ((1000 - 120) / (1 - 0.18))
// difalDupla.icmsDestino → ~193.17
// difalDupla.difal       → ~73.17

// Base reduzida (CST 20, benefício fiscal com pRedBC)
// Caller pré-calcula a base reduzida: 243.90 × (1 - 0.95) = 12.20
const difalReduzida = calcDifal({
  valorOperacao: '12.20',
  aliquotaInterestadual: '0.12',
  aliquotaInternaDestino: '0.18',
  destinatarioContribuinte: false,
  baseReduzida: true,
})
// difalReduzida.baseDifal   → 14.88 (12.20 / (1 - 0.18), ICMS por dentro)
// difalReduzida.icmsOrigem  → 1.46
// difalReduzida.icmsDestino → 2.68
// difalReduzida.difal       → 1.22

// Com FECOP
const difalFecop = calcDifal({
  valorOperacao: '1000',
  aliquotaInterestadual: '0.12',
  aliquotaInternaDestino: '0.18',
  destinatarioContribuinte: false,
  fecop: '0.02',
})
// difalFecop.icmsDestino → 200 (1000 x (0.18 + 0.02))
// difalFecop.difal       → 80

CBS e IBS (Reforma Tributária)

import { calcCbs, calcIbs } from 'tributos-br'

// CBS — substitui PIS/COFINS (LC 214/2025)
const cbs = calcCbs({
  base: '1000',
  aliquota: '0.009', // 0,9% (alíquota teste 2026)
})
// cbs.imposto → 9

// IBS — substitui ICMS/ISS (LC 214/2025)
const ibs = calcIbs({
  base: '1000',
  aliquota: '0.178', // 17,8% (soma estadual + municipal)
})
// ibs.imposto → 178

As alíquotas não são hardcoded porque variam por setor e ano-calendário durante a transição.


Precisão Decimal

A lib inclui a classe Decimal para aritmética de precisão arbitrária. Pode ser usada independentemente:

import { Decimal, RoundingMode } from 'tributos-br/precision'

// Criação
const a = Decimal.from('1.064')
const b = Decimal.from('39680')

// Operações (encadeamento imutável)
const resultado = a.mul(b).round(2)
console.log(resultado.toFixed(2)) // '42219.52' (exato!)

// Arredondamento monetário (2 casas, HALF_UP)
Decimal.from('1.235').toMoney().toFixed(2) // '1.24'

// Arredondamento de alíquota (4 casas, HALF_UP)
Decimal.from('0.12345').toRate().toFixed(4) // '0.1235'

// Comparações
Decimal.from('1.23').gt('1.22') // true
Decimal.from('1.23').eq('1.23') // true

// Min/Max
Decimal.max('100', '200', '150').toFixed(0) // '200'
Decimal.min('100', '200', '150').toFixed(0) // '100'

Modos de arredondamento

Modo Descrição
HALF_UP Metade para cima (padrão SEFAZ)
HALF_EVEN Metade para o par (Banker's rounding)
HALF_DOWN Metade para baixo
UP Para longe do zero
DOWN Para o zero (trunca)
CEILING Para +Infinity
FLOOR Para -Infinity

Entry points

Import Conteúdo
tributos-br Calculadoras (ICMS, IPI, DIFAL, ST, CBS/IBS) + Decimal
tributos-br/precision Apenas Decimal + RoundingMode
// Se você só precisa de Decimal (sem calculadoras)
import { Decimal } from 'tributos-br/precision'

// Se precisa das calculadoras (Decimal também incluso)
import { calcIcms, Decimal } from 'tributos-br'

Compatibilidade

  • Node.js >= 20
  • ESM e CommonJS
  • TypeScript strict

Motivação

A motivação veio de experiência real com cálculo de ICMS e IPI em código de produção. Arredondamentos IEEE 754 causavam rejeições de NF-e por centavos de diferença — erro 629, valor total não bate com preço unitário vezes quantidade.

Em vez de corrigir caso a caso com toFixed, a solução foi trocar a base: fazer aritmética em strings de dígitos, sem nunca passar por number nos cálculos intermediários. O resultado é uma engine de cálculo tributário que produz os mesmos valores que a SEFAZ espera, sem surpresas.

Os testes são derivados da legislação vigente e de exemplos oficiais. Valores esperados nunca vêm da implementação.


Alternativas

Até onde sabemos, tributos-br é a única lib JavaScript/TypeScript ativa para cálculo tributário brasileiro com aritmética de precisão arbitrária, audit trail e ground truth validado contra NF-e reais.

Existem ports de bibliotecas fiscais de outras linguagens (C#, Delphi) para JS/TS, mas nenhum com manutenção ativa ou cobertura de testes equivalente. Se você conhece outra lib nesse espaço, abre uma issue, a gente adiciona aqui.

Para precisão decimal genérica (sem calculadoras fiscais): decimal.js, big.js, bignumber.js.


Aviso legal

Esta biblioteca é fornecida "como está", sem garantias de qualquer tipo. Os cálculos são validados contra NF-e reais e legislação vigente, mas não substituem consultoria contábil ou fiscal. O autor não se responsabiliza por perdas decorrentes do uso em produção. Valide os resultados com seu contador antes de emitir documentos fiscais.


Licença

MIT

About

Motor de cálculo tributário brasileiro com precisão decimal arbitrária. ICMS, IPI, DIFAL, ST (5 cenários), CBS/IBS.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages