Skip to content

Instantly share code, notes, and snippets.

@alexishida
Created June 16, 2026 16:19
Show Gist options
  • Select an option

  • Save alexishida/9a0ca466c32cd76606647dc1ae6f20a8 to your computer and use it in GitHub Desktop.

Select an option

Save alexishida/9a0ca466c32cd76606647dc1ae6f20a8 to your computer and use it in GitHub Desktop.
Layout usado no DARE/RO em ruby
# 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