Guía Técnica: Generación y Descarga de Documentos Word (.docx) con DocxHelper en GeneXus
1. Requisitos Previos e Instalación
1.1 Librerías Java (Apache POI)
Para que DocxHelper funcione correctamente, el entorno debe contar con las librerías de Apache POI. Asegurarse de tener los siguientes archivos .jar (versión 5.2.2 o superior) en el classpath del servidor/proyecto:
Subi los archivos a una carpeta de Google Drive para el que las necesite. Link de Descarga:
https://drive.google.com/drive/folders/1E2Z1-K9ZQ_ur9SHQZd1BGq8rwO0X5Kya?usp=sharing
-
poi-5.2.2.jar -
poi-ooxml-5.2.2.jar -
poi-ooxml-lite-5.2.2.jar -
poi-scratchpad-5.2.2.jarEn mi caso las rutas serian:
C:\Program Files\Apache Software Foundation\Tomcat 10.1\webapps\GX18_GesRec_DSRDesarrollo\WEB-INF\lib
D:\Proyectos\GX18\GX18_GesRec_PrdFuncional\JavaDB2iSeries002\Web\lib1.2 Formato de Plantillas Obligatorio (.docx)
Las plantillas utilizadas deben ser obligatoriamente formato
.docxreal. Los formatos.doc,.dot, o.dotxfallarán.Script de Conversión Masiva (PowerShell): Si existen plantillas heredadas en formatos antiguos, ejecutar el siguiente script en una PC con Microsoft Word instalado para convertirlas por lotes a
.docx.
SCRIPT:
////////////////////////////////////////////////////////////////////////////////////////////////$word = New-Object -ComObject Word.Application
$word.Visible = $false$path = “D:\PLANTILLAS” # ← CAMBIAR ESTA RUTA POR LA CARPETA DE PLANTILLAS
Get-ChildItem -Path $path -Recurse -Include *.doc, *.dot, *.dotx | ForEach-Object {
$inputPath = $_.FullName
$outputPath = [System.IO.Path]::ChangeExtension($inputPath, “.docx”)try {
$doc = $word.Documents.Open($inputPath)
$doc.SaveAs([ref] $outputPath, [ref] 16) # 16 = formato DOCX
$doc.Close()
Write-Host “Convertido con éxito:” $inputPath
}
catch {
Write-Host “Error al convertir:” $inputPath
}
}$word.Quit()
////////////////////////////////////////////////////////////////////////////////////////////////Notas sobre la conversión:
-
Los archivos originales NO se eliminan.
-
Los archivos
.doty.dotx(plantillas de Word) pasan a ser documentos normales. -
Importante: Si alguna plantilla contenía macros, estas no se conservarán en el nuevo
.docx.2. Paso a Paso: Lógica de Generación del Documento
El proceso de generación debe realizarse utilizando rutas absolutas para evitar que Tomcat pierda la referencia del archivo.
2.1 Obtener la ruta de la plantilla
Se debe utilizar el procedimiento que devuelve la ruta física (
PCrtFileWeb), descartando versiones anteriores (PCrtFileoPCrtFileV2) que no lo hacían.
Fragmento de codigo a modo ejemplo:// Obtiene la ruta física de la plantilla en &PlantillaPath
Call(PCrtFileWeb, &PlaCod, ”, &NomArcDes, ‘N’, &PlaAbr, &Archi, &CantCopias, &Ok, &Mensaje, &PlantillaPath)If &Ok <> ‘S’
Msg(‘Error al obtener plantilla: ‘ + &Mensaje)
Return
EndIf2.2 Generación del nombre y manejo de concurrencia
Para evitar que dos usuarios pisen el mismo archivo si generan un documento al mismo tiempo, se añade un timestamp al nombre del archivo de salida.
Fragmento de codigo a modo ejemplo:&Now = ServerNow()
&SufijoHora = Trim(Str(Year(&Now), 4, 0)) + Trim(Str(Month(&Now), 2, 0)) + Trim(Str(Day(&Now), 2, 0)) + Trim(Str(Hour(&Now), 2, 0)) + Trim(Str(Minute(&Now), 2, 0)) + Trim(Str(Second(&Now), 2, 0))// OBLIGATORIO: Usar ruta física absoluta del servidor (Tomcat)
&DirectorioTomcat = !”C:\Program Files\Apache Software Foundation\Tomcat 10.1\static\publictempstorage\”
&PathFinal = &DirectorioTomcat + Trim(&NomArcDes) + !”_” + &SufijoHora + !”.docx”2.3 Reemplazo de Tags con DocxHelper
La lógica de reemplazo consta de dos fases críticas:
-
Reemplazo Inicial (Creación): Lee de la
&PlantillaPathoriginal y genera el archivo nuevo en&PathFinal. -
Reemplazos Subsiguientes (Actualización): Leen de
&PathFinaly sobrescriben el mismo&PathFinal.Fragmento de codigo a modo ejemplo:
// 1. EL PRIMER REEMPLAZO: Crea el archivo final a partir de la plantilla
DocxHelper.replaceText(&PlantillaPath, &PathFinal, ‘<<FECHA>>’, Trim(dtoc(&Today)))// 2. TODOS LOS DEMÁS REEMPLAZOS: Operan sobre el archivo recién creado
&HoyLetras = Trim(str(Day(&Today))) + ‘ de ‘ + Trim(cmonth(&Today)) + ‘ de ‘ + Trim(str(year(&Today)))
DocxHelper.replaceText(&PathFinal, &PathFinal, ‘<<FECHALETRAS>>’, Trim(&HoyLetras))
DocxHelper.replaceText(&PathFinal, &PathFinal, ‘<<HORA>>’, Trim(Time()))
// … continuar con el resto de las variables …3. Paso a Paso: Descarga del Archivo (HTTP Response)
⚠️ REGLA DE ARQUITECTURA CRÍTICA: La manipulación de
&HttpResponsepara forzar la descarga NUNCA debe hacerse dentro de un evento de un Web Panel. Hacerlo corromperá el HTML de la pantalla. Todo el código de generación y descarga debe estar en un Procedure (Procedimiento) configurado de la siguiente manera:-
Main program:
True -
Call protocol:
HTTP
3.1 Configuración de Cabeceras y Descarga
Al final del Procedure, una vez generado el
.docx, se inyecta el binario en la respuesta HTTP:If &PlaImp = ‘Y’ OR &PlaAbr = ‘Y’
&OutputFile.Source = &PathFinal // Asignamos la ruta absolutaIf &OutputFile.Exists()
&NombreArchivoDescarga = &OutputFile.GetName() + !”.docx”// Configuramos cabeceras para forzar descarga de Word
&HttpResponse.AddHeader(!”Content-Type”, !”application/vnd.openxmlformats-officedocument.wordprocessingml.document”)
&HttpResponse.AddHeader(!”X-Frame-Options”, !”deny”)
&HttpResponse.AddHeader(!”X-Content-Type-Options”, !”nosniff”)// Instrucción para el navegador: “Descarga esto”
&HttpResponse.AddHeader(!”Content-Disposition”, !”attachment;filename=” + Trim(&NombreArchivoDescarga))// Enviamos el archivo físico
&HttpResponse.AddFile(&OutputFile.GetAbsoluteName())
Else
msg(‘Error crítico: No se pudo encontrar el documento generado en la ruta: ‘ + &PathFinal)
EndIf
EndIf3.2 Invocación desde el Web Panel
Para ejecutar este procedimiento desde la pantalla del usuario sin romper la interfaz, se debe utilizar el método
.Link().
Fragmento de codigo a modo ejemplo:
Event ‘DescargarDocumento’
// Se invoca al procedure pasándole los parámetros necesarios
&UrlDescarga = PimpCedNew.Link(&ORGCOD, &TDeNume, &TDeSec, &ICPCod, &PlaCod, ‘N’, ‘Y’, ‘Y’)// Navegamos a esa URL, el navegador detectará los headers y bajará el archivo
Link(&UrlDescarga)
EndEvent////////////////////////////////////////////////////////////////////////////////////////
Espero que les alla sido util y cualquier cosa mi codigo completo esta en el archivo PImpCedNew, el cual es llamado desde CerDeuWW. -
-