Created
June 16, 2026 16:19
-
-
Save alexishida/9a0ca466c32cd76606647dc1ae6f20a8 to your computer and use it in GitHub Desktop.
Layout usado no DARE/RO em ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # frozen_string_literal: true | |
| # Layout usado no DARE/RO | |
| # Gerado a partir do dado público https://www.sefin.ro.gov.br/portalsefin/anexos/322.2825648129959IN16_004___ANEXO_MANUAL_DE_ARRECADACAO.pdf | |
| # Data 16/06/2026 | |
| require "json" | |
| require "date" | |
| class DareRoBarcodeParser | |
| DARE_TYPES = { | |
| "1" => "DARE de receitas diversas emitido pelo SITAFE", | |
| "2" => "DARE especial utilizado pelos fiscais - NÃO RECEBIDO PELA REDE BANCÁRIA", | |
| "3" => "DARE suspenso - NÃO RECEBER", | |
| "4" => "DARE suspenso - NÃO RECEBER", | |
| "5" => "DARE depósito emitido pelo SITAFE", | |
| "6" => "DARE emitido pelo contribuinte para recolhimento de ICMS declarado", | |
| "7" => "DARE emitido pelo contribuinte para recolhimentos sem lançamento prévio", | |
| "8" => "DARE emitido pelo SITAFE - exclusivo para IPVA" | |
| }.freeze | |
| ACCEPTED_TYPES = %w[1 5 6 7 8].freeze | |
| def self.parse(input) | |
| original = input.to_s | |
| # Remove espaços, pontos, traços, tabs, quebras de linha etc. | |
| clean = original.gsub(/\D/, "") | |
| unless [44, 48].include?(clean.length) | |
| return { | |
| valido: false, | |
| erros: [ | |
| "Código inválido. Informe 44 dígitos do código de barras ou 48 dígitos da representação numérica." | |
| ], | |
| entrada: { | |
| original: original, | |
| somente_digitos: clean, | |
| tamanho: clean.length | |
| } | |
| } | |
| end | |
| erros = [] | |
| validacoes = {} | |
| barcode = | |
| if clean.length == 48 | |
| linha = validate_linha_digitavel(clean) | |
| validacoes[:representacao_numerica] = linha | |
| unless linha[:valida] | |
| erros << "Representação numérica inválida. Um ou mais dígitos verificadores dos blocos estão incorretos." | |
| end | |
| convert_linha_digitavel_to_barcode(clean) | |
| else | |
| clean | |
| end | |
| dv_geral_calculado = modulo_10(barcode[0, 3] + barcode[4, 40]) | |
| dv_geral_informado = barcode[3].to_i | |
| validacoes[:digito_verificador_geral] = { | |
| informado: dv_geral_informado, | |
| calculado: dv_geral_calculado, | |
| valido: dv_geral_informado == dv_geral_calculado | |
| } | |
| unless dv_geral_informado == dv_geral_calculado | |
| erros << "Dígito verificador geral inválido." | |
| end | |
| data_juliana = barcode[20, 5] | |
| data_convertida = convert_julian_date(data_juliana) | |
| if data_convertida[:erro] | |
| erros << data_convertida[:erro] | |
| end | |
| tipo_dare = barcode[19] | |
| dados_comuns = { | |
| codigo_barras: barcode, | |
| produto: barcode[0], | |
| segmento: barcode[1], | |
| identificador_valor: barcode[2], | |
| digito_verificador_geral: barcode[3], | |
| valor_centavos: barcode[4, 11].to_i, | |
| valor: cents_to_decimal_string(barcode[4, 11].to_i), | |
| valor_formatado: format_brl(barcode[4, 11].to_i), | |
| empresa_orgao: barcode[15, 4], | |
| tipo_dare: tipo_dare, | |
| tipo_dare_descricao: DARE_TYPES[tipo_dare] || "Tipo de DARE desconhecido", | |
| data_vencimento_juliana: data_juliana, | |
| data_vencimento: data_convertida[:data_ddmmaaaa], | |
| ano_vencimento: data_convertida[:ano], | |
| dia_do_ano_vencimento: data_convertida[:dia_do_ano] | |
| } | |
| erros << "Produto inválido. Esperado '8' para arrecadação." unless dados_comuns[:produto] == "8" | |
| erros << "Segmento inválido. Esperado '5' para órgãos governamentais." unless dados_comuns[:segmento] == "5" | |
| erros << "Identificador de valor inválido. Esperado '6' para valor em reais." unless dados_comuns[:identificador_valor] == "6" | |
| erros << "Empresa/órgão inválido. Esperado '0022' para SEFIN/RO." unless dados_comuns[:empresa_orgao] == "0022" | |
| unless ACCEPTED_TYPES.include?(tipo_dare) | |
| erros << "Tipo de DARE não aceito ou desconhecido: #{tipo_dare}." | |
| end | |
| dados_especificos = parse_specific_fields(barcode, tipo_dare) | |
| { | |
| valido: erros.empty?, | |
| erros: erros, | |
| entrada: { | |
| original: original, | |
| somente_digitos: clean, | |
| tamanho: clean.length, | |
| tipo: clean.length == 48 ? "representacao_numerica_48" : "codigo_barras_44" | |
| }, | |
| validacoes: validacoes, | |
| dados: dados_comuns.merge(dados_especificos) | |
| } | |
| end | |
| def self.to_json(input) | |
| JSON.pretty_generate(parse(input)) | |
| end | |
| def self.parse_specific_fields(barcode, tipo_dare) | |
| case tipo_dare | |
| when "1", "5" | |
| { | |
| numero_documento: barcode[25, 12], | |
| parcela: barcode[37, 2], | |
| codigo_receita: barcode[39, 4], | |
| livre: barcode[43] | |
| } | |
| when "6" | |
| { | |
| inscricao_estadual: barcode[25, 14], | |
| codigo_receita: barcode[39, 4], | |
| indicador_mes_ano_referencia: barcode[43] | |
| } | |
| when "7" | |
| { | |
| sequencial_impressao: barcode[25, 2], | |
| identificador_contribuinte: barcode[27, 12], | |
| codigo_receita: barcode[39, 4], | |
| indicador_identificador: barcode[43] | |
| } | |
| when "8" | |
| { | |
| numero_documento: barcode[25, 12], | |
| parcela: barcode[37, 2], | |
| codigo_municipio: barcode[39, 4], | |
| livre: barcode[43] | |
| } | |
| else | |
| { | |
| area_livre: barcode[19, 25] | |
| } | |
| end | |
| end | |
| # Módulo 10 conforme o manual: | |
| # pesos 2, 1, 2, 1... da esquerda para a direita. | |
| # Quando o produto tiver dois dígitos, soma os dígitos. | |
| # | |
| # Exemplo: | |
| # 8 * 2 = 16 => 1 + 6 | |
| def self.modulo_10(number) | |
| soma = 0 | |
| number.each_char.with_index do |char, index| | |
| peso = index.even? ? 2 : 1 | |
| produto = char.to_i * peso | |
| soma += produto.to_s.chars.map(&:to_i).sum | |
| end | |
| resto = soma % 10 | |
| resto.zero? ? 0 : 10 - resto | |
| end | |
| # Representação numérica com 48 dígitos: | |
| # 4 grupos de 12 posições. | |
| # Em cada grupo, as 11 primeiras posições são dados | |
| # e a 12ª posição é o dígito verificador do grupo. | |
| def self.validate_linha_digitavel(line) | |
| grupos = line.scan(/.{12}/) | |
| resultados = grupos.map.with_index(1) do |grupo, index| | |
| base = grupo[0, 11] | |
| dv_informado = grupo[11].to_i | |
| dv_calculado = modulo_10(base) | |
| { | |
| grupo: index, | |
| base: base, | |
| dv_informado: dv_informado, | |
| dv_calculado: dv_calculado, | |
| valido: dv_informado == dv_calculado | |
| } | |
| end | |
| { | |
| valida: resultados.all? { |item| item[:valido] }, | |
| grupos: resultados | |
| } | |
| end | |
| # Remove o 12º dígito de cada grupo da representação numérica, | |
| # resultando no código de barras de 44 dígitos. | |
| def self.convert_linha_digitavel_to_barcode(line) | |
| line.scan(/.{12}/).map { |grupo| grupo[0, 11] }.join | |
| end | |
| # Data Juliana AAJJJ: | |
| # | |
| # AA menor que 70: | |
| # ano = 2000 + AA | |
| # | |
| # AA maior ou igual a 70: | |
| # ano = 1900 + AA | |
| # | |
| # JJJ é o dia do ano. | |
| def self.convert_julian_date(aajjj) | |
| aa = aajjj[0, 2].to_i | |
| jjj = aajjj[2, 3].to_i | |
| ano = aa < 70 ? 2000 + aa : 1900 + aa | |
| data = Date.ordinal(ano, jjj) | |
| { | |
| ano: ano, | |
| dia_do_ano: jjj, | |
| data_ddmmaaaa: data.strftime("%d/%m/%Y"), | |
| erro: nil | |
| } | |
| rescue Date::Error | |
| { | |
| ano: ano, | |
| dia_do_ano: jjj, | |
| data_ddmmaaaa: nil, | |
| erro: "Data Juliana inválida: #{aajjj}." | |
| } | |
| end | |
| def self.cents_to_decimal_string(cents) | |
| format("%.2f", cents / 100.0) | |
| end | |
| def self.format_brl(cents) | |
| inteiro = (cents / 100).to_s | |
| decimal = cents % 100 | |
| inteiro_formatado = inteiro.reverse.gsub(/(\d{3})(?=\d)/, '\\1.').reverse | |
| "R$ #{inteiro_formatado},#{format('%02d', decimal)}" | |
| end | |
| end | |
| if __FILE__ == $PROGRAM_NAME | |
| input = ARGV.join(" ") | |
| if input.strip.empty? | |
| warn "Uso:" | |
| warn " ruby dare_ro_parser.rb \"<codigo_de_barras_44_ou_representacao_numerica_48>\"" | |
| exit 1 | |
| end | |
| puts DareRoBarcodeParser.to_json(input) | |
| end | |
| # ruby app.rb "codigo" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment