pols-exceljs-plus
v2.4.0
Published
`pols-exceljs-plus` es una extensión de la biblioteca `exceljs` que proporciona utilidades avanzadas para agilizar la lectura, escritura y estructuración de datos en hojas de cálculo Excel.
Readme
pols-exceljs-plus
pols-exceljs-plus es una extensión de la biblioteca exceljs que proporciona utilidades avanzadas para agilizar la lectura, escritura y estructuración de datos en hojas de cálculo Excel.
Características
- PXls Workbook: Extiende el
Workbooknativo de exceljs inyectando métodos de ayuda en todas las hojas (Worksheet). - Lectura por Esquema Declarativo (
getValuesBySchema): Extrae y valida filas o columnas a partir de un esquema en forma de objeto, con conversión automática de tipos, validación de obligatoriedad, valores por defecto y parseadores personalizados. - Lectura de Tablas Dinámicas (
getTableValues): Extrae una tabla completa a partir de una fila de cabeceras, asociando dinámicamente columnas por nombre (soportaRegExp), acumulando múltiples coincidencias conparsey detectando el fin de la tabla automáticamente. - Lectura de Tablas con Cabecera Jerárquica (
getHierarchicalTableValues): Extrae una tabla completa cuya cabecera ocupa varias filas (con celdas combinadas formando niveles), calculando por columna una "ruta" jerárquica (ej."Principal > Secundario") y mapeándola a las propiedades del objeto resultante mediantepath(string,string[]oRegExp); sin esquema, trae todas las columnas detectadas. Devuelve{ data, structure }, dondestructuredescribe cómo se resolvió cada columna del esquema contra el archivo (exists,duplicated), ytransformrecibe unpreviewencadenado para columnas duplicadas. - Escritura simplificada: Métodos
setValues,setRowValuesysetColumnValuespara escribir arreglos bidimensionales y unidimensionales aplicando estilos y fusiones de celdas fácilmente. - Lectura rápida de valores (
getValue): Recuperación de valores de celdas con formateo de fechas, resolución de fórmulas y extracción de hipervínculos automáticos.
Instalación
npm install pols-exceljs-plusUso
Inicializar PXls
Para utilizar las utilidades extendidas, utiliza la clase PXls en lugar del Workbook nativo de exceljs:
import { PXls } from 'pols-exceljs-plus';
const workbook = new PXls();
await workbook.readFile('mi_archivo.xlsx');
// Obtener hoja (ya viene decorada con todos los métodos de utilidad)
const sheet = workbook.getWorksheet('Hoja1');Lectura por Esquema con getValuesBySchema
Este método permite extraer y formatear los valores de una fila o columna según un esquema definido por un objeto. El orden de las propiedades del objeto determina por defecto el orden secuencial de lectura.
Firma del método:
sheet.getValuesBySchema(schema, readMode, row, column)schema: Objeto declarativo que define la forma de la respuesta esperada.readMode:'row'para leer horizontalmente (columnas consecutivas) o'column'para leer verticalmente (filas consecutivas).row: Fila de inicio (1-indexed).column: Columna de inicio (1-indexed).
Opciones del Esquema
Cada propiedad del esquema puede ser:
- Un Constructor / Tipo abreviado:
String,Number,Boolean,Date,'string','number','boolean','date', o'any'. - Un Objeto de Configuración Completo:
type: Tipo de dato o constructor.cellIndex(opcional): Desplazamiento relativo explícito desde la celda inicial (permite saltarse celdas o leer en desorden).parse(opcional): Función callback para transformar/limpiar el valor obtenido:(value: any) => any.
Ejemplo de Lectura:
// Supongamos que en la fila 5 de Excel tenemos:
// Col 1 (A): 456
// Col 2 (B): "María Pérez"
// Col 3 (C): 2026-06-24 (Fecha)
// Col 4 (D): (Celda vacía)
// Col 5 (E): " texto con espacios "
const esquemaCliente = {
id: Number, // Convierte a número secuencialmente (Col A)
nombre: String, // Convierte a string (Col B)
fechaRegistro: 'date', // Convierte a objeto Date (Col C)
estado: { type: String, cellIndex: 3 }, // Lee Col D (vacía) y devuelve null
observaciones: {
parse: (val) => typeof val === 'string' ? val.trim() : val,
cellIndex: 4 // Lee Col E de forma explícita
}
};
const cliente = sheet.getValuesBySchema(esquemaCliente, 'row', 5, 1);
console.log(cliente);
/*
Output:
{
id: 456,
nombre: "María Pérez",
fechaRegistro: Date("2026-06-24..."),
estado: null,
observaciones: "texto con espacios"
}
*/Lectura de Tablas con getTableValues
Este método permite leer una tabla completa a partir de una fila de cabeceras. Busca dinámicamente las columnas basándose en sus nombres de cabecera definidos en el esquema (soporta coincidencia por texto exacto o expresión regular RegExp).
Firma del método:
sheet.getTableValues(schema, row, column)schema: Objeto que define las propiedades y cómo encontrarlas/formatearlas. Cada propiedad debe ser un objeto con los siguientes atributos:headerName: Cadena de texto (string) o expresión regular (RegExp) para identificar la cabecera de la columna correspondiente.type(opcional): Constructor o tipo de conversión (String,Number,Boolean,Date,'string','number','boolean','date', o'any').parse(opcional): Función callback para transformar el valor:(value: any, prevValue?: any) => any.
row: Fila donde se encuentra la cabecera (1-indexed).column: Columna inicial desde donde se empezará a buscar las cabeceras de forma horizontal (1-indexed).
Características particulares:
- Detección de Cabeceras: El método recorre horizontalmente la fila de partida (
row) desde la columna inicial (column) hasta toparse con una celda vacía (lo cual da por terminado el escaneo de cabeceras). - Detección de Fin de Tabla: Lee hacia abajo fila por fila. Para optimizar el rendimiento, sólo lee las celdas cuyas cabeceras coinciden con algún
headerName. Detiene su lectura cuando se topa con una fila completamente vacía en todas las columnas identificadas. - Múltiples Cabeceras para una propiedad:
- Si el
headerNamecoincide con más de una columna (por ejemplo/(base imponible)|impuesto/), por defecto se tomará el valor de la última columna procesada (de izquierda a derecha). - Si se define la función
parse, esta se invocará secuencialmente recibiendo como primer parámetro el valor de la columna actual y como segundo parámetro el valor acumulado de las columnas coincidentes previas, permitiendo realizar agregaciones (por ejemplo, sumas o concatenaciones).
- Si el
Ejemplo de Lectura de Tabla:
// Supongamos que en la fila 2 de Excel tenemos las cabeceras:
// B2: "Nombres", C2: "Edades", D2: "Monto 1", E2: "Monto 2"
// Y las filas siguientes contienen la información
const schema = {
nombre: {
type: 'string',
headerName: /Nombres/
},
montoTotal: {
type: 'number',
headerName: /Monto/,
parse: (val, prevVal) => (prevVal || 0) + (val || 0) // Suma las columnas que coincidan con "Monto"
}
};
const datos = sheet.getTableValues(schema, 2, 2);
console.log(datos);
/*
Output:
[
{ nombre: "Juan", montoTotal: 150 },
{ nombre: "Maria", montoTotal: 350 }
]
*/Lectura de Tablas con Cabecera Jerárquica con getHierarchicalTableValues
Este método permite leer una tabla completa cuya cabecera ocupa varias filas, típico de
reportes donde una columna "padre" agrupa a varias columnas "hijas" mediante celdas combinadas
(merge). Por cada columna de datos, el método arma una ruta jerárquica concatenando el valor
de cada fila de la cabecera (por ejemplo "Principal > Secundario"), y el esquema asocia esa ruta
exacta a la propiedad final del objeto devuelto.
A diferencia de getTableValues (que usa un objeto { clave: { headerName, ... } } y permite que
TypeScript infiera el tipo de retorno a partir del propio esquema), getHierarchicalTableValues
recibe el esquema como un arreglo y expone un único parámetro de tipo genérico <T> con la
forma del objeto que se desea recibir, ya que la ruta jerárquica no siempre es un identificador
práctico para inferir tipos automáticamente.
Firma del método:
sheet.getHierarchicalTableValues<T>({ schema, headerRows, r, c, separator? })Todos los parámetros se reciben en un único objeto:
schema(opcional, admitenull): Arreglo de definiciones de columna. Si se omite o se pasanull, no se filtra ni renombra nada: el objeto resultante incluye una propiedad por cada ruta jerárquica distinta detectada en la cabecera, usando esa ruta tal cual como nombre de propiedad (equivale a un esquema automático{ path: <ruta> }por cada columna encontrada). Cuando se define, cada elemento del arreglo es un objeto con:path(string | string[] | RegExp): Cómo identificar la(s) columna(s) a asociar:string: coincide por igualdad exacta con la ruta jerárquica calculada (ej."Principal > Secundario").string[]: coincide con cualquier columna cuya ruta esté incluida en el arreglo.RegExp: coincide con cualquier columna cuya ruta calculada matchee la expresión regular.
to(opcional,string): Nombre de la propiedad en el objeto resultante. Si se omite ypathes unstring, se usa el propiopathcomo nombre de propiedad. Es obligatorio cuandopathes unstring[]o unRegExp(no hay una ruta única de la cual derivar un nombre); si se omite en esos casos, el método lanza un error.trimed(opcional,boolean, por defectotrue): Controla cómo se lee cada celda de la cabecera antes de compararla conpath. Contrue(el valor por defecto), a cada segmento de la ruta se le recorta el espacio sobrante al inicio/fin y, si queda un apóstrofe inicial (el que Excel usa para forzar que un número se trate como texto), también se elimina. Confalse, se compara contra el texto tal como quedó leído (sin quitar ese apóstrofe inicial).transform(opcional): Función callback para transformar el valor encontrado:(value: any, preview?: any) => unknown. Se llama una vez por cada columna del archivo que coincide con elpath(ver más abajo qué pasa cuando hay varias columnas coincidentes). El segundo parámetro,preview, recibe el valor devuelto por la llamada anterior detransformpara esa misma fila; en la primera llamada de cada fila,previewesundefined.
headerRows: Cantidad de filas que ocupa el bloque de cabecera (number).r: Fila donde comienza la cabecera (1-indexed).c: Columna inicial desde donde se empezará a buscar las cabeceras de forma horizontal (1-indexed).separator(opcional): Separador usado para unir los niveles de la ruta jerárquica. Por defecto es" > ".
Cómo se arma la ruta jerárquica de cada columna
- Para cada columna, se recorren sus celdas de cabecera fila por fila (desde
rhastar + headerRows - 1). - Celdas combinadas horizontalmente: si la celda de cabecera está fusionada con una celda de
una columna anterior en la misma fila, se toma el valor de la celda "maestra" del merge. Esto
hace que, por ejemplo, si
B1:C1están combinadas con el texto "Principal", tanto la columna B como la columna C usen "Principal" como primer nivel de su ruta, sin necesidad de repetir el texto en cada celda. - Celdas combinadas verticalmente: si la celda de una fila de cabecera es la misma celda
fusionada que la de la fila anterior (dentro del mismo bloque de cabecera), ese nivel no se
repite en la ruta. Así, una columna "ID" cuya celda de cabecera ocupa las 2 filas del bloque
produce la ruta
"ID", no"ID > ID". - Los niveles vacíos se omiten (no generan separadores colgando en la ruta).
- Por cada segmento se calculan dos variantes: una "trimmed" (espacios recortados y sin el
apóstrofe inicial de escape) y una "cruda" (tal como se leyó). Cuál de las dos se usa para
comparar contra
pathdepende de la propiedadtrimedde cada item del esquema (ver arriba); sinschema, siempre se usa la variante "trimmed". - El escaneo de columnas se detiene apenas la celda de la primera fila de la cabecera está
vacía para esa columna (igual que
getTableValues).
Detección de fin de tabla
Igual que getTableValues: lee hacia abajo fila por fila a partir de r + headerRows, sólo
mirando las columnas que hicieron match con algún path del esquema, y se detiene al encontrar una
fila completamente vacía en esas columnas.
Forma del resultado
A diferencia de getTableValues, el método devuelve un objeto con dos propiedades:
data(T[]): Un objeto por cada fila de datos, igual que antes.structure(PHierarchicalSchemaResult): Un arreglo que describe, columna por columna, cómo se resolvió el esquema contra la cabecera real del archivo, en el orden en que las columnas aparecen en el archivo (de izquierda a derecha). Cada elemento es el item del esquema original (path,to,label,transform) más dos propiedades adicionales:exists(boolean): Si se definió unschema, indica si esa columna fue declarada en él.falsesignifica que el item se declaró en elschemapero no se encontró ninguna columna en el archivo que matcheara supath; estos items se agregan al final destructure(no respetan el orden de aparición porque no aparecieron). Sinschema(autogenerado), siempre estrue.duplicated(boolean): Indica que elpathde ese item coincidió con más de una columna del archivo (cabeceras repetidas, o unpathde tipostring[]/RegExpque matchea varias columnas). La primera columna encontrada para ese item tieneduplicated: false; cualquier columna adicional que matchee el mismo item tieneduplicated: true.
const { structure, data } = sheet.getHierarchicalTableValues<Fila>({ schema, headerRows: 2, r: 1, c: 2 });Columnas duplicadas y encadenamiento de transform con preview
Cuando el path de un item coincide con varias columnas del archivo (cabeceras repetidas, o un
path de tipo string[]/RegExp que matchea más de una columna), transform se invoca una vez
por cada columna coincidente, en el orden en que aparecen en el archivo (de izquierda a derecha).
El valor final asignado a la fila es el que devuelve la última llamada.
En cada llamada, el segundo parámetro (preview) recibe el valor devuelto por la llamada anterior
para esa misma fila (o undefined en la primera columna). Esto permite, por ejemplo, acumular
valores de columnas duplicadas:
// Cabecera con "Monto" repetido en las columnas B, C y D (valores 10, 20 y 30)
const schema = [
{
path: 'Monto',
to: 'total',
transform: (value: number, preview?: number) => (preview ?? 0) + value,
},
];
const { data } = sheet.getHierarchicalTableValues({ schema, headerRows: 1, r: 1, c: 2 });
// data[0] === { total: 60 } // 10 + 20 + 30, encadenado vía `preview`Ejemplo de Lectura:
// Cabecera en las filas 1 y 2, empezando en la columna B:
//
// B C D
// 1 | Principal (B1:C1 combinadas) | ID (D1:D2 combinadas)
// 2 | Secundario | Otra prop |
//
// Datos a partir de la fila 3:
// B3: "Juan" C3: 25 D3: 1001
// B4: "Maria" C4: 30 D4: 1002
type Fila = { nombre: string; edad: number; id: string };
const schema = [
{ path: 'Principal > Secundario', to: 'nombre' },
{ path: 'Principal > Otra prop', to: 'edad', transform: (val: any) => Number(val) },
{ path: 'ID', to: 'id', transform: (val: any) => `ID-${val}` },
];
const { data } = sheet.getHierarchicalTableValues<Fila>({ schema, headerRows: 2, r: 1, c: 2 });
console.log(data);
/*
Output:
[
{ nombre: "Juan", edad: 25, id: "ID-1001" },
{ nombre: "Maria", edad: 30, id: "ID-1002" }
]
*/Sin schema: traer todo lo que se encuentre
Si no interesa mapear a nombres de propiedad específicos, se puede omitir schema (o pasar
null) para obtener un objeto por fila con todas las columnas detectadas, usando la ruta
jerárquica calculada como nombre de propiedad:
const { data } = sheet.getHierarchicalTableValues({ headerRows: 2, r: 1, c: 2 });
console.log(data);
/*
Output:
[
{ 'Principal > Secundario': 'Juan', 'Principal > Otra prop': 25, 'ID': 1001 },
{ 'Principal > Secundario': 'Maria', 'Principal > Otra prop': 30, 'ID': 1002 }
]
*/path como string[] o RegExp: asociar varias columnas a una misma propiedad
const schema = [
// Coincide con cualquiera de las dos rutas exactas listadas
{ path: ['Principal > Secundario', 'Principal > Otra prop'], to: 'cualquieraDePrincipal' },
// Coincide con cualquier columna cuya ruta empiece con "Principal >"
{ path: /^Principal >/, to: 'cualquieraPorRegex' },
];
const { data, structure } = sheet.getHierarchicalTableValues({ schema, headerRows: 2, r: 1, c: 2 });Cuando un path (string[] o RegExp) matchea más de una columna, el valor final en data es
el de la última columna coincidente (de izquierda a derecha) — salvo que se use transform, en cuyo
caso se encadena vía preview (ver más arriba). En structure, cada columna coincidente aparece
como una entrada independiente: la primera con duplicated: false, las siguientes con
duplicated: true.
Notas
- Si ninguna columna coincide con un
pathdel esquema, la propiedad correspondiente queda ennullen cada fila (y si ningúnpathdel esquema encuentra columna, la tabla se considera vacía ydataes[]de inmediato). - Si dos o más columnas terminan generando la misma ruta jerárquica (cabeceras duplicadas), o si un
pathde tipostring[]/RegExpmatchea varias columnas, gana el valor de la última columna coincidente (de izquierda a derecha) endata— salvo que se usetransform, que se encadena víapreview; enstructure, cada columna coincidente genera su propia entrada marcada conduplicated. toes obligatorio cuandopathes unstring[]o unRegExp: al no haber una única ruta de la cual derivar el nombre de propiedad, omitirlo hace que el método lance un error.
Escritura Simplificada
Escribe matrices o arreglos lineales de manera ágil usando los métodos de escritura extendida:
// Escribir múltiples filas y columnas de una sola vez
sheet.setValues(1, 1, [
[1, "Producto A", 19.99],
[2, "Producto B", 25.50]
]);
// Escribir una sola fila con un estilo por defecto
sheet.setRowValues(3, 1, ["ID", "Descripción", "Precio"], {
backgroundColor: "E0E0E0",
color: "000000",
span: 1
});
// Escribir una columna
sheet.setColumnValues(4, 1, [100, 200, 300]);Lectura Rápida de Celdas
// Obtiene el valor parseado automáticamente resolviendo fechas, fórmulas e hipervínculos
const valor = sheet.getValue<string>(1, 2);Licencia
Este proyecto está bajo la licencia ISC.
