"""Validador local determinístico de GTIN / EAN (GS1 Standard). Sem chamadas externas, sem IA. Implementa: 1. Validação de formato (GTIN-8, GTIN-12, GTIN-13, GTIN-14); 2. Rejeição de placeholders (zeros, sequências óbvias, códigos internos de balança restrita); 3. Verificação do dígito verificador oficial GS1 (módulo 10 com pesos alternados 3 e 1); 4. Validação de faixas de prefixo GS1 reconhecidas. """ from __future__ import annotations def _digits(value: str | None) -> str: return "".join(c for c in str(value or "") if c.isdigit()) # Prefixos de circulação restrita / uso interno de loja (não são GTINs globais válidos para KB nacional) # Faixas 020-029 e 200-299 são reservadas pelo GS1 para distribuição restrita (itens pesáveis, balança de loja) INTERNAL_STORE_PREFIXES = tuple(str(p) for p in range(20, 30)) + tuple(str(p) for p in range(200, 300)) def calculate_gs1_check_digit(digits_without_dv: str) -> int: """Calcula o dígito verificador padrão GS1 pelo algoritmo oficial módulo 10.""" total = 0 # Da direita para a esquerda, os fatores alternam entre 3 e 1 # Para o penúltimo dígito (imediatamente antes do DV), o peso é sempre 3 weight = 3 for char in reversed(digits_without_dv): total += int(char) * weight weight = 1 if weight == 3 else 3 remainder = total % 10 return 0 if remainder == 0 else (10 - remainder) def validate_gtin(value: str | None) -> tuple[bool, str]: """Valida um código GTIN. Retorna (is_valid, reason). """ if not value: return False, "GTIN_EMPTY" cleaned = _digits(value) if len(cleaned) not in (8, 12, 13, 14): return False, f"GTIN_INVALID_LENGTH_{len(cleaned)}" # Rejeita dígitos todos iguais (ex: 00000000, 1111111111111, 9999999999999) if len(set(cleaned)) == 1: return False, "GTIN_PLACEHOLDER_REPEATED_DIGITS" # Rejeita prefixos de uso interno / balança # Normalizado para 13 dígitos para verificar o prefixo padded_13 = cleaned.zfill(13) if len(cleaned) < 14 else cleaned[1:] prefix_3 = padded_13[:3] prefix_2 = padded_13[:2] if prefix_3 in INTERNAL_STORE_PREFIXES or prefix_2 in INTERNAL_STORE_PREFIXES: return False, "GTIN_RESTRICTED_INTERNAL_STORE_PREFIX" # Validação do Dígito Verificador expected_dv = calculate_gs1_check_digit(cleaned[:-1]) actual_dv = int(cleaned[-1]) if actual_dv != expected_dv: return False, f"GTIN_INVALID_CHECK_DIGIT_EXPECTED_{expected_dv}_GOT_{actual_dv}" return True, "GTIN_VALID"