← Gemas Ruby on Rails

Verifactu Rails

Integración con VERIFACTU (AEAT) para aplicaciones Rails: libro registro encadenado, remisión y reconciliación

v0.1.0-pre Ruby >= 3.0, Rails >= 7.0 GitHub ↗ RubyGems ↗

verifactu-rails

Integración con VERI*FACTU (AEAT) para aplicaciones Rails: libro registro encadenado, remisión a la AEAT y reconciliación de lo anotado.

Es una librería, no un sistema de facturación: calcula la huella encadenada, genera el XML de los registros y habla con la AEAT por TLS mutuo. No numera facturas, no calcula impuestos y no emite PDF.

Puesta en marcha

1. Identifica tu SIF

En el initializer que dejó el generador. Describe tu sistema, no esta gema: verifactu-rails es un componente del SIF, no el SIF. Viene con valores inválidos a propósito, para que falle pronto en vez de remitir una identificación inventada.

VerifactuRails::Libro.configure do |c|
  c.productor_nombre = 'Tu Empresa SL'   # quién PRODUCE el software
  c.productor_nif    = '89890001K'
  c.nombre_sistema   = 'TuFactura'
  c.id_sistema       = '01'              # dos posiciones, mayúscula o dígito
  c.version          = TuApp::VERSION
  c.entorno          = Rails.env.production? ? :produccion : :pruebas

  # Las anomalías del art. 7.i) no interrumpen la facturación, pero si no las
  # mandas a algún sitio que mires, se pierden.
  c.al_detectar_anomalia = ->(anomalias, registro) { Rails.logger.warn(...) }
end

2. Abre una cadena por fuente de facturación

Una vez, y a conciencia: en un seed, una tarea rake o tu panel de administración. Nunca en un initializer. Cada tienda, TPV o sede es un SIF virtual distinto y lleva su propia cadena.

VerifactuRails::Libro::Cadena.abrir!(
  numero_instalacion: 'TIENDA-VALENCIA-20260807120000',
  nif_obligado: '89890001K', nombre_obligado: 'Tu Empresa SL'
)

El numero_instalacion no se autogenera nunca y no se reutiliza jamás, ni al reinstalar el mismo software en la misma máquina. Guárdalo en tu modelo (la tienda, el TPV) para recuperar la cadena después.

3. Anota cada factura

cadena = VerifactuRails::Libro::Cadena.find_by!(numero_instalacion: tienda.numero_instalacion)

registro = cadena.anotar_alta!(
  id_emisor: '89890001K', num_serie: 'FA/2026/0001',
  fecha_expedicion: Date.current, nombre_razon_emisor: 'Tu Empresa SL',
  tipo_factura: 'F1', descripcion_operacion: 'Servicios de agosto',
  desglose: [VerifactuRails::Detalle.new(base_imponible: BigDecimal('100.00'),
                                         calificacion: 'S1',
                                         tipo_impositivo: BigDecimal('21.00'),
                                         cuota_repercutida: BigDecimal('21.00'))],
  cuota_total: BigDecimal('21.00'), importe_total: BigDecimal('121.00'),
  fecha_hora_gen: Time.now,
  destinatarios: [VerifactuRails::Destinatario.new(nombre_razon: 'Cliente SL',
                                                   nif: '89890002E')]
)

registro.qr_url   # ya disponible: el QR no depende de la AEAT

Calcula la huella, encadena y genera el QR en una transacción con la fila de la cadena bloqueada. Es síncrono a propósito: el encadenamiento no se puede diferir, aunque el envío sí. También hay anotar_anulacion!.

4. Envía

class EnviarRemesaJob < ApplicationJob
  def perform(numero_instalacion)
    cadena = VerifactuRails::Libro::Cadena.find_by!(numero_instalacion: numero_instalacion)
    certificado = VerifactuRails::Certificado.desde_pkcs12(bytes_del_p12, password)
    transporte  = VerifactuRails::Transporte.new(
      certificado: certificado, entorno: VerifactuRails::Libro.configuracion.entorno
    )

    VerifactuRails::Libro::Remesa.new(cadena, transporte: transporte).enviar!
    # => #<Resultado estado: :enviado|:esperando|:nada_pendiente|:bloqueada_por_rechazo>
  end
end

La remesa no recibe registros: coge de la base de datos lo que esa cadena tenga pendiente. Encólalo tras anotar_alta! y ponle además un cron de seguridad por si un job se perdió; es idempotente, y un duplicado cuenta como éxito.

Construye certificado y transporte dentro del job, no al arrancar: Transporte no guarda conexión (reutilizarlo no ahorra ningún handshake) y Certificado comprueba la caducidad al construirse, que es un aviso que pierdes si lo cacheas durante meses.

Leer más en Github: https://github.com/sergiovaleromiacade/verifactu-rails

gem install Verifactu Rails