Documentação

Como o sistema interpreta os arquivos fiscais e valida Notas Fiscais contra a base de regras CFOP.

Visão geral

O sistema recebe três tipos de arquivos pela aba Upload: dados de NFs no padrão Rígido (RPS), no padrão Flexível, e a matriz de Regras CFOP que define o que é esperado para cada operação fiscal.

Os dados são processados no browser, normalizados, enviados ao backend e (no futuro) persistidos em SQLite para validação.

Tabs e seus papéis

TabPapelOrigem típica
RígidosLinhas de NF no padrão SAP/RPSExport do ERP
FlexíveisLinhas de NF no padrão flexívelExport do ERP
Regras CFOPMatriz de regras esperadas por CFOP × impostoPlanilha mantida pelo time fiscal

Pipeline de transformação (cliente)

  1. Skip de linhas — Regras CFOP descarta a 1ª linha (cabeçalho-mestre).
  2. Normalização de colunas — NFKD strip de acentos, lowercase, espaços → _.
  3. Trim de células — espaços nas pontas são removidos.
  4. Rename por ocorrência — em Regras CFOP, as 4 colunas duplicadas clas_item viram clas_item_icms, clas_item_ipi, clas_item_pis, clas_item_cofins.
  5. Dedupe — para Rígidos/Flexíveis, colunas com o mesmo nome mantêm a primeira ocorrência. Desativado em Regras CFOP.
  6. Numeric list — em Regras CFOP, células clas_item_* como "01,02,03 ou 10" ou "06 e 07" são convertidas em listas numéricas ([1, 2, 3, 10]).

Schema da Matriz de Regras CFOP

Após normalização, cada linha tem as colunas:

ColunaTipoDescrição
cfop_consolidadostringCódigo CFOP de 4 dígitos
descricao_cfopstringDescrição livre da operação
clas_item_icmsnumber[]Classificações aplicáveis (ICMS)
cst_icmsinteger?CST esperado de ICMS
base_icms_e_vlr_icmsCOM VALOR | VAZIO | ∅Exigência da dupla base/valor de ICMS
clas_item_ipinumber[]Classificações aplicáveis (IPI)
cod_trib_ipiinteger?Código tributário IPI esperado
base_ipi_e_vlr_ipiCOM VALOR | VAZIO | ∅Idem para IPI
clas_item_pisnumber[]Classificações aplicáveis (PIS)
cod_situacao_pisinteger?Código de situação PIS esperado
vlr_base_pis_e_vlr_pisCOM VALOR | VAZIO | ∅Idem para PIS
clas_item_cofinsnumber[]Classificações aplicáveis (COFINS)
cod_situacao_cofinsinteger?Código de situação COFINS esperado
vlr_base_cofins_e_vlr_cofinsCOM VALOR | VAZIO | ∅Idem para COFINS

Normalização para SQLite

O endpoint POST /api/upload/regras-cfop normaliza cada linha em registros 1-por-tributo:

{
  cfop: string,
  descricao_cfop: string,
  tax: 'icms' | 'ipi' | 'pis' | 'cofins',
  clas_item: number[],
  cst: number | null,
  value_requirement: 'COM VALOR' | 'VAZIO' | null
}

Linhas onde todos os três campos (clas_item, cst, value_requirement) estão vazios para um tributo são descartadas — significa que aquele tributo não se aplica àquele CFOP.

Tabela SQLite (apps/web/db/schema.sql):

CREATE TABLE cfop_rule (
  id                INTEGER PRIMARY KEY AUTOINCREMENT,
  cfop              TEXT    NOT NULL,
  descricao_cfop    TEXT    NOT NULL DEFAULT '',
  tax               TEXT    NOT NULL CHECK (tax IN ('icms','ipi','pis','cofins')),
  clas_item         INTEGER NOT NULL,
  cst               INTEGER,
  value_requirement TEXT    CHECK (value_requirement IN ('COM VALOR','VAZIO')),
  UNIQUE (cfop, tax, clas_item)
);

O array clas_item é explodido em múltiplas linhas — uma por classificação — pra que a validação seja um lookup direto sem JSON parsing.

Glossário — clas_item (Origem da Mercadoria)

Os códigos de clas_item usados pela matriz CFOP, pelo TRIB_ICMS em Rígidos e pelo Item - Origem produto (Código) em Flexíveis seguem a tabela CST de origem da mercadoria:

CódigoDescrição
0Nacional, exceto as indicadas nos códigos 3, 4, 5 e 8
1Estrangeira — importação direta, exceto a indicada no código 6
2Estrangeira — adquirida no mercado interno, exceto a indicada no código 7
3Nacional, com conteúdo de importação superior a 40%
4Nacional, produção em conformidade com PPB
5Nacional, com conteúdo de importação ≤ 40%
6Estrangeira — importação direta, sem similar nacional (lista CAMEX)
7Estrangeira — adquirida no mercado interno, sem similar nacional (lista CAMEX)
8Nacional, com conteúdo de importação superior a 70%
10Wildcard (curinga) — usado pela matriz para casar com qualquer origem

Interpretações adotadas

A documentação oficial é incompleta em alguns pontos. Estas são as convenções adotadas pelo sistema:

  • clas_item = 0 é wildcard. Os documentos não cravam isso, mas é a única interpretação que faz sentido prático — uma regra com clas_item = 0 casa com qualquer classificação na NF. A consulta de validação aplica WHERE clas_item = ? OR clas_item = 0.
  • COM VALOR exige base > 0 E valor > 0. Ambos os campos (base e valor do tributo) devem estar preenchidos com valor não nulo na NF.
  • VAZIO exige base = 0 E valor = 0. Ambos os campos devem estar zerados/em branco na NF.
  • Célula em branco = tributo não aplicável. Quando uma linha de regra deixa os campos de um tributo todos em branco, o sistema não emite nenhuma exigência para aquele tributo nessa operação.
  • Sem regra para um CFOP = warning, não erro. Quando a NF traz um CFOP que não aparece na matriz, a linha é sinalizada para revisão manual em vez de ser barrada.

Validação de NF (planejado)

Para cada linha de NF (Rígido ou Flexível), o motor:

  1. Busca regras em cfop_rule por (cod_cfo, tax), filtrando por clas_item = nf.clas_item OR clas_item = 0.
  2. Para cada tributo, compara o CST/cod_situação da NF com o da regra — divergência é violação.
  3. Aplica a exigência COM VALOR/VAZIO contra os pares de colunas correspondentes na NF (base_icms vlr_icms, etc.).
  4. Acumula violações por linha e retorna um relatório.

Observability

Um serviço Bun em apps/web/observability.ts escuta em :6969/log e grava cada evento em logs.txt. O package @compliance/logger expõe createLogger({ source }).info(...) e é usado tanto no cliente (passos de transformação no upload) quanto no servidor (endpoints de upload).