Saltar al pie de página
USANDO IRONWORD

Facturae, AEAT y LOPDGDD: Plantillas Word rellenables en C#

Durante años, los equipos .NET que necesitaban generar documentos Word personalizados a gran escala tenían dos opciones reales, y ninguna era buena.

La primera era automatizar Microsoft Word en sí mismo a través de COM interop. Funcionó, hasta que no lo hizo. La interoperabilidad de Word es lenta, frágil, requiere una licencia de Word en cada máquina y se desmorona en el momento en que intentas ejecutarla dentro de un contenedor Linux, una función de Azure o cualquier entorno de servidor donde Word no está instalado. La mayoría de los equipos que lo intentaron terminaron reescribiendo alrededor de ella.

La segunda era una tubería personalizada de búsqueda y reemplazo. Leer el .docx como XML o como texto, intercambiar los tokens de marcador de posición por valores reales y guardar el resultado. Eso funcionó para casos simples y se rompió en documentos reales de Word, que usan estructuras de campo complejas que no se parecen en nada a los simples {{FirstName}} tokens que los desarrolladores esperan encontrar.

Ahora IronWord admite fusión de correo nativamente. Las plantillas diseñadas en Microsoft Word, usando la misma sintaxis de campo de fusión que produce Word en sí mismo, se completan directamente desde el código .NET sin necesidad de instalación de Word.

Qué hace la característica

El motor de fusión de correo maneja ambos estilos de campo de fusión que Word produce:

  • Campos simples, en el formato "FieldName", que es lo que obtienes al escribir <> directamente en la plantilla
  • Campos complejos, la estructura XML de múltiples elementos que Word genera a través de Insertar → Quick Parts → Campo

Las plantillas pueden incluir marcadores de posición de valor único que se reemplazan una vez por documento, y regiones repetidas delimitadas por los marcadores "TableStart:Name" y "TableEnd:Name", que se expanden a una fila por registro de datos.

Esa segunda pieza es donde la característica se gana su lugar. Las regiones repetidas son cómo una sola fila de plantilla en una factura se expande en una tabla de artículos de línea de 50 filas, o cómo una ranura de contacto en una hoja de etiquetas se convierte en una cuadrícula completa de Avery poblada desde una base de datos.

Cartas de presentación personalizadas desde un CRM

La mayoría de los equipos realizan la combinación de correspondencia primero a través de operaciones de ventas. El CRM contiene registros de clientes; el marketing o las operaciones de ventas necesitan enviar cartas de presentación personalizadas, paquetes de bienvenida o avisos de renovación. El patrón es sencillo: una plantilla, una fila por cliente, un documento personalizado de salida.

// Template: "Dear &laquo;FirstName&raquo;, thank you for your order of &laquo;Amount&raquo;..."
DataTable customers = LoadFromSalesforce();   // FirstName, LastName, Amount, ...

foreach (DataRow row in customers.Rows)
{
    var doc = new WordDocument("cover-letter-template.docx");
    doc.MailMerge.Execute(row);
    doc.SaveAs($"letters/{row["LastName"]}.docx");
}
// Template: "Dear &laquo;FirstName&raquo;, thank you for your order of &laquo;Amount&raquo;..."
DataTable customers = LoadFromSalesforce();   // FirstName, LastName, Amount, ...

foreach (DataRow row in customers.Rows)
{
    var doc = new WordDocument("cover-letter-template.docx");
    doc.MailMerge.Execute(row);
    doc.SaveAs($"letters/{row["LastName"]}.docx");
}
Imports System.Data

' Template: "Dear «FirstName», thank you for your order of «Amount»..."
Dim customers As DataTable = LoadFromSalesforce() ' FirstName, LastName, Amount, ...

For Each row As DataRow In customers.Rows
    Dim doc As New WordDocument("cover-letter-template.docx")
    doc.MailMerge.Execute(row)
    doc.SaveAs($"letters/{row("LastName")}.docx")
Next
$vbLabelText   $csharpLabel

La fuente de datos es un DataTable, lo que significa que cualquier cosa que cargue en uno (Salesforce, HubSpot, SQL Server, un CSV, una respuesta de API) se convierte en una fuente de combinación de correspondencia sin código adaptador adicional.

Facturas con artículos de línea repetidos

La facturación es donde la capacidad de región repetida se justifica. Una plantilla de factura tiene un encabezado (nombre del cliente, ID de pedido, fechas), una fila en una tabla de artículos de línea y un pie de página (totales). Cuando se ejecuta la combinación, la fila de plantilla única se expande a una fila por producto, independientemente de si el pedido tiene un artículo de línea o cincuenta.

// Template:
//   Bill To: &laquo;CustomerName&raquo;  Order: &laquo;OrderId&raquo;
//   ┌──────────┬─────┬──────────┐
//   │ &laquo;TableStart:Items&raquo;&laquo;Product&raquo; │ &laquo;Qty&raquo; │ &laquo;LineTotal&raquo; &laquo;TableEnd:Items&raquo; │
//   └──────────┴─────┴──────────┘
//   Total: &laquo;Total&raquo;

var doc = new WordDocument("invoice-template.docx");

doc.MailMerge.ExecuteWithRegions(orderLineItems);  // one row per product
doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "CustomerName", "Acme Industries" },
    { "OrderId",      "10444" },
    { "Total",        "$1,117.71" },
});

doc.SaveAs("invoice-10444.docx");
// Template:
//   Bill To: &laquo;CustomerName&raquo;  Order: &laquo;OrderId&raquo;
//   ┌──────────┬─────┬──────────┐
//   │ &laquo;TableStart:Items&raquo;&laquo;Product&raquo; │ &laquo;Qty&raquo; │ &laquo;LineTotal&raquo; &laquo;TableEnd:Items&raquo; │
//   └──────────┴─────┴──────────┘
//   Total: &laquo;Total&raquo;

var doc = new WordDocument("invoice-template.docx");

doc.MailMerge.ExecuteWithRegions(orderLineItems);  // one row per product
doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "CustomerName", "Acme Industries" },
    { "OrderId",      "10444" },
    { "Total",        "$1,117.71" },
});

doc.SaveAs("invoice-10444.docx");
Imports System.Collections.Generic

' Template:
'   Bill To: «CustomerName»  Order: «OrderId»
'   ┌──────────┬─────┬──────────┐
'   │ «TableStart:Items»«Product» │ «Qty» │ «LineTotal» «TableEnd:Items» │
'   └──────────┴─────┴──────────┘
'   Total: «Total»

Dim doc As New WordDocument("invoice-template.docx")

doc.MailMerge.ExecuteWithRegions(orderLineItems)  ' one row per product
doc.MailMerge.Execute(New Dictionary(Of String, String) From {
    {"CustomerName", "Acme Industries"},
    {"OrderId", "10444"},
    {"Total", "$1,117.71"}
})

doc.SaveAs("invoice-10444.docx")
$vbLabelText   $csharpLabel

El mismo patrón funciona para cotizaciones, órdenes de compra, estados de cuenta y cualquier documento donde una lista de longitud variable se encuentra dentro de una página de forma fija.

Hojas de etiquetas Avery y sobres

Los equipos de envío y operaciones generan etiquetas y sobres a partir de listas de contactos, y los diseños (Avery 5160, 5161, 5163 y sus equivalentes internacionales) son plantillas estandarizadas que existen desde hace décadas. IronWord las rellena directamente: cada celda en la cuadrícula de etiquetas recoge el contacto adecuado de una sola llamada de API.

// Template: a 3&times;10 Avery 5160 grid, each cell has &laquo;CONTACT_FULLNAME&raquo; / &laquo;CONTACT_ADDRESS&raquo;
var doc = new WordDocument("avery-5160.docx");

doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "CONTACT_FULLNAME", "Jane Doe" },
    { "CONTACT_ADDRESS",  "100 Main Street, Boston, MA 02108" },
});

doc.SaveAs("labels-jane.docx");
// Template: a 3&times;10 Avery 5160 grid, each cell has &laquo;CONTACT_FULLNAME&raquo; / &laquo;CONTACT_ADDRESS&raquo;
var doc = new WordDocument("avery-5160.docx");

doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "CONTACT_FULLNAME", "Jane Doe" },
    { "CONTACT_ADDRESS",  "100 Main Street, Boston, MA 02108" },
});

doc.SaveAs("labels-jane.docx");
Imports System.Collections.Generic

' Template: a 3×10 Avery 5160 grid, each cell has «CONTACT_FULLNAME» / «CONTACT_ADDRESS»
Dim doc As New WordDocument("avery-5160.docx")

doc.MailMerge.Execute(New Dictionary(Of String, String) From {
    {"CONTACT_FULLNAME", "Jane Doe"},
    {"CONTACT_ADDRESS", "100 Main Street, Boston, MA 02108"}
})

doc.SaveAs("labels-jane.docx")
$vbLabelText   $csharpLabel

Para listas de contactos de longitud variable, se aplica el patrón ExecuteWithRegions del ejemplo de factura: define una celda como la unidad repetitiva, y el resto de la hoja se llena automáticamente.

Informes de múltiples regiones

La aplicación más poderosa son los informes de múltiples regiones. Un resumen trimestral podría incluir secciones de "Principales Clientes" y "Principales Productos", cada uno impulsado por su propia tabla de datos. Con un DataSet que contiene múltiples tablas, la combinación expande ambas regiones en una sola llamada.

// Template:
//   Top Customers
//   &laquo;TableStart:Customers&raquo; &bull; &laquo;Name&raquo; | &laquo;City&raquo; | &laquo;Revenue&raquo; &laquo;TableEnd:Customers&raquo;
//   Top Products
//   &laquo;TableStart:Products&raquo;  &laquo;ProductName&raquo;: &laquo;Revenue&raquo;     &laquo;TableEnd:Products&raquo;

var ds = new DataSet();
ds.Tables.Add(topCustomers);   // TableName = "Customers"
ds.Tables.Add(topProducts);    // TableName = "Products"

var doc = new WordDocument("quarterly-report.docx");
doc.MailMerge.ExecuteWithRegions(ds);          // expands both regions
doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "CompanyName", "Iron Software" },
    { "ReportDate",  "Q2 2026" },
});
doc.SaveAs("Q2-2026-report.docx");
// Template:
//   Top Customers
//   &laquo;TableStart:Customers&raquo; &bull; &laquo;Name&raquo; | &laquo;City&raquo; | &laquo;Revenue&raquo; &laquo;TableEnd:Customers&raquo;
//   Top Products
//   &laquo;TableStart:Products&raquo;  &laquo;ProductName&raquo;: &laquo;Revenue&raquo;     &laquo;TableEnd:Products&raquo;

var ds = new DataSet();
ds.Tables.Add(topCustomers);   // TableName = "Customers"
ds.Tables.Add(topProducts);    // TableName = "Products"

var doc = new WordDocument("quarterly-report.docx");
doc.MailMerge.ExecuteWithRegions(ds);          // expands both regions
doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "CompanyName", "Iron Software" },
    { "ReportDate",  "Q2 2026" },
});
doc.SaveAs("Q2-2026-report.docx");
Imports System.Data
Imports System.Collections.Generic

' Template:
'   Top Customers
'   «TableStart:Customers» • «Name» | «City» | «Revenue» «TableEnd:Customers»
'   Top Products
'   «TableStart:Products»  «ProductName»: «Revenue»     «TableEnd:Products»

Dim ds As New DataSet()
ds.Tables.Add(topCustomers)   ' TableName = "Customers"
ds.Tables.Add(topProducts)    ' TableName = "Products"

Dim doc As New WordDocument("quarterly-report.docx")
doc.MailMerge.ExecuteWithRegions(ds)          ' expands both regions
doc.MailMerge.Execute(New Dictionary(Of String, String) From {
    {"CompanyName", "Iron Software"},
    {"ReportDate", "Q2 2026"}
})
doc.SaveAs("Q2-2026-report.docx")
$vbLabelText   $csharpLabel

Cada DataTable en el DataSet coincide con su correspondiente región "TableStart:..." por nombre, y los campos escalares llenan los valores del encabezado en el mismo paso.

Para equipos con flujos de trabajo de documentos como estos que esperan en una implementación limpia de .NET, el Iron Suite agrupa IronWord junto a IronPDF, IronOCR, IronXL e IronBarcode en una sola licencia.

Empieza una prueba gratuita, no se requiere tarjeta de crédito.

Por qué esto funciona

Algunas cosas distinguen este enfoque de las alternativas:

Sintaxis nativa de Word. Las plantillas se diseñan en Microsoft Word, utilizando las herramientas de campos de combinación que Word ya proporciona. Guardar como .docx, entregar el archivo a IronWord y la combinación se ejecuta. No hay un lenguaje de plantilla propietario, sin capa de traducción, sin curva de aprendizaje separada para el equipo que diseña los documentos.

Ambos formatos de campo simples y complejos. Word produce dos estructuras de campo distintas dependiendo de cómo se insertó el campo. IronWord maneja ambos, lo que significa que las plantillas existentes diseñadas por diferentes miembros del equipo de diferentes maneras, todas funcionan sin modificaciones.

Sin dependencias externas. La biblioteca se ejecuta completamente en .NET. Sin instalación de Word, sin COM, sin licencia de Office, sin fragilidad de interoperabilidad. El mismo código que se ejecuta en un equipo de desarrollo de Windows se ejecuta de manera idéntica en un contenedor Linux, una Azure Function o un agente de compilación serverless.

Resumen

La combinación de correspondencia ha sido una brecha de larga data en el espacio de herramientas de documentos .NET, y las soluciones alternativas no han envejecido bien. La combinación de correspondencia nativa de IronWord cierra esa brecha con un modelo que refleja la propia sintaxis de campos de combinación de Word, admite tanto valores simples como regiones repetidas, y se ejecuta sin ninguno de los problemas de implementación que históricamente han acompañado a la automatización de documentos en .NET.

Para equipos que generan facturas, etiquetas, cartas, contratos e informes a gran escala, este es el camino más limpio desde una plantilla única a miles de documentos personalizados.

Preguntas Frecuentes

¿Qué es la combinación de correspondencia de IronWord?

La combinación de correspondencia de IronWord es una función que te permite generar documentos Word personalizados desde aplicaciones .NET sin requerir una instalación de Microsoft Word.

¿Puede la combinación de correspondencia de IronWord generar documentos a gran escala?

Sí, la combinación de correspondencia de IronWord está diseñada para gestionar la generación de documentos Word personalizados a gran escala, lo que la hace ideal para el procesamiento por lotes grandes.

¿Se requiere Microsoft Word para usar la combinación de correspondencia de IronWord?

No, la combinación de correspondencia de IronWord no requiere que Microsoft Word esté instalado, permitiendo una experiencia de generación de documentos sin complicaciones dentro de aplicaciones .NET.

¿Qué tipos de documentos se pueden generar usando la combinación de correspondencia de IronWord?

La combinación de correspondencia de IronWord puede generar una variedad de documentos incluyendo facturas, cartas, etiquetas e informes usando plantillas de Word.

¿Cómo se integra la combinación de correspondencia de IronWord con .NET?

La combinación de correspondencia de IronWord está nativamente soportada en .NET, brindando a los desarrolladores una forma sencilla y eficiente de implementar funcionalidades de combinación de correspondencia en sus aplicaciones.

¿Cuáles son los beneficios de usar la combinación de correspondencia de IronWord sobre métodos tradicionales?

El uso de la combinación de correspondencia de IronWord elimina la necesidad de Microsoft Word, reduce la dependencia de software de terceros y proporciona una solución escalable para la generación de documentos desde aplicaciones .NET.

¿Puede usarse la combinación de correspondencia de IronWord para generar informes?

Sí, la combinación de correspondencia de IronWord puede usarse para generar informes detallados desde plantillas de Word, haciéndola versátil para variadas necesidades de documentación.

¿Es adecuada la combinación de correspondencia de IronWord para la generación automatizada de documentos?

Sí, la combinación de correspondencia de IronWord es ideal para la generación automatizada de documentos, permitiendo a los desarrolladores crear documentos personalizados programáticamente dentro de sus aplicaciones .NET.

Curtis Chau
Escritor Técnico

Curtis Chau tiene una licenciatura en Ciencias de la Computación (Carleton University) y se especializa en el desarrollo front-end con experiencia en Node.js, TypeScript, JavaScript y React. Apasionado por crear interfaces de usuario intuitivas y estéticamente agradables, disfruta trabajando con frameworks modernos y creando manuales bien ...

Leer más

Equipo de soporte de Iron

Estamos disponibles online las 24 horas, 5 días a la semana.
Chat
Email
Llámame