From 3a7f8d3647c6a9ce5f56711402a410b3db809dd8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Jun 2025 16:23:01 -0600 Subject: [PATCH] validate advanced func sin utilizar en base al ejemplo de rosario --- src/excel/excel.service.ts | 194 +++++++++++++++++++++++++++---------- 1 file changed, 142 insertions(+), 52 deletions(-) diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts index 04c33f4..c83cec3 100644 --- a/src/excel/excel.service.ts +++ b/src/excel/excel.service.ts @@ -23,6 +23,9 @@ export interface UsuarioRow { rfc: string; } +type RuleFn = (r: UsuarioRow, rowNum: number, errors: string[]) => void; + + @Injectable() export class ExcelService { private readonly logger = new Logger(ExcelService.name); @@ -43,59 +46,7 @@ export class ExcelService { // … inyecta otros repositorios si los usarás ) { } - /** Lee el buffer del Excel y devuelve un arreglo de filas tipadas */ - private async parseFile(buffer: Buffer): Promise { - const wb = new Workbook(); - await wb.xlsx.load(buffer); - const sheet = wb.worksheets[0]; - const rows: UsuarioRow[] = []; - // Asume que la primera fila es encabezados - sheet.eachRow((row, idx) => { - if (idx === 1) return; // salto encabezados - - // 1) Asegurarnos de que row.values no sea null/undefined - if (!row.values) { - this.logger.warn(`Fila ${idx + 1}: row.values vacío, se omite.`); - return; - } - - // 2) row.values[0] es null, así que slice(1) sí existe - const [ - cuenta, - nombreCompleto, - clave, - nomCarr, - gen, - fechnac, - apellidopa, - apellidoma, - nombres, - sexo, - tipo, - correo, - rfc, - ] = (row.values as any[]).slice(1); - - rows.push({ - cuenta, - nombreCompleto, - clave, - nomCarr, - gen, - fechnac, - apellidopa, - apellidoma, - nombres, - sexo, - tipo, - correo, - rfc, - }); - }); - - return rows; - } /** Valida duplicados, celdas vacías y patrones básicos */ validateRows(rows: UsuarioRow[]) { @@ -148,6 +99,145 @@ export class ExcelService { } + + + // Describes which rules aplicar por tipo + private typeRules: Record = { + // Regla común para todos los tipos + __default: [ + this.ruleNonEmpty(['cuenta', 'nombreCompleto', 'tipo']), + this.ruleUniqueCuenta(), + this.ruleNumericField('gen', /^[0-9]{4}$/), + this.ruleDateField('fechnac'), + this.ruleRFC('rfc'), + ], + + // Añade reglas extra para Licenciatura + Licenciatura: [ + this.ruleNonEmpty(['clave', 'nomCarr']), + (r, rowNum, errors) => { + if (!/^[0-9]{5}$/.test(r.clave)) { + errors.push(`Fila ${rowNum}: clave de Licenciatura debe tener 5 dígitos.`); + } + }, + ], + + // Añade reglas extra para Profesor + Profesor: [ + (r, rowNum, errors) => { + if (!r.correo?.includes('@')) { + errors.push(`Fila ${rowNum}: Profesor debe tener correo válido.`); + } + }, + ], + + // … puedes definir más tipos aquí … + }; + + /** Métodos auxiliares que devuelven funciones de regla */ + private ruleNonEmpty(fields: (keyof UsuarioRow)[]): RuleFn { + return (r, rowNum, errors) => { + for (const f of fields) { + const v = (r[f] ?? '').toString().trim(); + if (!v) { + errors.push(`Fila ${rowNum}: campo "${f}" está vacío.`); + } + } + }; + } + + private ruleUniqueCuenta(): RuleFn { + const seen = new Set(); + return (r, rowNum, errors) => { + const c = (r.cuenta ?? '').toString().trim(); + if (seen.has(c)) { + errors.push(`Fila ${rowNum}: cuenta duplicada "${c}".`); + } else { + seen.add(c); + } + }; + } + + private ruleNumericField(field: keyof UsuarioRow, regex: RegExp): RuleFn { + return (r, rowNum, errors) => { + const v = (r[field] ?? '').toString().trim(); + if (v && !regex.test(v)) { + errors.push(`Fila ${rowNum}: campo "${field}" inválido: "${v}".`); + } + }; + } + + private ruleDateField(field: keyof UsuarioRow): RuleFn { + return (r, rowNum, errors) => { + const v = (r[field] ?? '').toString().trim(); + if (v && !/^[0-9]{8}$/.test(v)) { + errors.push(`Fila ${rowNum}: fecha "${field}" debe ser YYYYMMDD: "${v}".`); + } + }; + } + + private ruleRFC(field: keyof UsuarioRow): RuleFn { + return (r, rowNum, errors) => { + const v = (r[field] ?? '').toString().trim(); + if (v && !/^[A-Z0-9]+$/.test(v)) { + errors.push(`Fila ${rowNum}: RFC contiene caracteres inválidos: "${v}".`); + } + }; + } + + /** Lee el buffer del Excel y devuelve un arreglo de filas tipadas */ + private async parseFile(buffer: Buffer): Promise { + const wb = new Workbook(); + await wb.xlsx.load(buffer); + const sheet = wb.worksheets[0]; + const rows: UsuarioRow[] = []; + + sheet.eachRow((row, idx) => { + if (idx === 1) return; // salto encabezados + const vals = row.values as any[]; + rows.push({ + cuenta: vals[1] ?? '', + nombreCompleto: vals[2] ?? '', + clave: vals[3] ?? '', + nomCarr: vals[4] ?? '', + gen: vals[5] ?? '', + fechnac: vals[6] ?? '', + apellidopa: vals[7] ?? '', + apellidoma: vals[8] ?? '', + nombres: vals[9] ?? '', + sexo: vals[10] ?? '', + tipo: vals[11] ?? '', + correo: vals[12] ?? '', + rfc: vals[13] ?? '', + }); + }); + + return rows; + } + + /** + * Valida cada fila según reglas comunes y específicas por tipo. + * Retorna un array de mensajes de error. + */ + validateRowsAdvanced(rows: UsuarioRow[]): string[] { + const errors: string[] = []; + rows.forEach((r, i) => { + const rowNum = i + 2; + // 1) Aplica reglas comunes + for (const rule of this.typeRules.__default) { + rule.call(this, r, rowNum, errors); + } + // 2) Aplica reglas del tipo específico + const specificRules = this.typeRules[r.tipo] ?? []; + for (const rule of specificRules) { + rule.call(this, r, rowNum, errors); + } + }); + return errors; + } + + + /** * Solo valida: retorna lista de errores (vacío = ok) */