Files
GTI_GERENCIADOR_FISCAL/app/normalize.py
T

85 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Funções de normalização e validação de documento (CPF/CNPJ) e telefone.
Formato de CNPJ alfanumérico vigente desde 31/07/2026 (Instrução Normativa
RFB nº 2.229/2024, Nota Técnica COCAD/SUARA/RFB nº 49/2024): 14 posições,
sendo as 12 primeiras alfanuméricas (0-9, A-Z — raiz + filial) e as 2
últimas numéricas (dígitos verificadores, módulo 11 sobre valor ASCII − 48
de cada caractere). CNPJs numéricos antigos continuam válidos e geram o
mesmo DV pela fórmula nova (retrocompatível).
`normalizar_documento` valida só o FORMATO (tamanho/charset) — é o mesmo
contrato já usado no workflow n8n (node "05 - Normalizar CNPJ") e não deve
divergir dele. `validar_dv_cpf`/`validar_dv_cnpj` são checagens ADICIONAIS
de dígito verificador, usadas pela UI como aviso (não bloqueio) — o legado
aceita documentos com DV matematicamente inválido.
"""
import re
_RE_NAO_ALFANUM = re.compile(r"[^0-9A-Z]")
_RE_CPF = re.compile(r"^[0-9]{11}$")
_RE_CNPJ = re.compile(r"^[0-9A-Z]{12}[0-9]{2}$")
_RE_NAO_DIGITO = re.compile(r"[^0-9]")
def normalizar_documento(bruto: str | None) -> tuple[str, bool, str | None]:
"""
Normaliza CPF/CNPJ: maiúsculas, só dígitos e letras A-Z (remove pontos,
barras, hífens, espaços).
CPF: 11 dígitos numéricos.
CNPJ: 12 caracteres alfanuméricos (0-9, A-Z) + 2 dígitos verificadores
numéricos.
Retorna (documento_normalizado, formato_valido, tipo) onde tipo é "CPF",
"CNPJ" ou None se inválido.
"""
limpo = _RE_NAO_ALFANUM.sub("", (bruto or "").upper())
eh_cpf = bool(_RE_CPF.match(limpo))
eh_cnpj = bool(_RE_CNPJ.match(limpo))
tipo = "CPF" if eh_cpf else ("CNPJ" if eh_cnpj else None)
return limpo, (eh_cpf or eh_cnpj), tipo
def normalizar_telefone(bruto: str | None) -> str:
"""Remove tudo que não for dígito (espaços, parênteses, traços, "+")."""
return _RE_NAO_DIGITO.sub("", bruto or "")
_PESOS_CPF_DV1 = [10, 9, 8, 7, 6, 5, 4, 3, 2]
_PESOS_CPF_DV2 = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]
_PESOS_CNPJ_DV1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
_PESOS_CNPJ_DV2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
def _digito_verificador(valores: list[int], pesos: list[int]) -> int:
soma = sum(v * p for v, p in zip(valores, pesos))
resto = soma % 11
return 0 if resto < 2 else 11 - resto
def validar_dv_cpf(cpf_normalizado: str) -> bool:
"""Confere os 2 dígitos verificadores de um CPF já normalizado (11 dígitos)."""
digitos = [int(c) for c in cpf_normalizado]
dv1 = _digito_verificador(digitos[:9], _PESOS_CPF_DV1)
dv2 = _digito_verificador(digitos[:9] + [dv1], _PESOS_CPF_DV2)
return digitos[9] == dv1 and digitos[10] == dv2
def _valor_caractere(c: str) -> int:
"""Valor ASCII − 48: dígitos 0-9 valem 0-9, letras A-Z valem 17-42."""
return ord(c) - 48
def validar_dv_cnpj(cnpj_normalizado: str) -> bool:
"""
Confere os 2 dígitos verificadores de um CNPJ já normalizado (12
alfanuméricos + 2 numéricos). Funciona tanto para CNPJ numérico antigo
quanto para o novo formato alfanumérico (mesma fórmula, retrocompatível).
"""
base = [_valor_caractere(c) for c in cnpj_normalizado[:12]]
dv_informado = [int(c) for c in cnpj_normalizado[12:14]]
dv1 = _digito_verificador(base, _PESOS_CNPJ_DV1)
dv2 = _digito_verificador(base + [dv1], _PESOS_CNPJ_DV2)
return dv_informado[0] == dv1 and dv_informado[1] == dv2