La forma confiable de producir un informe de Excel con estilo desde Delphi es partir de un libro de trabajo que un diseñador ya construyó. Alguien de finanzas diseña la factura en Excel: el logotipo, los encabezados de columna, los bordes de la banda de detalle, la fila de totales en negrita, los formatos de moneda. Tu código abre ese archivo, vuelca datos reales en las celdas que el diseñador reservó para ello y guarda el resultado. La apariencia es de ellos; los números son tuyos. HotXLS, una biblioteca nativa para Delphi y C++Builder que lee y escribe libros de trabajo XLS y XLSX sin controlar Excel, te da las tres operaciones que este enfoque necesita: buscar una celda por su texto, copiar un rango con sus estilos y fórmulas intactos, e insertar filas de modo que todo lo que está debajo se desplace hacia abajo junto con los datos
La única regla que separa a un generador que sobrevive a las ediciones de la plantilla de uno que se rompe con la primera es no direccionar nunca las celdas por números literales de fila y columna. Una plantilla es un documento que otras personas editan. El equipo de finanzas agrega una línea de impuestos, aumenta la altura de la fila del logotipo, reordena el bloque de dirección, y el formato de archivo no te ayuda en nada: un guardado BIFF u OOXML tiene éxito tanto si la fila 10 sigue significando lo que significaba el trimestre pasado como si no. Un generador que escribe la primera línea de detalle en una fila 10 codificada a mano, la primera vez que alguien inserte un bloque encima de la sección de detalle, estampará las partidas sobre las celdas equivocadas y sumará un rango de totales que ya no cubre los datos. Nada lanza una excepción, cada guardado devuelve éxito, y la única señal es un cliente que nota una factura incorrecta
Ancla cada coordenada a un token marcador
La solución es hacer que la plantilla lleve sus propias coordenadas. El diseñador escribe tokens como {{CUSTOMER}}, {{DATE}} y {{DETAIL_START}} en las celdas que el generador debe tocar, y el generador calcula cada posición en tiempo de ejecución a partir de dónde encuentra esos tokens. Las ediciones de diseño dejan de importar, porque el token se mueve junto con la celda en la que está. La segunda mitad del contrato es la regla de fallo: si falta un token obligatorio, el trabajo se detiene antes de que ningún dato de cliente llegue al archivo. Una plantilla que se ha desviado debe producir un ticket de trabajo fallido, no un documento entregado
Encontrar los tokens: FindText y ReplaceText
Ambas familias de clases de HotXLS exponen búsqueda a nivel de hoja de cálculo. FindText devuelve la fila y la columna de la primera celda cuyo texto coincide, con una sobrecarga que agrega distinción de mayúsculas y minúsculas. ReplaceText sustituye cada ocurrencia y devuelve cuántas cambió. Las dos cubren los dos tipos de token que sueles tener. Un ancla única como el nombre del cliente la localizas una vez y escribes junto a ella; un token que debe aparecer exactamente una vez, como la fecha del informe, lo reemplazas y compruebas el conteo. Del lado XLSX, un relleno que se ancla de esta manera se ve así:
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
R, C: Integer;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('invoice-template.xlsx') <> 1 then
raise Exception.Create('Cannot open invoice template');
Sheet := Book.Sheets[0]; // TXLSXSheets.Items tiene base cero
if not Sheet.FindText('{{CUSTOMER}}', R, C) then
raise Exception.Create('Template drift: {{CUSTOMER}} anchor missing');
Sheet.Cells[R, C].Value := 'ACME Corp';
if Sheet.ReplaceText('{{DATE}}',
FormatDateTime('yyyy-mm-dd', Date)) = 0 then
raise Exception.Create('Template drift: {{DATE}} token missing');
// la expansión del detalle y el guardado siguen abajo
finally
Book.Free;
end;
end;
Dos detalles importan. Primero, FindText y ReplaceText comparan contra el valor de texto de una celda; un token incrustado dentro de una cadena de fórmula es invisible para ellos, así que los tokens marcadores van en celdas simples, nunca dentro de fórmulas. Segundo, el conteo de reemplazos es tu detector de desviación. Una plantilla que debería contener exactamente un token {{DATE}} pero reporta cero reemplazos ha sido editada, y lanzar una excepción en ese momento es precisamente lo que convierte una desviación silenciosa del diseño en un fallo visible
Clonar la fila de detalle sin perder estilos ni fórmulas
La sección de detalle de una factura crece con los datos. Escribir valores directamente en filas en blanco debajo de la línea de muestra descarta todo lo que el diseñador preparó: los bordes, los formatos de número, las fórmulas por fila. El patrón que conserva todo eso consiste en dejar una fila de muestra completamente formateada en la plantilla y clonarla para cada partida. CopyRange duplica estilos y fórmulas en una sola llamada, tras lo cual el generador sobrescribe únicamente las celdas de valor
const
DetailRow = 10; // la fila de muestra formateada en la plantilla
var
I: Integer;
begin
// Abre espacio antes del bloque de totales primero, para que el rango SUM
// debajo de la banda de detalle se estire junto con los datos.
if Length(Items) > 1 then
Sheet.InsertRows(DetailRow + 1, Length(Items) - 1);
for I := 0 to High(Items) do
begin
if I > 0 then // clona estilos + fórmulas desde la fila de muestra
Sheet.CopyRange(DetailRow, 1, DetailRow, 5, DetailRow + I, 1);
Sheet.Cells[DetailRow + I, 1].Value := Items[I].Name;
Sheet.Cells[DetailRow + I, 2].Value := Items[I].Qty;
Sheet.Cells[DetailRow + I, 3].Value := Items[I].UnitPrice;
Sheet.Cells[DetailRow + I, 4].Formula :=
Format('B%d*C%d', [DetailRow + I, DetailRow + I]); // sin prefijo '='
end;
end;
Observa con atención la asignación de la fórmula. La propiedad Formula de XLSX toma la expresión sin el signo igual inicial, mientras que la fachada XLS espera '=B10*C10' asignado a través de Value. Mezclar las dos convenciones es el error de portabilidad más común entre las familias de clases, y falla sin queja alguna: la celda simplemente contiene una cadena literal que Excel muestra como texto. Si la plantilla decora la banda de detalle con filas de título combinadas, recuerda que solo la celda superior izquierda de un área combinada lleva un valor. Las reglas de diseño en el artículo complementario sobre celdas combinadas en plantillas de informe orientadas al diseño explican por qué las regiones combinadas deben quedar completamente fuera de la banda de datos
Qué mueve InsertRows y qué deja atrás
Insertar filas antes del bloque de totales es lo que mantiene un rango SUM estirándose a medida que crece la sección de detalle. Del lado XLSX, InsertRows arrastra hacia abajo junto con las celdas una larga lista de estructuras dependientes: rangos combinados, alturas de fila, hipervínculos, comentarios, paneles inmovilizados, rangos de autofiltro, formatos condicionales, validaciones de datos, tablas, nombres definidos, y anclas de imágenes y gráficos. Hay un límite en esa lista que vale la pena memorizar. La reescritura de fórmulas alcanza solo las referencias dentro de la misma hoja. Una fórmula en una hoja de resumen que apunta a la región movida conserva sus coordenadas antiguas y lee silenciosamente las celdas equivocadas, razón por la cual los totales extraídos entre hojas son más seguros expresados mediante nombres a nivel de libro de trabajo. El artículo complementario sobre nombres definidos y fórmulas entre hojas desarrolla ese patrón
El formato XLS heredado traza la línea en un lugar más duro. HotXLS conserva las tablas dinámicas, las tablas de consulta y las conexiones de datos externas de los archivos BIFF como bloques de bytes sin procesar. Sobreviven a la apertura y el guardado sin cambios, pero no están modelados, así que la inserción de filas nunca los toca. Una plantilla que coloca una tabla dinámica debajo de un bloque de detalle en expansión se guarda sin ninguna advertencia mientras el rectángulo de origen de la tabla dinámica se desvía de los datos. La salida es estructural, no defensiva: mantén el contenido dinámico y de consulta en hojas en las que el generador nunca inserta, y la desactualización no puede ocurrir
Recalcula antes de entregar, o ten claro por qué lo omitiste
HotXLS no evalúa fórmulas durante SaveAs. Cuando una persona abre el archivo, Excel recalcula todo (la fachada XLS expone CalculationMode y RecalcOnSave si necesitas controlar eso), así que un informe que va a la bandeja de entrada de un humano no necesita nada más de ti. El panorama cambia en el momento en que el libro de trabajo alimenta a otro programa. La exportación a CSV escribe las fórmulas como su texto literal y nunca las calcula, y cualquier analizador posterior que confíe en valores en caché leerá números desactualizados o celdas en blanco. Para esas rutas, calcula en el servidor con Calculate, que evalúa una expresión arbitraria contra el libro de trabajo cargado y devuelve el resultado:
var
Total: Variant;
LastDetail: Integer;
begin
LastDetail := DetailRow + Length(Items) - 1;
Total := Book.Calculate(Format('SUM(Invoice!D%d:D%d)',
[DetailRow, LastDetail]));
if (not VarIsNumeric(Total)) or
(Abs(Total - ExpectedTotal) > 0.005) then
raise Exception.Create('Invoice total does not match the order record');
if Book.SaveAs('invoice-2026-0611.xlsx') <> 1 then
raise Exception.Create('Save failed: check output path and permissions');
end;
Comprobar el total calculado contra el registro del pedido antes de guardar es un seguro barato con un buen rendimiento. Convierte una factura incorrecta en un trabajo fallido. Un operador puede reintentar un trabajo fallido en segundos; una factura incorrecta que ya está en el buzón de un cliente le cuesta a un gerente de cuenta una disculpa y una corrección
Dos familias de clases, un solo algoritmo
La misma lógica se porta entre formatos, pero no el mismo código. TXLSWorkbook para .xls heredado está basado en interfaces y usa conteo de referencias, con indexación de hojas en base uno, y nunca lo liberas a mano. TXLSXWorkbook para .xlsx es un objeto común que debes liberar en un try..finally, con indexación de hojas en base cero y la convención de fórmulas mostrada arriba. FindText, ReplaceText, CopyRange e InsertRows existen en ambos lados, así que la forma anclar-clonar-recalcular se traslada limpiamente. El consejo práctico es comprometerse con un formato por pipeline, u ocultar los dos ciclos de vida de objeto detrás de un adaptador delgado propio en lugar de dispersar la diferencia por todo el generador
El tamaño rara vez importa para el tipo de informe que produce este patrón. Clonar una fila con estilo unos cuantos miles de veces no es nada para el hardware actual. La ruta de guardado solo se convierte en el cuello de botella cuando una banda de detalle llega a las seis cifras de filas, y en ese punto activar StreamingWrite envía el XML de la hoja de cálculo directamente al paquete de salida en lugar de almacenarlo en búfer; el artículo sobre escrituras en streaming para trabajos por lotes en servidor cubre cuándo vale la pena hacer ese intercambio. Los gráficos se comportan como el resto del diseño: del lado XLSX tanto el ancla del gráfico como sus referencias de series se mueven cuando InsertRows se ejecuta por encima de ellos, así que un gráfico debajo de la fila de totales permanece vinculado a los datos correctos, mientras que del lado XLS los gráficos residen en sus propias hojas de gráfico y, como las tablas dinámicas, nunca se desplazan. Ese es un argumento más para mantener las hojas de presentación separadas de la hoja que el generador expande
Este enfoque de anclar-clonar-recalcular permite que un diseñador sea dueño de cómo se ve un libro de trabajo mientras tu código es dueño de lo que dice, que suele ser lo que hace que la salida de Excel generada valga la pena mantener. Las llamadas de búsqueda, copia e inserción mostradas aquí, junto con el motor de fórmulas usado para la comprobación del total previa a la entrega, se incluyen con el componente HotXLS para Delphi para Delphi y C++Builder