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:
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:
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
Reenvío de una factura no enviada por fallo en las comunicaciones
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. |
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.
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
}
}
};
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}");
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}");
[!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:
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()}");
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:
Este mecanismo se utiliza en las colas diseñadas para la gestión de flujo.
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.
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
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
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:
// 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);
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();
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.
// 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");
' 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")
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:
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 nuestraServiceKey
accediendo a este enlace. Una vez tengamos nuestraServiceKey
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();
// 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}");
}
// 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}");
}
// 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));
}
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}");
}
// 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}");
}
// 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}");
}
// 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}");
}
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}");
}
// 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}");
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}");
// 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}");
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}");
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}");
// 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}");
// 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}");
// 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}");
// 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}");
// 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}");
// 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}");
// 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}");
// 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}");
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).
Si abrimos la aplicación Excel, en el menú de la izquierda en la parte de abajo encontraremos el item 'Cuenta':
Al pulsar 'Cuenta' encontraremos un panel dónde está la opción de 'Acerca de Excel':
En este punto encontraremos si tenemos la versión de 32 bits o de 64 bits:
En el ejemplo tenemos 32 bits:
Entonces necesitamos utilizar la biblioteca compilada para esta plataforma de destino:
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:
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
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}");