Artículo técnico

HotXLS: merged cells and layout-driven report templates

Recorra las celdas de una plantilla de informe recién abierta y un título combinado se comporta como una fuga. Lee A1 y obtiene "Quarterly Statement"; lee de B1 a F1, que visiblemente están bajo el mismo banner, y no obtiene nada. Escribe un valor en C1 para parchear la cabecera y nunca aparece en pantalla. La cuadrícula no perdió sus datos. Está haciendo exactamente lo que significa una combinación: tanto en XLS como en XLSX, un rectángulo combinado representa el contenido de una celda, el ancla superior izquierda, y trata el resto como espacio cubierto que contiene valores pero nunca los muestra. Los usuarios de Excel absorben esto por ensayo y error. Un generador de informes tiene que codificarlo como una regla, porque en código generado el síntoma es una región en blanco sin ninguna excepción que lo rastree. HotXLS, una biblioteca nativa de Object Pascal que lee y escribe ambos formatos de Excel desde Delphi y C++Builder, expone la tabla de combinaciones con suficiente claridad como para programar contra la regla en lugar de redescubrirla en un ticket de soporte

Un valor, un ancla

Una combinación es una instrucción de visualización superpuesta a una cuadrícula que no cambia de forma. Cada celda cubierta sigue existiendo en el archivo como su propia ranura; el registro de combinación solo le indica a un consumidor que pinte el contenido del ancla a través del rectángulo. Esa distinción impulsa tres comportamientos que merece la pena interiorizar antes de escribir cualquier código de diseño. Leer una celda cubierta devuelve su propio valor almacenado, que para un banner que usted construyó normalmente está vacío, así que cualquier código que inspeccione un título combinado tiene que resolver y leer el ancla. Escribir en una celda cubierta tiene éxito a nivel de archivo y no aparece en ningún sitio, que es la trampa de cabecera invisible del principio. Y separar una región expone lo que llevaba todo el tiempo debajo, así que un valor perdido escrito en espacio cubierto se convierte en un defecto visible el día en que alguien disuelve la combinación

Diagrama de un banner combinado de HotXLS donde las celdas cubiertas conservan sus propias ranuras mientras las lecturas se resuelven al ancla A1 en hojas de cálculo de Delphi
HotXLS conserva cada celda cubierta como una ranura real y repinta solo el ancla, de modo que las lecturas se resuelven a través de A1 mientras que las escrituras en el espacio cubierto permanecen invisibles hasta una separación

En el lado XLSX esa tabla es un objeto de primera clase. Sheet.MergedCells lleva Add('A1:C1'), FindAt(Row, Col), DeleteAt e Items, y la llamada a la que más recurrirá es FindAt: entréguele cualquier coordenada y devuelve la región combinada que cubre esa celda, o nil cuando la celda está sola. Esa única búsqueda es la base de las dos mitades del manejo correcto de combinaciones, la lectura segura y la protección de escritura, y ambas aparecen más adelante

Dos fachadas, dos modismos de combinación

HotXLS mantiene el motor BIFF8 .xls clásico y el motor OOXML .xlsx como modelos de objetos separados, y expresan la combinación de forma distinta porque descienden de convenciones diferentes. La fachada XLS sigue el modismo COM de Excel: se toma un rango de una propiedad indexada de dos argumentos y se llama a Merge con un OleVariant cuyo valor decide la geometría con la que termina

var
  Book: IXLSWorkbook;   // con recuento de referencias: sin Free manual
  Sh: IXLSWorksheet;
begin
  Book := TXLSWorkbook.Create;
  Sh := Book.Sheets[1];                 // la colección de hojas XLS es de base 1
  Sh.Range['A1', 'F1'].Merge(False);    // False = un solo bloque combinado
  Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
  Sh.Range['A3', 'F4'].Merge(True);     // True = combinar a través: una combinación por fila
  Book.SaveAs('layout.xls');
end;

El argumento de Merge es la parte que la gente hace mal. En un rango de dos filas, Merge(True) produce dos combinaciones independientes de una fila, que es el "Merge Across" de Excel y exactamente lo que se quiere para una banda de cabecera apilada cuyas filas deban seguir siendo separables. Merge(False) fusiona todo el rectángulo en un único bloque. El rango también reporta MergeCells como un indicador de estado, devuelve la región contenedora mediante MergeArea, y se disuelve a sí mismo con Unmerge. La fachada XLSX expone las mismas operaciones bajo nombres distintos: Sheet.MergeCells(Row1, Col1, Row2, Col2) toma límites enteros, TXLSXRange.Merge acepta la variante Across equivalente, y la colección MergedCells guarda el resultado

Una plantilla que crece con sus datos

Una plantilla de informe real no es una cuadrícula fija. La cabecera y los totales son fijos, pero la sección de detalle entre ellos se estira hasta lo que devuelva la consulta. El patrón que se sostiene mantiene una fila de detalle completamente estilizada en la plantilla, la clona una vez por registro, y luego abre un hueco antes del bloque de totales para que todo lo anclado debajo se deslice hacia abajo sin perder su formato

Plantilla de informe HotXLS creciendo en Delphi: la fila de detalle con estilo se clona por registro e InsertRows abre un hueco para que el bloque de totales baje con las combinaciones intactas
Clonar la fila de detalle con estilo lleva sus estilos y fórmulas a cada copia, e InsertRows desliza después la banda de totales hacia abajo con combinaciones y formatos intactos
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611';    // el valor va al ancla, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1;        // índice del pool en base 0, lado de la celda en base 1

// la fila 5 es la línea de plantilla de detalle con estilo
for I := 0 to ItemCount - 1 do
  Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1);             // los estilos y las fórmulas viajan con ella

// abre un hueco encima del bloque de totales; el contenido de debajo se desplaza hacia abajo
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);

Dos líneas merecen una segunda mirada. La asignación de fuente lleva un error de desplazamiento por uno que muerde en silencio: Fonts.Add devuelve una posición de pool en base 0, mientras que una celda almacena una referencia de fuente en base 1 donde 0 significa la fuente por defecto, así que omitir el + 1 no lanza nada, simplemente da estilo a su título en la tipografía equivocada. La otra línea es CopyRange, que mueve el formato y las fórmulas junto con los valores. Esa es toda la razón para clonar una fila de plantilla construida a mano en lugar de reconstruir su aspecto en código. Un diseñador es dueño de la apariencia una sola vez, en la plantilla; el generador solo vierte datos en copias de ella

Esa división escala todavía más cuando el diseño reutilizable vive en su propio libro de trabajo, digamos una hoja de bandas de cabecera y pie compartida entre informes. CopyRangeTo realiza el mismo clonado a través de los límites de hoja, tomando una hoja de destino más coordenadas de destino, de modo que un generador puede mantener una hoja de plantilla intacta y estampar sus regiones en tantas hojas de salida como necesite un job. La alternativa, mutar la plantilla in situ e intentar restaurarla después, es el tipo de cosa que funciona hasta el día en que una ejecución se aborta a medias

Qué mueve InsertRows, y qué no

El patrón de plantilla que crece solo funciona porque InsertRows de XLSX es una edición estructural y no un simple desplazamiento de celdas. Cuando abre un hueco, reubica las regiones combinadas, las alturas de fila, los hipervínculos, los comentarios, los paneles inmovilizados, los rangos de autofiltro, los formatos condicionales, las validaciones de datos, las tablas, los nombres definidos, los anclajes de imagen y los anclajes de gráfico que están debajo del punto de inserción, no solo los valores de celda. Eso es lo que permite que el bloque de totales llegue a su nueva fila con sus combinaciones y formatos numéricos intactos en lugar de llegar despojado

Sus dos límites documentados son con los que hay que diseñar. El ajuste de fórmulas se limita a la hoja que se está editando: las referencias dentro de esa hoja se reescriben, y una fórmula en otra hoja que apunta al área desplazada también se reescribe, pero el ajuste solo sigue las referencias que apuntan a la hoja editada, así que cualquier esquema de referencias entre libros de trabajo merece su propia auditoría en lugar de confianza ciega. El segundo límite es más marcado, y está en el lado XLS. Las tablas dinámicas sobreviven a los ciclos de abrir y guardar como registros preservados en bruto, no como objetos modelados que HotXLS pueda mover, así que insertar filas no reubica la huella de una tabla dinámica. Cualquier plantilla que construya para el formato .xls debería mantener sus regiones de tabla dinámica bien alejadas de cualquier banda que crezca

Negarse a escribir datos en espacio de diseño

El fallo de celdas combinadas que realmente llega a producción no es el cosmético. Es estructural: una fila de detalle se desliza hacia una banda de diseño combinada, sus valores caen en celdas cubiertas y se vuelven invisibles, y los totales de columna dejan silenciosamente de coincidir con lo que puede ver cualquiera que lea la hoja. Como FindAt responde a la pregunta de la región de cobertura para cualquier coordenada, el generador puede rechazar esa escritura en el momento en que ocurriría en lugar de entregar un informe que subcuenta en silencio

// rechaza escribir datos de detalle en una región de diseño combinada
if Sheet.MergedCells.FindAt(Row, 1) <> nil then
  raise Exception.CreateFmt('row %d overlaps a merged layout region', [Row]);
Sheet.Cells[Row, 1].Value := Detail.Description;

La misma comprobación de límite pertenece a cualquier lugar donde un usuario vaya a ordenar o filtrar la salida más adelante. Un rango con combinaciones dentro no puede ordenarse limpiamente, porque la ordenación mueve las filas de forma independiente y una combinación que abarca varias filas no tiene una sola fila con la que viajar; Excel responde con un error o un diseño desordenado. La disciplina que mantiene correctos los informes es geográfica. Confine las combinaciones a bandas de título, divisores de sección y bloques de firma, y mantenga plano el centro tabular de la hoja. El artículo sobre generación de informes basados en plantilla desarrolla esta separación entre diseño y datos hasta convertirla en un flujo completo basado en marcadores de posición, y el artículo sobre formato condicional y texto enriquecido cubre el estilo de esa banda de datos plana

Cómo se degradan las combinaciones al exportar

Una combinación es un concepto de libro de trabajo, y cada formato de exportación orientado a texto la respeta en distinto grado. Conocer los tres comportamientos de antemano ahorra un ciclo de QA. La exportación a HTML reproduce las combinaciones fielmente, emitiendo colspan y rowspan en una sola tabla, así que un informe destinado al navegador conserva su aspecto en bandas. La exportación a RTF no abarca columnas en absoluto: el texto del ancla cae en su propia celda y el ancho restante de la combinación sale como celdas vacías, lo que deja un título ancho visualmente empujado hacia la izquierda en un procesador de textos. CSV no tiene ningún concepto de combinación, así que el valor del ancla ocupa un campo y cada celda cubierta se emite como un campo vacío. La conclusión para un libro de trabajo que también alimenta exportaciones delimitadas es mantener fuera de la geometría combinada todo lo que sea estructuralmente importante; el artículo sobre exportación a CSV, TSV y HTML recorre cada formato en detalle

Título combinado de HotXLS exportado desde Delphi a HTML con colspan y rowspan, a RTF sin spans y a CSV como campos aplanados
El mismo título combinado sobrevive a la exportación HTML mediante colspan y rowspan, degrada en RTF a una única celda bloqueada a la izquierda, y se aplana a un valor más campos vacíos en CSV

Una tranquilidad para quien sopese esto frente al tamaño de archivo: las combinaciones cuestan casi nada a escala de informe. La tabla de combinaciones es minúscula al lado de los datos de celda, y leer una celda cubierta sigue pasando por FindAt en lugar de un escaneo. La presión de rendimiento en libros de trabajo grandes viene de otra parte, principalmente del crecimiento del pool de estilos y de la memoria que retiene la ruta de guardado, lo que aborda directamente el artículo sobre rendimiento con libros de trabajo grandes. Ambas API de combinación, las operaciones de edición estructural, y las demostraciones de plantilla se distribuyen con HotXLS Delphi Component