Invoice Processing in C#: Generate, Extract, and Automate PDF Invoices with .NET
Procesamiento de facturas en C# .NET con IronPDF cubre todo el ciclo de vida del documento: generación de facturas PDF profesionales a partir de plantillas HTML, cumplimiento de los estándares de facturación electrónica ZUGFeRD y Factur-X, extracción de datos estructurados de las facturas recibidas mediante análisis sintáctico de texto y procesamiento impulsado por inteligencia artificial, y creación de conductos de automatización por lotes que se integran con sistemas de contabilidad como QuickBooks, Xero y SAP.
TL;DR: Guía de inicio rápido
Este tutorial cubre la generación, extracción y automatización de facturas PDF en C# .NET, incluido el cumplimiento de la facturación electrónica, el análisis sintáctico basado en IA y la integración de sistemas contables.
- A quién va dirigido: Desarrolladores .NET que crean módulos de facturación, automatización de cuentas por pagar o cumplimiento de facturación electrónica.
- Qué harás: Generación de facturas en HTML con partidas y cálculos de impuestos, códigos QR para enlaces de pago, salida PDF/A-3 compatible con ZUGFeRD/Factur-X, extracción de texto con regex, análisis de facturas con IA y procesamiento por lotes con integración de sistemas de contabilidad.
- Dónde funciona: .NET 10, .NET 8 LTS, .NET Framework 4.6.2+ y .NET Standard 2.0. Sin dependencias de servicios externos.
- Cuándo utilizar este enfoque: Cuando necesite generar PDF de facturas, cumplir con los mandatos de facturación electrónica de la UE o extraer datos de facturas de proveedores para cuentas por pagar.
- Por qué es importante desde el punto de vista técnico: IronPDF convierte HTML a PDF con una precisión de píxeles perfecta, admite PDF/A-3 para XML incrustado y proporciona API de extracción de texto que se combinan con regex o IA para convertir facturas no estructuradas en datos estructurados.
Genere su primera factura en PDF con solo unas líneas de código:
-
Instala IronPDF con el Administrador de Paquetes NuGet
PM > Install-Package IronPdf -
Copie y ejecute este fragmento de código.
var renderer = new IronPdf.ChromePdfRenderer(); var pdf = renderer.RenderHtmlAsPdf("<h1>Invoice #1001</h1><p>Total: $500.00</p>"); pdf.SaveAs("invoice.pdf"); -
Despliegue para probar en su entorno real
Comienza a usar IronPDF en tu proyecto hoy mismo con una prueba gratuita
Una vez que haya adquirido IronPDF o se haya suscrito a una versión de prueba de 30 días, añada su clave de licencia al inicio de su solicitud.
IronPdf.License.LicenseKey = "KEY";
IronPdf.License.LicenseKey = "KEY";
Imports IronPdf
IronPdf.License.LicenseKey = "KEY"
Comience a usar IronPDF en su proyecto hoy con una prueba gratuita.
Tabla de contenido
- TL;DR: Guía de inicio rápido
- Generar facturas PDF profesionales
- Cumplimiento de las normas de facturación electrónica
- Extraer datos de facturas PDF
- Procesamiento de facturas basado en IA
- Integración con sistemas de contabilidad
¿Qué es el ciclo de vida de una factura y por qué el PDF sigue siendo el estándar?
Antes de sumergirse en el código, es útil comprender el recorrido completo de una factura a través de un sistema empresarial moderno. El ciclo de vida de una factura consta de cinco fases distintas: generación, distribución, recepción, extracción de datos e integración contable.
El proceso de facturación comienza con la generación. Una empresa crea una factura que incluye partidas, precios, cálculos de impuestos, condiciones de pago y marca. La factura debe tener un aspecto profesional y cumplir todos los requisitos legales. A continuación viene la distribución, en la que la factura se envía al cliente por correo electrónico, a través de un portal de clientes o por correo tradicional. Cuando el cliente lo recibe, el equipo de cuentas por pagar captura el documento y lo prepara para su procesamiento. La extracción de datos extrae información clave de la factura, como los detalles del proveedor, las partidas, los totales y las fechas de vencimiento, para que pueda comprobarse y cotejarse con los pedidos de compra. Por último, la integración contable traslada estos datos a sistemas financieros como QuickBooks, Xero o SAP para el pago y el mantenimiento de registros.
¿Por qué el PDF sigue siendo el formato más utilizado después de tantos años? Se trata de una mezcla única de ventajas. Los PDF mantienen la coherencia del formato de las facturas independientemente del dispositivo o sistema operativo que se utilice. Tanto si alguien abre su factura en Windows, Mac o su teléfono, se verá exactamente como la diseñó. Además, los PDF son difíciles de modificar por error, por lo que protegen la integridad de los documentos mejor que formatos como Word o Excel. Puede añadir firmas digitales para garantizar la autenticidad y utilizar el cifrado para mayor seguridad. Lo más importante es que los PDF se han convertido en un estándar universal que todos los sistemas empresariales reconocen y admiten.
Por supuesto, hay un reto. Los PDF están pensados para que las personas los lean con facilidad, no para que los procesen los ordenadores. En lugar de almacenar información en datos estructurados, un PDF guarda texto, líneas, formas e imágenes en función del lugar de la página en el que aparecen. Por eso son tan útiles herramientas como IronPDF, que permiten convertir documentos amigables para el ser humano en datos con los que puede trabajar el software.
How to Generate Professional PDF Invoices in C
Generar facturas mediante programación requiere transformar datos estructurados, como información de clientes, partidas y cálculos, en un documento PDF pulido. IronPDF hace que esto sea notablemente sencillo al aprovechar HTML y CSS, tecnologías que la mayoría de los desarrolladores ya conocen bien.
En este tutorial, repasaremos los escenarios más probables con los que te encontrarás en el mundo real. También puede descargar el proyecto que se muestra a continuación aquí.
Cómo crear una plantilla HTML de factura
La base de la generación de facturas con IronPDF es HTML. En lugar de luchar con comandos de dibujo PDF de bajo nivel, usted diseña su factura utilizando HTML y CSS estándar, y luego deja que el motor de renderizado basado en Chrome de IronPDF lo convierta en un PDF perfecto en píxeles.
He aquí una plantilla de factura básica que demuestra el enfoque:
:path=/static-assets/pdf/content-code-examples/tutorials/csharp-invoice-processing/basic-invoice-template.cs
using IronPdf;
// Define the HTML template for a basic invoice
// Uses inline CSS for styling headers, tables, and totals
string invoiceHtml = @"
E html>
le>
body { font-family: Arial, sans-serif; padding: 40px; }
.header { text-align: right; margin-bottom: 40px; }
.company-name { font-size: 24px; font-weight: bold; color: #333; }
.invoice-title { font-size: 32px; margin: 20px 0; }
.bill-to { margin: 20px 0; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th { background-color: #2A95D5; color: white; padding: 10px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #ddd; }
.total { text-align: right; font-size: 20px; font-weight: bold; margin-top: 20px; }
yle>
class='header'>
<div class='company-name'>Your Company Name</div>
<div>123 Business Street</div>
<div>City, State 12345</div>
v>
class='invoice-title'>INVOICE</div>
class='bill-to'>
<strong>Bill To:</strong><br>
Customer Name<br>
456 Customer Avenue<br>
City, State 67890
v>
le>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
<tr>
<td>Web Development Services</td>
<td>10 hours</td>
<td>$100.00</td>
<td>$1,000.00</td>
</tr>
<tr>
<td>Consulting</td>
<td>5 hours</td>
<td>$150.00</td>
<td>$750.00</td>
</tr>
ble>
class='total'>Total: $1,750.00</div>
;
// Initialize the Chrome-based PDF renderer
var renderer = new ChromePdfRenderer();
// Convert the HTML string to a PDF document
var pdf = renderer.RenderHtmlAsPdf(invoiceHtml);
// Save the generated PDF to disk
pdf.SaveAs("basic-invoice.pdf");
Imports IronPdf
' Define the HTML template for a basic invoice
' Uses inline CSS for styling headers, tables, and totals
Dim invoiceHtml As String = "
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
.header { text-align: right; margin-bottom: 40px; }
.company-name { font-size: 24px; font-weight: bold; color: #333; }
.invoice-title { font-size: 32px; margin: 20px 0; }
.bill-to { margin: 20px 0; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th { background-color: #2A95D5; color: white; padding: 10px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #ddd; }
.total { text-align: right; font-size: 20px; font-weight: bold; margin-top: 20px; }
</style>
</head>
<body>
<div class='header'>
<div class='company-name'>Your Company Name</div>
<div>123 Business Street</div>
<div>City, State 12345</div>
</div>
<div class='invoice-title'>INVOICE</div>
<div class='bill-to'>
<strong>Bill To:</strong><br>
Customer Name<br>
456 Customer Avenue<br>
City, State 67890
</div>
<table>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
<tr>
<td>Web Development Services</td>
<td>10 hours</td>
<td>$100.00</td>
<td>$1,000.00</td>
</tr>
<tr>
<td>Consulting</td>
<td>5 hours</td>
<td>$150.00</td>
<td>$750.00</td>
</tr>
</table>
<div class='total'>Total: $1,750.00</div>
</body>
</html>
"
' Initialize the Chrome-based PDF renderer
Dim renderer As New ChromePdfRenderer()
' Convert the HTML string to a PDF document
Dim pdf = renderer.RenderHtmlAsPdf(invoiceHtml)
' Save the generated PDF to disk
pdf.SaveAs("basic-invoice.pdf")
Muestra de resultado
Este enfoque ofrece una enorme flexibilidad. Cualquier CSS que funcione en Chrome funcionará en su PDF, incluidas funciones modernas como flexbox, diseños de cuadrícula y fuentes personalizadas. Incluso se pueden utilizar hojas de estilo e imágenes externas haciendo referencia a URL o rutas de archivos locales.
Cómo añadir partidas dinámicas y calcular totales
Las facturas reales rara vez tienen un contenido estático. Es necesario rellenar partidas de una base de datos, calcular subtotales, aplicar tipos impositivos y formatear valores monetarios. El siguiente ejemplo muestra un patrón listo para producción para la generación dinámica de facturas:
using IronPdf;
using System;
using System.Collections.Generic;
using System.Linq;
// Represents a single line item on an invoice
public class InvoiceLineItem
{
public string Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
// Auto-calculates line total from quantity and unit price
public decimal Total => Quantity * UnitPrice;
}
// Represents a complete invoice with customer details and line items
public class Invoice
{
public string InvoiceNumber { get; set; }
public DateTime InvoiceDate { get; set; }
public string CustomerName { get; set; }
public string CustomerAddress { get; set; }
public List<InvoiceLineItem> LineItems { get; set; }
// Computed properties for invoice totals
public decimal Subtotal => LineItems.Sum(item => item.Total);
public decimal TaxRate { get; set; } = 0.08m; // Default 8% tax rate
public decimal Tax => Subtotal * TaxRate;
public decimal Total => Subtotal + Tax;
}
// Generates PDF invoices from Invoice objects using HTML templates
public class InvoiceGenerator
{
public PdfDocument GenerateInvoice(Invoice invoice)
{
// Build HTML table rows dynamically from line items
string lineItemsHtml = string.Join("", invoice.LineItems.Select(item => $@"
<tr>
<td>{item.Description}</td>
<td>{item.Quantity}</td>
<td>${item.UnitPrice:F2}</td>
<td>${item.Total:F2}</td>
</tr>
"));
// Build the complete HTML invoice using string interpolation
// All invoice data is injected into the template dynamically
string invoiceHtml = $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; padding: 40px; }}
.header {{ text-align: right; margin-bottom: 40px; }}
.company-name {{ font-size: 24px; font-weight: bold; color: #333; }}
.invoice-details {{ margin: 20px 0; }}
table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
th {{ background-color: #2A95D5; color: white; padding: 10px; text-align: left; }}
td {{ padding: 10px; border-bottom: 1px solid #ddd; }}
.totals {{ text-align: right; margin-top: 20px; }}
.totals div {{ margin: 5px 0; }}
.grand-total {{ font-size: 20px; font-weight: bold; color: #2A95D5; }}
</style>
</head>
<body>
<div class='header'>
<div class='company-name'>Your Company Name</div>
</div>
<h1>INVOICE</h1>
<div class='invoice-details'>
<strong>Invoice Number:</strong> {invoice.InvoiceNumber}<br>
<strong>Date:</strong> {invoice.InvoiceDate:MMM dd, yyyy}<br>
<strong>Bill To:</strong> {invoice.CustomerName}<br>
{invoice.CustomerAddress}
</div>
<table>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total</th>
</tr>
{lineItemsHtml}
</table>
<div class='totals'>
<div>Subtotal: ${invoice.Subtotal:F2}</div>
<div>Tax ({invoice.TaxRate:P0}): ${invoice.Tax:F2}</div>
<div class='grand-total'>Total: ${invoice.Total:F2}</div>
</div>
</body>
</html>";
// Render HTML to PDF and return the document
var renderer = new ChromePdfRenderer();
return renderer.RenderHtmlAsPdf(invoiceHtml);
}
}
using IronPdf;
using System;
using System.Collections.Generic;
using System.Linq;
// Represents a single line item on an invoice
public class InvoiceLineItem
{
public string Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
// Auto-calculates line total from quantity and unit price
public decimal Total => Quantity * UnitPrice;
}
// Represents a complete invoice with customer details and line items
public class Invoice
{
public string InvoiceNumber { get; set; }
public DateTime InvoiceDate { get; set; }
public string CustomerName { get; set; }
public string CustomerAddress { get; set; }
public List<InvoiceLineItem> LineItems { get; set; }
// Computed properties for invoice totals
public decimal Subtotal => LineItems.Sum(item => item.Total);
public decimal TaxRate { get; set; } = 0.08m; // Default 8% tax rate
public decimal Tax => Subtotal * TaxRate;
public decimal Total => Subtotal + Tax;
}
// Generates PDF invoices from Invoice objects using HTML templates
public class InvoiceGenerator
{
public PdfDocument GenerateInvoice(Invoice invoice)
{
// Build HTML table rows dynamically from line items
string lineItemsHtml = string.Join("", invoice.LineItems.Select(item => $@"
<tr>
<td>{item.Description}</td>
<td>{item.Quantity}</td>
<td>${item.UnitPrice:F2}</td>
<td>${item.Total:F2}</td>
</tr>
"));
// Build the complete HTML invoice using string interpolation
// All invoice data is injected into the template dynamically
string invoiceHtml = $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; padding: 40px; }}
.header {{ text-align: right; margin-bottom: 40px; }}
.company-name {{ font-size: 24px; font-weight: bold; color: #333; }}
.invoice-details {{ margin: 20px 0; }}
table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
th {{ background-color: #2A95D5; color: white; padding: 10px; text-align: left; }}
td {{ padding: 10px; border-bottom: 1px solid #ddd; }}
.totals {{ text-align: right; margin-top: 20px; }}
.totals div {{ margin: 5px 0; }}
.grand-total {{ font-size: 20px; font-weight: bold; color: #2A95D5; }}
</style>
</head>
<body>
<div class='header'>
<div class='company-name'>Your Company Name</div>
</div>
<h1>INVOICE</h1>
<div class='invoice-details'>
<strong>Invoice Number:</strong> {invoice.InvoiceNumber}<br>
<strong>Date:</strong> {invoice.InvoiceDate:MMM dd, yyyy}<br>
<strong>Bill To:</strong> {invoice.CustomerName}<br>
{invoice.CustomerAddress}
</div>
<table>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total</th>
</tr>
{lineItemsHtml}
</table>
<div class='totals'>
<div>Subtotal: ${invoice.Subtotal:F2}</div>
<div>Tax ({invoice.TaxRate:P0}): ${invoice.Tax:F2}</div>
<div class='grand-total'>Total: ${invoice.Total:F2}</div>
</div>
</body>
</html>";
// Render HTML to PDF and return the document
var renderer = new ChromePdfRenderer();
return renderer.RenderHtmlAsPdf(invoiceHtml);
}
}
Imports IronPdf
Imports System
Imports System.Collections.Generic
Imports System.Linq
' Represents a single line item on an invoice
Public Class InvoiceLineItem
Public Property Description As String
Public Property Quantity As Decimal
Public Property UnitPrice As Decimal
' Auto-calculates line total from quantity and unit price
Public ReadOnly Property Total As Decimal
Get
Return Quantity * UnitPrice
End Get
End Property
End Class
' Represents a complete invoice with customer details and line items
Public Class Invoice
Public Property InvoiceNumber As String
Public Property InvoiceDate As DateTime
Public Property CustomerName As String
Public Property CustomerAddress As String
Public Property LineItems As List(Of InvoiceLineItem)
' Computed properties for invoice totals
Public ReadOnly Property Subtotal As Decimal
Get
Return LineItems.Sum(Function(item) item.Total)
End Get
End Property
Public Property TaxRate As Decimal = 0.08D ' Default 8% tax rate
Public ReadOnly Property Tax As Decimal
Get
Return Subtotal * TaxRate
End Get
End Property
Public ReadOnly Property Total As Decimal
Get
Return Subtotal + Tax
End Get
End Property
End Class
' Generates PDF invoices from Invoice objects using HTML templates
Public Class InvoiceGenerator
Public Function GenerateInvoice(invoice As Invoice) As PdfDocument
' Build HTML table rows dynamically from line items
Dim lineItemsHtml As String = String.Join("", invoice.LineItems.Select(Function(item) $"
<tr>
<td>{item.Description}</td>
<td>{item.Quantity}</td>
<td>${item.UnitPrice:F2}</td>
<td>${item.Total:F2}</td>
</tr>
"))
' Build the complete HTML invoice using string interpolation
' All invoice data is injected into the template dynamically
Dim invoiceHtml As String = $"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; padding: 40px; }}
.header {{ text-align: right; margin-bottom: 40px; }}
.company-name {{ font-size: 24px; font-weight: bold; color: #333; }}
.invoice-details {{ margin: 20px 0; }}
table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
th {{ background-color: #2A95D5; color: white; padding: 10px; text-align: left; }}
td {{ padding: 10px; border-bottom: 1px solid #ddd; }}
.totals {{ text-align: right; margin-top: 20px; }}
.totals div {{ margin: 5px 0; }}
.grand-total {{ font-size: 20px; font-weight: bold; color: #2A95D5; }}
</style>
</head>
<body>
<div class='header'>
<div class='company-name'>Your Company Name</div>
</div>
<h1>INVOICE</h1>
<div class='invoice-details'>
<strong>Invoice Number:</strong> {invoice.InvoiceNumber}<br>
<strong>Date:</strong> {invoice.InvoiceDate:MMM dd, yyyy}<br>
<strong>Bill To:</strong> {invoice.CustomerName}<br>
{invoice.CustomerAddress}
</div>
<table>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total</th>
</tr>
{lineItemsHtml}
</table>
<div class='totals'>
<div>Subtotal: ${invoice.Subtotal:F2}</div>
<div>Tax ({invoice.TaxRate:P0}): ${invoice.Tax:F2}</div>
<div class='grand-total'>Total: ${invoice.Total:F2}</div>
</div>
</body>
</html>"
' Render HTML to PDF and return the document
Dim renderer As New ChromePdfRenderer()
Return renderer.RenderHtmlAsPdf(invoiceHtml)
End Function
End Class
Muestra de resultado
La clase Invoice encapsula todos los datos de la factura con propiedades calculadas para subtotal, impuestos y total. El generador transforma estos datos en HTML mediante interpolación de cadenas y, a continuación, los convierte en PDF. Esta separación de preocupaciones hace que el código sea mantenible y comprobable.
Cómo añadir la marca de la empresa y marcas de agua a las facturas
Las facturas profesionales necesitan elementos de marca como logotipos y, en algunos casos, marcas de agua para indicar el estado del pago. IronPDF admite tanto imágenes incrustadas en HTML como marcas de agua programáticas después de la renderización.
:path=/static-assets/pdf/content-code-examples/tutorials/csharp-invoice-processing/branding-watermarks.cs
using IronPdf;
using IronPdf;
var renderer = new ChromePdfRenderer();
// Invoice HTML template with company logo embedded via URL
// Logo can also be Base64-encoded or a local file path
string htmlWithLogo = @"
E html>
le>
body { font-family: Arial, sans-serif; padding: 40px; }
.logo { width: 200px; margin-bottom: 20px; }
yle>
style='text-align: center;'>
<img src='https://yourcompany.com/logo.png' alt='Company Logo' class='logo' />
v>
INVOICE</h1>
strong>Invoice Number:</strong> INV-2024-001</p>
strong>Total:</strong> $1,250.00</p>
;
// Render the HTML to PDF
var pdf = renderer.RenderHtmlAsPdf(htmlWithLogo);
// Apply a diagonal "UNPAID" watermark to mark invoice status
// 30% opacity keeps the content readable while the watermark is visible
pdf.ApplyWatermark("<h1 style='color: red;'>UNPAID</h1>",
opacity: 30,
rotation: 45,
verticalAlignment: IronPdf.Editing.VerticalAlignment.Middle);
pdf.SaveAs("invoice-with-watermark.pdf");
using IronPdf;
Imports IronPdf
Dim renderer As New ChromePdfRenderer()
' Invoice HTML template with company logo embedded via URL
' Logo can also be Base64-encoded or a local file path
Dim htmlWithLogo As String = "
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
.logo { width: 200px; margin-bottom: 20px; }
</style>
</head>
<body>
<div style='text-align: center;'>
<img src='https://yourcompany.com/logo.png' alt='Company Logo' class='logo' />
<h1>INVOICE</h1>
<p><strong>Invoice Number:</strong> INV-2024-001</p>
<p><strong>Total:</strong> $1,250.00</p>
</div>
</body>
</html>
"
' Render the HTML to PDF
Dim pdf = renderer.RenderHtmlAsPdf(htmlWithLogo)
' Apply a diagonal "UNPAID" watermark to mark invoice status
' 30% opacity keeps the content readable while the watermark is visible
pdf.ApplyWatermark("<h1 style='color: red;'>UNPAID</h1>",
opacity:=30,
rotation:=45,
verticalAlignment:=IronPdf.Editing.VerticalAlignment.Middle)
pdf.SaveAs("invoice-with-watermark.pdf")
Muestra de resultado
El método ApplyWatermark acepta contenido HTML, lo que le brinda control total sobre la apariencia de la marca de agua. Puede ajustar la opacidad, la rotación y la posición para conseguir exactamente el aspecto que necesita. Esto resulta especialmente útil para marcar facturas como "PAGADA", "BORRADOR" o "CANCELADA" sin tener que regenerar todo el documento.
Cómo incrustar códigos QR para enlaces de pago
Las facturas modernas suelen incluir códigos QR que los clientes pueden escanear para realizar pagos rápidamente. Aunque IronPDF se centra en la generación de PDF, funciona perfectamente con IronQR para la creación de códigos de barras:
:path=/static-assets/pdf/content-code-examples/tutorials/csharp-invoice-processing/qr-code-payment.cs
using IronPdf;
using IronQr;
using IronSoftware.Drawing;
string invoiceNumber = "INV-2026-002";
decimal amount = 1500.00m;
// Create a payment URL with invoice details as query parameters
string paymentUrl = $"https://yourcompany.com/pay?invoice={invoiceNumber}&amount={amount}";
// Generate QR code from the payment URL using IronQR
QrCode qrCode = QrWriter.Write(paymentUrl);
AnyBitmap qrImage = qrCode.Save();
qrImage.SaveAs("payment-qr.png", AnyBitmap.ImageFormat.Png);
// Build invoice HTML with the QR code image embedded
// Customers can scan the QR to pay directly from their phone
string invoiceHtml = $@"
E html>
le>
body {{ font-family: Arial, sans-serif; padding: 40px; }}
.payment-section {{ margin-top: 40px; text-align: center;
border-top: 2px solid #eee; padding-top: 20px; }}
.qr-code {{ width: 150px; height: 150px; }}
yle>
INVOICE {invoiceNumber}</h1>
strong>Amount Due:</strong> ${amount:F2}</p>
class='payment-section'>
<p><strong>Scan to Pay Instantly:</strong></p>
<img src='payment-qr.png' alt='Payment QR Code' class='qr-code' />
<p style='font-size: 12px; color: #666;'>
Or visit: {paymentUrl}
</p>
v>
;
// Convert HTML to PDF and save
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(invoiceHtml);
pdf.SaveAs($"invoice-{invoiceNumber}.pdf");
Imports IronPdf
Imports IronQr
Imports IronSoftware.Drawing
Dim invoiceNumber As String = "INV-2026-002"
Dim amount As Decimal = 1500.00D
' Create a payment URL with invoice details as query parameters
Dim paymentUrl As String = $"https://yourcompany.com/pay?invoice={invoiceNumber}&amount={amount}"
' Generate QR code from the payment URL using IronQR
Dim qrCode As QrCode = QrWriter.Write(paymentUrl)
Dim qrImage As AnyBitmap = qrCode.Save()
qrImage.SaveAs("payment-qr.png", AnyBitmap.ImageFormat.Png)
' Build invoice HTML with the QR code image embedded
' Customers can scan the QR to pay directly from their phone
Dim invoiceHtml As String = $"
<!DOCTYPE html>
<html>
<head>
<title>Invoice</title>
<style>
body {{ font-family: Arial, sans-serif; padding: 40px; }}
.payment-section {{ margin-top: 40px; text-align: center; border-top: 2px solid #eee; padding-top: 20px; }}
.qr-code {{ width: 150px; height: 150px; }}
</style>
</head>
<body>
<h1>INVOICE {invoiceNumber}</h1>
<p><strong>Amount Due:</strong> ${amount:F2}</p>
<div class='payment-section'>
<p><strong>Scan to Pay Instantly:</strong></p>
<img src='payment-qr.png' alt='Payment QR Code' class='qr-code' />
<p style='font-size: 12px; color: #666;'>
Or visit: {paymentUrl}
</p>
</div>
</body>
</html>"
' Convert HTML to PDF and save
Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf(invoiceHtml)
pdf.SaveAs($"invoice-{invoiceNumber}.pdf")
Muestra de resultado
El código QR enlaza directamente con una página de pago, lo que reduce la fricción para los clientes y acelera su flujo de caja. Este patrón funciona con cualquier proveedor de pagos que admita la iniciación de pagos basada en URL.
How to Comply with ZUGFeRD and Factur-X E-Invoicing Standards in C
La facturación electrónica se está convirtiendo rápidamente en obligatoria en toda Europa. Alemania abrió el camino con ZUGFeRD, y Francia le siguió con Factur-X. Estas normas incorporan datos XML legibles por máquina en facturas PDF, lo que permite el procesamiento automatizado al tiempo que se mantienen documentos legibles por humanos. Comprender y aplicar estas normas es cada vez más esencial para las empresas que operan en los mercados europeos.
¿Qué es ZUGFeRD y cómo funciona?
ZUGFeRD (Zentraler User Guide des Forums elektronische Rechnung Deutschland) es un estándar alemán de facturación electrónica que incorpora los datos de la factura como archivo adjunto XML dentro de un documento compatible con PDF/A-3. El XML incrustado permite la extracción automática de datos sin OCR ni análisis sintáctico.
La norma define tres niveles de conformidad, cada uno de los cuales ofrece datos progresivamente más estructurados:
- Basic: Contiene datos básicos de facturas adecuados para un procesamiento automatizado sencillo
- Comfort: Añade información detallada que permite una gestión de facturas totalmente automatizada
- Extended: Incluye datos completos para escenarios empresariales complejos en todos los sectores
El XML sigue el esquema UN/CEFACT Cross-Industry Invoice (CII), que se ha convertido en la base de la normalización europea de la facturación electrónica.
¿Qué es Factur-X y en qué se diferencia de ZUGFeRD?
Factur-X es la implementación francesa de la misma norma subyacente. ZUGFeRD 2.0 y Factur-X son técnicamente idénticos. Comparten el mismo esquema XML y perfiles de conformidad basados en la norma europea EN 16931. La diferencia es puramente de nomenclatura regional: una factura creada según las especificaciones de ZUGFeRD será válida en Factur-X, y viceversa.
Cómo incrustar datos XML en facturas PDF/A-3
IronPDF ofrece las funciones de adjuntado necesarias para crear facturas electrónicas conformes. El proceso consiste en generar el PDF de la factura, crear los datos XML de acuerdo con el esquema CII e incrustar el XML como archivo adjunto con las convenciones de nomenclatura correctas:
using System;
using System.Xml.Linq;
// Generates ZUGFeRD-compliant invoices by embedding structured XML data
// ZUGFeRD allows automated processing while keeping a human-readable PDF
public class ZUGFeRDInvoiceGenerator
{
public void GenerateZUGFeRDInvoice(Invoice invoice)
{
// First, create the visual PDF that humans will read
var renderer = new ChromePdfRenderer();
string invoiceHtml = BuildInvoiceHtml(invoice);
var pdf = renderer.RenderHtmlAsPdf(invoiceHtml);
// Define the UN/CEFACT namespaces required by the ZUGFeRD standard
// These are mandatory for compliance with European e-invoicing regulations
XNamespace rsm = "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100";
XNamespace ram = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100";
XNamespace udt = "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100";
// Build the ZUGFeRD XML structure following the Cross-Industry Invoice schema
var zugferdXml = new XDocument(
new XDeclaration("1.0", "UTF-8", null),
new XElement(rsm + "CrossIndustryInvoice",
new XAttribute(XNamespace.Xmlns + "rsm", rsm.NamespaceName),
new XAttribute(XNamespace.Xmlns + "ram", ram.NamespaceName),
new XAttribute(XNamespace.Xmlns + "udt", udt.NamespaceName),
// Document context identifies which e-invoicing guideline is being followed
new XElement(rsm + "ExchangedDocumentContext",
new XElement(ram + "GuidelineSpecifiedDocumentContextParameter",
new XElement(ram + "ID", "urn:cen.eu:en16931:2017")
)
),
// Core document identification: invoice number, type, and date
new XElement(rsm + "ExchangedDocument",
new XElement(ram + "ID", invoice.InvoiceNumber),
new XElement(ram + "TypeCode", "380"), // 380 = Commercial Invoice per UN/CEFACT
new XElement(ram + "IssueDateTime",
new XElement(udt + "DateTimeString",
new XAttribute("format", "102"),
invoice.InvoiceDate.ToString("yyyyMMdd")
)
)
),
// A complete implementation would include additional sections:
// - Seller information (ram:SellerTradeParty)
// - Buyer information (ram:BuyerTradeParty)
// - Line items (ram:IncludedSupplyChainTradeLineItem)
// - Payment terms (ram:SpecifiedTradePaymentTerms)
// - Tax summaries (ram:ApplicableTradeTax)
// Financial summary with all monetary totals
new XElement(rsm + "SupplyChainTradeTransaction",
new XElement(ram + "ApplicableHeaderTradeSettlement",
new XElement(ram + "InvoiceCurrencyCode", "EUR"),
new XElement(ram + "SpecifiedTradeSettlementHeaderMonetarySummation",
new XElement(ram + "TaxBasisTotalAmount", invoice.Subtotal),
new XElement(ram + "TaxTotalAmount",
new XAttribute("currencyID", "EUR"),
invoice.Tax),
new XElement(ram + "GrandTotalAmount", invoice.Total),
new XElement(ram + "DuePayableAmount", invoice.Total)
)
)
)
)
);
// Save the XML to a temp file for embedding
string xmlPath = $"zugferd-{invoice.InvoiceNumber}.xml";
zugferdXml.Save(xmlPath);
// Attach the XML to the PDF - filename must follow ZUGFeRD conventions
pdf.Attachments.AddFile(xmlPath, "zugferd-invoice.xml", "ZUGFeRD Invoice Data");
// Final PDF contains both visual invoice and machine-readable XML
pdf.SaveAs($"invoice-{invoice.InvoiceNumber}-zugferd.pdf");
}
// Generates simple HTML for the visual portion of the invoice
private string BuildInvoiceHtml(Invoice invoice)
{
return $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; padding: 40px; }}
h1 {{ color: #333; }}
.zugferd-notice {{
margin-top: 30px; padding: 10px;
background: #f0f0f0; font-size: 11px;
}}
</style>
</head>
<body>
<h1>RECHNUNG / INVOICE</h1>
<p><strong>Rechnungsnummer:</strong> {invoice.InvoiceNumber}</p>
<p><strong>Datum:</strong> {invoice.InvoiceDate:dd.MM.yyyy}</p>
<p><strong>Betrag:</strong> €{invoice.Total:F2}</p>
<div class='zugferd-notice'>
This invoice contains embedded ZUGFeRD data for automated processing.
</div>
</body>
</html>";
}
}
using System;
using System.Xml.Linq;
// Generates ZUGFeRD-compliant invoices by embedding structured XML data
// ZUGFeRD allows automated processing while keeping a human-readable PDF
public class ZUGFeRDInvoiceGenerator
{
public void GenerateZUGFeRDInvoice(Invoice invoice)
{
// First, create the visual PDF that humans will read
var renderer = new ChromePdfRenderer();
string invoiceHtml = BuildInvoiceHtml(invoice);
var pdf = renderer.RenderHtmlAsPdf(invoiceHtml);
// Define the UN/CEFACT namespaces required by the ZUGFeRD standard
// These are mandatory for compliance with European e-invoicing regulations
XNamespace rsm = "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100";
XNamespace ram = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100";
XNamespace udt = "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100";
// Build the ZUGFeRD XML structure following the Cross-Industry Invoice schema
var zugferdXml = new XDocument(
new XDeclaration("1.0", "UTF-8", null),
new XElement(rsm + "CrossIndustryInvoice",
new XAttribute(XNamespace.Xmlns + "rsm", rsm.NamespaceName),
new XAttribute(XNamespace.Xmlns + "ram", ram.NamespaceName),
new XAttribute(XNamespace.Xmlns + "udt", udt.NamespaceName),
// Document context identifies which e-invoicing guideline is being followed
new XElement(rsm + "ExchangedDocumentContext",
new XElement(ram + "GuidelineSpecifiedDocumentContextParameter",
new XElement(ram + "ID", "urn:cen.eu:en16931:2017")
)
),
// Core document identification: invoice number, type, and date
new XElement(rsm + "ExchangedDocument",
new XElement(ram + "ID", invoice.InvoiceNumber),
new XElement(ram + "TypeCode", "380"), // 380 = Commercial Invoice per UN/CEFACT
new XElement(ram + "IssueDateTime",
new XElement(udt + "DateTimeString",
new XAttribute("format", "102"),
invoice.InvoiceDate.ToString("yyyyMMdd")
)
)
),
// A complete implementation would include additional sections:
// - Seller information (ram:SellerTradeParty)
// - Buyer information (ram:BuyerTradeParty)
// - Line items (ram:IncludedSupplyChainTradeLineItem)
// - Payment terms (ram:SpecifiedTradePaymentTerms)
// - Tax summaries (ram:ApplicableTradeTax)
// Financial summary with all monetary totals
new XElement(rsm + "SupplyChainTradeTransaction",
new XElement(ram + "ApplicableHeaderTradeSettlement",
new XElement(ram + "InvoiceCurrencyCode", "EUR"),
new XElement(ram + "SpecifiedTradeSettlementHeaderMonetarySummation",
new XElement(ram + "TaxBasisTotalAmount", invoice.Subtotal),
new XElement(ram + "TaxTotalAmount",
new XAttribute("currencyID", "EUR"),
invoice.Tax),
new XElement(ram + "GrandTotalAmount", invoice.Total),
new XElement(ram + "DuePayableAmount", invoice.Total)
)
)
)
)
);
// Save the XML to a temp file for embedding
string xmlPath = $"zugferd-{invoice.InvoiceNumber}.xml";
zugferdXml.Save(xmlPath);
// Attach the XML to the PDF - filename must follow ZUGFeRD conventions
pdf.Attachments.AddFile(xmlPath, "zugferd-invoice.xml", "ZUGFeRD Invoice Data");
// Final PDF contains both visual invoice and machine-readable XML
pdf.SaveAs($"invoice-{invoice.InvoiceNumber}-zugferd.pdf");
}
// Generates simple HTML for the visual portion of the invoice
private string BuildInvoiceHtml(Invoice invoice)
{
return $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; padding: 40px; }}
h1 {{ color: #333; }}
.zugferd-notice {{
margin-top: 30px; padding: 10px;
background: #f0f0f0; font-size: 11px;
}}
</style>
</head>
<body>
<h1>RECHNUNG / INVOICE</h1>
<p><strong>Rechnungsnummer:</strong> {invoice.InvoiceNumber}</p>
<p><strong>Datum:</strong> {invoice.InvoiceDate:dd.MM.yyyy}</p>
<p><strong>Betrag:</strong> €{invoice.Total:F2}</p>
<div class='zugferd-notice'>
This invoice contains embedded ZUGFeRD data for automated processing.
</div>
</body>
</html>";
}
}
Imports System
Imports System.Xml.Linq
' Generates ZUGFeRD-compliant invoices by embedding structured XML data
' ZUGFeRD allows automated processing while keeping a human-readable PDF
Public Class ZUGFeRDInvoiceGenerator
Public Sub GenerateZUGFeRDInvoice(invoice As Invoice)
' First, create the visual PDF that humans will read
Dim renderer = New ChromePdfRenderer()
Dim invoiceHtml As String = BuildInvoiceHtml(invoice)
Dim pdf = renderer.RenderHtmlAsPdf(invoiceHtml)
' Define the UN/CEFACT namespaces required by the ZUGFeRD standard
' These are mandatory for compliance with European e-invoicing regulations
Dim rsm As XNamespace = "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
Dim ram As XNamespace = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
Dim udt As XNamespace = "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
' Build the ZUGFeRD XML structure following the Cross-Industry Invoice schema
Dim zugferdXml = New XDocument(
New XDeclaration("1.0", "UTF-8", Nothing),
New XElement(rsm + "CrossIndustryInvoice",
New XAttribute(XNamespace.Xmlns + "rsm", rsm.NamespaceName),
New XAttribute(XNamespace.Xmlns + "ram", ram.NamespaceName),
New XAttribute(XNamespace.Xmlns + "udt", udt.NamespaceName),
' Document context identifies which e-invoicing guideline is being followed
New XElement(rsm + "ExchangedDocumentContext",
New XElement(ram + "GuidelineSpecifiedDocumentContextParameter",
New XElement(ram + "ID", "urn:cen.eu:en16931:2017")
)
),
' Core document identification: invoice number, type, and date
New XElement(rsm + "ExchangedDocument",
New XElement(ram + "ID", invoice.InvoiceNumber),
New XElement(ram + "TypeCode", "380"), ' 380 = Commercial Invoice per UN/CEFACT
New XElement(ram + "IssueDateTime",
New XElement(udt + "DateTimeString",
New XAttribute("format", "102"),
invoice.InvoiceDate.ToString("yyyyMMdd")
)
)
),
' A complete implementation would include additional sections:
' - Seller information (ram:SellerTradeParty)
' - Buyer information (ram:BuyerTradeParty)
' - Line items (ram:IncludedSupplyChainTradeLineItem)
' - Payment terms (ram:SpecifiedTradePaymentTerms)
' - Tax summaries (ram:ApplicableTradeTax)
' Financial summary with all monetary totals
New XElement(rsm + "SupplyChainTradeTransaction",
New XElement(ram + "ApplicableHeaderTradeSettlement",
New XElement(ram + "InvoiceCurrencyCode", "EUR"),
New XElement(ram + "SpecifiedTradeSettlementHeaderMonetarySummation",
New XElement(ram + "TaxBasisTotalAmount", invoice.Subtotal),
New XElement(ram + "TaxTotalAmount",
New XAttribute("currencyID", "EUR"),
invoice.Tax),
New XElement(ram + "GrandTotalAmount", invoice.Total),
New XElement(ram + "DuePayableAmount", invoice.Total)
)
)
)
)
)
' Save the XML to a temp file for embedding
Dim xmlPath As String = $"zugferd-{invoice.InvoiceNumber}.xml"
zugferdXml.Save(xmlPath)
' Attach the XML to the PDF - filename must follow ZUGFeRD conventions
pdf.Attachments.AddFile(xmlPath, "zugferd-invoice.xml", "ZUGFeRD Invoice Data")
' Final PDF contains both visual invoice and machine-readable XML
pdf.SaveAs($"invoice-{invoice.InvoiceNumber}-zugferd.pdf")
End Sub
' Generates simple HTML for the visual portion of the invoice
Private Function BuildInvoiceHtml(invoice As Invoice) As String
Return $"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; padding: 40px; }}
h1 {{ color: #333; }}
.zugferd-notice {{
margin-top: 30px; padding: 10px;
background: #f0f0f0; font-size: 11px;
}}
</style>
</head>
<body>
<h1>RECHNUNG / INVOICE</h1>
<p><strong>Rechnungsnummer:</strong> {invoice.InvoiceNumber}</p>
<p><strong>Datum:</strong> {invoice.InvoiceDate:dd.MM.yyyy}</p>
<p><strong>Betrag:</strong> €{invoice.Total:F2}</p>
<div class='zugferd-notice'>
This invoice contains embedded ZUGFeRD data for automated processing.
</div>
</body>
</html>"
End Function
End Class
Muestra de resultado
Los aspectos clave de la conformidad son utilizar los espacios de nombres XML correctos, seguir la estructura del esquema CII e incrustar el XML con un nombre de archivo adecuado. El TypeCode "380" identifica específicamente el documento como una factura comercial dentro del estándar UN/CEFACT.
Cómo preparar las facturas para los mandatos de la UE
La Unión Europea está imponiendo progresivamente la facturación electrónica en todos los Estados miembros. Italia ya lo exige para las transacciones B2B, Francia está introduciendo gradualmente los requisitos hasta 2026 y Alemania ha anunciado la obligatoriedad de la facturación electrónica B2B a partir de 2025. Crear ahora la compatibilidad con ZUGFeRD/Factur-X prepara su sistema para estos requisitos normativos.
A continuación se muestra un patrón para un generador de facturas compatible con el cumplimiento que puede dirigirse a diferentes estándares:
using IronPdf;
using System;
// Enum representing supported European e-invoicing standards
public enum InvoiceStandard
{
None,
ZUGFeRD, // German standard - uses CII XML format
FacturX, // French standard - technically identical to ZUGFeRD 2.0
Peppol // Pan-European standard - uses UBL XML format
}
// Factory class that generates invoices compliant with different e-invoicing standards
// Allows switching between standards without changing core invoice generation logic
public class CompliantInvoiceGenerator
{
public PdfDocument GenerateCompliantInvoice(Invoice invoice, InvoiceStandard standard)
{
// Generate the base PDF from HTML
var renderer = new ChromePdfRenderer();
string html = BuildInvoiceHtml(invoice);
var pdf = renderer.RenderHtmlAsPdf(html);
// Attach the appropriate XML format based on target market/regulation
switch (standard)
{
case InvoiceStandard.ZUGFeRD:
case InvoiceStandard.FacturX:
// Both use Cross-Industry Invoice format, just different filenames
EmbedCIIXmlData(pdf, invoice, standard);
break;
case InvoiceStandard.Peppol:
// Peppol uses Universal Business Language format
EmbedUBLXmlData(pdf, invoice);
break;
}
return pdf;
}
// Creates and embeds CII-format XML (used by ZUGFeRD and Factur-X)
private void EmbedCIIXmlData(PdfDocument pdf, Invoice invoice, InvoiceStandard standard)
{
string xml = GenerateCIIXml(invoice);
// Filename convention differs between German and French standards
string filename = standard == InvoiceStandard.ZUGFeRD
? "zugferd-invoice.xml"
: "factur-x.xml";
System.IO.File.WriteAllText("temp-invoice.xml", xml);
pdf.Attachments.AddFile("temp-invoice.xml", filename, $"{standard} Invoice Data");
}
// Creates and embeds UBL-format XML for Peppol network compliance
private void EmbedUBLXmlData(PdfDocument pdf, Invoice invoice)
{
// UBL (Universal Business Language) is the Peppol standard format
string xml = $@"<?xml version='1.0' encoding='UTF-8'?>
<Invoice xmlns='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'>
<id>{invoice.InvoiceNumber}</id>
<IssueDate>{invoice.InvoiceDate:yyyy-MM-dd}</IssueDate>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<LegalMonetaryTotal>
<PayableAmount currencyID='EUR'>{invoice.Total}</PayableAmount>
</LegalMonetaryTotal>
</Invoice>";
System.IO.File.WriteAllText("peppol-invoice.xml", xml);
pdf.Attachments.AddFile("peppol-invoice.xml", "invoice.xml", "Peppol UBL Invoice");
}
// Generates minimal CII XML structure for demonstration
private string GenerateCIIXml(Invoice invoice)
{
return $@"<?xml version='1.0' encoding='UTF-8'?>
<rsm:CrossIndustryInvoice
xmlns:rsm='urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'
xmlns:ram='urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'>
<rsm:ExchangedDocument>
<ram:ID>{invoice.InvoiceNumber}</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
</rsm:ExchangedDocument>
</rsm:CrossIndustryInvoice>";
}
private string BuildInvoiceHtml(Invoice invoice)
{
return $"<html><body><h1>Invoice {invoice.InvoiceNumber}</h1></body></html>";
}
}
using IronPdf;
using System;
// Enum representing supported European e-invoicing standards
public enum InvoiceStandard
{
None,
ZUGFeRD, // German standard - uses CII XML format
FacturX, // French standard - technically identical to ZUGFeRD 2.0
Peppol // Pan-European standard - uses UBL XML format
}
// Factory class that generates invoices compliant with different e-invoicing standards
// Allows switching between standards without changing core invoice generation logic
public class CompliantInvoiceGenerator
{
public PdfDocument GenerateCompliantInvoice(Invoice invoice, InvoiceStandard standard)
{
// Generate the base PDF from HTML
var renderer = new ChromePdfRenderer();
string html = BuildInvoiceHtml(invoice);
var pdf = renderer.RenderHtmlAsPdf(html);
// Attach the appropriate XML format based on target market/regulation
switch (standard)
{
case InvoiceStandard.ZUGFeRD:
case InvoiceStandard.FacturX:
// Both use Cross-Industry Invoice format, just different filenames
EmbedCIIXmlData(pdf, invoice, standard);
break;
case InvoiceStandard.Peppol:
// Peppol uses Universal Business Language format
EmbedUBLXmlData(pdf, invoice);
break;
}
return pdf;
}
// Creates and embeds CII-format XML (used by ZUGFeRD and Factur-X)
private void EmbedCIIXmlData(PdfDocument pdf, Invoice invoice, InvoiceStandard standard)
{
string xml = GenerateCIIXml(invoice);
// Filename convention differs between German and French standards
string filename = standard == InvoiceStandard.ZUGFeRD
? "zugferd-invoice.xml"
: "factur-x.xml";
System.IO.File.WriteAllText("temp-invoice.xml", xml);
pdf.Attachments.AddFile("temp-invoice.xml", filename, $"{standard} Invoice Data");
}
// Creates and embeds UBL-format XML for Peppol network compliance
private void EmbedUBLXmlData(PdfDocument pdf, Invoice invoice)
{
// UBL (Universal Business Language) is the Peppol standard format
string xml = $@"<?xml version='1.0' encoding='UTF-8'?>
<Invoice xmlns='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'>
<id>{invoice.InvoiceNumber}</id>
<IssueDate>{invoice.InvoiceDate:yyyy-MM-dd}</IssueDate>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<LegalMonetaryTotal>
<PayableAmount currencyID='EUR'>{invoice.Total}</PayableAmount>
</LegalMonetaryTotal>
</Invoice>";
System.IO.File.WriteAllText("peppol-invoice.xml", xml);
pdf.Attachments.AddFile("peppol-invoice.xml", "invoice.xml", "Peppol UBL Invoice");
}
// Generates minimal CII XML structure for demonstration
private string GenerateCIIXml(Invoice invoice)
{
return $@"<?xml version='1.0' encoding='UTF-8'?>
<rsm:CrossIndustryInvoice
xmlns:rsm='urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'
xmlns:ram='urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'>
<rsm:ExchangedDocument>
<ram:ID>{invoice.InvoiceNumber}</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
</rsm:ExchangedDocument>
</rsm:CrossIndustryInvoice>";
}
private string BuildInvoiceHtml(Invoice invoice)
{
return $"<html><body><h1>Invoice {invoice.InvoiceNumber}</h1></body></html>";
}
}
Imports IronPdf
Imports System
' Enum representing supported European e-invoicing standards
Public Enum InvoiceStandard
None
ZUGFeRD ' German standard - uses CII XML format
FacturX ' French standard - technically identical to ZUGFeRD 2.0
Peppol ' Pan-European standard - uses UBL XML format
End Enum
' Factory class that generates invoices compliant with different e-invoicing standards
' Allows switching between standards without changing core invoice generation logic
Public Class CompliantInvoiceGenerator
Public Function GenerateCompliantInvoice(invoice As Invoice, standard As InvoiceStandard) As PdfDocument
' Generate the base PDF from HTML
Dim renderer As New ChromePdfRenderer()
Dim html As String = BuildInvoiceHtml(invoice)
Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf(html)
' Attach the appropriate XML format based on target market/regulation
Select Case standard
Case InvoiceStandard.ZUGFeRD, InvoiceStandard.FacturX
' Both use Cross-Industry Invoice format, just different filenames
EmbedCIIXmlData(pdf, invoice, standard)
Case InvoiceStandard.Peppol
' Peppol uses Universal Business Language format
EmbedUBLXmlData(pdf, invoice)
End Select
Return pdf
End Function
' Creates and embeds CII-format XML (used by ZUGFeRD and Factur-X)
Private Sub EmbedCIIXmlData(pdf As PdfDocument, invoice As Invoice, standard As InvoiceStandard)
Dim xml As String = GenerateCIIXml(invoice)
' Filename convention differs between German and French standards
Dim filename As String = If(standard = InvoiceStandard.ZUGFeRD, "zugferd-invoice.xml", "factur_x.xml")
System.IO.File.WriteAllText("temp-invoice.xml", xml)
pdf.Attachments.AddFile("temp-invoice.xml", filename, $"{standard} Invoice Data")
End Sub
' Creates and embeds UBL-format XML for Peppol network compliance
Private Sub EmbedUBLXmlData(pdf As PdfDocument, invoice As Invoice)
' UBL (Universal Business Language) is the Peppol standard format
Dim xml As String = $"<?xml version='1.0' encoding='UTF-8'?>
<Invoice xmlns='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'>
<id>{invoice.InvoiceNumber}</id>
<IssueDate>{invoice.InvoiceDate:yyyy-MM-dd}</IssueDate>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<LegalMonetaryTotal>
<PayableAmount currencyID='EUR'>{invoice.Total}</PayableAmount>
</LegalMonetaryTotal>
</Invoice>"
System.IO.File.WriteAllText("peppol-invoice.xml", xml)
pdf.Attachments.AddFile("peppol-invoice.xml", "invoice.xml", "Peppol UBL Invoice")
End Sub
' Generates minimal CII XML structure for demonstration
Private Function GenerateCIIXml(invoice As Invoice) As String
Return $"<?xml version='1.0' encoding='UTF-8'?>
<rsm:CrossIndustryInvoice
xmlns:rsm='urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'
xmlns:ram='urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'>
<rsm:ExchangedDocument>
<ram:ID>{invoice.InvoiceNumber}</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
</rsm:ExchangedDocument>
</rsm:CrossIndustryInvoice>"
End Function
Private Function BuildInvoiceHtml(invoice As Invoice) As String
Return $"<html><body><h1>Invoice {invoice.InvoiceNumber}</h1></body></html>"
End Function
End Class
Esta arquitectura le permite añadir nuevas normas a medida que surgen sin reestructurar su lógica central de generación de facturas. El enfoque basado en enum facilita que los usuarios o la configuración determinen qué modo de conformidad utilizar.
How to Extract Data from PDF Invoices in C
Generar facturas es solo la mitad de la ecuación. La mayoría de las empresas también reciben facturas de proveedores y necesitan extraer datos para procesarlos. IronPDF ofrece potentes funciones de extracción de texto que constituyen la base de la captura de datos de facturas.
Cómo extraer texto de una factura PDF
La operación de extracción más básica recupera todo el contenido de texto de un PDF. El método ExtractAllText de IronPDF maneja la complejidad de la codificación y el posicionamiento del texto PDF:
using IronPdf;
using System;
// Extracts raw text content from PDF invoices for further processing
public class InvoiceTextExtractor
{
// Extracts all text from a PDF in one operation
// Best for single-page invoices or when you need the complete content
public string ExtractInvoiceText(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
// IronPDF handles the complexity of PDF text encoding and positioning
string allText = pdf.ExtractAllText();
Console.WriteLine("Full invoice text:");
Console.WriteLine(allText);
return allText;
}
// Extracts text page by page - useful for multi-page invoices
// Allows you to process header info separately from line items
public void ExtractTextByPage(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
// Iterate through each page (0-indexed)
for (int i = 0; i < pdf.PageCount; i++)
{
string pageText = pdf.ExtractTextFromPage(i);
Console.WriteLine($"\n--- Page {i + 1} ---");
Console.WriteLine(pageText);
}
}
}
using IronPdf;
using System;
// Extracts raw text content from PDF invoices for further processing
public class InvoiceTextExtractor
{
// Extracts all text from a PDF in one operation
// Best for single-page invoices or when you need the complete content
public string ExtractInvoiceText(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
// IronPDF handles the complexity of PDF text encoding and positioning
string allText = pdf.ExtractAllText();
Console.WriteLine("Full invoice text:");
Console.WriteLine(allText);
return allText;
}
// Extracts text page by page - useful for multi-page invoices
// Allows you to process header info separately from line items
public void ExtractTextByPage(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
// Iterate through each page (0-indexed)
for (int i = 0; i < pdf.PageCount; i++)
{
string pageText = pdf.ExtractTextFromPage(i);
Console.WriteLine($"\n--- Page {i + 1} ---");
Console.WriteLine(pageText);
}
}
}
Imports IronPdf
Imports System
' Extracts raw text content from PDF invoices for further processing
Public Class InvoiceTextExtractor
' Extracts all text from a PDF in one operation
' Best for single-page invoices or when you need the complete content
Public Function ExtractInvoiceText(pdfPath As String) As String
Dim pdf = PdfDocument.FromFile(pdfPath)
' IronPDF handles the complexity of PDF text encoding and positioning
Dim allText As String = pdf.ExtractAllText()
Console.WriteLine("Full invoice text:")
Console.WriteLine(allText)
Return allText
End Function
' Extracts text page by page - useful for multi-page invoices
' Allows you to process header info separately from line items
Public Sub ExtractTextByPage(pdfPath As String)
Dim pdf = PdfDocument.FromFile(pdfPath)
' Iterate through each page (0-indexed)
For i As Integer = 0 To pdf.PageCount - 1
Dim pageText As String = pdf.ExtractTextFromPage(i)
Console.WriteLine(vbCrLf & "--- Page " & (i + 1).ToString() & " ---")
Console.WriteLine(pageText)
Next
End Sub
End Class
La extracción página por página es especialmente útil para facturas de varias páginas en las que es necesario localizar secciones específicas, como encontrar partidas que abarcan varias páginas mientras que la información de cabecera solo aparece en la primera página.
Cómo extraer datos de tabla para partidas
Las partidas de la factura suelen aparecer en formato tabular. Los PDF carecen de una estructura nativa de tablas, pero se puede extraer texto y analizarlo para reconstruir los datos de las tablas:
using IronPdf;
using System;
using System.Collections.Generic;
// Data model for a single invoice line item
public class InvoiceLineItem
{
public string Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total { get; set; }
}
// Extracts tabular line item data from PDF invoices
// Note: PDFs don't have native table structure, so this uses text parsing
public class InvoiceTableExtractor
{
public List<InvoiceLineItem> ExtractLineItems(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
string text = pdf.ExtractAllText();
var lineItems = new List<InvoiceLineItem>();
string[] lines = text.Split('\n');
foreach (string line in lines)
{
// Currency symbols indicate potential line items with amounts
if (line.Contains("$") || line.Contains("€"))
{
Console.WriteLine($"Potential line item: {line.Trim()}");
// Split on whitespace to separate columns
// Actual parsing logic depends on your invoice format
string[] parts = line.Split(new[] { '\t', ' ' },
StringSplitOptions.RemoveEmptyEntries);
// Try to find numeric values that could be amounts
foreach (string part in parts)
{
string cleaned = part.Replace("$", "").Replace("€", "").Replace(",", "");
if (decimal.TryParse(cleaned, out decimal amount))
{
Console.WriteLine($" Found amount: {amount:C}");
}
}
}
}
return lineItems;
}
}
using IronPdf;
using System;
using System.Collections.Generic;
// Data model for a single invoice line item
public class InvoiceLineItem
{
public string Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total { get; set; }
}
// Extracts tabular line item data from PDF invoices
// Note: PDFs don't have native table structure, so this uses text parsing
public class InvoiceTableExtractor
{
public List<InvoiceLineItem> ExtractLineItems(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
string text = pdf.ExtractAllText();
var lineItems = new List<InvoiceLineItem>();
string[] lines = text.Split('\n');
foreach (string line in lines)
{
// Currency symbols indicate potential line items with amounts
if (line.Contains("$") || line.Contains("€"))
{
Console.WriteLine($"Potential line item: {line.Trim()}");
// Split on whitespace to separate columns
// Actual parsing logic depends on your invoice format
string[] parts = line.Split(new[] { '\t', ' ' },
StringSplitOptions.RemoveEmptyEntries);
// Try to find numeric values that could be amounts
foreach (string part in parts)
{
string cleaned = part.Replace("$", "").Replace("€", "").Replace(",", "");
if (decimal.TryParse(cleaned, out decimal amount))
{
Console.WriteLine($" Found amount: {amount:C}");
}
}
}
}
return lineItems;
}
}
Imports IronPdf
Imports System
Imports System.Collections.Generic
' Data model for a single invoice line item
Public Class InvoiceLineItem
Public Property Description As String
Public Property Quantity As Decimal
Public Property UnitPrice As Decimal
Public Property Total As Decimal
End Class
' Extracts tabular line item data from PDF invoices
' Note: PDFs don't have native table structure, so this uses text parsing
Public Class InvoiceTableExtractor
Public Function ExtractLineItems(pdfPath As String) As List(Of InvoiceLineItem)
Dim pdf = PdfDocument.FromFile(pdfPath)
Dim text As String = pdf.ExtractAllText()
Dim lineItems As New List(Of InvoiceLineItem)()
Dim lines() As String = text.Split(ControlChars.Lf)
For Each line As String In lines
' Currency symbols indicate potential line items with amounts
If line.Contains("$") OrElse line.Contains("€") Then
Console.WriteLine($"Potential line item: {line.Trim()}")
' Split on whitespace to separate columns
' Actual parsing logic depends on your invoice format
Dim parts() As String = line.Split(New Char() {ControlChars.Tab, " "c}, StringSplitOptions.RemoveEmptyEntries)
' Try to find numeric values that could be amounts
For Each part As String In parts
Dim cleaned As String = part.Replace("$", "").Replace("€", "").Replace(",", "")
Dim amount As Decimal
If Decimal.TryParse(cleaned, amount) Then
Console.WriteLine($" Found amount: {amount:C}")
End If
Next
End If
Next
Return lineItems
End Function
End Class
La lógica de análisis variará en función de los formatos de las facturas. Para las facturas con diseños coherentes de proveedores conocidos, puede crear analizadores específicos de formato. Para formatos variados, considere la extracción asistida por IA que se trata más adelante en este artículo.
Cómo utilizar la concordancia de patrones para números de factura, fechas y totales
Las expresiones regulares son muy útiles para extraer datos específicos del texto de una factura. Los campos clave, como números de factura, fechas y totales, suelen seguir patrones reconocibles:
using IronPdf;
using System;
using System.Text.RegularExpressions;
// Data model for extracted invoice information
public class InvoiceData
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public decimal TotalAmount { get; set; }
public string VendorName { get; set; }
}
// Extracts key invoice fields using regex pattern matching
// Multiple patterns handle variations across different vendors
public class InvoiceParser
{
public InvoiceData ParseInvoice(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
string text = pdf.ExtractAllText();
var invoiceData = new InvoiceData();
// Try multiple patterns to find invoice number
// Handles: "Invoice #123", "INV-123", "Invoice Number: 123", German "Rechnungsnummer"
string[] invoiceNumberPatterns = new[]
{
@"Invoice\s*#?\s*:?\s*([A-Z0-9-]+)",
@"INV[-\s]?(\d+)",
@"Invoice\s+Number\s*:?\s*([A-Z0-9-]+)",
@"Rechnungsnummer\s*:?\s*([A-Z0-9-]+)"
};
foreach (string pattern in invoiceNumberPatterns)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
invoiceData.InvoiceNumber = match.Groups[1].Value;
Console.WriteLine($"Found Invoice Number: {invoiceData.InvoiceNumber}");
break;
}
}
// Date patterns for US, European, and written formats
string[] datePatterns = new[]
{
@"Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
@"Invoice\s+Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
@"(\d{1,2}\.\d{1,2}\.\d{4})", // European: DD.MM.YYYY
@"(\w+\s+\d{1,2},?\s+\d{4})" // Written: January 15, 2024
};
foreach (string pattern in datePatterns)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
invoiceData.InvoiceDate = match.Groups[1].Value;
Console.WriteLine($"Found Date: {invoiceData.InvoiceDate}");
break;
}
}
// Look for total amount with various labels
string[] totalPatterns = new[]
{
@"Total\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
@"Amount\s+Due\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
@"Grand\s+Total\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
@"Balance\s+Due\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})"
};
foreach (string pattern in totalPatterns)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
// Remove commas before parsing
string amountStr = match.Groups[1].Value.Replace(",", "");
if (decimal.TryParse(amountStr, out decimal amount))
{
invoiceData.TotalAmount = amount;
Console.WriteLine($"Found Total: ${invoiceData.TotalAmount:F2}");
break;
}
}
}
return invoiceData;
}
}
using IronPdf;
using System;
using System.Text.RegularExpressions;
// Data model for extracted invoice information
public class InvoiceData
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public decimal TotalAmount { get; set; }
public string VendorName { get; set; }
}
// Extracts key invoice fields using regex pattern matching
// Multiple patterns handle variations across different vendors
public class InvoiceParser
{
public InvoiceData ParseInvoice(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
string text = pdf.ExtractAllText();
var invoiceData = new InvoiceData();
// Try multiple patterns to find invoice number
// Handles: "Invoice #123", "INV-123", "Invoice Number: 123", German "Rechnungsnummer"
string[] invoiceNumberPatterns = new[]
{
@"Invoice\s*#?\s*:?\s*([A-Z0-9-]+)",
@"INV[-\s]?(\d+)",
@"Invoice\s+Number\s*:?\s*([A-Z0-9-]+)",
@"Rechnungsnummer\s*:?\s*([A-Z0-9-]+)"
};
foreach (string pattern in invoiceNumberPatterns)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
invoiceData.InvoiceNumber = match.Groups[1].Value;
Console.WriteLine($"Found Invoice Number: {invoiceData.InvoiceNumber}");
break;
}
}
// Date patterns for US, European, and written formats
string[] datePatterns = new[]
{
@"Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
@"Invoice\s+Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
@"(\d{1,2}\.\d{1,2}\.\d{4})", // European: DD.MM.YYYY
@"(\w+\s+\d{1,2},?\s+\d{4})" // Written: January 15, 2024
};
foreach (string pattern in datePatterns)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
invoiceData.InvoiceDate = match.Groups[1].Value;
Console.WriteLine($"Found Date: {invoiceData.InvoiceDate}");
break;
}
}
// Look for total amount with various labels
string[] totalPatterns = new[]
{
@"Total\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
@"Amount\s+Due\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
@"Grand\s+Total\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
@"Balance\s+Due\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})"
};
foreach (string pattern in totalPatterns)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
// Remove commas before parsing
string amountStr = match.Groups[1].Value.Replace(",", "");
if (decimal.TryParse(amountStr, out decimal amount))
{
invoiceData.TotalAmount = amount;
Console.WriteLine($"Found Total: ${invoiceData.TotalAmount:F2}");
break;
}
}
}
return invoiceData;
}
}
Imports IronPdf
Imports System
Imports System.Text.RegularExpressions
' Data model for extracted invoice information
Public Class InvoiceData
Public Property InvoiceNumber As String
Public Property InvoiceDate As String
Public Property TotalAmount As Decimal
Public Property VendorName As String
End Class
' Extracts key invoice fields using regex pattern matching
' Multiple patterns handle variations across different vendors
Public Class InvoiceParser
Public Function ParseInvoice(pdfPath As String) As InvoiceData
Dim pdf = PdfDocument.FromFile(pdfPath)
Dim text As String = pdf.ExtractAllText()
Dim invoiceData As New InvoiceData()
' Try multiple patterns to find invoice number
' Handles: "Invoice #123", "INV-123", "Invoice Number: 123", German "Rechnungsnummer"
Dim invoiceNumberPatterns As String() = {
"Invoice\s*#?\s*:?\s*([A-Z0-9-]+)",
"INV[-\s]?(\d+)",
"Invoice\s+Number\s*:?\s*([A-Z0-9-]+)",
"Rechnungsnummer\s*:?\s*([A-Z0-9-]+)"
}
For Each pattern As String In invoiceNumberPatterns
Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
If match.Success Then
invoiceData.InvoiceNumber = match.Groups(1).Value
Console.WriteLine($"Found Invoice Number: {invoiceData.InvoiceNumber}")
Exit For
End If
Next
' Date patterns for US, European, and written formats
Dim datePatterns As String() = {
"Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
"Invoice\s+Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
"(\d{1,2}\.\d{1,2}\.\d{4})", ' European: DD.MM.YYYY
"(\w+\s+\d{1,2},?\s+\d{4})" ' Written: January 15, 2024
}
For Each pattern As String In datePatterns
Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
If match.Success Then
invoiceData.InvoiceDate = match.Groups(1).Value
Console.WriteLine($"Found Date: {invoiceData.InvoiceDate}")
Exit For
End If
Next
' Look for total amount with various labels
Dim totalPatterns As String() = {
"Total\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
"Amount\s+Due\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
"Grand\s+Total\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})",
"Balance\s+Due\s*:?\s*[\$€]?\s*([\d,]+\.\d{2})"
}
For Each pattern As String In totalPatterns
Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
If match.Success Then
' Remove commas before parsing
Dim amountStr As String = match.Groups(1).Value.Replace(",", "")
Dim amount As Decimal
If Decimal.TryParse(amountStr, amount) Then
invoiceData.TotalAmount = amount
Console.WriteLine($"Found Total: ${invoiceData.TotalAmount:F2}")
Exit For
End If
End If
Next
Return invoiceData
End Function
End Class
Este enfoque basado en patrones funciona bien para facturas con formatos predecibles. Las múltiples variaciones de patrones manejan diferencias de formato comunes entre proveedores, como "Invoice #" y "Invoice Number:"
¿Qué ocurre con las facturas escaneadas o basadas en imágenes?
Los métodos de extracción de texto mostrados anteriormente funcionan con PDF que contienen texto incrustado. Sin embargo, los documentos escaneados y los PDF basados en imágenes no tienen texto extraíble. Son esencialmente imágenes de facturas.
Cómo utilizar la IA para procesar facturas en .NET
La concordancia de patrones tradicional funciona bien para facturas estandarizadas, pero los departamentos de cuentas por pagar del mundo real reciben documentos en innumerables formatos. Aquí es donde brilla la extracción asistida por IA. Los grandes modelos lingüísticos pueden comprender la semántica de las facturas y extraer datos estructurados incluso de diseños desconocidos.
Cómo integrar IA para el análisis sintáctico de facturas
El patrón para el procesamiento de facturas basado en IA combina la extracción de texto de IronPDF con las llamadas a la API de LLM. A continuación se presenta una implementación genérica que funciona con cualquier API compatible con OpenAI:
using IronPdf;
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
// Data model for extracted invoice information
public class InvoiceData
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public string VendorName { get; set; }
public decimal TotalAmount { get; set; }
}
// Leverages AI/LLM APIs to extract structured data from any invoice format
// Works with OpenAI or any compatible API endpoint
public class AIInvoiceParser
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _apiUrl;
public AIInvoiceParser(string apiKey, string apiUrl = "https://api.openai.com/v1/chat/completions")
{
_apiKey = apiKey;
_apiUrl = apiUrl;
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");
}
public async Task<InvoiceData> ParseInvoiceWithAI(string pdfPath)
{
// First extract raw text from the PDF using IronPDF
var pdf = PdfDocument.FromFile(pdfPath);
string invoiceText = pdf.ExtractAllText();
// Construct a prompt that instructs the AI to return structured JSON
// Being explicit about the format reduces parsing errors
string prompt = $@"Extract the following information from this invoice text.
Return ONLY valid JSON with no additional text or markdown formatting.
Required fields:
- InvoiceNumber: The invoice or document number
- InvoiceDate: The invoice date in YYYY-MM-DD format
- VendorName: The company or person who sent the invoice
- TotalAmount: The total amount due as a number (no currency symbols)
Invoice text:
{invoiceText}
JSON response:";
// Build the API request with a system prompt for context
var requestBody = new
{
model = "gpt-4",
messages = new[]
{
new {
role = "system",
content = "You are an invoice data extraction assistant. Extract structured data from invoices and return valid JSON only."
},
new { role = "user", content = prompt }
},
temperature = 0.1 // Low temperature ensures consistent, deterministic results
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(_apiUrl, content);
var responseJson = await response.Content.ReadAsStringAsync();
// Navigate the API response structure to get the extracted content
using var doc = JsonDocument.Parse(responseJson);
var messageContent = doc.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString();
Console.WriteLine("AI Extracted Data:");
Console.WriteLine(messageContent);
// Deserialize the AI's JSON response into our data class
var invoiceData = JsonSerializer.Deserialize<InvoiceData>(messageContent,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return invoiceData;
}
}
using IronPdf;
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
// Data model for extracted invoice information
public class InvoiceData
{
public string InvoiceNumber { get; set; }
public string InvoiceDate { get; set; }
public string VendorName { get; set; }
public decimal TotalAmount { get; set; }
}
// Leverages AI/LLM APIs to extract structured data from any invoice format
// Works with OpenAI or any compatible API endpoint
public class AIInvoiceParser
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _apiUrl;
public AIInvoiceParser(string apiKey, string apiUrl = "https://api.openai.com/v1/chat/completions")
{
_apiKey = apiKey;
_apiUrl = apiUrl;
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");
}
public async Task<InvoiceData> ParseInvoiceWithAI(string pdfPath)
{
// First extract raw text from the PDF using IronPDF
var pdf = PdfDocument.FromFile(pdfPath);
string invoiceText = pdf.ExtractAllText();
// Construct a prompt that instructs the AI to return structured JSON
// Being explicit about the format reduces parsing errors
string prompt = $@"Extract the following information from this invoice text.
Return ONLY valid JSON with no additional text or markdown formatting.
Required fields:
- InvoiceNumber: The invoice or document number
- InvoiceDate: The invoice date in YYYY-MM-DD format
- VendorName: The company or person who sent the invoice
- TotalAmount: The total amount due as a number (no currency symbols)
Invoice text:
{invoiceText}
JSON response:";
// Build the API request with a system prompt for context
var requestBody = new
{
model = "gpt-4",
messages = new[]
{
new {
role = "system",
content = "You are an invoice data extraction assistant. Extract structured data from invoices and return valid JSON only."
},
new { role = "user", content = prompt }
},
temperature = 0.1 // Low temperature ensures consistent, deterministic results
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(_apiUrl, content);
var responseJson = await response.Content.ReadAsStringAsync();
// Navigate the API response structure to get the extracted content
using var doc = JsonDocument.Parse(responseJson);
var messageContent = doc.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString();
Console.WriteLine("AI Extracted Data:");
Console.WriteLine(messageContent);
// Deserialize the AI's JSON response into our data class
var invoiceData = JsonSerializer.Deserialize<InvoiceData>(messageContent,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return invoiceData;
}
}
Imports IronPdf
Imports System
Imports System.Net.Http
Imports System.Text
Imports System.Text.Json
Imports System.Threading.Tasks
' Data model for extracted invoice information
Public Class InvoiceData
Public Property InvoiceNumber As String
Public Property InvoiceDate As String
Public Property VendorName As String
Public Property TotalAmount As Decimal
End Class
' Leverages AI/LLM APIs to extract structured data from any invoice format
' Works with OpenAI or any compatible API endpoint
Public Class AIInvoiceParser
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _apiKey As String
Private ReadOnly _apiUrl As String
Public Sub New(apiKey As String, Optional apiUrl As String = "https://api.openai.com/v1/chat/completions")
_apiKey = apiKey
_apiUrl = apiUrl
_httpClient = New HttpClient()
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}")
End Sub
Public Async Function ParseInvoiceWithAI(pdfPath As String) As Task(Of InvoiceData)
' First extract raw text from the PDF using IronPDF
Dim pdf = PdfDocument.FromFile(pdfPath)
Dim invoiceText As String = pdf.ExtractAllText()
' Construct a prompt that instructs the AI to return structured JSON
' Being explicit about the format reduces parsing errors
Dim prompt As String = $"Extract the following information from this invoice text.
Return ONLY valid JSON with no additional text or markdown formatting.
Required fields:
- InvoiceNumber: The invoice or document number
- InvoiceDate: The invoice date in YYYY-MM-DD format
- VendorName: The company or person who sent the invoice
- TotalAmount: The total amount due as a number (no currency symbols)
Invoice text:
{invoiceText}
JSON response:"
' Build the API request with a system prompt for context
Dim requestBody = New With {
.model = "gpt-4",
.messages = New Object() {
New With {
.role = "system",
.content = "You are an invoice data extraction assistant. Extract structured data from invoices and return valid JSON only."
},
New With {
.role = "user",
.content = prompt
}
},
.temperature = 0.1 ' Low temperature ensures consistent, deterministic results
}
Dim json As String = JsonSerializer.Serialize(requestBody)
Dim content As New StringContent(json, Encoding.UTF8, "application/json")
Dim response = Await _httpClient.PostAsync(_apiUrl, content)
Dim responseJson As String = Await response.Content.ReadAsStringAsync()
' Navigate the API response structure to get the extracted content
Using doc = JsonDocument.Parse(responseJson)
Dim messageContent As String = doc.RootElement _
.GetProperty("choices")(0) _
.GetProperty("message") _
.GetProperty("content") _
.GetString()
Console.WriteLine("AI Extracted Data:")
Console.WriteLine(messageContent)
' Deserialize the AI's JSON response into our data class
Dim invoiceData As InvoiceData = JsonSerializer.Deserialize(Of InvoiceData)(messageContent, New JsonSerializerOptions With {.PropertyNameCaseInsensitive = True})
Return invoiceData
End Using
End Function
End Class
El ajuste de baja temperatura (0,1) fomenta los resultados deterministas, lo que es importante para las tareas de extracción de datos en las que se desean resultados coherentes para la misma entrada.
Cómo extraer datos JSON estructurados de facturas
Para facturas más complejas con partidas, detalles del proveedor e información del cliente, puede solicitar una estructura JSON más rica:
using IronPdf;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
// Comprehensive invoice data model with all details
public class DetailedInvoiceData
{
public string InvoiceNumber { get; set; }
public DateTime InvoiceDate { get; set; }
public DateTime DueDate { get; set; }
public VendorInfo Vendor { get; set; }
public CustomerInfo Customer { get; set; }
public List<LineItem> LineItems { get; set; }
public decimal Subtotal { get; set; }
public decimal Tax { get; set; }
public decimal Total { get; set; }
}
public class VendorInfo
{
public string Name { get; set; }
public string Address { get; set; }
public string TaxId { get; set; }
}
public class CustomerInfo
{
public string Name { get; set; }
public string Address { get; set; }
}
public class LineItem
{
public string Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total { get; set; }
}
// Extracts comprehensive invoice data including line items and party details
public class StructuredInvoiceExtractor
{
private readonly AIInvoiceParser _aiParser;
public StructuredInvoiceExtractor(string apiKey)
{
_aiParser = new AIInvoiceParser(apiKey);
}
public async Task<DetailedInvoiceData> ExtractDetailedData(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
string text = pdf.ExtractAllText();
// Define the exact JSON structure we want the AI to return
// This schema guides the AI to extract all relevant fields
string jsonSchema = @"{
""InvoiceNumber"": ""string"",
""InvoiceDate"": ""YYYY-MM-DD"",
""DueDate"": ""YYYY-MM-DD"",
""Vendor"": {
""Name"": ""string"",
""Address"": ""string"",
""TaxId"": ""string or null""
},
""Customer"": {
""Name"": ""string"",
""Address"": ""string""
},
""LineItems"": [
{
""Description"": ""string"",
""Quantity"": 0.0,
""UnitPrice"": 0.00,
""Total"": 0.00
}
],
""Subtotal"": 0.00,
""Tax"": 0.00,
""Total"": 0.00
}";
// Prompt includes both the schema and the extracted text
string prompt = $@"Extract all invoice data and return it in this exact JSON structure:
{jsonSchema}
Invoice text:
{text}
Return only valid JSON, no markdown formatting or additional text.";
// Call AI API and parse response (implementation as shown above)
// Return deserialized DetailedInvoiceData
return new DetailedInvoiceData(); // Placeholder
}
}
using IronPdf;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
// Comprehensive invoice data model with all details
public class DetailedInvoiceData
{
public string InvoiceNumber { get; set; }
public DateTime InvoiceDate { get; set; }
public DateTime DueDate { get; set; }
public VendorInfo Vendor { get; set; }
public CustomerInfo Customer { get; set; }
public List<LineItem> LineItems { get; set; }
public decimal Subtotal { get; set; }
public decimal Tax { get; set; }
public decimal Total { get; set; }
}
public class VendorInfo
{
public string Name { get; set; }
public string Address { get; set; }
public string TaxId { get; set; }
}
public class CustomerInfo
{
public string Name { get; set; }
public string Address { get; set; }
}
public class LineItem
{
public string Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total { get; set; }
}
// Extracts comprehensive invoice data including line items and party details
public class StructuredInvoiceExtractor
{
private readonly AIInvoiceParser _aiParser;
public StructuredInvoiceExtractor(string apiKey)
{
_aiParser = new AIInvoiceParser(apiKey);
}
public async Task<DetailedInvoiceData> ExtractDetailedData(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
string text = pdf.ExtractAllText();
// Define the exact JSON structure we want the AI to return
// This schema guides the AI to extract all relevant fields
string jsonSchema = @"{
""InvoiceNumber"": ""string"",
""InvoiceDate"": ""YYYY-MM-DD"",
""DueDate"": ""YYYY-MM-DD"",
""Vendor"": {
""Name"": ""string"",
""Address"": ""string"",
""TaxId"": ""string or null""
},
""Customer"": {
""Name"": ""string"",
""Address"": ""string""
},
""LineItems"": [
{
""Description"": ""string"",
""Quantity"": 0.0,
""UnitPrice"": 0.00,
""Total"": 0.00
}
],
""Subtotal"": 0.00,
""Tax"": 0.00,
""Total"": 0.00
}";
// Prompt includes both the schema and the extracted text
string prompt = $@"Extract all invoice data and return it in this exact JSON structure:
{jsonSchema}
Invoice text:
{text}
Return only valid JSON, no markdown formatting or additional text.";
// Call AI API and parse response (implementation as shown above)
// Return deserialized DetailedInvoiceData
return new DetailedInvoiceData(); // Placeholder
}
}
Imports IronPdf
Imports System
Imports System.Collections.Generic
Imports System.Text.Json
Imports System.Threading.Tasks
' Comprehensive invoice data model with all details
Public Class DetailedInvoiceData
Public Property InvoiceNumber As String
Public Property InvoiceDate As DateTime
Public Property DueDate As DateTime
Public Property Vendor As VendorInfo
Public Property Customer As CustomerInfo
Public Property LineItems As List(Of LineItem)
Public Property Subtotal As Decimal
Public Property Tax As Decimal
Public Property Total As Decimal
End Class
Public Class VendorInfo
Public Property Name As String
Public Property Address As String
Public Property TaxId As String
End Class
Public Class CustomerInfo
Public Property Name As String
Public Property Address As String
End Class
Public Class LineItem
Public Property Description As String
Public Property Quantity As Decimal
Public Property UnitPrice As Decimal
Public Property Total As Decimal
End Class
' Extracts comprehensive invoice data including line items and party details
Public Class StructuredInvoiceExtractor
Private ReadOnly _aiParser As AIInvoiceParser
Public Sub New(apiKey As String)
_aiParser = New AIInvoiceParser(apiKey)
End Sub
Public Async Function ExtractDetailedData(pdfPath As String) As Task(Of DetailedInvoiceData)
Dim pdf = PdfDocument.FromFile(pdfPath)
Dim text As String = pdf.ExtractAllText()
' Define the exact JSON structure we want the AI to return
' This schema guides the AI to extract all relevant fields
Dim jsonSchema As String = "{
""InvoiceNumber"": ""string"",
""InvoiceDate"": ""YYYY-MM-DD"",
""DueDate"": ""YYYY-MM-DD"",
""Vendor"": {
""Name"": ""string"",
""Address"": ""string"",
""TaxId"": ""string or null""
},
""Customer"": {
""Name"": ""string"",
""Address"": ""string""
},
""LineItems"": [
{
""Description"": ""string"",
""Quantity"": 0.0,
""UnitPrice"": 0.00,
""Total"": 0.00
}
],
""Subtotal"": 0.00,
""Tax"": 0.00,
""Total"": 0.00
}"
' Prompt includes both the schema and the extracted text
Dim prompt As String = $"
Extract all invoice data and return it in this exact JSON structure:
{jsonSchema}
Invoice text:
{text}
Return only valid JSON, no markdown formatting or additional text."
' Call AI API and parse response (implementation as shown above)
' Return deserialized DetailedInvoiceData
Return New DetailedInvoiceData() ' Placeholder
End Function
End Class
Cómo gestionar formatos de factura incoherentes
La verdadera potencia de la extracción de IA surge al procesar facturas de varios proveedores, cada uno con formatos únicos. Un procesador inteligente puede intentar primero la extracción basada en patrones (más rápida y gratuita) y recurrir a la IA solo cuando sea necesario:
using IronPdf;
using System.Threading.Tasks;
// Hybrid processor that optimizes for cost and capability
// Tries fast regex patterns first, uses AI only when patterns fail
public class SmartInvoiceProcessor
{
private readonly AIInvoiceParser _aiParser;
public SmartInvoiceProcessor(string aiApiKey)
{
_aiParser = new AIInvoiceParser(aiApiKey);
}
public async Task<InvoiceData> ProcessAnyInvoice(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
string text = pdf.ExtractAllText();
// First attempt: regex patterns (fast and free)
var patternParser = new InvoiceParser();
var standardResult = patternParser.ParseInvoiceFromText(text);
// If pattern matching found all required fields, use that result
if (IsComplete(standardResult))
{
Console.WriteLine("Pattern extraction successful");
return standardResult;
}
// Fallback: use AI for complex or unusual invoice formats
// This costs money but handles any layout
Console.WriteLine("Using AI extraction for complex invoice format");
var aiResult = await _aiParser.ParseInvoiceWithAI(pdfPath);
return aiResult;
}
// Validates that we have the minimum required fields
private bool IsComplete(InvoiceData data)
{
return !string.IsNullOrEmpty(data.InvoiceNumber) &&
!string.IsNullOrEmpty(data.InvoiceDate) &&
data.TotalAmount > 0;
}
}
using IronPdf;
using System.Threading.Tasks;
// Hybrid processor that optimizes for cost and capability
// Tries fast regex patterns first, uses AI only when patterns fail
public class SmartInvoiceProcessor
{
private readonly AIInvoiceParser _aiParser;
public SmartInvoiceProcessor(string aiApiKey)
{
_aiParser = new AIInvoiceParser(aiApiKey);
}
public async Task<InvoiceData> ProcessAnyInvoice(string pdfPath)
{
var pdf = PdfDocument.FromFile(pdfPath);
string text = pdf.ExtractAllText();
// First attempt: regex patterns (fast and free)
var patternParser = new InvoiceParser();
var standardResult = patternParser.ParseInvoiceFromText(text);
// If pattern matching found all required fields, use that result
if (IsComplete(standardResult))
{
Console.WriteLine("Pattern extraction successful");
return standardResult;
}
// Fallback: use AI for complex or unusual invoice formats
// This costs money but handles any layout
Console.WriteLine("Using AI extraction for complex invoice format");
var aiResult = await _aiParser.ParseInvoiceWithAI(pdfPath);
return aiResult;
}
// Validates that we have the minimum required fields
private bool IsComplete(InvoiceData data)
{
return !string.IsNullOrEmpty(data.InvoiceNumber) &&
!string.IsNullOrEmpty(data.InvoiceDate) &&
data.TotalAmount > 0;
}
}
Imports IronPdf
Imports System.Threading.Tasks
' Hybrid processor that optimizes for cost and capability
' Tries fast regex patterns first, uses AI only when patterns fail
Public Class SmartInvoiceProcessor
Private ReadOnly _aiParser As AIInvoiceParser
Public Sub New(aiApiKey As String)
_aiParser = New AIInvoiceParser(aiApiKey)
End Sub
Public Async Function ProcessAnyInvoice(pdfPath As String) As Task(Of InvoiceData)
Dim pdf = PdfDocument.FromFile(pdfPath)
Dim text As String = pdf.ExtractAllText()
' First attempt: regex patterns (fast and free)
Dim patternParser = New InvoiceParser()
Dim standardResult = patternParser.ParseInvoiceFromText(text)
' If pattern matching found all required fields, use that result
If IsComplete(standardResult) Then
Console.WriteLine("Pattern extraction successful")
Return standardResult
End If
' Fallback: use AI for complex or unusual invoice formats
' This costs money but handles any layout
Console.WriteLine("Using AI extraction for complex invoice format")
Dim aiResult = Await _aiParser.ParseInvoiceWithAI(pdfPath)
Return aiResult
End Function
' Validates that we have the minimum required fields
Private Function IsComplete(data As InvoiceData) As Boolean
Return Not String.IsNullOrEmpty(data.InvoiceNumber) AndAlso
Not String.IsNullOrEmpty(data.InvoiceDate) AndAlso
data.TotalAmount > 0
End Function
End Class
Cómo crear un canal de automatización de cuentas por pagar
Uniendo todas estas piezas, he aquí un proceso de automatización completo que procesa las facturas entrantes, extrae datos, los valida y los prepara para su sistema de contabilidad:
using IronPdf;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
// Tracks the outcome of processing each invoice
public class ProcessingResult
{
public string FileName { get; set; }
public bool Success { get; set; }
public string InvoiceNumber { get; set; }
public string ErrorMessage { get; set; }
}
// Complete automation pipeline for accounts payable
// Watches a folder, extracts data, validates, and routes to accounting system
public class InvoiceAutomationPipeline
{
private readonly SmartInvoiceProcessor _processor;
private readonly string _inputFolder;
private readonly string _processedFolder;
private readonly string _errorFolder;
public InvoiceAutomationPipeline(string apiKey, string inputFolder)
{
_processor = new SmartInvoiceProcessor(apiKey);
_inputFolder = inputFolder;
_processedFolder = Path.Combine(inputFolder, "processed");
_errorFolder = Path.Combine(inputFolder, "errors");
// Create output directories if they don't exist
Directory.CreateDirectory(_processedFolder);
Directory.CreateDirectory(_errorFolder);
}
// Main entry point - processes all PDFs in the input folder
public async Task<List<ProcessingResult>> ProcessInvoiceBatch()
{
string[] invoiceFiles = Directory.GetFiles(_inputFolder, "*.pdf");
Console.WriteLine($"Found {invoiceFiles.Length} invoices to process");
var results = new List<ProcessingResult>();
foreach (string invoicePath in invoiceFiles)
{
string fileName = Path.GetFileName(invoicePath);
try
{
Console.WriteLine($"Processing: {fileName}");
// Extract data using smart processor (patterns first, then AI)
var invoiceData = await _processor.ProcessAnyInvoice(invoicePath);
// Ensure we have minimum required fields before proceeding
if (ValidateInvoiceData(invoiceData))
{
// Send to accounting system (QuickBooks, Xero, etc.)
await SaveToAccountingSystem(invoiceData);
// Archive successful invoices
string destPath = Path.Combine(_processedFolder, fileName);
File.Move(invoicePath, destPath, overwrite: true);
results.Add(new ProcessingResult
{
FileName = fileName,
Success = true,
InvoiceNumber = invoiceData.InvoiceNumber
});
Console.WriteLine($"✓ Processed: {invoiceData.InvoiceNumber}");
}
else
{
throw new Exception("Validation failed - missing required fields");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Failed: {fileName} - {ex.Message}");
// Quarantine failed invoices for manual review
string destPath = Path.Combine(_errorFolder, fileName);
File.Move(invoicePath, destPath, overwrite: true);
results.Add(new ProcessingResult
{
FileName = fileName,
Success = false,
ErrorMessage = ex.Message
});
}
}
GenerateReport(results);
return results;
}
// Checks for minimum required fields
private bool ValidateInvoiceData(InvoiceData data)
{
return !string.IsNullOrEmpty(data.InvoiceNumber) &&
!string.IsNullOrEmpty(data.VendorName) &&
data.TotalAmount > 0;
}
// Placeholder for accounting system integration
private async Task SaveToAccountingSystem(InvoiceData data)
{
// Integrate with your accounting system here
// Examples: QuickBooks API, Xero API, SAP, or database storage
Console.WriteLine($" Saved invoice {data.InvoiceNumber} to accounting system");
await Task.CompletedTask;
}
// Outputs a summary of the batch processing results
private void GenerateReport(List<ProcessingResult> results)
{
int successful = results.Count(r => r.Success);
int failed = results.Count(r => !r.Success);
Console.WriteLine($"\n========== Processing Complete ==========");
Console.WriteLine($"Total Processed: {results.Count}");
Console.WriteLine($"Successful: {successful}");
Console.WriteLine($"Failed: {failed}");
if (failed > 0)
{
Console.WriteLine("\nFailed invoices requiring review:");
foreach (var failure in results.Where(r => !r.Success))
{
Console.WriteLine($" • {failure.FileName}: {failure.ErrorMessage}");
}
}
}
}
using IronPdf;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
// Tracks the outcome of processing each invoice
public class ProcessingResult
{
public string FileName { get; set; }
public bool Success { get; set; }
public string InvoiceNumber { get; set; }
public string ErrorMessage { get; set; }
}
// Complete automation pipeline for accounts payable
// Watches a folder, extracts data, validates, and routes to accounting system
public class InvoiceAutomationPipeline
{
private readonly SmartInvoiceProcessor _processor;
private readonly string _inputFolder;
private readonly string _processedFolder;
private readonly string _errorFolder;
public InvoiceAutomationPipeline(string apiKey, string inputFolder)
{
_processor = new SmartInvoiceProcessor(apiKey);
_inputFolder = inputFolder;
_processedFolder = Path.Combine(inputFolder, "processed");
_errorFolder = Path.Combine(inputFolder, "errors");
// Create output directories if they don't exist
Directory.CreateDirectory(_processedFolder);
Directory.CreateDirectory(_errorFolder);
}
// Main entry point - processes all PDFs in the input folder
public async Task<List<ProcessingResult>> ProcessInvoiceBatch()
{
string[] invoiceFiles = Directory.GetFiles(_inputFolder, "*.pdf");
Console.WriteLine($"Found {invoiceFiles.Length} invoices to process");
var results = new List<ProcessingResult>();
foreach (string invoicePath in invoiceFiles)
{
string fileName = Path.GetFileName(invoicePath);
try
{
Console.WriteLine($"Processing: {fileName}");
// Extract data using smart processor (patterns first, then AI)
var invoiceData = await _processor.ProcessAnyInvoice(invoicePath);
// Ensure we have minimum required fields before proceeding
if (ValidateInvoiceData(invoiceData))
{
// Send to accounting system (QuickBooks, Xero, etc.)
await SaveToAccountingSystem(invoiceData);
// Archive successful invoices
string destPath = Path.Combine(_processedFolder, fileName);
File.Move(invoicePath, destPath, overwrite: true);
results.Add(new ProcessingResult
{
FileName = fileName,
Success = true,
InvoiceNumber = invoiceData.InvoiceNumber
});
Console.WriteLine($"✓ Processed: {invoiceData.InvoiceNumber}");
}
else
{
throw new Exception("Validation failed - missing required fields");
}
}
catch (Exception ex)
{
Console.WriteLine($"✗ Failed: {fileName} - {ex.Message}");
// Quarantine failed invoices for manual review
string destPath = Path.Combine(_errorFolder, fileName);
File.Move(invoicePath, destPath, overwrite: true);
results.Add(new ProcessingResult
{
FileName = fileName,
Success = false,
ErrorMessage = ex.Message
});
}
}
GenerateReport(results);
return results;
}
// Checks for minimum required fields
private bool ValidateInvoiceData(InvoiceData data)
{
return !string.IsNullOrEmpty(data.InvoiceNumber) &&
!string.IsNullOrEmpty(data.VendorName) &&
data.TotalAmount > 0;
}
// Placeholder for accounting system integration
private async Task SaveToAccountingSystem(InvoiceData data)
{
// Integrate with your accounting system here
// Examples: QuickBooks API, Xero API, SAP, or database storage
Console.WriteLine($" Saved invoice {data.InvoiceNumber} to accounting system");
await Task.CompletedTask;
}
// Outputs a summary of the batch processing results
private void GenerateReport(List<ProcessingResult> results)
{
int successful = results.Count(r => r.Success);
int failed = results.Count(r => !r.Success);
Console.WriteLine($"\n========== Processing Complete ==========");
Console.WriteLine($"Total Processed: {results.Count}");
Console.WriteLine($"Successful: {successful}");
Console.WriteLine($"Failed: {failed}");
if (failed > 0)
{
Console.WriteLine("\nFailed invoices requiring review:");
foreach (var failure in results.Where(r => !r.Success))
{
Console.WriteLine($" • {failure.FileName}: {failure.ErrorMessage}");
}
}
}
}
Imports IronPdf
Imports System
Imports System.IO
Imports System.Threading.Tasks
Imports System.Collections.Generic
Imports System.Linq
' Tracks the outcome of processing each invoice
Public Class ProcessingResult
Public Property FileName As String
Public Property Success As Boolean
Public Property InvoiceNumber As String
Public Property ErrorMessage As String
End Class
' Complete automation pipeline for accounts payable
' Watches a folder, extracts data, validates, and routes to accounting system
Public Class InvoiceAutomationPipeline
Private ReadOnly _processor As SmartInvoiceProcessor
Private ReadOnly _inputFolder As String
Private ReadOnly _processedFolder As String
Private ReadOnly _errorFolder As String
Public Sub New(apiKey As String, inputFolder As String)
_processor = New SmartInvoiceProcessor(apiKey)
_inputFolder = inputFolder
_processedFolder = Path.Combine(inputFolder, "processed")
_errorFolder = Path.Combine(inputFolder, "errors")
' Create output directories if they don't exist
Directory.CreateDirectory(_processedFolder)
Directory.CreateDirectory(_errorFolder)
End Sub
' Main entry point - processes all PDFs in the input folder
Public Async Function ProcessInvoiceBatch() As Task(Of List(Of ProcessingResult))
Dim invoiceFiles As String() = Directory.GetFiles(_inputFolder, "*.pdf")
Console.WriteLine($"Found {invoiceFiles.Length} invoices to process")
Dim results As New List(Of ProcessingResult)()
For Each invoicePath As String In invoiceFiles
Dim fileName As String = Path.GetFileName(invoicePath)
Try
Console.WriteLine($"Processing: {fileName}")
' Extract data using smart processor (patterns first, then AI)
Dim invoiceData = Await _processor.ProcessAnyInvoice(invoicePath)
' Ensure we have minimum required fields before proceeding
If ValidateInvoiceData(invoiceData) Then
' Send to accounting system (QuickBooks, Xero, etc.)
Await SaveToAccountingSystem(invoiceData)
' Archive successful invoices
Dim destPath As String = Path.Combine(_processedFolder, fileName)
File.Move(invoicePath, destPath, overwrite:=True)
results.Add(New ProcessingResult With {
.FileName = fileName,
.Success = True,
.InvoiceNumber = invoiceData.InvoiceNumber
})
Console.WriteLine($"✓ Processed: {invoiceData.InvoiceNumber}")
Else
Throw New Exception("Validation failed - missing required fields")
End If
Catch ex As Exception
Console.WriteLine($"✗ Failed: {fileName} - {ex.Message}")
' Quarantine failed invoices for manual review
Dim destPath As String = Path.Combine(_errorFolder, fileName)
File.Move(invoicePath, destPath, overwrite:=True)
results.Add(New ProcessingResult With {
.FileName = fileName,
.Success = False,
.ErrorMessage = ex.Message
})
End Try
Next
GenerateReport(results)
Return results
End Function
' Checks for minimum required fields
Private Function ValidateInvoiceData(data As InvoiceData) As Boolean
Return Not String.IsNullOrEmpty(data.InvoiceNumber) AndAlso
Not String.IsNullOrEmpty(data.VendorName) AndAlso
data.TotalAmount > 0
End Function
' Placeholder for accounting system integration
Private Async Function SaveToAccountingSystem(data As InvoiceData) As Task
' Integrate with your accounting system here
' Examples: QuickBooks API, Xero API, SAP, or database storage
Console.WriteLine($" Saved invoice {data.InvoiceNumber} to accounting system")
Await Task.CompletedTask
End Function
' Outputs a summary of the batch processing results
Private Sub GenerateReport(results As List(Of ProcessingResult))
Dim successful As Integer = results.Count(Function(r) r.Success)
Dim failed As Integer = results.Count(Function(r) Not r.Success)
Console.WriteLine(vbCrLf & "========== Processing Complete ==========")
Console.WriteLine($"Total Processed: {results.Count}")
Console.WriteLine($"Successful: {successful}")
Console.WriteLine($"Failed: {failed}")
If failed > 0 Then
Console.WriteLine(vbCrLf & "Failed invoices requiring review:")
For Each failure In results.Where(Function(r) Not r.Success)
Console.WriteLine($" • {failure.FileName}: {failure.ErrorMessage}")
Next
End If
End Sub
End Class
Esta canalización implementa un flujo de trabajo completo: escanea una carpeta en busca de archivos PDF entrantes, procesa cada uno de ellos, valida los datos extraídos, envía las extracciones correctas a su sistema de contabilidad y pone en cuarentena los errores para su revisión manual. El informe resumido ofrece visibilidad de los resultados del procesamiento.
How to Integrate C# Invoice Processing with Accounting Systems
En última instancia, los datos de las facturas extraídos deben pasar a los sistemas de contabilidad para su pago y registro. Los detalles varían según la plataforma, pero los patrones de integración son los mismos.
¿Cuáles son los patrones de integración comunes para QuickBooks, Xero y SAP?
La mayoría de las plataformas de contabilidad ofrecen API REST para crear facturas mediante programación. A continuación se presenta un patrón generalizado que puede adaptar a su plataforma específica:
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
// Generic integration layer for pushing invoice data to accounting systems
// Adapt the API calls based on your specific platform
public class AccountingSystemIntegration
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _baseUrl;
public AccountingSystemIntegration(string apiKey, string baseUrl)
{
_apiKey = apiKey;
_baseUrl = baseUrl;
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");
}
// Creates a Bill in QuickBooks (vendor invoices are called "Bills")
public async Task SendToQuickBooks(InvoiceData invoice)
{
// QuickBooks Bill structure - see their API docs for full schema
var bill = new
{
VendorRef = new { name = invoice.VendorName },
TxnDate = invoice.InvoiceDate,
DocNumber = invoice.InvoiceNumber,
TotalAmt = invoice.TotalAmount,
Line = new[]
{
new
{
Amount = invoice.TotalAmount,
DetailType = "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail = new
{
AccountRef = new { name = "Accounts Payable" }
}
}
}
};
await PostToApi("/v3/company/{companyId}/bill", bill);
}
// Creates an accounts payable invoice in Xero
public async Task SendToXero(InvoiceData invoice)
{
// ACCPAY type indicates this is a bill to pay (not a sales invoice)
var bill = new
{
Type = "ACCPAY",
Contact = new { Name = invoice.VendorName },
Date = invoice.InvoiceDate,
InvoiceNumber = invoice.InvoiceNumber,
Total = invoice.TotalAmount
};
await PostToApi("/api.xro/2.0/Invoices", bill);
}
// Generic POST helper with error handling
private async Task PostToApi(string endpoint, object payload)
{
string json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{_baseUrl}{endpoint}", content);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
throw new Exception($"API Error: {response.StatusCode} - {error}");
}
Console.WriteLine($"Successfully posted to {endpoint}");
}
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
// Generic integration layer for pushing invoice data to accounting systems
// Adapt the API calls based on your specific platform
public class AccountingSystemIntegration
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _baseUrl;
public AccountingSystemIntegration(string apiKey, string baseUrl)
{
_apiKey = apiKey;
_baseUrl = baseUrl;
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");
}
// Creates a Bill in QuickBooks (vendor invoices are called "Bills")
public async Task SendToQuickBooks(InvoiceData invoice)
{
// QuickBooks Bill structure - see their API docs for full schema
var bill = new
{
VendorRef = new { name = invoice.VendorName },
TxnDate = invoice.InvoiceDate,
DocNumber = invoice.InvoiceNumber,
TotalAmt = invoice.TotalAmount,
Line = new[]
{
new
{
Amount = invoice.TotalAmount,
DetailType = "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail = new
{
AccountRef = new { name = "Accounts Payable" }
}
}
}
};
await PostToApi("/v3/company/{companyId}/bill", bill);
}
// Creates an accounts payable invoice in Xero
public async Task SendToXero(InvoiceData invoice)
{
// ACCPAY type indicates this is a bill to pay (not a sales invoice)
var bill = new
{
Type = "ACCPAY",
Contact = new { Name = invoice.VendorName },
Date = invoice.InvoiceDate,
InvoiceNumber = invoice.InvoiceNumber,
Total = invoice.TotalAmount
};
await PostToApi("/api.xro/2.0/Invoices", bill);
}
// Generic POST helper with error handling
private async Task PostToApi(string endpoint, object payload)
{
string json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{_baseUrl}{endpoint}", content);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
throw new Exception($"API Error: {response.StatusCode} - {error}");
}
Console.WriteLine($"Successfully posted to {endpoint}");
}
}
Imports System
Imports System.Net.Http
Imports System.Text
Imports System.Text.Json
Imports System.Threading.Tasks
' Generic integration layer for pushing invoice data to accounting systems
' Adapt the API calls based on your specific platform
Public Class AccountingSystemIntegration
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _apiKey As String
Private ReadOnly _baseUrl As String
Public Sub New(apiKey As String, baseUrl As String)
_apiKey = apiKey
_baseUrl = baseUrl
_httpClient = New HttpClient()
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}")
End Sub
' Creates a Bill in QuickBooks (vendor invoices are called "Bills")
Public Async Function SendToQuickBooks(invoice As InvoiceData) As Task
' QuickBooks Bill structure - see their API docs for full schema
Dim bill = New With {
.VendorRef = New With {.name = invoice.VendorName},
.TxnDate = invoice.InvoiceDate,
.DocNumber = invoice.InvoiceNumber,
.TotalAmt = invoice.TotalAmount,
.Line = New Object() {
New With {
.Amount = invoice.TotalAmount,
.DetailType = "AccountBasedExpenseLineDetail",
.AccountBasedExpenseLineDetail = New With {
.AccountRef = New With {.name = "Accounts Payable"}
}
}
}
}
Await PostToApi("/v3/company/{companyId}/bill", bill)
End Function
' Creates an accounts payable invoice in Xero
Public Async Function SendToXero(invoice As InvoiceData) As Task
' ACCPAY type indicates this is a bill to pay (not a sales invoice)
Dim bill = New With {
.Type = "ACCPAY",
.Contact = New With {.Name = invoice.VendorName},
.Date = invoice.InvoiceDate,
.InvoiceNumber = invoice.InvoiceNumber,
.Total = invoice.TotalAmount
}
Await PostToApi("/api.xro/2.0/Invoices", bill)
End Function
' Generic POST helper with error handling
Private Async Function PostToApi(endpoint As String, payload As Object) As Task
Dim json As String = JsonSerializer.Serialize(payload)
Dim content = New StringContent(json, Encoding.UTF8, "application/json")
Dim response = Await _httpClient.PostAsync($"{_baseUrl}{endpoint}", content)
If Not response.IsSuccessStatusCode Then
Dim error As String = Await response.Content.ReadAsStringAsync()
Throw New Exception($"API Error: {response.StatusCode} - {error}")
End If
Console.WriteLine($"Successfully posted to {endpoint}")
End Function
End Class
Cada plataforma tiene su propio mecanismo de autenticación (OAuth para QuickBooks y Xero, varios métodos para SAP), campos obligatorios y convenciones de API. Consulte la documentación de la plataforma de destino para obtener información específica, pero el patrón de transformación de los datos de facturación extraídos en cargas útiles de API sigue siendo el mismo.
Cómo procesar cientos de facturas por lotes
El procesamiento de grandes volúmenes de facturas requiere una cuidadosa atención a la concurrencia y la gestión de recursos. He aquí un patrón que utiliza el procesamiento paralelo con concurrencia controlada:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
// Tracks the result of processing a single invoice in a batch
public class BatchResult
{
public string FilePath { get; set; }
public bool Success { get; set; }
public string InvoiceNumber { get; set; }
public string Error { get; set; }
}
// High-volume invoice processor with controlled parallelism
// Prevents overwhelming APIs while maximizing throughput
public class BatchInvoiceProcessor
{
private readonly SmartInvoiceProcessor _invoiceProcessor;
private readonly AccountingSystemIntegration _accountingIntegration;
private readonly int _maxConcurrency;
public BatchInvoiceProcessor(string aiApiKey, string accountingApiKey,
string accountingUrl, int maxConcurrency = 5)
{
_invoiceProcessor = new SmartInvoiceProcessor(aiApiKey);
_accountingIntegration = new AccountingSystemIntegration(accountingApiKey, accountingUrl);
_maxConcurrency = maxConcurrency; // Adjust based on API rate limits
}
// Processes multiple invoices in parallel with controlled concurrency
public async Task<List<BatchResult>> ProcessInvoiceBatch(List<string> invoicePaths)
{
// Thread-safe collection for gathering results from parallel tasks
var results = new ConcurrentBag<BatchResult>();
// Semaphore limits how many invoices process simultaneously
var semaphore = new SemaphoreSlim(_maxConcurrency);
// Create a task for each invoice
var tasks = invoicePaths.Select(async path =>
{
// Wait for a slot to become available
await semaphore.WaitAsync();
try
{
var result = await ProcessSingleInvoice(path);
results.Add(result);
}
finally
{
// Release slot for next invoice
semaphore.Release();
}
});
// Wait for all invoices to complete
await Task.WhenAll(tasks);
// Output summary statistics
var resultList = results.ToList();
int successful = resultList.Count(r => r.Success);
int failed = resultList.Count(r => !r.Success);
Console.WriteLine($"\nBatch Processing Complete:");
Console.WriteLine($" Total: {resultList.Count}");
Console.WriteLine($" Successful: {successful}");
Console.WriteLine($" Failed: {failed}");
return resultList;
}
// Processes one invoice: extract data and send to accounting system
private async Task<BatchResult> ProcessSingleInvoice(string pdfPath)
{
try
{
Console.WriteLine($"Processing: {pdfPath}");
var invoiceData = await _invoiceProcessor.ProcessAnyInvoice(pdfPath);
await _accountingIntegration.SendToQuickBooks(invoiceData);
Console.WriteLine($"✓ Completed: {invoiceData.InvoiceNumber}");
return new BatchResult
{
FilePath = pdfPath,
Success = true,
InvoiceNumber = invoiceData.InvoiceNumber
};
}
catch (Exception ex)
{
Console.WriteLine($"✗ Failed: {pdfPath}");
return new BatchResult
{
FilePath = pdfPath,
Success = false,
Error = ex.Message
};
}
}
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
// Tracks the result of processing a single invoice in a batch
public class BatchResult
{
public string FilePath { get; set; }
public bool Success { get; set; }
public string InvoiceNumber { get; set; }
public string Error { get; set; }
}
// High-volume invoice processor with controlled parallelism
// Prevents overwhelming APIs while maximizing throughput
public class BatchInvoiceProcessor
{
private readonly SmartInvoiceProcessor _invoiceProcessor;
private readonly AccountingSystemIntegration _accountingIntegration;
private readonly int _maxConcurrency;
public BatchInvoiceProcessor(string aiApiKey, string accountingApiKey,
string accountingUrl, int maxConcurrency = 5)
{
_invoiceProcessor = new SmartInvoiceProcessor(aiApiKey);
_accountingIntegration = new AccountingSystemIntegration(accountingApiKey, accountingUrl);
_maxConcurrency = maxConcurrency; // Adjust based on API rate limits
}
// Processes multiple invoices in parallel with controlled concurrency
public async Task<List<BatchResult>> ProcessInvoiceBatch(List<string> invoicePaths)
{
// Thread-safe collection for gathering results from parallel tasks
var results = new ConcurrentBag<BatchResult>();
// Semaphore limits how many invoices process simultaneously
var semaphore = new SemaphoreSlim(_maxConcurrency);
// Create a task for each invoice
var tasks = invoicePaths.Select(async path =>
{
// Wait for a slot to become available
await semaphore.WaitAsync();
try
{
var result = await ProcessSingleInvoice(path);
results.Add(result);
}
finally
{
// Release slot for next invoice
semaphore.Release();
}
});
// Wait for all invoices to complete
await Task.WhenAll(tasks);
// Output summary statistics
var resultList = results.ToList();
int successful = resultList.Count(r => r.Success);
int failed = resultList.Count(r => !r.Success);
Console.WriteLine($"\nBatch Processing Complete:");
Console.WriteLine($" Total: {resultList.Count}");
Console.WriteLine($" Successful: {successful}");
Console.WriteLine($" Failed: {failed}");
return resultList;
}
// Processes one invoice: extract data and send to accounting system
private async Task<BatchResult> ProcessSingleInvoice(string pdfPath)
{
try
{
Console.WriteLine($"Processing: {pdfPath}");
var invoiceData = await _invoiceProcessor.ProcessAnyInvoice(pdfPath);
await _accountingIntegration.SendToQuickBooks(invoiceData);
Console.WriteLine($"✓ Completed: {invoiceData.InvoiceNumber}");
return new BatchResult
{
FilePath = pdfPath,
Success = true,
InvoiceNumber = invoiceData.InvoiceNumber
};
}
catch (Exception ex)
{
Console.WriteLine($"✗ Failed: {pdfPath}");
return new BatchResult
{
FilePath = pdfPath,
Success = false,
Error = ex.Message
};
}
}
}
Imports System
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Linq
Imports System.Threading
Imports System.Threading.Tasks
' Tracks the result of processing a single invoice in a batch
Public Class BatchResult
Public Property FilePath As String
Public Property Success As Boolean
Public Property InvoiceNumber As String
Public Property Error As String
End Class
' High-volume invoice processor with controlled parallelism
' Prevents overwhelming APIs while maximizing throughput
Public Class BatchInvoiceProcessor
Private ReadOnly _invoiceProcessor As SmartInvoiceProcessor
Private ReadOnly _accountingIntegration As AccountingSystemIntegration
Private ReadOnly _maxConcurrency As Integer
Public Sub New(aiApiKey As String, accountingApiKey As String, accountingUrl As String, Optional maxConcurrency As Integer = 5)
_invoiceProcessor = New SmartInvoiceProcessor(aiApiKey)
_accountingIntegration = New AccountingSystemIntegration(accountingApiKey, accountingUrl)
_maxConcurrency = maxConcurrency ' Adjust based on API rate limits
End Sub
' Processes multiple invoices in parallel with controlled concurrency
Public Async Function ProcessInvoiceBatch(invoicePaths As List(Of String)) As Task(Of List(Of BatchResult))
' Thread-safe collection for gathering results from parallel tasks
Dim results = New ConcurrentBag(Of BatchResult)()
' Semaphore limits how many invoices process simultaneously
Dim semaphore = New SemaphoreSlim(_maxConcurrency)
' Create a task for each invoice
Dim tasks = invoicePaths.Select(Async Function(path)
' Wait for a slot to become available
Await semaphore.WaitAsync()
Try
Dim result = Await ProcessSingleInvoice(path)
results.Add(result)
Finally
' Release slot for next invoice
semaphore.Release()
End Try
End Function)
' Wait for all invoices to complete
Await Task.WhenAll(tasks)
' Output summary statistics
Dim resultList = results.ToList()
Dim successful = resultList.Count(Function(r) r.Success)
Dim failed = resultList.Count(Function(r) Not r.Success)
Console.WriteLine(vbCrLf & "Batch Processing Complete:")
Console.WriteLine($" Total: {resultList.Count}")
Console.WriteLine($" Successful: {successful}")
Console.WriteLine($" Failed: {failed}")
Return resultList
End Function
' Processes one invoice: extract data and send to accounting system
Private Async Function ProcessSingleInvoice(pdfPath As String) As Task(Of BatchResult)
Try
Console.WriteLine($"Processing: {pdfPath}")
Dim invoiceData = Await _invoiceProcessor.ProcessAnyInvoice(pdfPath)
Await _accountingIntegration.SendToQuickBooks(invoiceData)
Console.WriteLine($"✓ Completed: {invoiceData.InvoiceNumber}")
Return New BatchResult With {
.FilePath = pdfPath,
.Success = True,
.InvoiceNumber = invoiceData.InvoiceNumber
}
Catch ex As Exception
Console.WriteLine($"✗ Failed: {pdfPath}")
Return New BatchResult With {
.FilePath = pdfPath,
.Success = False,
.Error = ex.Message
}
End Try
End Function
End Class
El SemaphoreSlim garantiza que no sature las API externas ni agote los recursos del sistema. Ajuste _maxConcurrency según los límites de velocidad de su API y la capacidad del servidor. El ConcurrentBag recopila de forma segura resultados de operaciones paralelas.
Próximos pasos
La automatización de facturas representa una gran oportunidad para reducir el trabajo manual, minimizar los errores y acelerar los procesos empresariales. Esta guía le ha guiado a través del ciclo de vida completo: generación de facturas profesionales a partir de plantillas HTML, cumpliendo con los estándares de facturación electrónica ZUGFeRD y Factur-X, extracción de datos de las facturas recibidas mediante concordancia de patrones y procesamiento basado en IA, y creación de canales de automatización escalables.
IronPDF es la base de estas capacidades, ya que proporciona un sólido renderizado de HTML a PDF, una extracción de texto fiable y las funciones de adjunto necesarias para cumplir con la normativa de facturación electrónica PDF/A-3. Su motor de renderizado basado en Chrome garantiza que las facturas tengan exactamente el aspecto diseñado, mientras que sus métodos de extracción gestionan automáticamente la complejidad de la codificación de texto en PDF.
Los patrones que se muestran aquí son puntos de partida. Las implementaciones reales necesitarán una adaptación para sus formatos de factura, sistemas de contabilidad y reglas de negocio específicos. Para escenarios de gran volumen, el tutorial de procesamiento por lotes cubre la ejecución paralela con concurrencia controlada y recuperación de errores.
¿Listo para empezar a construir? Descargue IronPDF y pruébelo gratuitamente. La biblioteca incluye una licencia de desarrollo gratuita, para que pueda evaluar completamente la generación de facturas, la extracción de datos y las capacidades de los informes PDF antes de comprometerse con una licencia de producción. Si tiene alguna pregunta sobre la automatización de facturas o la integración de sistemas de contabilidad, póngase en contacto con nuestro equipo de asistencia técnica.
Preguntas Frecuentes
¿Para qué se utiliza IronPDF en el procesamiento de facturas en C#?
IronPDF se utiliza en el procesamiento de facturas en C# para generar facturas profesionales en PDF, extraer datos estructurados y automatizar flujos de trabajo de facturas, garantizando al mismo tiempo el cumplimiento de estándares como ZUGFeRD y Factur-X.
¿Cómo puedo generar una factura en PDF utilizando IronPDF en C#?
Puede generar una factura PDF utilizando IronPDF en C# aprovechando su API para crear y personalizar documentos PDF mediante programación. Esto incluye añadir elementos como texto, tablas e imágenes que constituyen una factura.
¿Qué son ZUGFeRD y Factur-X, y cómo los soporta IronPDF?
ZUGFeRD y Factur-X son normas de facturación electrónica que garantizan que las facturas sean tanto legibles por humanos como por máquinas. IronPDF es compatible con estas normas al permitirle generar facturas en PDF que cumplan estas especificaciones.
¿Cómo puede IronPDF ayudar a automatizar los procesos de cuentas por pagar?
IronPDF puede automatizar los procesos de cuentas por pagar extrayendo datos estructurados de las facturas e integrándolos con canales de automatización, reduciendo la introducción manual de datos y mejorando la eficiencia.
¿Puede IronPDF extraer datos de facturas PDF existentes?
Sí, IronPDF puede extraer datos estructurados de facturas PDF existentes, lo que facilita el procesamiento y el análisis automático de la información de las facturas.
¿Cuáles son las ventajas de utilizar IronPDF para el procesamiento de facturas en C#?
Las ventajas de utilizar IronPDF para el procesamiento de facturas en C# incluyen la generación optimizada de facturas, el cumplimiento de las normas internacionales de facturación, la extracción eficaz de datos y la mejora de las capacidades de automatización.
¿Es posible personalizar el aspecto de una factura PDF con IronPDF?
Sí, IronPDF permite personalizar el aspecto de una factura PDF añadiendo diversos elementos de diseño, como logotipos, formato de texto y ajustes de diseño para cumplir los requisitos de la marca.
¿Cuáles son los pasos típicos para automatizar el procesamiento de facturas con IronPDF?
Para automatizar el procesamiento de facturas mediante IronPDF, normalmente se genera la factura, se extraen los datos necesarios y se integra con otros sistemas o herramientas de automatización para agilizar los flujos de trabajo.
¿Cómo gestiona IronPDF los distintos formatos de factura?
IronPDF puede manejar varios formatos de factura proporcionando herramientas para generar, manipular y leer documentos PDF, garantizando la compatibilidad con los estándares comunes de facturación electrónica.

