nfcom 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +115 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +66 -0
  5. data/LICENSE +21 -0
  6. data/README.md +280 -0
  7. data/Rakefile +22 -0
  8. data/examples/.env.example +41 -0
  9. data/examples/emitir_nota.rb +91 -0
  10. data/examples/rails_initializer.rb +72 -0
  11. data/lib/nfcom/builder/danfe_com.rb +564 -0
  12. data/lib/nfcom/builder/qrcode.rb +68 -0
  13. data/lib/nfcom/builder/signature.rb +156 -0
  14. data/lib/nfcom/builder/xml_builder.rb +362 -0
  15. data/lib/nfcom/client.rb +106 -0
  16. data/lib/nfcom/configuration.rb +134 -0
  17. data/lib/nfcom/errors.rb +27 -0
  18. data/lib/nfcom/helpers/consulta.rb +28 -0
  19. data/lib/nfcom/models/assinante.rb +146 -0
  20. data/lib/nfcom/models/destinatario.rb +138 -0
  21. data/lib/nfcom/models/emitente.rb +105 -0
  22. data/lib/nfcom/models/endereco.rb +123 -0
  23. data/lib/nfcom/models/fatura/codigo_de_barras/formato_44.rb +52 -0
  24. data/lib/nfcom/models/fatura/codigo_de_barras.rb +57 -0
  25. data/lib/nfcom/models/fatura.rb +172 -0
  26. data/lib/nfcom/models/item.rb +353 -0
  27. data/lib/nfcom/models/nota.rb +398 -0
  28. data/lib/nfcom/models/total.rb +60 -0
  29. data/lib/nfcom/parsers/autorizacao.rb +28 -0
  30. data/lib/nfcom/parsers/base.rb +30 -0
  31. data/lib/nfcom/parsers/consulta.rb +34 -0
  32. data/lib/nfcom/parsers/inutilizacao.rb +23 -0
  33. data/lib/nfcom/parsers/status.rb +23 -0
  34. data/lib/nfcom/utils/certificate.rb +109 -0
  35. data/lib/nfcom/utils/compressor.rb +47 -0
  36. data/lib/nfcom/utils/helpers.rb +141 -0
  37. data/lib/nfcom/utils/response_decompressor.rb +47 -0
  38. data/lib/nfcom/utils/xml_authorized.rb +29 -0
  39. data/lib/nfcom/utils/xml_cleaner.rb +68 -0
  40. data/lib/nfcom/validators/business_rules.rb +45 -0
  41. data/lib/nfcom/validators/schema_validator.rb +316 -0
  42. data/lib/nfcom/validators/xml_validator.rb +29 -0
  43. data/lib/nfcom/version.rb +5 -0
  44. data/lib/nfcom/webservices/autorizacao.rb +36 -0
  45. data/lib/nfcom/webservices/base.rb +96 -0
  46. data/lib/nfcom/webservices/consulta.rb +59 -0
  47. data/lib/nfcom/webservices/inutilizacao.rb +71 -0
  48. data/lib/nfcom/webservices/status.rb +64 -0
  49. data/lib/nfcom.rb +98 -0
  50. data/nfcom.gemspec +42 -0
  51. metadata +242 -0
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Nfcom
6
+ class Configuration
7
+ AMBIENTES = {
8
+ homologacao: 2,
9
+ producao: 1
10
+ }.freeze
11
+
12
+ ESTADOS = {
13
+ 'AC' => '12', 'AL' => '27', 'AP' => '16', 'AM' => '13',
14
+ 'BA' => '29', 'CE' => '23', 'DF' => '53', 'ES' => '32',
15
+ 'GO' => '52', 'MA' => '21', 'MT' => '51', 'MS' => '50',
16
+ 'MG' => '31', 'PA' => '15', 'PB' => '25', 'PR' => '41',
17
+ 'PE' => '26', 'PI' => '22', 'RJ' => '33', 'RN' => '24',
18
+ 'RS' => '43', 'RO' => '11', 'RR' => '14', 'SC' => '42',
19
+ 'SP' => '35', 'SE' => '28', 'TO' => '17'
20
+ }.freeze
21
+
22
+ # Configurações básicas
23
+ attr_accessor :ambiente, :estado, :timeout
24
+
25
+ # Certificado digital
26
+ attr_accessor :certificado_path, :certificado_senha
27
+
28
+ # Dados do emitente
29
+ attr_accessor :cnpj, :razao_social, :inscricao_estadual, :regime_tributario
30
+
31
+ # Configurações de série e numeração
32
+ attr_accessor :serie_padrao, :numero_inicial
33
+
34
+ # Logging
35
+ attr_accessor :logger
36
+ attr_reader :log_level
37
+
38
+ # Retry
39
+ attr_accessor :max_tentativas, :tempo_espera_retry
40
+
41
+ def initialize
42
+ @ambiente = :homologacao
43
+ @estado = 'PE'
44
+ @timeout = 30
45
+ @serie_padrao = 1
46
+ @numero_inicial = 1
47
+ @max_tentativas = 3
48
+ @tempo_espera_retry = 2
49
+ @log_level = :info
50
+ @logger = Logger.new($stdout)
51
+ @logger.level = Logger::INFO
52
+ end
53
+
54
+ def ambiente_codigo
55
+ AMBIENTES[ambiente]
56
+ end
57
+
58
+ def codigo_uf
59
+ ESTADOS[estado]
60
+ end
61
+
62
+ def homologacao?
63
+ ambiente == :homologacao
64
+ end
65
+
66
+ def producao?
67
+ ambiente == :producao
68
+ end
69
+
70
+ def desabilitar_logs
71
+ @logger = Logger.new(IO::NULL)
72
+ end
73
+
74
+ def habilitar_logs(output = $stdout)
75
+ @logger = Logger.new(output)
76
+ atualizar_log_level
77
+ end
78
+
79
+ # Setter para log_level (atualiza também o logger)
80
+ def log_level=(level)
81
+ @log_level = level
82
+ atualizar_log_level
83
+ end
84
+
85
+ def webservice_url(servico)
86
+ base_url = if homologacao?
87
+ webservices_homologacao[estado.to_sym]
88
+ else
89
+ webservices_producao[estado.to_sym]
90
+ end
91
+
92
+ return nil unless base_url
93
+
94
+ base_url[servico]
95
+ end
96
+
97
+ private
98
+
99
+ def atualizar_log_level
100
+ return unless @logger
101
+
102
+ @logger.level = case @log_level
103
+ when :debug then Logger::DEBUG
104
+ when :info then Logger::INFO
105
+ when :warn then Logger::WARN
106
+ when :error then Logger::ERROR
107
+ when :fatal then Logger::FATAL
108
+ else Logger::INFO # rubocop:disable Lint/DuplicateBranch
109
+ end
110
+ end
111
+
112
+ def webservices_homologacao
113
+ {
114
+ PE: {
115
+ recepcao: 'https://nfcom-homologacao.svrs.rs.gov.br/WS/NFComRecepcao/NFComRecepcao.asmx',
116
+ consulta: 'https://nfcom-homologacao.svrs.rs.gov.br/WS/NFComConsulta/NFComConsulta.asmx',
117
+ status: 'https://nfcom-homologacao.svrs.rs.gov.br/WS/NFComStatusServico/NFComStatusServico.asmx',
118
+ evento: 'https://nfcom-homologacao.svrs.rs.gov.br/WS/NFComRecepcaoEvento/NFComRecepcaoEvento.asmx'
119
+ }
120
+ }
121
+ end
122
+
123
+ def webservices_producao
124
+ {
125
+ PE: {
126
+ recepcao: 'https://nfcom.svrs.rs.gov.br/WS/NFComRecepcao/NFComRecepcao.asmx',
127
+ consulta: 'https://nfcom.svrs.rs.gov.br/WS/NFComConsulta/NFComConsulta.asmx',
128
+ status: 'https://nfcom.svrs.rs.gov.br/WS/NFComStatusServico/NFComStatusServico.asmx',
129
+ evento: 'https://nfcom.svrs.rs.gov.br/WS/NFComRecepcaoEvento/NFComRecepcaoEvento.asmx'
130
+ }
131
+ }
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nfcom
4
+ module Errors
5
+ class Error < StandardError; end
6
+
7
+ class ConfigurationError < Error; end
8
+ class CertificateError < Error; end
9
+ class ValidationError < Error; end
10
+ class XmlError < Error; end
11
+ class SefazError < Error; end
12
+ class SefazIndisponivel < SefazError; end
13
+
14
+ class NotaRejeitada < SefazError
15
+ attr_reader :codigo, :motivo
16
+
17
+ def initialize(codigo, motivo)
18
+ @codigo = codigo
19
+ @motivo = motivo
20
+ super("Nota rejeitada [#{codigo}]: #{motivo}")
21
+ end
22
+ end
23
+
24
+ class NotaDenegada < SefazError; end
25
+ class TimeoutError < Error; end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nfcom
4
+ module Helpers
5
+ module Consulta
6
+ # Returns the URL to confirm an already-authorized NFCom
7
+ #
8
+ # @param chave [String] Chave de acesso da NFCom (44 chars)
9
+ # @param ambiente [Symbol] :homologacao or :producao (optional, defaults to current config)
10
+ # @return [String] URL to confirm the nota
11
+ def self.url(chave:, ambiente: Nfcom.configuration.ambiente)
12
+ raise ArgumentError, 'Chave de acesso inválida' unless chave&.length == 44
13
+
14
+ tp_amb = (ambiente == :producao ? 1 : 2)
15
+ "https://dfe-portal.svrs.rs.gov.br/nfcom/qrcode?chNFCom=#{chave}&tpAmb=#{tp_amb}"
16
+ end
17
+
18
+ # Optional: parse the chave from a full XML string
19
+ #
20
+ # @param xml [String] NFCom XML (as stored in :xml_autorizado)
21
+ # @return [String] chave de acesso
22
+ def self.chave_from_xml(xml)
23
+ doc = Nokogiri::XML(xml)
24
+ doc.at_xpath('//NFCom/infNFCom/@Id')&.value&.sub(/^NFCom/, '')
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nfcom
4
+ module Models
5
+ # Representa os dados do assinante do serviço de comunicação
6
+ class Assinante
7
+ include Utils::Helpers
8
+
9
+ attr_accessor :codigo, # iCodAssinante - Código único (1-30 chars)
10
+ :tipo, # tpAssinante - Tipo de assinante (1-8, 99)
11
+ :tipo_servico, # tpServUtil - Tipo de serviço (1-7)
12
+ :numero_contrato, # nContrato - Número do contrato (opcional)
13
+ :data_inicio_contrato, # dContratoIni - Data início (opcional)
14
+ :data_fim_contrato, # dContratoFim - Data fim (opcional)
15
+ :terminal_principal, # NroTermPrinc - Terminal principal (condicional)
16
+ :uf_terminal_principal, # cUFPrinc - UF do terminal (condicional)
17
+ :terminais_adicionais # Array de { numero:, uf: } (opcional)
18
+
19
+ # Tipos de assinante (tpAssinante) - Domain D18
20
+ TIPO_COMERCIAL = 1
21
+ TIPO_INDUSTRIAL = 2
22
+ TIPO_RESIDENCIAL = 3
23
+ TIPO_PRODUTOR_RURAL = 4
24
+ TIPO_ORGAO_PUBLICO = 5
25
+ TIPO_PRESTADOR_TELECOM = 6
26
+ TIPO_DIPLOMATICO = 7
27
+ TIPO_RELIGIOSO = 8
28
+ TIPO_OUTROS = 99
29
+
30
+ # Tipos de serviço (tpServUtil) - Domain D24
31
+ SERVICO_TELEFONIA = 1
32
+ SERVICO_DADOS = 2
33
+ SERVICO_TV = 3
34
+ SERVICO_INTERNET = 4
35
+ SERVICO_MULTIMIDIA = 5
36
+ SERVICO_OUTROS = 6
37
+ SERVICO_VARIOS = 7
38
+
39
+ def initialize(attrs = {})
40
+ @codigo = attrs[:codigo]
41
+ @tipo = attrs[:tipo]
42
+ @tipo_servico = attrs[:tipo_servico]
43
+ @numero_contrato = attrs[:numero_contrato]
44
+ @data_inicio_contrato = attrs[:data_inicio_contrato]
45
+ @data_fim_contrato = attrs[:data_fim_contrato]
46
+ @terminal_principal = attrs[:terminal_principal]
47
+ @uf_terminal_principal = attrs[:uf_terminal_principal]
48
+ @terminais_adicionais = attrs[:terminais_adicionais] || []
49
+ end
50
+
51
+ def valido?
52
+ erros.empty?
53
+ end
54
+
55
+ def erros # rubocop:disable Metrics/MethodLength
56
+ errors = []
57
+
58
+ # Validações de campos obrigatórios
59
+ errors << 'Código do assinante é obrigatório' if codigo.to_s.strip.empty?
60
+ errors << 'Tipo de assinante é obrigatório' if tipo.nil?
61
+ errors << 'Tipo de serviço é obrigatório' if tipo_servico.nil?
62
+
63
+ # Validações declarativas de formato/schema
64
+ campos = {
65
+ codigo: { valor: codigo, validador: :er59, nome: 'Código do assinante' }
66
+ }
67
+
68
+ # Só valida tipo se não for nil (já checado acima)
69
+ campos[:tipo] = { valor: tipo, validador: :d18, nome: 'Tipo de assinante' } unless tipo.nil?
70
+ unless tipo_servico.nil?
71
+ campos[:tipo_servico] =
72
+ { valor: tipo_servico, validador: :d24, nome: 'Tipo de serviço' }
73
+ end
74
+
75
+ # Adicionar campos opcionais apenas se informados
76
+ if numero_contrato && !numero_contrato.to_s.strip.empty?
77
+ campos[:numero_contrato] = { valor: numero_contrato, validador: :er60, nome: 'Número do contrato' }
78
+ end
79
+
80
+ if data_inicio_contrato && !data_inicio_contrato.to_s.strip.empty?
81
+ campos[:data_inicio] = { valor: data_inicio_contrato, validador: :er48, nome: 'Data de início' }
82
+ end
83
+
84
+ if data_fim_contrato && !data_fim_contrato.to_s.strip.empty?
85
+ campos[:data_fim] = { valor: data_fim_contrato, validador: :er48, nome: 'Data de fim' }
86
+ end
87
+
88
+ # Executar validações declarativas
89
+ errors.concat(Validators::SchemaValidator.validar_campos(campos))
90
+
91
+ # Validação lógica: data fim >= data início
92
+ if data_inicio_contrato && data_fim_contrato &&
93
+ !data_inicio_contrato.to_s.strip.empty? && !data_fim_contrato.to_s.strip.empty?
94
+ begin
95
+ inicio = Date.parse(data_inicio_contrato.to_s)
96
+ fim = Date.parse(data_fim_contrato.to_s)
97
+ errors << 'Data de fim do contrato não pode ser anterior à data de início' if fim < inicio
98
+ rescue ArgumentError
99
+ # Erro de parsing já foi capturado pelas validações declarativas
100
+ end
101
+ end
102
+
103
+ # Validações de terminal principal (condicional)
104
+ if terminal_principal && !terminal_principal.to_s.strip.empty?
105
+ # Se informou terminal, UF é obrigatória
106
+ if uf_terminal_principal.to_s.strip.empty?
107
+ errors << 'UF do terminal principal é obrigatória quando o número do terminal é informado'
108
+ else
109
+ # Validar formato do terminal e UF
110
+ campos_terminal = {
111
+ terminal: { valor: terminal_principal, validador: :telefone, nome: 'Terminal principal' },
112
+ uf_terminal: { valor: uf_terminal_principal, validador: :d5, nome: 'UF do terminal' }
113
+ }
114
+ errors.concat(Validators::SchemaValidator.validar_campos(campos_terminal))
115
+ end
116
+ elsif uf_terminal_principal && !uf_terminal_principal.to_s.strip.empty?
117
+ # Se informou UF mas não informou terminal
118
+ errors << 'Número do terminal principal é obrigatório quando a UF é informada'
119
+ end
120
+
121
+ # Validações de terminais adicionais
122
+ terminais_adicionais&.each_with_index do |terminal, index|
123
+ if terminal[:numero].to_s.strip.empty?
124
+ errors << "Terminal adicional #{index + 1}: número é obrigatório"
125
+ else
126
+ campos_adicional = {
127
+ numero: { valor: terminal[:numero], validador: :telefone, nome: "Terminal adicional #{index + 1}" }
128
+ }
129
+ errors.concat(Validators::SchemaValidator.validar_campos(campos_adicional))
130
+ end
131
+
132
+ if terminal[:uf].to_s.strip.empty?
133
+ errors << "Terminal adicional #{index + 1}: UF é obrigatória"
134
+ else
135
+ campos_uf = {
136
+ uf: { valor: terminal[:uf], validador: :d5, nome: "UF do terminal adicional #{index + 1}" }
137
+ }
138
+ errors.concat(Validators::SchemaValidator.validar_campos(campos_uf))
139
+ end
140
+ end
141
+
142
+ errors
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nfcom
4
+ module Models
5
+ # Representa o destinatário (cliente) da NF-COM
6
+ #
7
+ # O destinatário é o tomador do serviço de comunicação/telecomunicação,
8
+ # podendo ser pessoa física (CPF) ou pessoa jurídica (CNPJ).
9
+ #
10
+ # @example Criar destinatário pessoa física
11
+ # destinatario = Nfcom::Models::Destinatario.new(
12
+ # cpf: '12345678901',
13
+ # razao_social: 'João da Silva',
14
+ # tipo_assinante: :residencial,
15
+ # email: 'joao@email.com',
16
+ # endereco: {
17
+ # logradouro: 'Rua das Flores',
18
+ # numero: '123',
19
+ # bairro: 'Centro',
20
+ # municipio: 'Recife',
21
+ # uf: 'PE',
22
+ # cep: '50000-000',
23
+ # codigo_municipio: '2611606'
24
+ # }
25
+ # )
26
+ #
27
+ # @example Criar destinatário pessoa jurídica
28
+ # destinatario = Nfcom::Models::Destinatario.new(
29
+ # cnpj: '12345678000100',
30
+ # razao_social: 'Empresa LTDA',
31
+ # tipo_assinante: :comercial,
32
+ # inscricao_estadual: '0123456789',
33
+ # email: 'contato@empresa.com',
34
+ # endereco: { ... }
35
+ # )
36
+ #
37
+ # @example Validar destinatário
38
+ # if destinatario.valido?
39
+ # puts "Destinatário válido"
40
+ # else
41
+ # puts "Erros: #{destinatario.erros.join(', ')}"
42
+ # end
43
+ #
44
+ # Tipos de assinante disponíveis:
45
+ # - :comercial (1) - Estabelecimentos comerciais
46
+ # - :industrial (2) - Indústrias
47
+ # - :residencial (3) - Residências (padrão para provedores)
48
+ # - :produtor_rural (4) - Produtores rurais
49
+ # - :orgao_publico (5) - Órgãos públicos
50
+ # - :prestador_servico (6) - Prestadores de serviço
51
+ # - :concessionaria (7) - Concessionárias
52
+ # - :outros (99) - Outros
53
+ #
54
+ # Atributos obrigatórios:
55
+ # - CNPJ ou CPF (pelo menos um)
56
+ # - razao_social (nome ou razão social)
57
+ # - endereco completo
58
+ #
59
+ # Atributos opcionais:
60
+ # - inscricao_estadual (apenas para PJ)
61
+ # - email (recomendado para envio da nota)
62
+ #
63
+ # Validações automáticas:
64
+ # - Validação de dígitos verificadores de CPF/CNPJ
65
+ # - Rejeita CPF/CNPJ com todos dígitos iguais
66
+ # - Valida campos obrigatórios do endereço
67
+ class Destinatario
68
+ include Utils::Helpers
69
+
70
+ attr_accessor :cnpj, :cpf, :razao_social, :inscricao_estadual,
71
+ :tipo_assinante, :endereco, :email
72
+
73
+ # Tipos de assinante
74
+ TIPO_ASSINANTE = {
75
+ comercial: 1,
76
+ industrial: 2,
77
+ residencial: 3,
78
+ produtor_rural: 4,
79
+ orgao_publico: 5,
80
+ prestador_servico: 6,
81
+ concessionaria: 7,
82
+ outros: 99
83
+ }.freeze
84
+
85
+ def initialize(attributes = {})
86
+ @endereco = Endereco.new
87
+ @tipo_assinante = :residencial # padrão para provedor de internet
88
+
89
+ attributes.each do |key, value|
90
+ if key == :endereco && value.is_a?(Hash)
91
+ @endereco = Endereco.new(value)
92
+ elsif respond_to?("#{key}=")
93
+ send("#{key}=", value)
94
+ end
95
+ end
96
+ end
97
+
98
+ def valido?
99
+ erros.empty?
100
+ end
101
+
102
+ def erros
103
+ errors = []
104
+ errors << 'CNPJ ou CPF é obrigatório' if cnpj.to_s.strip.empty? && cpf.to_s.strip.empty?
105
+ errors << 'CNPJ inválido' if !cnpj.to_s.strip.empty? && !cnpj_valido?(cnpj)
106
+ errors << 'CPF inválido' if !cpf.to_s.strip.empty? && !cpf_valido?(cpf)
107
+ errors << 'Razão social é obrigatória' if razao_social.to_s.strip.empty?
108
+ errors.concat(endereco.erros.map { |e| "Endereço: #{e}" }) unless endereco.valido?
109
+
110
+ campos = {}
111
+
112
+ campos[:razao_social] = {
113
+ valor: razao_social,
114
+ validador: :er47,
115
+ nome: 'Razão social',
116
+ max: 60
117
+ }
118
+
119
+ campos[:email] = { valor: email, validador: :er72, nome: 'Email' } if email && !email.to_s.strip.empty?
120
+ errors.concat(Validators::SchemaValidator.validar_campos(campos))
121
+
122
+ errors
123
+ end
124
+
125
+ def tipo_assinante_codigo
126
+ TIPO_ASSINANTE[tipo_assinante] || TIPO_ASSINANTE[:residencial]
127
+ end
128
+
129
+ def pessoa_fisica?
130
+ !cpf.to_s.strip.empty?
131
+ end
132
+
133
+ def pessoa_juridica?
134
+ !cnpj.to_s.strip.empty?
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nfcom
4
+ module Models
5
+ # Representa o emitente da NF-COM
6
+ #
7
+ # O emitente é a empresa prestadora do serviço de comunicação que está
8
+ # emitindo a nota fiscal. Deve ser sempre pessoa jurídica (CNPJ).
9
+ #
10
+ # @example Criar emitente completo
11
+ # emitente = Nfcom::Models::Emitente.new(
12
+ # cnpj: '12345678000100',
13
+ # razao_social: 'Provedor Internet LTDA',
14
+ # nome_fantasia: 'Meu Provedor',
15
+ # inscricao_estadual: '0123456789',
16
+ # inscricao_municipal: '987654',
17
+ # cnae: '6190-6/01',
18
+ # regime_tributario: :simples_nacional,
19
+ # endereco: {
20
+ # logradouro: 'Av. Principal',
21
+ # numero: '1000',
22
+ # complemento: 'Sala 101',
23
+ # bairro: 'Centro',
24
+ # municipio: 'Recife',
25
+ # uf: 'PE',
26
+ # cep: '50000-000',
27
+ # codigo_municipio: '2611606'
28
+ # }
29
+ # )
30
+ #
31
+ # @example Validar emitente
32
+ # if emitente.valido?
33
+ # puts "Emitente válido"
34
+ # else
35
+ # puts "Erros: #{emitente.erros.join(', ')}"
36
+ # end
37
+ #
38
+ # Atributos obrigatórios:
39
+ # - cnpj (14 dígitos, com validação)
40
+ # - razao_social (razão social da empresa)
41
+ # - inscricao_estadual (IE do estado)
42
+ # - endereco completo
43
+ #
44
+ # Atributos opcionais:
45
+ # - nome_fantasia (nome de fantasia/comercial)
46
+ # - inscricao_municipal (IM do município)
47
+ # - cnae (classificação da atividade econômica)
48
+ # - regime_tributario (1=Simples Nacional, 2=Simples Excesso, 3=Normal)
49
+ #
50
+ # Validações automáticas:
51
+ # - Validação de dígitos verificadores do CNPJ
52
+ # - Rejeita CNPJ com todos dígitos iguais
53
+ # - Valida presença de campos obrigatórios
54
+ # - Valida campos obrigatórios do endereço
55
+ #
56
+ # @note O emitente deve estar cadastrado e credenciado na SEFAZ para
57
+ # emissão de NF-COM antes de usar esta gem.
58
+ class Emitente
59
+ include Utils::Helpers
60
+
61
+ attr_accessor :cnpj, :razao_social, :nome_fantasia, :inscricao_estadual,
62
+ :inscricao_municipal, :cnae, :regime_tributario, :endereco
63
+
64
+ # Códigos de Regime Tributário (CRT)
65
+ REGIME_TRIBUTARIO = {
66
+ simples_nacional: 1,
67
+ simples_excesso: 2,
68
+ normal: 3
69
+ }.freeze
70
+
71
+ def initialize(attributes = {})
72
+ @endereco = Endereco.new
73
+ @regime_tributario = :normal # Padrão: Regime Normal
74
+
75
+ attributes.each do |key, value|
76
+ if key == :endereco && value.is_a?(Hash)
77
+ @endereco = Endereco.new(value)
78
+ elsif respond_to?("#{key}=")
79
+ send("#{key}=", value)
80
+ end
81
+ end
82
+ end
83
+
84
+ # Retorna o código do regime tributário para o XML
85
+ # @return [Integer] Código do regime tributário (1, 2 ou 3)
86
+ def regime_tributario_codigo
87
+ REGIME_TRIBUTARIO[regime_tributario] || REGIME_TRIBUTARIO[:normal]
88
+ end
89
+
90
+ def valido?
91
+ erros.empty?
92
+ end
93
+
94
+ def erros
95
+ errors = []
96
+ errors << 'CNPJ é obrigatório' if cnpj.to_s.strip.empty?
97
+ errors << 'CNPJ inválido' unless cnpj_valido?(cnpj)
98
+ errors << 'Razão social é obrigatória' if razao_social.to_s.strip.empty?
99
+ errors << 'Inscrição estadual é obrigatória' if inscricao_estadual.to_s.strip.empty?
100
+ errors.concat(endereco.erros.map { |e| "Endereço: #{e}" }) unless endereco.valido?
101
+ errors
102
+ end
103
+ end
104
+ end
105
+ end