VeriFactu Wiki

Repositorio en Github
Irene Solutions SLAPI REST

Esta biblioteca tiene como objetivo facilitar la generación, conservación y envío de registros de facturación a la AEAT, cumpliendo con los requisitos del sistema VERI*FACTU.

VERI*FACTU es un sistema diseñado para combatir el fraude fiscal, centrado específicamente en la gestión del IVA/IGIC.

Se trata de un sistema cuya finalidad es combatir el fraude fiscal, y por lo tanto en eso se basan sus especificaciones. Concretamente, se circunscribe al ámbito del IVA / IGIC.

VERI*FACTU no es un sistema de facturación electrónica, como EDIFACT o Facturae. Tampoco incluye:

Por lo tanto no se trata de una especificación más de factura electrónica. Se trata de un detalle de líneas que recogen las distintas bases imponibles, tipos y cuotas tributarias, las cuales deben ser comunicadas en tiempo real a la AEAT, el cual es inalterable respecto a los siguientes datos:

  1. NIF del emisor.
  2. Numero de factura y serie.
  3. Fecha de expedición de la factura.
  4. Tipo de factura.
  5. Cuota total.
  6. Importe total.
  7. Fecha, hora y huso horario de generación del registro.

Así como respecto al orden de generación de los documentos. VERI*FACTU utiliza tecnología Blockchain para garantizar la inalterabilidad de los registros. Cada registro incluye la huella del anterior, creando una cadena segura e ininterrumpida de datos.

Esta se trata de la segunda aplicación de esta tecnología a nivel tributario, tras el proyecto Batuz iniciado en el ámbito foral del País Vasco hace ya un tiempo.

No todo el mundo está obligado a utilizar el sistema definido en esta nueva normativa:

No obligados: Si está en el SII, si no actúa como empresario (alquiler de locales o viviendas por parte de particulares...) o si emite facturas en papel sin el uso de ningún sistema informático.

Si es un empresario no acogido al SII que utiliza un sistema informático para la emisión de sus facturas, este sistema tiene que cumplir con lo establecido en VERI * FACTU.

Respecto a los plazos de adopción los desarrolladores tenemos hasta el 29 de julio de 2025 para adaptar nuestros sistemas. En cuanto a los empresarios están aún por confirmar, pero en principio empresas a partir del 1 de enero de 2026 y autónomos a partir del 1 de julio de 2026.

Plazos de adopción:

Tabla de contenidos

A continuación, se detallan los apartados principales de esta documentación:

Configuración
Sistema de archivos
Operaciones envío
Operaciones cadena de bloques
Operaciones huella
Operaciones código QR
Control de flujo

Trabajo con diferentes certificados por OT o configuraciones distintas

API REST

Ejemplos altas

Interoperabilidad COM

Reenvío de una factura no enviada por fallo en las comunicaciones

Configuración

La configuración del sistema se almacena en un archivo xml que tiene por defecto el nombre de 'Settings.xml'. La ubicación del archivo de configuración se encuentra en la siguiente carpeta del sistema:

// Carpeta del sistema dónde se ubica el archivo Settings.xml var settingsFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + System.IO.Path.DirectorySeparatorChar + "VeriFactu";

Por lo general en sistemas Windows 'C:\ProgramData\VeriFactu' y en sistemas linux '/usr/share/VeriFactu'. Las distintas variables de configuración resultan resumidas en la siguiente tabla:

Propiedad Descripción
BlockchainPath Ruta al directorio que actuará como almacén de las distintas cadenas de bloques por emisor.
CertificatePassword Password del certificado. Este valor sólo es necesario si tenemos establecido el valor para 'CertificatePath' y el certificado tiene clave de acceso. Sólo se utiliza en los certificados cargados desde el sistema de archivos.
CertificatePath Ruta al archivo del certificado a utilizar. Sólo se utiliza en los certificados cargados desde el sistema de archivos.
CertificateSerial Número de serie del certificado a utilizar. Mediante este número de serie se selecciona del almacén de certificados de windows el certificado con el que realizar las comunicaciones.
CertificateThumbprint Hash o Huella digital del certificado a utilizar. Mediante esta huella digital se selecciona del almacén de certificados de windows el certificado con el que realizar las comunicaciones.
IDVersion Identificación de la versión actual del esquema o estructura de información utilizada para la generación y conservación / remisión de los registros de facturación. Este campo forma parte del detalle de las circunstancias de generación de los registros de facturación. Alfanumérico(3) L15: 1.0: Versión actual (1.0) del esquema utilizado
InboxPath Ruta al directorio que actuará como bandeja de entrada. En este directorio se almacenarán todos los mensajes recibidos de la AEAT mediante VERI*FACTU.
InvoicePath Ruta al directorio que actuará almacenamiento de las facturas emitidas por emisor.
LoggingEnabled Indica si está activado el log de mensajes del sistema.
LogPath Ruta al directorio que actuará almacenamiento del registro de mensajes del sistema.
OutboxPath Ruta al directorio que actuará como bandeja de salida. En este directorio se almacenará una copia de cualquier envío realizado a la AEAT mediante el VERI * FACTU.
SistemaInformatico Datos del sistema informático.
SkipNifAeatValidation Indica si salta la validación en línea de NIF con la AEAT.
SkipViesVatNumberValidation Indica si salta la validación en línea de en el censo VIES de los VAT numbers intracomunitarios.
VeriFactuEndPointPrefix EndPoint del web service de la AEAT para envío registros alta y anulación.
VeriFactuEndPointValidatePrefix EndPoint del web service de la AEAT de validación de Verifactu.
VeriFactuHashAlgorithm Algoritmo a utilizar para el cálculo de hash. Alfanumérico(2) L12.
VeriFactuHashInputEncoding Codificación del texto de entrada para el hash. UTF8 según las especificaciones.

Las propiedades del bloque SistemaInformatico de la clase Settings son las siguientes:

Propiedad Descripción
NombreRazon Nombre productor sistema. Alfanumérico (120).
NIF NIF productor sistema. FormatoNIF(9).
NombreSistemaInformatico Nombre dado por el productor al sistema informático de facturación utilizado. Alfanumérico (30).
IdSistemaInformatico Código ID del sistema informático de facturación utilizado. Alfanumérico(2).
Version Identificación de la Versión del sistema de facturación utilizado. Alfanumérico(50).
NumeroInstalacion Número de instalación del sistema informático de facturación (SIF) utilizado. Deberá distinguirlo de otros posibles SIF utilizados para realizar la facturación del obligado a expedir facturas, es decir, de otras posibles instalaciones de SIF pasadas, presentes o futuras utilizadas para realizar la facturación del obligado a expedir facturas, incluso aunque en dichas instalaciones se emplee el mismo SIF de un productor. Alfanumérico(100).
TipoUsoPosibleSoloVerifactu Especifica si para cumplir el Reglamento el sistema informático de facturación solo puede funcionar exclusivamente como Veri*Factu. Alfanumérico (1) L4: S: Sí / N: No. En el caso de esta biblioteca siempre debe ser 'S'.
TipoUsoPosibleMultiOT Especifica si el sistema informático de facturación permite llevar independientemente la facturación de varios obligados tributarios (valor "S") o solo de uno (valor "N"). Obligatorio en registros de facturación de alta y de anulación, y opcional en registros de evento. Alfanumérico (1) L4: S: Sí / N: No. En el caso de esta biblioteca siempre debe ser 'S'.
IndicadorMultiplesOT Indicador de que el sistema informático, en el momento de la generación de este registro, está soportando la facturación de más de un obligado tributario. Este valor deberá obtenerlo automáticamente el sistema informático a partir del número de obligados tributarios contenidos y/o gestionados en él en ese momento, independientemente de su estado operativo (alta, baja...), no pudiendo obtenerse a partir de otra información ni ser introducido directamente por el usuario del sistema informático ni cambiado por él. El valor "N" significará que el sistema informático solo contiene y/o gestiona un único obligado tributario (de alta o de baja o en cualquier otro estado), que se corresponderá con el obligado a expedir factura de este registro de facturación. En cualquier otro caso, se deberá informar este campo con el valor "S". Obligatorio en registros de facturación de alta y de anulación, y opcional en registros de evento.

Sistema de archivos

El software almacena en disco todos los xml relacionados con los mensajes emitidos y recibidos de la AEAT; Así como todos los xml de registro de manera individual (alta o anulación). También se almacenan los datos de la cadena de bloques en formato csv. El sistema de log en caso de estar activado guarda los mensajes del sistema en archivos de texto en la carpeta asignada en la configuración.

El directorio por defecto de almacenamiento de todos los ficheros es la siguiente carpeta del sistema:

// Carpeta del sistema dónde se ubica el archivo Settings.xml var settingsFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + System.IO.Path.DirectorySeparatorChar + "VeriFactu";

Por lo general en sistemas Windows 'C:\ProgramData\VeriFactu' y en sistemas linux '/usr/share/VeriFactu'. Las subcarpetas de datos por defecto son:

Respecto a las carpetas Inbox, Outbox e Invoices, los datos se almacenan siguiendo una estructura de subcarpetas Emisor > Año. Se crea una subcarpeta por cada emisor. Dentro de la subcarpeta de cada emisor, se almacenan los datos en subcarpetas por años (el año se extrae de la fecha del cálculo de la huella del registro relacionado con el archivo). La información en la carpeta Blockchains se almacena en subcarpetas por emisor.

Operaciones envío

La capa de negocio de la aplicación utiliza el objeto Invoice para representar una factura, sobre la cual podemos realizar distintos tipos de operaciones. La clase Invoice contiene la información de una factura relacionada con las especificaciones de Verifactu (vendedor, comprador, número y fecha factura y detalle de bases imponibles, tipos y cuotas). Las operaciones de alta y anulación de una factura en Verifactu toman como parámetro de entrada una instancia del objeto Invoice. Estas operaciones se representan con los objetos InvoiceEntry e InvoiceCancellation para los registros de alta y cancelación respectivamente.

// Creación de una instancia de Invoice var invoice = new Invoice("GIT-EJ-0001", new DateTime(2024, 11, 17), "B12959755") { InvoiceType = TipoFactura.F1, SellerName = "IRENE SOLUTIONS SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } };

Alta

Para crear y enviar un registro de alta, lo primero que debemos hacer es crear la instancia del objeto Invoice a enviar; para realizar el envío creamos una instancia del la clase InvoiceEntry, pasándole en el constructor como parámetro de entrada la instancia de Invoice a enviar. Una vez creada nuestra instancia de InvoiceEntry podemos contabilizarla en la cadena de bloques del vendedor y enviarla utilizando el método Save.

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ-0002", new DateTime(2024, 11, 15), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el estado Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Status}"); if (invoiceEntry.Status == "Correcto") { // Consultamos el CSV Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.CSV}"); } else { // Consultamos el error Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.ErrorCode}: {invoiceEntry.ErrorDescription}"); } // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Anulación

Para crear y enviar un registro de anulación de un alta previamente envíada, lo primero que debemos hacer es crear la instancia del objeto Invoice a enviar; para realizar el envío creamos una instancia del la clase InvoiceCancellation, pasándole en el constructor como parámetro de entrada la instancia de Invoice cuyo registro de anulación queremos enviar. Una vez creada nuestra instancia de InvoiceCancellation podemos contabilizarla en la cadena de bloques del vendedor y enviarla utilizando el método Save.

// Creamos una instacia de la clase factura con los datos del documento a anular var invoice = new Invoice("GIT-EJ-0002", new DateTime(2024, 11, 15), "B72877814") { SellerName = "WEFINZ GANDIA SL", }; // Creamos la cancelación de la factura var invoiceCancellation = new InvoiceCancellation(invoice); // Guardamos la cancelación factura invoiceCancellation.Save(); // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceCancellation.Response}");

Operaciones cadena de bloques

[!CAUTION] Por lo general la biblioteca se encarga de realizar las operaciones en la cadena de bloques por nosotros. Es importante señalar que el contenido del presente apartado no es necesario para el uso normal de la biblioteca, y que está redactado con la finalidad de documentar en profundidad el funcionamiento de la misma. Si no es un usuario avanzado no realice modificaciones sobre las cadenas de bloques o de lo contrario podría romper el encadenamiento.

Como indicábamos en la sección del sistema de archivos, la información de las cadenas de bloques de almacena en la carpeta Blockchains cuya ruta se encuentra en el archivo de configuración. La información se almacena en formato csv en archivos con la siguiente nomenclatura:

Clase Blockchain

La clase Blockchain es la encargada de la gestión de la cadena de bloques de un emisor de facturas concreto. Únicamente puede existir una instancia de está clase por vendedor durante la ejecución del programa. La forma de obtener la instancia de esta clase para un vendedor concreto es mediante el método Get de la clase:

// Obtenemos el controlador de la cadena de bloques del vendedor Irene Solutions SL // mediante el método Get, al cual la pasamos el NIF de Irene Solutions SL var sellerBlockchain = Blockchain.Get("B12959755");

Propiedades

Propiedad Descripción
BlockchainDataFileName Archivo que almacena una porción del Blockchain correspondiente a los movimientos de un mes.
BlockchainDataPreviousFileName Archivo copia de seguridad que almacena una porción del Blockchain correspondiente a los movimientos de un mes excepto el último movimiento. Es la copia del archivo con el nombre BlockchainDataFileName antes del registro del último movimiento.
BlockchainPath Path del directorio de archivado de los datos de la cadena.
BlockchainVarFileName Archivo que almacena el valor de las variables en curso de la cadena.
Current Último elemento de la cadena.
CurrentID Identificador del último eslabón de la cadena.
CurrentTimeStamp Momento de generación del último eslabón de la cadena.
Initialized Indica si el sistema de cadena de bloques está inicializado.
Key Identificador único de la instancia. Se hereda de SingletonByKey<T>.
Previous Penúltimo elemento de la cadena.
PreviousID Identificador del penúltimo eslabón de la cadena.
PreviousTimeStamp Momento de generación del penúltimo eslabón de la cadena.
SellerID Identificador del vendedor. Debe utilizarse el identificador fiscal si existe (NIF, VAT Number...). En caso de no existir, se puede utilizar el número DUNS o cualquier otro identificador acordado.

Métodos

Método Descripción
Add(List<Registro> ) Añade una lista de elementos a la cadena de bloques.
Add(Registro) Añade un elemento a la cadena de bloques.
Delete Elimina el último elememto añadido a la cadena.
Get Devuelve la instancia correspondiente a la cadena de bloques de un emisor de facturas.

Ejemplo inicialización de la cadena de bloques de un vendedor

// Creamos una instacia de la clase factura (primera factura) var invoiceFirst = new Invoice("TEST001", new DateTime(2024, 10, 14), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, TaxType = CalificacionOperacion.S1, TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, TaxType = CalificacionOperacion.S1, TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Obtenemos una instancia de la clase RegistroAlta a partir de // la instancia del objeto de negocio Invoice var registroFirst = invoiceFirst.GetRegistroAlta(); // Ahora obtenemos el controlador de la cadena de bloques del vendedor var blockchain = Blockchain.Get(invoiceFirst.SellerID); // Añadimos el registro de alta blockchain.Add(registroFirst); // Creamos una instacia de la clase factura (segunda factura) var invoiceSecond = new Invoice("TEST002", new DateTime(2024, 10, 14), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, TaxType = CalificacionOperacion.S1, TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, TaxType = CalificacionOperacion.S1, TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Obtenemos una instancia de la clase RegistroAlta a partir de // la instancia del objeto de negocio Invoice var registroSecond = invoiceSecond.GetRegistroAlta(); // Añadimos el registro de alta blockchain.Add(registroSecond); Debug.Print($"La huella de la primera factura es: {registroFirst.GetHashOutput()}"); Debug.Print($"La huella de la segunda factura es: {registroSecond.GetHashOutput()}");

Contabilización de una factura

El proceso de contabilización de una factura se realiza mediante el método Save de las clases InvoiceEntry o InvoiceCancellation. Este proceso realiza las siguientes operaciones:

  1. Añade el registro de la factura a la cadena de bloques, lo cual actualiza su fecha de generación y su huella.
  2. Obtiene el xml del registro y lo almacena en la carpeta de almacenamiento de registros establecida en la configuración.
  3. Marca el objeto InvoiceEntry o InvoiceCancellation como contabilizado (Posted = true).
  4. Realiza el envío a la AEAT y procesa la respuesta almacenándola también el el sistema de archivos.

Contabilización de varias facturas

Este mecanismo se utiliza en las colas diseñadas para la gestión de flujo.

Eliminación de facturas

Tanto los registros de alta como los de cancelación tienen la consideración de facturas para la biblioteca. Cuando un registro de alta es erróneo y ha sido transmitido a la AEAT, debemos realizar el envío de cancelación del mismo, lo cual no supone "eliminar" el registro de la cadena de bloques. En caso de que necesitara realizar una operación de este tipo, póngase en contacto con nosotros support@irenesolutions.com, para que podamos examinar el caso en profundidad en busca de la solución.

Operaciones huella

Generalmente no deberemos preocuparnos por la gestión de la cadena de bloques, ya que el programa se encarga de realizar las operaciones necesarias. Si queremos realizar el cálculo del valor del hash para un registro de alta o anulación determinado, lo podemos hacer del siguiente modo:

// Creamos una instacia de la clase factura var invoice = new Invoice("GITHUB-EJ-003", new DateTime(2024, 11, 4), "B72877814") { BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Obtenemos una instancia de la clase RegistroAlta a partir de // la instancia del objeto de negocio Invoice var registro = invoice.GetRegistroAlta(); // El registro no ha sido envíado, pero forzamos el valor de // FechaHoraHusoGenRegistro para que coincida con el últomo envío a la AEAT var fechaHoraHusoGenRegistro = new DateTime(2024, 11, 4, 12, 36, 39); //2024-01-01T19:20:30+01:00 en España peninsula registro.FechaHoraHusoGenRegistro = XmlParser.GetXmlDateTimeIso8601(fechaHoraHusoGenRegistro); // Establecemos el valor del encadenamiento anterior registro.Encadenamiento = new Encadenamiento() { RegistroAnterior = new RegistroAnterior() { Huella = "8C8DCEFB120522E0C71BC19902F44D5334FF6C98E74F0E3AC1D1E5A30C2EA836" } }; // Obtenemos el valor de la huella var hash = registro.GetHashOutput(); // 4EECCE4DD48C0539665385D61D451BA921B7160CA6FEF46CD3C2E2BC5C778E14

Operaciones código QR

1. Obtención de la «URL» de cotejo o remisión de información de la factura contenida en el código «QR»

En este ejemplo obtendremos la url para el servicio de validación de una factura envíada al entorno de pruebas de la AEAT.

// Creamos una instacia de la clase factura var invoice = new Invoice("GITHUB-EJ-004", new DateTime(2024, 11, 4), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Obtenemos una instancia de la clase RegistroAlta a partir de // la instancia del objeto de negocio Invoice var registro = invoice.GetRegistroAlta(); // Obtenemos la url var urlValidacion = registro.GetUrlValidate(); //https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?nif=B72877814&numserie=GITHUB-EJ-004&fecha=04-11-2024&importe=131.4

2. Obtención de Bitmap con el QR con la URL de cotejo o remisión de información de la factura

En este ejemplo obtendremos la imágen del QR de la url para el servicio de validación de una factura envíada previamente al entorno de pruebas de la AEAT:

image

// Creamos una instacia de la clase factura var invoice = new Invoice("GITHUB-EJ-004", new DateTime(2024, 11, 4), "B72877814") { SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Obtenemos una instancia de la clase RegistroAlta a partir de // la instancia del objeto de negocio Invoice var registro = invoice.GetRegistroAlta(); // Obtenemos la imágen del QR var bmQr = registro.GetValidateQr(); File.WriteAllBytes(@"C:\Users\usuario\Downloads\ValidateQrSampe.bmp", bmQr);

Control de flujo

La AEAT establece en sus especificaciones, que la aplicación debe disponer de un control de flujo que respete los tiempos de espera entre envíos. Estos tiempos vienen en las respuestas que remite. El sistema informático debe esperar antes de realizar otro envío a que transcurra este tiempo o se alcancen los 1.000 registros pendientes de envío. En el siguiente ejemplo veremos como podemos gestionar este flujo con Verifactu:

// En este ejemplo añadimos a la cola de envío de registros 1.005 registros de // alta y 10 registros de cancelación (de los 10 primeros registros de alta) // Si activamos el log podremos revisar en el mismo que el sistema realiza 2 // envíos. El primero al alcanzar los 1.000 resgistros, y el segundo al transcurrir // el tiempo de espera establecido en la respuesta a la primera llamada. // Activo el log Settings.Current.LoggingEnabled = true; var testId = "08"; int start = 0; // Deshabilito la validación de NIF en linea con la AEAT Settings.Current.SkipNifAeatValidation = true; // Deshabilito la validación de NIFs intracomunitarios Settings.Current.SkipViesVatNumberValidation = true; // Añado 1.005 registros de alta for (int i = 1; i < 1006; i++) { // Creamos una instacia de la clase factura var invoice = new Invoice($"TEST{testId}" + $"{start + i}".PadLeft(8, '0'), new DateTime(2024, 10, 29), "B10795649") { InvoiceType = TipoFactura.F1, SellerName = "KIVU SOLUTIONS SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, TaxType = CalificacionOperacion.S1, TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, TaxType = CalificacionOperacion.S1, TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Creamos el documento de alta Debug.Print($"Añadiendo factura {invoice} {DateTime.Now}"); var invoiceEntry = new InvoiceEntry(invoice); // Añadimos el documentos a la cola de procesamiento: // En la cola se irán realizando los envíos cuando // los documentos en espera sean 1.000 o cuando el // tiempo de espera haya finalizado InvoiceQueue.ActiveInvoiceQueue.Add(invoiceEntry); } // Añado la cancelación de los primeros 10 registros for (int i = 1; i < 11; i++) { // Creamos una instacia de la clase factura var invoice = new Invoice($"TEST{testId}" + $"{start + i}".PadLeft(8, '0'), new DateTime(2024, 10, 29), "B10795649") { InvoiceType = TipoFactura.F1, SellerName = "KIVU SOLUTIONS SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, TaxType = CalificacionOperacion.S1, TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, TaxType = CalificacionOperacion.S1, TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Creamos el documento de alta Debug.Print($"Añadiendo factura {invoice} {DateTime.Now}"); var invoiceCencellation = new InvoiceCancellation(invoice); // Añadimos el documentos a la cola de procesamiento: // En la cola se irán realizando los envíos cuando // los documentos en espera sean 1.000 o cuando el // tiempo de espera haya finalizado InvoiceQueue.ActiveInvoiceQueue.Add(invoiceCencellation); }

Al ejecutar este código, si tenemos el log activado obtendremos la siguiente información en el mismo:

[000001] 2024-11-15 17:31:48: Ejecutando por cola (B10795649) tras tiempo espera en segundos: 60 desde 01/01/0001 0:00:00 hasta 01/01/0001 0:01:00 [000002] 2024-11-15 17:31:48: Actualizando datos de la cadena de bloques (B10795649) en 1000 elementos 15/11/2024 17:31:48 [000003] 2024-11-15 17:31:49: Finalizada actualización de datos de la cadena de bloques en 1000 elementos 15/11/2024 17:31:49 [000004] 2024-11-15 17:31:49: Enviando datos a la AEAT B10795649 de 1000 elementos 15/11/2024 17:31:49 [000005] 2024-11-15 17:31:52: Finalizado envío de datos B10795649 a la AEAT de 1000 elementos (quedan 15 registros) 15/11/2024 17:31:52 [000006] 2024-11-15 17:31:52: Establecido momento próxima ejecución B10795649 (LastProcessMoment: 15/11/2024 17:31:52 + CurrentWaitSecods: 60) = 15/11/2024 17:32:52 [000007] 2024-11-15 17:32:53: Ejecutando por cola (B10795649) tras tiempo espera en segundos: 60 desde 15/11/2024 17:31:52 hasta 15/11/2024 17:32:52 [000008] 2024-11-15 17:32:53: Actualizando datos de la cadena de bloques (B10795649) en 15 elementos 15/11/2024 17:32:53 [000009] 2024-11-15 17:32:53: Finalizada actualización de datos de la cadena de bloques en 15 elementos 15/11/2024 17:32:53 [000010] 2024-11-15 17:32:53: Enviando datos a la AEAT B10795649 de 15 elementos 15/11/2024 17:32:53 [000011] 2024-11-15 17:32:53: Finalizado envío de datos B10795649 a la AEAT de 15 elementos (quedan 0 registros) 15/11/2024 17:32:53 [000012] 2024-11-15 17:32:53: Establecido momento próxima ejecución B10795649 (LastProcessMoment: 15/11/2024 17:32:53 + CurrentWaitSecods: 60) = 15/11/2024 17:33:53

[!IMPORTANT] Es importante señalar que el control de flujo se desarrolla en un hilo diferente al principal. Es importante asegurarnos, antes de finalizar la ejecución de nuestra aplicación en el hilo principal, de que finalizamos el proceso de control de flujo.

El control de flujo se finaliza de la siguiente manera:

// Cerramos la cola asegurandonos previamente de que no queda nada pendiente if (InvoiceQueue.ActiveInvoiceQueue.Count == 0) InvoiceQueue.Exit();

Trabajo con diferentes certificados por OT o configuraciones distintas

En ocasiones podemos tener la necesidad de trabajar con distintas opciones de configuración, por ejemplo, podemos tener dos empresas en nuestro sistema de facturación, y desearíamos trabajar en una de ellas en el entorno de pruebas y en la otra en el entorno de producción de la AEAT. Otro problema habitual puede ser el que no dispongamos de un certificado digital de colaborador social o empresa fabricante de software que autorizado a realizar envíos en nombre de varios obligados tributarios (OT).

Una aproximación para resolver este tipo de situaciones es la de utilizar un archivo de configuración distinto por cada obligado tributario (OT), e ir cargando la configuración de trabajo del sistema según trabajemos con uno u otro.

[!CAUTION] Es importante asegurarse que la cola de envíos está vacía, si nuestra aplicación hace uso de la funcionalidad de control de flujo.

En el ejemplo siguiente se muestra como se guardan distintos archivos de configuración, y como se establece el uso de uno u otro en la aplicación. Cada vez que se realiza un envío a la AEAT, se carga el certificado establecido en la configuración en ese momento, de modo que cambiando de archivo de configuración podemos (entre otras cosas) cambiar el certificado con el que se va a realizar el envío.

C#

// Trabajos con varios obligados tributarios (OT) utilizando distintos certificados: // Para trabajar con un certificado digital para distintos tributarios necesitaremos // disponer de un archivo de configuración distinto para cada uno. // Posteriormente, en el entorno de ejecución, le diremos a la aplicación con qué // archivo de configuración queremos trabajar. // IMPORTANTE !!!!! // La cola de envíos debe estar vacía antes de cambiar de OT si se van a utilizar // distintos certificados o configuraciónes del sistema diferentes. if (InvoiceQueue.ActiveInvoiceQueue.Count > 0) return; // Si la cola no está vacía cancelamos // En el ejemplo vamos a crear dos archivos de configuración distintos para dos // obligados tributarios B12959755 IRENE SOLUTIONS SL y B44531218 WEFINZ SOLUTIONS SL // 1. Creamos los archivos de configuración, incluyendo en cada uno el certificado // correspondiente al OT Settings.SetConfigFileName("B12959755.xml"); // Establecemos el nombre del archivo de configuración Settings.Current.CertificatePath = @"C:\Certificado_B12959755.pfx"; Settings.Current.CertificatePassword = "password certificado 1"; Settings.Save(); // Guardamos el archivo de configuraciíon 1 Settings.SetConfigFileName("B44531218.xml"); // Establecemos el nombre del archivo de configuración Settings.Current.CertificatePath = @"C:\Certificado_B44531218.p12"; Settings.Current.CertificatePassword = "password certificado 2"; Settings.Save(); // Guardamos el archivo de configuraciíon 2 // Cuando queramos trabajar con la configuración 1 Settings.SetConfigFileName("B12959755.xml"); // Cuando queramos trabajar con la configuración 2 Settings.SetConfigFileName("B44531218.xml");

VB

' Trabajos con varios obligados tributarios (OT) utilizando distintos certificados ' Para trabajar con un certificado digital para distintos tributarios necesitaremos ' disponer de un archivo de configuración distinto para cada uno. ' Posteriormente, en el entorno de ejecución, le diremos a la aplicación con qué ' archivo de configuración queremos trabajar. ' IMPORTANTE !!!!! ' La cola de envíos debe estar vacía antes de cambiar de OT si se van a utilizar ' distintos certificados o configuraciónes del sistema diferentes. If (InvoiceQueue.ActiveInvoiceQueue.Count > 0) Then Exit Sub ' Si la cola no está vacía cancelamos End If ' En el ejemplo vamos a crear dos archivos de configuración distintos para dos ' obligados tributarios B12959755 IRENE SOLUTIONS SL y B44531218 WEFINZ SOLUTIONS SL ' 1. Creamos los archivos de configuración, incluyendo en cada uno el certificado ' correspondiente al OT Settings.SetConfigFileName("B12959755.xml") ' Establecemos el nombre del archivo de configuración Settings.Current.CertificatePath = @"C:\Certificado_B12959755.pfx" Settings.Current.CertificatePassword = "password certificado 1" Settings.Save() ' Guardamos el archivo de configuraciíon 1 Settings.SetConfigFileName("B44531218.xml") ' Establecemos el nombre del archivo de configuración Settings.Current.CertificatePath = @"C:\Certificado_B44531218.p12" Settings.Current.CertificatePassword = "password certificado 2" Settings.Save(); ' Guardamos el archivo de configuraciíon 2 ' Cuando queramos trabajar con la configuración 1 Settings.SetConfigFileName("B12959755.xml") ' Cuando queramos trabajar con la configuración 2 Settings.SetConfigFileName("B44531218.xml")

API REST

La funcionalidad de la biblioteca Verifactu está disponible en línea mediante un API REST. El uso del API REST elimina la necesidad de preocuparse de los certificados digitales, del almacenamiento de las cadenas de bloques y de los documentos xml; Ya que todas estas funciones se realizan en la nube. Los Endpoints disponibles son:

  1. Envío registros alta: https://facturae.irenesolutions.com:8050/Kivu/Taxes/Verifactu/Invoices/Create
  2. Envío registro anulación: https://facturae.irenesolutions.com:8050/Kivu/Taxes/Verifactu/Invoices/Cancel
  3. Generación de código QR: https://facturae.irenesolutions.com:8050/Kivu/Taxes/Verifactu/Invoices/GetQrCode
  4. Consulta de emisores: https://facturae.irenesolutions.com:8050/Kivu/Taxes/Verifactu/Invoices/GetSellers
  5. Consulta de envíos realizados: https://facturae.irenesolutions.com:8050/Kivu/Taxes/Verifactu/Invoices/GetFilteredList

La clase ApiClient del espacio de nombres VeriFactu.Net.Rest encapsula las llamadas al API REST para que se puedan realizar desde el propio software.

[!IMPORTANT]
Es importante antes de utilizar esta clase que hayamos obtenido nuestra ServiceKey accediendo a este
enlace. Una vez tengamos nuestra ServiceKey debemos guardarla en la configuración editando nuestro archivo Settings.xml o mediante programación.

// Guardar ServiceKey Settings.Current.Api.ServiceKey = "My_ServiceKey"; Settings.Save();

Envío de un registro de alta mediante el API REST

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ-00039", new DateTime(2024, 11, 15), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 1000, TaxAmount = 40m }, new TaxItem() { TaxRate = 21, TaxBase = 1000, TaxAmount = 210 } } }; dynamic result = ApiClient.Create(invoice); if (result.ResultCode != 0) { Debug.Print($"Se ha producido un error al llamar al API: {result.ResultMessage}"); } else { var csv = $"{result.Return.CSV}"; if (!string.IsNullOrEmpty(csv)) Debug.Print($"Documento envíado con CSV: {csv}"); else Debug.Print($"El envío no se ha realizado con éxito: {result.Return.ErrorDescription}"); }

Envío de un registro de anulación mediante el API REST

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ-00039", new DateTime(2024, 11, 15), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL" }; dynamic result = ApiClient.Delete(invoice); if (result.ResultCode != 0) { Debug.Print($"Se ha producido un error al llamar al API: {result.ResultMessage}"); } else { var csv = $"{result.Return.CSV}"; if (!string.IsNullOrEmpty(csv)) Debug.Print($"Documento anulado con CSV: {csv}"); else Debug.Print($"El envío de anulación no se ha realizado con éxito: {result.Return.ErrorDescription}"); }

Obtención del código QR mediante el API REST

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ-00039", new DateTime(2024, 11, 15), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 1000, TaxAmount = 40m }, new TaxItem() { TaxRate = 21, TaxBase = 1000, TaxAmount = 210 } } }; dynamic result = ApiClient.GetQr(invoice); if (result.ResultCode != 0) { Debug.Print($"Se ha producido un error al llamar al API: {result.ResultMessage}"); } else { var bitMapBytes = Convert.FromBase64String(result.Return); Bitmap bm = new Bitmap(new MemoryStream(bitMapBytes)); }

Consulta de los emisores de factura mediante el API REST

dynamic result = ApiClient.GetSellers(); if (result.ResultCode != 0) { Debug.Print($"Se ha producido un error al llamar al API: {result.ResultMessage}"); } else { foreach (var seller in result.Return) Debug.Print($"{seller.SellerID}: {seller.CompanyName}"); }

Ejemplo factura simplificada API REST

// Creamos una instacia de la clase factura var invoice = new Invoice("FEJ060", new DateTime(2024, 11, 15), "B72877814") { InvoiceType = TipoFactura.F2, SellerName = "WEFINZ GANDIA SL", Text = "PRESTACION SERVICIOS CLIENTES VARIOS", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 21, TaxBase = 1000, TaxAmount = 210 } } }; dynamic result = ApiClient.Create(invoice); if (result.ResultCode != 0) { Debug.Print($"Se ha producido un error al llamar al API: {result.ResultMessage}"); } else { var csv = $"{result.Return.CSV}"; if (!string.IsNullOrEmpty(csv)) Debug.Print($"Documento envíado con CSV: {csv}"); else Debug.Print($"El envío no se ha realizado con éxito: {result.Return.ErrorDescription}"); }

Ejemplo factura recargo equivalencia API REST

// Creamos una instacia de la clase factura var invoice = new Invoice("FEJ062", new DateTime(2024, 11, 15), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS CLIENTES VARIOS", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 21, TaxBase = 1000, TaxAmount = 210, TaxRateSurcharge = 5.2m, TaxAmountSurcharge = 52 } } }; dynamic result = ApiClient.Create(invoice); if (result.ResultCode != 0) { Debug.Print($"Se ha producido un error al llamar al API: {result.ResultMessage}"); } else { var csv = $"{result.Return.CSV}"; if (!string.IsNullOrEmpty(csv)) Debug.Print($"Documento envíado con CSV: {csv}"); else Debug.Print($"El envío no se ha realizado con éxito: {result.Return.ErrorDescription}"); }

Ejemplo factura rectificativa API REST

// Creamos una instacia de la clase factura var invoice = new Invoice("FEJAB065", new DateTime(2024, 11, 15), "B72877814") { InvoiceType = TipoFactura.R1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS CLIENTES VARIOS", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 21, TaxBase = -100, TaxAmount = -21, } }, RectificationItems = new List<RectificationItem>() { new RectificationItem() { InvoiceID = "FEJ062", InvoiceDate = new DateTime(2024, 11, 15) } } }; dynamic result = ApiClient.Create(invoice); if (result.ResultCode != 0) { Debug.Print($"Se ha producido un error al llamar al API: {result.ResultMessage}"); } else { var csv = $"{result.Return.CSV}"; if (!string.IsNullOrEmpty(csv)) Debug.Print($"Documento envíado con CSV: {csv}"); else Debug.Print($"El envío no se ha realizado con éxito: {result.Return.ErrorDescription}"); }

Factura a la Administración con IVA diferido API REST

Este tipo de facturas se emiten a organismos públicos y es obligatorio informar de la fecha de operación.

// Creamos una instacia de la clase factura var invoice = new Invoice("TS08-001-086", new DateTime(2024, 12, 11), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "P1203200I", BuyerName = "AYUNTAMIENTO DE BURRIANA", Text = "CONSTRUCCION NUEVAS OFICINAS", OperationDate = new DateTime(2024, 11, 11), // Fecha operación obligatoria TaxItems = new List<TaxItem>() { new TaxItem() { TaxScheme = ClaveRegimen.ObraPteDevengoAdmonPublica, TaxRate = 21, TaxBase = 1000, TaxAmount = 210 } } }; dynamic result = ApiClient.Create(invoice); if (result.ResultCode != 0) { Debug.Print($"Se ha producido un error al llamar al API: {result.ResultMessage}"); } else { var csv = $"{result.Return.CSV}"; if (!string.IsNullOrEmpty(csv)) Debug.Print($"Documento envíado con CSV: {csv}"); else Debug.Print($"El envío no se ha realizado con éxito: {result.Return.ErrorDescription}"); }

Ejemplos altas

Factura normal régimen general

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ-0002", new DateTime(2024, 11, 15), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el estado Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Status}"); if (invoiceEntry.Status == "Correcto") { // Consultamos el CSV Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.CSV}"); } else { // Consultamos el error Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.ErrorCode}: {invoiceEntry.ErrorDescription}"); } // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura simplificada

La factura simplificada es el TIPO F2, y no es necesario informar datos del comprador.

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ-F2-0050", new DateTime(2024, 12, 4), "B72877814") { InvoiceType = TipoFactura.F2, SellerName = "WEFINZ GANDIA SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el estado Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Status}"); if (invoiceEntry.Status == "Correcto") { // Consultamos el CSV Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.CSV}"); } else { // Consultamos el error Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.ErrorCode}: {invoiceEntry.ErrorDescription}"); } // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura recargo de equivalencia

// Creamos una instacia de la clase factura var invoice = new Invoice("TS08-001-022", new DateTime(2024, 10, 14), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "VENTA A COMERCIO MINORISTA", TaxItems = new List<TaxItem>() { new TaxItem() { TaxScheme = ClaveRegimen.RecEquivPeqEmp, TaxType = CalificacionOperacion.S1, TaxRate = 21, TaxBase = 1000, TaxAmount = 210m, TaxRateSurcharge = 5.2m, // Tipo recargo equivalencia TaxAmountSurcharge = 52m // Cuota recargo equivalencia } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura rectificativa

Enviamos una factura normal y luego su rectificación

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ-0076", new DateTime(2024, 12, 4), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 21, TaxBase = 200, TaxAmount = 42 } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}"); // Creamos una instacia de la clase factura para la factura rectificativa var invoiceRectif = new Invoice("GIT-AB-0076", new DateTime(2024, 12, 10), "B72877814") { InvoiceType = TipoFactura.R1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "ABONO ERROR PRECIO FACTURA GIT-EJ-0065", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 21, TaxBase = -100, TaxAmount = -21 } }, RectificationItems = new List<RectificationItem>() { new RectificationItem() { InvoiceID = "GIT-EJ-0076", InvoiceDate = new DateTime(2024, 12, 4) } } }; // Creamos la entrada de la factura rectificativa var invoiceEntryRectif = new InvoiceEntry(invoiceRectif); // Guardamos la factura rectificativa envíandola a la AEAT invoiceEntryRectif.Save(); // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntryRectif.Response}");

Factura a la Administración con IVA diferido

Este tipo de facturas se emiten a organismos públicos y es obligatorio informar de la fecha de operación.

// Creamos una instacia de la clase factura var invoice = new Invoice("TS08-001-081", new DateTime(2024, 10, 10), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "P1203200I", BuyerName = "AYUNTAMIENTO DE BURRIANA", Text = "CONSTRUCCION NUEVAS OFICINAS", OperationDate = new DateTime(2024, 12, 10), // Fecha operación obligatoria TaxItems = new List<TaxItem>() { new TaxItem() { TaxScheme = ClaveRegimen.ObraPteDevengoAdmonPublica, TaxType = CalificacionOperacion.S1, TaxRate = 21, TaxBase = 1000, TaxAmount = 210m, } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura operación No Sujeta artículo 7, 14, otros

// Creamos una instacia de la clase factura var invoice = new Invoice("TS08-001-094", new DateTime(2024, 10, 14), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "123456789", BuyerName = "CLIENTE EXTRANJERO SERVICIOS INFORMATICOS", BuyerIDType = IDType.PASAPORTE, BuyerCountryID = "US", Text = "SERVICIOS INFORMATICOS", TaxItems = new List<TaxItem>() { new TaxItem() { Tax = Impuesto.OTROS, TaxType = CalificacionOperacion.N1, TaxBase = 1000, } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura operación No Sujeta por Reglas de localización

// Creamos una instacia de la clase factura var invoice = new Invoice("TS08-001-095", new DateTime(2024, 10, 14), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "123456789", BuyerName = "CLIENTE EXTRANJERO SERVICIOS INFORMATICOS", BuyerIDType = IDType.PASAPORTE, BuyerCountryID = "US", Text = "CLIENTE EXTRANJERO SERVICIOS INFORMATICOS", TaxItems = new List<TaxItem>() { new TaxItem() { Tax = Impuesto.OTROS, TaxType = CalificacionOperacion.N2, TaxBase = 1000, } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura con Suplidos

// Creamos una instacia de la clase factura var invoice = new Invoice("TS08-001-095", new DateTime(2025, 1, 16), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B12959755", BuyerName = "IRENE SOLUTIONS SL", Text = "FACTURA CON SUPLIDOS", TaxItems = new List<TaxItem>() { // Línea sujeta tipo IVA normal new TaxItem() { TaxRate = 21, TaxBase = 650, TaxAmount = 136.50m }, // Línea de suplidos new TaxItem() { Tax = Impuesto.OTROS, TaxType = CalificacionOperacion.N1, TaxBase = 100, } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura exenta articulo 20 LIVA

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ25-0002", new DateTime(2024, 12, 11), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "C# PROGRAMMING COURSE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxException = CausaExencion.E1, TaxBase = 200, } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el estado Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Status}"); if (invoiceEntry.Status == "Correcto") { // Consultamos el CSV Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.CSV}"); } else { // Consultamos el error Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.ErrorCode}: {invoiceEntry.ErrorDescription}"); } // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura a cliente con VAT number de la UE

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ25-00001", new DateTime(2025, 1, 9), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "IE3668997OH", BuyerName = "GOOGLE CLOUD EMEA LIMITED", BuyerIDType = IDType.NIF_IVA, BuyerCountryID = "IE", Text = "SERVICIOS INFORMATICOS", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el estado Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Status}"); if (invoiceEntry.Status == "Correcto") { // Consultamos el CSV Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.CSV}"); } else { // Consultamos el error Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.ErrorCode}: {invoiceEntry.ErrorDescription}"); } // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura a cliente con pasaporte como identificador

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ25-00002", new DateTime(2025, 1, 9), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "P4366918", BuyerName = "ESTIVE SOTANO MENGANO", BuyerIDType = IDType.PASAPORTE, BuyerCountryID = "US", Text = "SERVICIOS INFORMATICOS", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el estado Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Status}"); if (invoiceEntry.Status == "Correcto") { // Consultamos el CSV Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.CSV}"); } else { // Consultamos el error Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.ErrorCode}: {invoiceEntry.ErrorDescription}"); } // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura a cliente otro documento probatorio como identificador

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ25-00004", new DateTime(2025, 1, 9), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "999", BuyerName = "ESTIVE SOTANO MENGANO", BuyerIDType = IDType.OTRO_DOC_PROBATORIO, BuyerCountryID = "US", Text = "SERVICIOS INFORMATICOS", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el estado Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Status}"); if (invoiceEntry.Status == "Correcto") { // Consultamos el CSV Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.CSV}"); } else { // Consultamos el error Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.ErrorCode}: {invoiceEntry.ErrorDescription}"); } // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Factura IGIC Canarias

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ25-0095", new DateTime(2024, 12, 4), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxScheme = ClaveRegimen.RegimenGeneral, Tax = Impuesto.IGIC, TaxRate = 7, TaxBase = 100, TaxAmount = 7 } } }; // Creamos la entrada de la factura var invoiceEntry = new InvoiceEntry(invoice); // Guardamos la factura invoiceEntry.Save(); // Consultamos el estado Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Status}"); if (invoiceEntry.Status == "Correcto") { // Consultamos el CSV Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.CSV}"); } else { // Consultamos el error Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.ErrorCode}: {invoiceEntry.ErrorDescription}"); } // Consultamos el resultado devuelto por la AEAT Debug.Print($"Respuesta de la AEAT:\n{invoiceEntry.Response}");

Interoperabilidad COM

Podemos hacer uso de la Interoperabilidad COM con objeto de utilizar la funcionalidad de Verifactu en Visual Basic 6, php, Microsoft Excel, Ms Office con VBA... Si compilamos el proyecto VeriFactu.Com.Interop.csproj para la plataforma de destino que necesitemos, podremos hacer uso de la Iteroperabilidad COM de Microsoft para utilizar las funcionalidades de Verifactu. En el ejemplo siguiente vamos a ver como utilizar la librería dll generada en Excel, haciendo uso de VBA (Visual Basic para Aplicaciones).

Determinación de la plataforma de destino

Si abrimos la aplicación Excel, en el menú de la izquierda en la parte de abajo encontraremos el item 'Cuenta':

image

Al pulsar 'Cuenta' encontraremos un panel dónde está la opción de 'Acerca de Excel':

image

En este punto encontraremos si tenemos la versión de 32 bits o de 64 bits:

image

En el ejemplo tenemos 32 bits:

image

Entonces necesitamos utilizar la biblioteca compilada para esta plataforma de destino:

image

Puede descargar la librería según sus necesidades:

Descargue el archivo dll de la plataforma que necesite. Tendrá que registrarla mediante el programa RegAsm.exe que se encuentra en la carpeta de su Net Framework (ejecutando la ventana de comandos como administrador):

C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe "C:\Program Files (x86)\Irene Solutions SL\VerifactuLib\VeriFactu.dll" /register /codebase /tlb

Una vez registrada la biblioteca con RegAsm.exe podemos utilizar el componente COM desde VBA añadiendo la correspondiente referencia:

image

Después de este trabajo, ya podemos utilizar la funcionalidades de Verifactu. En el código siguiente ilustra la utilización de distintas funciones de la biblioteca:

Dim invoice As New VeriFactu.VfInvoice ' Factura Dim taxItem As New VeriFactu.VfTaxItem ' Línea de impuestos ' Datos factura With invoice .InvoiceID = "FR-VB-003" .InvoiceDate = DateTime.Now .SellerID = "B12959755" .SellerName = "IRENE SOLUTIONS SL" .BuyerID = "B44531218" .BuyerName = "WEFINZ SOLUTIONS SL" .Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE" End With ' Datos línea de impuestos With taxItem .TaxBase = 1000 .TaxRate = 21 .TaxAmount = 210 End With ' Añadimos la línea de impuestos a la factura invoice.InsertTaxItem taxItem Dim urlValidate As String urlValidate = invoice.GetUrlValidate ' Obtiene la url de validación de la factura Debug.Print urlValidate ' Muestra la url de validación Dim xmlRegistroAlta As String xmlRegistroAlta = invoice.GetRegistroAlta ' Obtiene el xml de alta de la factura Debug.Print xmlRegistroAlta ' Muestra el xml de alta Dim xmlRegistroAnulacion As String xmlRegistroAnulacion = invoice.GetRegistroAnulacion ' Obtiene el xml de anulación de la factura Debug.Print xmlRegistroAnulacion ' Muestra el xml de anulación ' Guarda la imágen del QR de validación en la ruta indicada invoice.GetValidateQr "C:\qr.bmp" ' Resultado de la operación de alta a la AEAT Dim altaResult As VeriFactu.VfInvoiceResult ' Envía el registro de alta de la factura Set altaResult = invoice.Send ' Muestra el resultado del envío Debug.Print altaResult.ResultCode Debug.Print altaResult.ResultMessage Debug.Print altaResult.CSV ' Resultado de la operación de anulación a la AEAT ' Esta prueba anula el alta anterior de la factura !!! Dim anulacionResult As VeriFactu.VfInvoiceResult ' Envía el registro de anulación de la factura ' Esta prueba anula el alta anterior de la factura !!! Set anulacionResult = invoice.Delete ' Muestra el resultado del envío Debug.Print anulacionResult.ResultCode Debug.Print anulacionResult.ResultMessage Debug.Print anulacionResult.CSV

Ejemplo de trabajo con las funciones de configuración

Dim settings As New VfSettings ' Cargo la configuración settings.Load Debug.Print settings.SistemaInformaticoNombre ' Cambio el nombre del sistema informatico settings.SistemaInformaticoNombre = "VERIFACTU EXCEL" ' Cambio el nombre del archivo de configuración settings.SetConfigFileName ("MICONFIG.xml") ' Guardo la configuración settings.Save

Reenvío de una factura no enviada por fallo en las comunicaciones

Siempre puedes reintentar el envío, en caso de que exista el registro la AEAT te devolverá el mensaje "Registro de facturación duplicado."

En el siguiente ejemplo se vuelve a enviar el archivo xml de registro de alta correspondiente a una factura ya enviada:

// Creamos una instacia de la clase factura var invoice = new Invoice("GIT-EJ25-00110", new DateTime(2025, 1, 25), "B72877814") { InvoiceType = TipoFactura.F1, SellerName = "WEFINZ GANDIA SL", BuyerID = "B44531218", BuyerName = "WEFINZ SOLUTIONS SL", Text = "PRESTACION SERVICIOS DESARROLLO SOFTWARE", TaxItems = new List<TaxItem>() { new TaxItem() { TaxRate = 4, TaxBase = 10, TaxAmount = 0.4m }, new TaxItem() { TaxRate = 21, TaxBase = 100, TaxAmount = 21 } } }; // Mensaje correspondiente a la factura var message = new InvoiceActionMessage(invoice); // Path del archivo xml del registro de alta correspondiente a la factura var path = message.InvoiceFilePath; // Envíamos otra vez el fichero var response = InvoiceActionMessage.SendXmlBytes(File.ReadAllBytes(path)); // Obtenemos la respuesta var envelopeResponse = Envelope.FromXml(response); var respuesta = (envelopeResponse.Body.Registro as VeriFactu.Xml.Factu.Respuesta.RespuestaRegFactuSistemaFacturacion); // Vemos que la AEAT responde que se trata de un registro duplicado Debug.Print($"La respuesta de la AEAT al intento de reenvío es:\n{respuesta.RespuestaLinea[0].CodigoErrorRegistro}-{respuesta.RespuestaLinea[0].DescripcionErrorRegistro}");