Artículo técnico

Celdas combinadas de HotXLS y plantillas de reporte guiadas por diseño en Delphi

El reporte de bug decía que el título de la factura había desaparecido. El generador combinó A1:F1 correctamente, le aplicó estilo correctamente, y escribió el título en B1. Tanto en XLS como en XLSX, un rango combinado muestra el contenido de exactamente una celda: el ancla superior izquierda. Lo que termine en las celdas cubiertas no se renderiza, y el contenido escrito allí antes de combinar deja de ser visible en cuanto se aplica la combinación. Los usuarios de Excel aprenden esto por ensayo y error; los generadores de reportes tienen que aprenderlo como regla, porque en código generado el síntoma es una región en blanco y ningún error en ninguna parte. HotXLS, una biblioteca nativa de Object Pascal que lee y escribe ambos formatos de Excel desde Delphi y C++Builder, modela las combinaciones con suficiente claridad para que puedan construir sobre la regla en lugar de tropezar con ella.

Un valor, un ancla

Piensen en una combinación como una instrucción de visualización colocada sobre una cuadrícula que no cambió. Las celdas siguen existiendo individualmente en el archivo; el registro de combinación dice a los consumidores que rendericen el contenido del ancla a lo largo del rectángulo. De ahí se desprenden tres consecuencias prácticas. Leer cualquier celda cubierta que no sea el ancla no produce nada útil, así que el código que inspecciona un título combinado debe dirigirse al ancla. Escribir en cualquier lugar excepto el ancla produce contenido invisible, el "título desaparecido" anterior. Y descombinar vuelve a sacar a la superficie lo que estaba almacenado debajo, por eso los valores perdidos bajo regiones de diseño terminan siendo una sorpresa visible para el cliente.

Del lado XLSX, HotXLS expone directamente la tabla de combinaciones: Sheet.MergedCells es una colección con Add('A1:C1'), FindAt(Row, Col), DeleteAt e Items, y FindAt responde la pregunta esencial: dada una celda arbitraria, ¿qué región combinada, si existe, la cubre? Esa única búsqueda alimenta tanto lecturas seguras (encontrar la región y luego leer su ancla) como la guardia de región de datos mostrada más adelante.

Las llamadas de combinación en cada fachada

La fachada XLS sigue el idioma COM de Excel: los rangos vienen de una propiedad indexada de dos argumentos y Merge recibe un OleVariant cuyo valor cambia la geometría de lo que se obtiene.

var
  Book: IXLSWorkbook;   // interface-counted: no manual Free
  Sh: IXLSWorksheet;
begin
  Book := TXLSWorkbook.Create;
  Sh := Book.Sheets[1];                 // XLS sheet collection is 1-based
  Sh.Range['A1', 'F1'].Merge(False);    // False = one merged block
  Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
  Sh.Range['A3', 'F4'].Merge(True);     // True = merge across: one merge per row
  Book.SaveAs('layout.xls');
end;

La distinción True/False importa para bandas de encabezado: Merge(True) sobre un rango de dos filas produce dos combinaciones independientes de una fila (el "Merge Across" de Excel), mientras que Merge(False) produce un solo rectángulo. El rango también expone MergeCells como bandera de estado legible, MergeArea para recuperar la región contenedora y Unmerge para disolverla. Del lado XLSX, las mismas operaciones aparecen como Sheet.MergeCells(Row1, Col1, Row2, Col2), TXLSXRange.Merge con una variante equivalente Across, y la colección MergedCells.

Una plantilla que crece: CopyRange más InsertRows

Las plantillas reales de reporte no son estáticas: una sección de detalle se expande para coincidir con los datos. El patrón viable mantiene una fila de plantilla con estilo en el diseño y la clona por cada fila de datos, luego abre un espacio antes del bloque de totales para que todo lo anclado debajo se desplace intacto:

Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611';    // value goes to the anchor, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1;        // pool index 0-based, cell side 1-based

// row 5 is the styled detail template line
for I := 0 to ItemCount - 1 do
  Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1);             // styles and formulas travel with it

// open a gap above the totals block; content below shifts down
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);

Dos líneas merecen escrutinio. La asignación de fuente lleva el desplazamiento de índice de pool: Fonts.Add devuelve una posición de pool 0-based mientras que las celdas almacenan referencias 1-based con 0 como fuente predeterminada, así que omitir + 1 estiliza silenciosamente el título con la fuente equivocada. Y CopyRange copia formato y fórmulas junto con valores, que es precisamente por eso que clonar una fila de plantilla estilizada a mano supera reconstruir su formato en código: el diseñador de la plantilla controla la apariencia, el generador controla los datos.

Cuando la biblioteca de diseño vive en un libro separado, por ejemplo una hoja de bandas de encabezado reutilizables, CopyRangeTo extiende la misma operación a través de límites de hoja, recibiendo una hoja destino más coordenadas de destino. Eso permite que un generador conserve una hoja de plantilla prístina y selle sus regiones en tantas hojas de salida como necesite el reporte, en lugar de mutar la plantilla en sitio y esperar restaurarla.

Qué desplaza InsertRows, y qué deja atrás

InsertRows en XLSX es una edición estructural, no un desplazamiento simple de celdas. Reubica regiones combinadas, alturas de fila, hipervínculos, comentarios, paneles congelados, rangos de autofiltro, formatos condicionales, validaciones de datos, tablas, nombres definidos, anclajes de imagen y anclajes de gráfico junto con las celdas. Esa amplitud es lo que hace seguro el patrón de hacer crecer una plantilla: el bloque de totales bajo el punto de inserción llega a su nueva fila con sus combinaciones y formatos intactos.

Hay dos límites documentados y vale la pena diseñar alrededor de ellos. El ajuste de fórmulas cubre referencias a la hoja editada: una fórmula en otra hoja que apunta al área desplazada también se reescribe, pero solo participan referencias que apuntan a la misma hoja, así que auditen por separado los patrones de referencia entre libros. Y del lado XLS, las tablas dinámicas sobreviven ciclos abrir-guardar como registros preservados en bruto, no como objetos modelados, lo que significa que la inserción de filas no reubica la huella de una tabla dinámica. Las plantillas destinadas al formato XLS deben mantener las regiones de pivot bien lejos de cualquier banda que crezca.

Proteger regiones de datos contra regiones de diseño

La falla clásica de celdas combinadas en producción no es visual, es estructural: una fila de datos cae dentro de una banda de diseño combinada, sus valores se vuelven invisibles y los totales dejan de coincidir con la hoja visible. Como MergedCells.FindAt puede interrogar la tabla de combinaciones para cualquier coordenada, el generador puede hacer cumplir el límite en lugar de confiar:

// refuse to write detail data into a merged layout region
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 disciplina aplica a funciones interactivas: los rangos que los usuarios ordenarán o filtrarán no deben contener combinaciones, porque las celdas combinadas dentro de un rango de ordenamiento producen errores o diseño revuelto en Excel. Mantengan combinaciones en bandas de título, separadores de sección y bloques de firma; mantengan plana la zona tabular central de la hoja. El artículo de generación de reportes con plantillas extiende esta separación entre diseño y datos a un workflow completo de placeholders, y el artículo de formato condicional y texto enriquecido cubre el estilo de la banda plana de datos.

Cómo sobreviven las combinaciones a la exportación

El diseño combinado es un concepto de libro, y cada otro formato de salida lo degrada de manera distinta; saber cómo ahorra una ronda de sorpresas de QA. La exportación HTML renderiza combinaciones fielmente como atributos colspan y rowspan en una sola tabla, así que los reportes destinados a navegador conservan su apariencia por bandas. La exportación RTF no abarca columnas: el texto del ancla cae en su propia columna y el resto del ancho combinado sale como celdas vacías, por lo que títulos combinados anchos se ven desplazados a la izquierda en procesadores de texto. CSV no tiene representación de combinación: el valor del ancla ocupa un campo y las celdas cubiertas emiten campos vacíos. Si un libro también sirve como fuente para feeds delimitados, diseñen la región de datos para que nada significativo dependa de la geometría de combinación; el artículo de exportación CSV, TSV y HTML detalla el comportamiento de cada formato.

Preguntas sobre combinaciones desde la cola de soporte

¿Cómo leo el valor de una región combinada si solo tengo una celda cubierta?

Resuelvan primero la región y luego lean su ancla. En XLSX, MergedCells.FindAt(Row, Col) devuelve el rango combinado que cubre la celda o nil; en XLS, Range.MergeArea entrega la región completa para cualquier celda dentro de ella. Leer directamente la coordenada cubierta devuelve contenido vacío incluso cuando la región muestra texto visiblemente.

¿Por qué ordenar mi reporte desordenó el diseño?

El rango de ordenamiento casi seguro intersectó una región combinada. Ordenar reacomoda filas de forma independiente, y una combinación que abarca filas no puede moverse con solo una de ellas. Mantengan las combinaciones fuera de rangos ordenables, o descombinen, ordenen y vuelvan a combinar alrededor de la operación.

¿Las celdas combinadas vuelven lentos los libros grandes?

No de forma significativa a escala de reportes: la tabla de combinaciones es pequeña comparada con los datos de celdas. Las preocupaciones de rendimiento en archivos grandes viven en otra parte: crecimiento del pool de estilos y memoria de la ruta de guardado, cubiertos en el artículo de rendimiento de libros grandes.

Ambas APIs de combinación, las operaciones de edición estructural y los demos de plantillas se incluyen con HotXLS Component.