validate advanced func sin utilizar en base al ejemplo de rosario

This commit is contained in:
Your Name
2025-06-18 16:23:01 -06:00
parent 2a1a3bc5c0
commit 3a7f8d3647
+142 -52
View File
@@ -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<UsuarioRow[]> {
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<string, RuleFn[]> = {
// 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<string>();
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<UsuarioRow[]> {
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)
*/