Skip to main content
Back to blog
dotnetsharpdocxworddocumentosbackendarquitecturacsharpenterpriseDESTACADO

Generación de documentos Word en .NET con SharpDocx: arquitectura real para sistemas empresariales

11 min read

El problema real

Imagina un sistema ERP para una institución educativa. Cada fin de semestre, el área académica necesita emitir cientos de certificados de calificaciones, constancias de estudio y actas de grado. El sistema ya tiene todos los datos: estudiantes, materias, notas, fechas. El problema es que nadie pensó en cómo sacar esos datos en un documento Word formateado, con el logo institucional, la firma del rector y el sello oficial.

Lo que pasa en la práctica es predecible: alguien del equipo propone usar Office Interop porque "ya lo usaron antes". Se instala Word en el servidor de producción. Funciona en desarrollo. En producción, el servidor lanza excepciones COM intermitentes, los procesos de Word quedan huérfanos consumiendo memoria, y después de tres semanas el servidor necesita reiniciarse manualmente cada dos días. El equipo termina generando los documentos a mano o exportando a PDF desde una plantilla estática que alguien actualiza en su máquina local.

Este patrón se repite en sistemas de contratos legales, reportes financieros, órdenes de compra, cartas de oferta de empleo. El problema no es la falta de datos — es la falta de una estrategia seria para generar documentos Word dinámicos en el servidor, sin dependencias frágiles, sin intervención manual, con el formato exacto que el negocio exige.

Qué resuelve SharpDocx

SharpDocx es una librería .NET que permite generar documentos .docx a partir de plantillas. La plantilla es un archivo Word normal — el mismo que el equipo de diseño o el área legal ya tiene — con expresiones C# embebidas directamente en el texto. En tiempo de ejecución, SharpDocx procesa la plantilla, evalúa las expresiones contra un modelo de datos que tú provees, y produce un documento final con el contenido dinámico resuelto.

Lo que hace esto útil en sistemas reales es la separación de responsabilidades: el diseño del documento vive en la plantilla .docx, que puede ser editada por alguien sin conocimientos de programación. La lógica de datos vive en el backend. SharpDocx conecta ambos mundos sin que ninguno tenga que conocer los detalles del otro.

No requiere Microsoft Office instalado. Trabaja sobre Open XML directamente. Puede ejecutarse en Linux, en contenedores Docker, en Azure App Service, en cualquier entorno donde corra .NET. La generación es síncrona y en memoria — el resultado es un stream que puedes escribir a disco, a blob storage o devolver directamente como respuesta HTTP.

Por qué no usar Office Interop

Office Interop es la automatización COM de Microsoft Office. Funciona invocando la aplicación Word real como un proceso externo desde tu código .NET. Esto significa que Word tiene que estar instalado en el servidor, con una licencia válida, con una sesión de usuario activa o configurada correctamente para ejecución sin cabeza — algo que Microsoft explícitamente desaconseja en entornos de servidor.

Los problemas que aparecen en producción no son teóricos. Los procesos COM no se liberan correctamente bajo carga concurrente. Word abre diálogos de error que bloquean el proceso indefinidamente cuando encuentra un archivo corrupto o un formato inesperado. El modelo de threading de COM es STA (Single-Threaded Apartment), lo que lo hace incompatible con el modelo de concurrencia de ASP.NET. Cada instancia de Word consume entre 50 y 150 MB de RAM. Con diez solicitudes simultáneas, el servidor empieza a degradarse.

El problema de licenciamiento es real y frecuentemente ignorado. Una licencia de Office para escritorio no cubre uso en servidor. Microsoft tiene productos específicos para esto (como Microsoft 365 Apps for Enterprise con derechos de servidor), pero el costo y la complejidad de licenciamiento rara vez se evalúan antes de que el equipo ya esté en producción con Interop.

El despliegue es otro punto de quiebre. Cada vez que actualizas el servidor, tienes que verificar que Office sigue instalado, que la versión es compatible, que los permisos DCOM están configurados. En pipelines de CI/CD modernos, esto es un obstáculo que no tiene justificación técnica.

Comparación con alternativas

Open XML SDK

La librería oficial de Microsoft para manipular archivos .docx, .xlsx y .pptx. No requiere Office. Es la base sobre la que trabajan casi todas las demás librerías, incluyendo SharpDocx. La ventaja es control total sobre la estructura del documento. La desventaja es que escribir un documento con formato complejo desde cero en Open XML es extremadamente verboso — crear un párrafo con texto en negrita requiere instanciar varios objetos anidados. Para generación de documentos con plantillas, no es la herramienta correcta. Para manipulación quirúrgica de documentos existentes, es insustituible.

Office Interop

Ya cubierto en la sección anterior. Tiene sentido únicamente en aplicaciones de escritorio donde Word ya está presente y el usuario es el operador. En servidores, es una deuda técnica desde el primer día.

DocX / NPOI

DocX (ahora parte de Xceed) y NPOI son librerías que abstraen Open XML con una API más amigable. Son buenas para crear documentos programáticamente con estructura conocida en tiempo de compilación. El problema es que no tienen un sistema de plantillas: tú construyes el documento en código, lo que significa que cualquier cambio de diseño requiere modificar y redesplegar el backend. Para documentos con layouts complejos o que cambian frecuentemente, esto no escala.

QuestPDF

Excelente librería para generación de PDF con una API fluida y predecible. Si el output final es PDF y el diseño puede definirse en código con un modelo de caja similar a CSS, QuestPDF es probablemente la mejor opción disponible en el ecosistema .NET hoy. No genera .docx. Si el cliente necesita un archivo editable en Word, QuestPDF no aplica.

SharpDocx

El punto fuerte es el sistema de plantillas: el diseño vive en un .docx que cualquiera puede editar en Word, y las expresiones C# se evalúan en tiempo de ejecución. Ideal cuando el output debe ser un .docx editable, cuando el diseño cambia con frecuencia, o cuando el equipo no técnico necesita controlar el layout. La limitación es que documentos con lógica de layout muy compleja (columnas dinámicas, secciones condicionales anidadas) pueden volverse difíciles de mantener en la plantilla.

Implementación práctica

Instalación

dotnet add package SharpDocx

Modelo de datos y plantilla

La plantilla es un archivo .docx con expresiones C# entre llaves. El modelo es una clase C# que SharpDocx expone como variable dentro de la plantilla. Por ejemplo, si tu modelo tiene una propiedad StudentName, en la plantilla escribes <%= Model.StudentName %> y SharpDocx lo reemplaza en tiempo de ejecución.

public class CertificateModel
{
    public string StudentName { get; set; }
    public string Program { get; set; }
    public decimal GPA { get; set; }
    public DateTime IssueDate { get; set; }
    public List<CourseRecord> Courses { get; set; }
}

public class CourseRecord
{
    public string Name { get; set; }
    public int Credits { get; set; }
    public string Grade { get; set; }
}

Generación del documento

var model = new CertificateModel
{
    StudentName = "Ana Torres",
    Program = "Ingeniería de Sistemas",
    GPA = 4.2m,
    IssueDate = DateTime.Today,
    Courses = courseRepository.GetByStudent(studentId)
};

var templatePath = Path.Combine(_env.ContentRootPath, "Templates", "certificate.docx");
var outputPath = Path.GetTempFileName() + ".docx";

DocumentFactory.Create<CertificateModel>(templatePath, model).Generate(outputPath);

Endpoint ASP.NET Core

[HttpGet("{studentId}/certificate")]
public async Task<IActionResult> GetCertificate(int studentId)
{
    var outputPath = await _documentService.GenerateCertificateAsync(studentId);
    var bytes = await System.IO.File.ReadAllBytesAsync(outputPath);
    System.IO.File.Delete(outputPath);

    return File(
        bytes,
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        $"certificado_{studentId}.docx"
    );
}

En producción, evita escribir a disco temporal si puedes. SharpDocx puede generar a un MemoryStream con una pequeña abstracción adicional, lo que elimina la necesidad de gestionar archivos temporales y reduce la latencia de I/O.

Problemas reales encontrados

Estilos que se rompen al reemplazar contenido

Cuando el texto reemplazado es más largo que el placeholder, Word puede fragmentar el run en múltiples elementos XML internos, lo que hace que SharpDocx no encuentre la expresión completa. La solución es asegurarse de que el placeholder esté en un solo run en la plantilla — si Word lo fragmenta al editar, hay que borrar el texto y reescribirlo sin interrupciones de formato.

Tablas dinámicas con filas repetidas

SharpDocx soporta iteración sobre colecciones usando directivas de control en la plantilla. El patrón es definir una fila de tabla como plantilla de iteración y dejar que SharpDocx la repita por cada elemento de la lista. El problema aparece cuando la colección puede estar vacía: si no hay filas, la tabla queda con la fila de plantilla visible. Hay que manejar explícitamente el caso vacío con una condición en la plantilla o eliminar la tabla completa desde el modelo.

Placeholders nulos que causan excepciones

Si una expresión en la plantilla evalúa a null, SharpDocx lanza una excepción en tiempo de ejecución. La solución más pragmática es usar el operador null-coalescing directamente en la expresión de la plantilla: <%= Model.MiddleName ?? string.Empty %>. Alternativamente, normalizar el modelo antes de pasarlo a SharpDocx para garantizar que ninguna propiedad de tipo string sea null.

Incrustación de imágenes

Insertar imágenes dinámicas (logos por tenant, firmas escaneadas) requiere que la imagen esté disponible como archivo en disco en el momento de la generación. Si las imágenes vienen de blob storage, hay que descargarlas a un directorio temporal antes de invocar SharpDocx. Intentar pasar un stream directamente no funciona con la API estándar — hay que materializar el archivo primero.

Presión de memoria con documentos grandes

Para documentos con cientos de páginas o tablas con miles de filas, la generación en memoria puede consumir varios cientos de MB por solicitud. Bajo carga concurrente, esto puede saturar el heap del proceso. La solución es mover la generación a un worker en background y limitar la concurrencia del pool de generación. No intentes generar documentos grandes de forma síncrona en el request pipeline de ASP.NET.

Arquitectura recomendada

En un sistema real, la generación de documentos no debería ocurrir de forma síncrona en el request pipeline. El patrón que funciona bien en producción separa la solicitud de generación de la ejecución real.

La capa de API recibe la solicitud y encola un job de generación — usando Hangfire, MassTransit, o cualquier sistema de colas que ya esté en el stack. El job contiene el identificador del documento a generar y los parámetros necesarios. La API responde inmediatamente con un identificador de trabajo y una URL de polling o un mecanismo de notificación.

El worker que procesa el job tiene acceso a un servicio de generación de documentos que encapsula SharpDocx. Este servicio recibe el modelo de datos (construido desde la base de datos o desde el contexto del job), selecciona la plantilla correcta según el tipo de documento y el tenant, invoca SharpDocx, y escribe el resultado a blob storage (Azure Blob, S3, o equivalente). Una vez almacenado, actualiza el estado del job con la URL del documento generado.

La gestión de plantillas es una responsabilidad separada. En sistemas multi-tenant, cada tenant puede tener su propia versión de una plantilla. Las plantillas se almacenan en blob storage con versionado, y el servicio de generación las descarga y cachea localmente en el worker. Esto permite actualizar plantillas sin redesplegar el backend.

Las capas quedan así: API (recibe solicitud, encola job) → Queue (Hangfire/MassTransit) → Worker (construye modelo, selecciona plantilla, invoca servicio de generación) → DocumentGenerationService (abstrae SharpDocx, maneja errores, escribe a storage) → BlobStorage (almacena el .docx generado) → API de descarga (genera URL firmada o sirve el archivo directamente). Esta separación permite escalar los workers independientemente de la API, reintentar jobs fallidos, y auditar cada generación.

Cuándo NO usar SharpDocx

Si el output final debe ser PDF con control preciso de paginación, márgenes, fuentes embebidas y layout tipo diseño gráfico, SharpDocx no es la herramienta. Genera .docx, y la conversión posterior a PDF (via LibreOffice headless o Microsoft Graph API) introduce variabilidad en el renderizado que puede ser inaceptable para documentos legales o de presentación.

Si el documento requiere edición colaborativa en tiempo real, necesitas una solución diferente — Google Docs API, Microsoft Graph con OneDrive, o un editor embebido como TipTap o Quill con persistencia propia.

Si el layout del documento es extremadamente complejo — columnas dinámicas que dependen del contenido, secciones que se reorganizan según condiciones, elementos flotantes con posicionamiento preciso — la plantilla .docx se vuelve difícil de mantener y propensa a errores visuales. En esos casos, construir el documento programáticamente con Open XML SDK o generar PDF directamente con QuestPDF es más predecible.

SharpDocx brilla cuando el documento tiene un layout estable, el contenido es dinámico, y el equipo necesita poder modificar el diseño sin tocar código. Fuera de ese escenario, evalúa otras opciones.

Conclusión técnica

La generación de documentos es uno de esos problemas que parece trivial hasta que estás en producción con un servidor que tiene Word instalado, procesos COM huérfanos y un equipo que no entiende por qué el sistema falla cada tres días. La solución no es más compleja que el problema — es elegir la herramienta correcta desde el principio.

SharpDocx resuelve un caso de uso específico y lo resuelve bien: plantillas .docx con contenido dinámico, sin dependencias de Office, ejecutable en cualquier entorno donde corra .NET. No es la solución para todo, pero para sistemas que necesitan emitir documentos Word formateados a escala, es una de las opciones más pragmáticas disponibles en el ecosistema.

La automatización de documentos bien hecha no se nota. El sistema genera, almacena y entrega. El usuario descarga su certificado, su contrato, su reporte. Nadie sabe qué librería está detrás, y eso es exactamente lo que debería ser.