Class: VerifactuRails::Detalle

Inherits:
Object
  • Object
show all
Defined in:
lib/verifactu_rails/desglose.rb

Overview

Una línea del desglose de la factura (sf:DetalleType).

Todos los importes pasan por Importe.formatear: el string que sale de aquí es el mismo que la AEAT usará para recalcular la huella. No formatear por otro lado, nunca.

Constant Summary collapse

IMPUESTOS =

01 IVA, 02 IPSI, 03 IGIC, 05 Otros

%w[01 02 03 05].freeze
CALIFICACIONES =
%w[S1 S2 N1 N2].freeze
EXENCIONES =

Lista L10. E7 y E8 NO son valores generales: solo se admiten con IGIC (Validaciones v1.2.2, ap. 15.5).

%w[E1 E2 E3 E4 E5 E6].freeze
EXENCIONES_IGIC =
(EXENCIONES + %w[E7 E8]).freeze
REGIMENES_IVA =

ClaveRegimen admisible por impuesto: L8A para IVA, L8B (+20) para IGIC y el subconjunto del ap. 15.6 para IPSI. Con Impuesto = 05 (Otros) el campo no se puede informar en absoluto.

%w[01 02 03 04 05 06 07 08 09 10 11 14 15 17 18 19 20].freeze
REGIMENES_IGIC =
(REGIMENES_IVA + %w[21]).freeze
REGIMENES_IPSI =
%w[01 08 11 18 19 20].freeze
REGIMENES =
(REGIMENES_IGIC | REGIMENES_IVA).freeze
TIPOS_IMPOSITIVOS_IVA =

Ap. 15.1 y 15.3, para IVA con operación sujeta y no exenta.

%w[0.00 2.00 4.00 5.00 7.50 10.00 21.00].freeze
RECARGOS_IVA =
%w[0.00 0.26 0.50 0.62 1.00 1.40 1.75 5.20].freeze
VENTANAS_TIPO =

Ap. 15.1. Tres de los tipos NO son de aplicación permanente: fueron rebajas temporales y solo se admiten si la fecha de referencia cae dentro de su ventana. Los demás no llevan ventana.

Consecuencia práctica que sorprende: como FechaExpedicionFactura no puede ser anterior al 28-10-2024, el 5 % ya NO es declarable salvo que se informe una FechaOperacion dentro de su ventana. Es el origen de los errores 1235 y 1236 de la AEAT.

{
  '5.00' => [Date.new(2022, 7, 1), Date.new(2024, 9, 30)],
  '2.00' => [Date.new(2024, 10, 1), Date.new(2024, 12, 31)],
  '7.50' => [Date.new(2024, 10, 1), Date.new(2024, 12, 31)]
}.freeze
RECARGOS_POR_TIPO =

Ap. 15.3. Qué recargo de equivalencia admite cada tipo impositivo. Cada entrada es [ventana, recargos]; ventana nil significa "siempre".

Solo se exige lo que la norma afirma. Para el 0 % y el 5 % fuera de sus ventanas el texto no dice nada, y ahí NO se inventa una restricción: ser más estricto que la norma ya bloqueó casos válidos antes en esta gema.

{
  '21.00' => [[nil, %w[5.20 1.75]]],
  '10.00' => [[nil, %w[1.40]]],
  '7.50' => [[nil, %w[1.00]]],
  '4.00' => [[nil, %w[0.50]]],
  '2.00' => [[nil, %w[0.26]]],
  '5.00' => [[[nil, Date.new(2022, 12, 31)], %w[0.50]],
             [[Date.new(2023, 1, 1), Date.new(2024, 9, 30)], %w[0.62]]],
  '0.00' => [[[Date.new(2023, 1, 1), Date.new(2024, 9, 30)], %w[0.00]]]
}.freeze
CAMPOS_DE_SUJECION =

Campos que solo tienen sentido en una operación sujeta y no exenta.

%i[tipo_impositivo cuota_repercutida
tipo_recargo cuota_recargo].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_imponible:, calificacion: nil, exenta: nil, tipo_impositivo: nil, cuota_repercutida: nil, impuesto: '01', clave_regimen: '01', tipo_recargo: nil, cuota_recargo: nil) ⇒ Detalle

calificacion y exenta son excluyentes: el XSD las modela como .



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/verifactu_rails/desglose.rb', line 75

def initialize(base_imponible:, calificacion: nil, exenta: nil,
               tipo_impositivo: nil, cuota_repercutida: nil,
               impuesto: '01', clave_regimen: '01',
               tipo_recargo: nil, cuota_recargo: nil)
  if calificacion.nil? == exenta.nil?
    raise ValidacionError,
          'Indica exactamente una de calificacion: o exenta: ' \
          '(el esquema las modela como choice, no caben las dos ni ninguna)'
  end

  @base_imponible = Importe.formatear(base_imponible)
  @calificacion = calificacion && Formato.enumerado(calificacion, 'CalificacionOperacion', CALIFICACIONES)
  @impuesto = impuesto && Formato.enumerado(impuesto, 'Impuesto', IMPUESTOS)
  @exenta = exenta && Formato.enumerado(exenta, 'OperacionExenta', exenciones_admitidas)
  # Sin normalizar todavía: con Impuesto=05 no cabe ninguna clave, y un
  # enumerado de lista vacía daría "Admitidos: " en lugar del motivo real.
  # validar_clave_regimen! resuelve primero ese caso.
  @clave_regimen = clave_regimen&.to_s
  @tipo_impositivo = tipo_impositivo && Importe.porcentaje(tipo_impositivo, 'TipoImpositivo')
  @cuota_repercutida = cuota_repercutida && Importe.formatear(cuota_repercutida)
  @tipo_recargo = tipo_recargo && Importe.porcentaje(tipo_recargo, 'TipoRecargoEquivalencia')
  @cuota_recargo = cuota_recargo && Importe.formatear(cuota_recargo)

  validar_coherencia!
end

Instance Attribute Details

#base_imponibleObject (readonly)

Returns the value of attribute base_imponible.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def base_imponible
  @base_imponible
end

#calificacionObject (readonly)

Returns the value of attribute calificacion.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def calificacion
  @calificacion
end

#clave_regimenObject (readonly)

Returns the value of attribute clave_regimen.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def clave_regimen
  @clave_regimen
end

#cuota_recargoObject (readonly)

Returns the value of attribute cuota_recargo.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def cuota_recargo
  @cuota_recargo
end

#cuota_repercutidaObject (readonly)

Returns the value of attribute cuota_repercutida.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def cuota_repercutida
  @cuota_repercutida
end

#exentaObject (readonly)

Returns the value of attribute exenta.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def exenta
  @exenta
end

#impuestoObject (readonly)

Returns the value of attribute impuesto.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def impuesto
  @impuesto
end

#tipo_impositivoObject (readonly)

Returns the value of attribute tipo_impositivo.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def tipo_impositivo
  @tipo_impositivo
end

#tipo_recargoObject (readonly)

Returns the value of attribute tipo_recargo.



70
71
72
# File 'lib/verifactu_rails/desglose.rb', line 70

def tipo_recargo
  @tipo_recargo
end

Instance Method Details

#a_paresObject

Orden EXACTO de sf:DetalleType ().



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/verifactu_rails/desglose.rb', line 117

def a_pares
  pares = {}
  pares['Impuesto'] = impuesto if impuesto
  pares['ClaveRegimen'] = clave_regimen if clave_regimen
  if exenta?
    pares['OperacionExenta'] = exenta
  else
    pares['CalificacionOperacion'] = calificacion
  end
  pares['TipoImpositivo'] = tipo_impositivo if tipo_impositivo
  pares['BaseImponibleOimporteNoSujeto'] = base_imponible
  pares['CuotaRepercutida'] = cuota_repercutida if cuota_repercutida
  pares['TipoRecargoEquivalencia'] = tipo_recargo if tipo_recargo
  pares['CuotaRecargoEquivalencia'] = cuota_recargo if cuota_recargo
  pares
end

#exenta?Boolean

Returns:

  • (Boolean)


101
# File 'lib/verifactu_rails/desglose.rb', line 101

def exenta? = !@exenta.nil?

#iva?Boolean

si no se informa, se asume IVA

Returns:

  • (Boolean)


134
# File 'lib/verifactu_rails/desglose.rb', line 134

def iva? = impuesto.nil? || impuesto == '01' # si no se informa, se asume IVA

#validar_en_fecha!(fecha) ⇒ Object

Reglas del ap. 15.1 y 15.3 que dependen de la fecha de la operación, que esta clase no conoce: la aporta RegistroAlta, que es quien sabe si hay FechaOperacion o hay que caer en FechaExpedicionFactura. Se deja aquí y no allí para que la tabla viva junto al resto de reglas del desglose.

Ambas cuelgan de "Impuesto = 01 (IVA) y CalificacionOperacion = S1".



109
110
111
112
113
114
# File 'lib/verifactu_rails/desglose.rb', line 109

def validar_en_fecha!(fecha)
  return unless iva? && calificacion == 'S1'

  validar_ventana_tipo!(fecha)
  validar_recargo_en_fecha!(fecha)
end