El ticket de soporte dice: "El libro de partes de horas que generan está roto; nuestro personal ya no puede escribir en la columna de horas". Solo cambió una cosa en la última versión: el generador empezó a llamar a Protect en la hoja. Nadie tocó la columna de horas, y ese es precisamente el bug. En el modelo de celdas OOXML, cada celda lleva Locked = True de forma predeterminada; el indicador permanece dormido hasta que se activa la protección de hoja, y en ese momento toda la cuadrícula se congela a la vez. HotXLS, una biblioteca nativa de hojas de cálculo para Delphi y C++Builder, expone toda la superficie de protección y configuración de página de .xls y .xlsx; eso significa que también reproduce fielmente cada semántica contraintuitiva de Excel en esa superficie. Este artículo recorre las que generan tickets.
Toda celda nace bloqueada
ECMA-376 define locked como parte del registro de formato de una celda, no como una propiedad de la protección en sí, y su valor predeterminado es true. La protección de hoja es solo el interruptor que hace exigible el indicador. Por lo tanto, el orden correcto de generación es: construir el diseño, desbloquear explícitamente los rangos que los usuarios deben editar y solo entonces proteger:
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Timesheet');
// ... header row, name column, and rate formulas written here ...
Sheet.Range['B2:B50'].SetLocked(False); // staff type hours here
Sheet.Range['F2:F50'].SetFormulaHidden(True); // keep the rate math private
Sheet.Protect('review-2026'); // now the lock flags bite
Book.SaveAs('timesheet.xlsx');
finally
Book.Free;
end;
SetFormulaHidden merece una nota: mientras la protección está activa, la celda sigue mostrando su valor calculado pero la barra de fórmulas no muestra nada. Eso importa cuando las fórmulas incorporan tarifas, márgenes o ponderaciones de puntuación que preferirían no entregar a cada destinatario que haga clic en un total. En la fachada XLS, la misma intención se expresa por rango mediante IXLSRange.Locked y FormulaHidden, y la hoja de cálculo agrega quince indicadores Allow*: AllowSort, AllowAutoFilter, AllowFormatCells y otros, para que una hoja protegida siga siendo usable para ordenar y filtrar en vez de convertirse en una vitrina sellada.
Qué es, y qué no es, la contraseña de protección
Ambos formatos almacenan la contraseña de protección de hoja y libro como un hash heredado de 4 dígitos hexadecimales. Dieciséis bits significan que innumerables cadenas colisionan con cualquier contraseña dada, y las herramientas de eliminación están a una búsqueda de distancia, así que traten la protección como un cinturón de seguridad contra ediciones accidentales, nunca como control de acceso. Es la herramienta correcta para "evitar que los revisores escriban sobre la columna de fórmula" y la herramienta equivocada para cualquier cosa que incluya la palabra confidencial.
Un nivel arriba, ProtectWorkbook en la fachada XLSX bloquea la estructura del libro: no se pueden agregar, renombrar, eliminar ni reordenar hojas. Vale la pena activarlo siempre que la lista de hojas sea un contrato con un parser posterior que indexa hojas por nombre o posición; una hoja renombrada rompe la importación del otro lado tan seguramente como una columna eliminada. La fachada XLS refleja las capas con TXLSWorkbook.Protect a nivel de libro y llamadas Protect por hoja, con isProtected disponible para código que debe inspeccionar archivos heredados antes de modificarlos.
Cuando el requisito es confidencialidad real, el mecanismo cambia por completo: SaveAsEncrypted produce un paquete cifrado con AES según el esquema ECMA-376 Standard Encryption, cubierto en detalle en la guía de salida XLSX protegida con AES, y la fachada XLS heredada escribe y lee archivos .xls cifrados con RC4 mediante EncryptionPassword y la sobrecarga de Open con contraseña. La distinción no es académica: una hoja protegida viaja en texto claro y cualquier herramienta zip puede leer sus valores de celda, mientras que un paquete cifrado es ilegible sin la contraseña. Los requisitos de auditoría que dicen "el archivo de nómina debe estar protegido" casi siempre se refieren al segundo mecanismo, cualquiera sea el vocabulario que use el requisito.
La configuración de página forma parte del contrato del documento
El comportamiento de impresión es invisible en pantalla, por eso se entrega roto tan seguido. En cuanto un cliente imprime el libro, o lo exporta a PDF para un auditor, márgenes, escala y títulos repetidos se vuelven requisitos funcionales. En la fachada XLSX, la configuración cuelga directamente de la hoja:
Sheet.PageLandscape := True;
Sheet.PaperSize := xlsxPaperA4;
Sheet.SetPageMargins(0.5, 0.5, 0.75, 0.75, 0.3, 0.3);
Sheet.CenterHeader := 'Monthly Timesheet';
Sheet.RightFooter := 'Page &P of &N';
Sheet.PrintArea := '$A$1:$F$60'; // bare reference: no sheet name here
Sheet.PrintTitleRows := '$1:$1'; // header row repeats on every page
Sheet.FitToWidth := 1;
Sheet.FitToHeight := 0; // grow downward as the data grows
Sheet.PrintGridlines := False;
Dos de esas líneas esconden trampas. Las cadenas de encabezado y pie usan los códigos de formato de Excel: &P para la página actual, &N para el conteo total, con &L, &C y &R disponibles para dirigirse explícitamente a las tres secciones. Y PrintArea recibe una referencia de celda desnuda a propósito: HotXLS la almacena sin calificar y antepone el nombre de la hoja al escribir el archivo, así que pasar ustedes mismos 'Timesheet!$A$1:$F$60' produce una referencia mal formada. Relacionado con eso, las áreas de impresión y los títulos de impresión se persisten como los nombres definidos integrados _xlnm.Print_Area y _xlnm.Print_Titles; nunca agreguen entradas _xlnm.* manualmente mediante DefinedNames, o los dos mecanismos pelearán por el mismo espacio.
Escala que sobrevive a volúmenes de datos de producción
La combinación FitToWidth := 1 con FitToHeight := 0 se lee como "ajusta siempre las columnas a una página de ancho, y toma tantas páginas hacia abajo como necesiten los datos", y es el valor predeterminado correcto para cualquier reporte cuyo conteo de filas varía. La trampa es ajustar un porcentaje fijo o un par de ajuste a página contra un archivo de prueba de treinta filas: alimenten la misma configuración con seiscientas filas de producción y la salida explota en docenas de páginas recortadas o se encoge por debajo de lo legible. Escalen el ancho, dejen crecer el largo y repitan la fila de encabezado mediante PrintTitleRows para que la página diecisiete siga siendo legible por sí sola.
Los saltos manuales siguen la misma disciplina de regeneración que todo lo demás en libros generados. AddRowBreak(BeforeRow) inicia una página nueva antes de un límite de sección, pero cuando el generador se vuelve a ejecutar y las filas se desplazan, los saltos obsoletos caen en medio de una tabla; por eso llamen primero a ClearAllPageBreaks y vuelvan a agregar saltos desde los contadores de fila propios del generador en lugar de parchar posiciones viejas. En la fachada XLS, los controles equivalentes viven en Sheet.PageSetup (orientación, tamaño de papel, márgenes, cadenas de encabezado y pie, ajuste a páginas), con RepeatRows y RepeatColumns cubriendo los títulos de impresión.
Revisar el resultado antes que el cliente
Los bugs de protección e impresión comparten una propiedad: son triviales de verificar manualmente y casi nunca se verifican. Abran el archivo generado en Excel y dediquen noventa segundos: escriban en una celda de entrada (debe funcionar), escriban en una celda bloqueada (debe mostrar el aviso de protección), revisen que las fórmulas ocultas muestren una barra de fórmulas vacía y ejecuten Vista previa de impresión con un dataset de tamaño producción para confirmar el conteo de páginas, la fila de título repetida y la numeración del pie. El paso de vista previa se paga solo: la geometría de impresión depende de configuraciones que no tienen renderizado en pantalla, así que es el único lugar, aparte de una impresora física, donde un error de escala se vuelve visible.
Dos configuraciones adyacentes completan la experiencia de revisión. FreezePane(ACol, ARow) mantiene visible el bloque de encabezado mientras un revisor se desplaza; es comportamiento de pantalla, no de impresión, pero los revisores juzgan el entregable completo. Y los libros que empiezan como diseños mantenidos por diseñadores reciben gratis buena parte de este artículo: el flujo de generación de reportes con plantillas mantiene la configuración de página en la plantilla, donde una persona la ajustó contra una impresora real, dejando que el código llene datos y vuelva a aplicar protección después de estabilizar el diseño.
Preguntas frecuentes
¿Por qué toda la hoja queda de solo lectura después de llamar a Protect?
Porque cada celda tiene Locked = True de forma predeterminada y la protección activa el indicador globalmente. Llamen a SetLocked(False) en los rangos de entrada antes de Protect, no después.
¿La contraseña de protección de hoja es segura?
No. Se almacena como un hash heredado de 16 bits que colisiona y se elimina trivialmente. Úsenla para evitar accidentes; usen cifrado de archivo AES cuando el contenido en sí deba protegerse.
¿Cómo repito la fila de encabezado en cada página impresa?
Configuren PrintTitleRows := '$1:$1' en la fachada XLSX, o llamen a RepeatRows(0, 0) en la hoja de la fachada XLS; ambas se persisten como el nombre definido _xlnm.Print_Titles que Excel lee.
HotXLS es una biblioteca nativa Object Pascal para hojas de cálculo en Delphi y C++Builder; la referencia completa de la API de protección y configuración de página está en la página de producto de HotXLS Component.