Verifactu Rails
Integración con VERIFACTU (AEAT) para aplicaciones Rails: libro registro encadenado, remisión y reconciliación
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(...) }
end2. 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 AEATCalcula 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
endLa 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