290 lines
7.3 KiB
TypeScript
290 lines
7.3 KiB
TypeScript
/**
|
|
* Módulo de validación para respuestas de cuestionarios
|
|
* Este módulo puede ser utilizado tanto en backend (NestJS) como en frontend (Angular, React, etc.)
|
|
*/
|
|
|
|
// Interfaz para el resultado de validación
|
|
export interface ResultadoValidacion {
|
|
valido: boolean;
|
|
mensaje: string;
|
|
}
|
|
|
|
// Clase base para todos los validadores
|
|
export abstract class Validador {
|
|
abstract validar(valor: string): ResultadoValidacion;
|
|
}
|
|
|
|
// Clase para validar correos electrónicos
|
|
export class ValidadorCorreo extends Validador {
|
|
validar(valor: string): ResultadoValidacion {
|
|
if (!valor) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El correo electrónico es requerido'
|
|
};
|
|
}
|
|
|
|
// Expresión regular para validar correos
|
|
// Valida el formato básico de correos: usuario@dominio.extensión
|
|
const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
|
|
if (!regex.test(valor)) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El formato del correo electrónico no es válido'
|
|
};
|
|
}
|
|
|
|
// Para correos institucionales (opcional)
|
|
if (this.validarDominioInstitucional) {
|
|
const dominios = ['acatlan.unam.mx', 'unam.mx', 'comunidad.unam.mx'];
|
|
const dominio = valor.split('@')[1];
|
|
|
|
if (!dominios.includes(dominio)) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'Debe ser un correo institucional (@acatlan.unam.mx, @unam.mx, @comunidad.unam.mx)'
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
valido: true,
|
|
mensaje: 'Correo electrónico válido'
|
|
};
|
|
}
|
|
|
|
// Propiedad que permite configurar si se validan sólo correos institucionales
|
|
constructor(public validarDominioInstitucional: boolean = false) {
|
|
super();
|
|
}
|
|
}
|
|
|
|
// Clase para validar números telefónicos
|
|
export class ValidadorTelefono extends Validador {
|
|
validar(valor: string): ResultadoValidacion {
|
|
if (!valor) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El número telefónico es requerido'
|
|
};
|
|
}
|
|
|
|
// Eliminar espacios, guiones y paréntesis para la validación
|
|
const numeroLimpio = valor.replace(/[\s\-()]/g, '');
|
|
|
|
// Verificar que solo contenga dígitos
|
|
if (!/^\d+$/.test(numeroLimpio)) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El número telefónico solo debe contener dígitos'
|
|
};
|
|
}
|
|
|
|
// Verificar longitud (para México, 10 dígitos)
|
|
if (numeroLimpio.length !== 10) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El número telefónico debe tener 10 dígitos'
|
|
};
|
|
}
|
|
|
|
return {
|
|
valido: true,
|
|
mensaje: 'Número telefónico válido'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Clase para validar nombres
|
|
export class ValidadorNombre extends Validador {
|
|
validar(valor: string): ResultadoValidacion {
|
|
const regex = new RegExp(
|
|
'/^[A-Za-zÁÉÍÓÚáéíóúÑñ]+(?: [A-Za-zÁÉÍÓÚáéíóúÑñ]+)*$/'
|
|
);
|
|
if (!valor) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El nombre es requerido'
|
|
};
|
|
}
|
|
|
|
// Verificar longitud mínima
|
|
if (valor.trim().length < 2) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El nombre debe tener al menos 2 caracteres'
|
|
};
|
|
}
|
|
|
|
// Verificar que solo contenga letras y espacios
|
|
if (regex.test(valor)) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El nombre solo debe contener letras y espacios'
|
|
};
|
|
}
|
|
|
|
return {
|
|
valido: true,
|
|
mensaje: 'Nombre válido'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Clase para validar números enteros
|
|
export class ValidadorEntero extends Validador {
|
|
constructor(
|
|
private min: number = Number.MIN_SAFE_INTEGER,
|
|
private max: number = Number.MAX_SAFE_INTEGER
|
|
) {
|
|
super();
|
|
}
|
|
|
|
validar(valor: string): ResultadoValidacion {
|
|
if (!valor) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El valor numérico es requerido'
|
|
};
|
|
}
|
|
|
|
// Verificar que sea un número entero
|
|
if (!/^-?\d+$/.test(valor)) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El valor debe ser un número entero'
|
|
};
|
|
}
|
|
|
|
const numero = parseInt(valor, 10);
|
|
|
|
// Verificar rango
|
|
if (numero < this.min || numero > this.max) {
|
|
return {
|
|
valido: false,
|
|
mensaje: `El valor debe estar entre ${this.min} y ${this.max}`
|
|
};
|
|
}
|
|
|
|
return {
|
|
valido: true,
|
|
mensaje: 'Número entero válido'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Clase para validar números decimales
|
|
export class ValidadorDecimal extends Validador {
|
|
constructor(
|
|
private min: number = Number.MIN_SAFE_INTEGER,
|
|
private max: number = Number.MAX_SAFE_INTEGER,
|
|
private decimales: number = 2
|
|
) {
|
|
super();
|
|
}
|
|
|
|
validar(valor: string): ResultadoValidacion {
|
|
if (!valor) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El valor numérico es requerido'
|
|
};
|
|
}
|
|
|
|
// Verificar que sea un número decimal válido
|
|
if (!/^-?\d+(\.\d+)?$/.test(valor)) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'El valor debe ser un número decimal válido'
|
|
};
|
|
}
|
|
|
|
const numero = parseFloat(valor);
|
|
|
|
// Verificar rango
|
|
if (numero < this.min || numero > this.max) {
|
|
return {
|
|
valido: false,
|
|
mensaje: `El valor debe estar entre ${this.min} y ${this.max}`
|
|
};
|
|
}
|
|
|
|
// Verificar precisión decimal
|
|
const partes = valor.split('.');
|
|
if (partes.length > 1 && partes[1].length > this.decimales) {
|
|
return {
|
|
valido: false,
|
|
mensaje: `El valor debe tener máximo ${this.decimales} decimales`
|
|
};
|
|
}
|
|
|
|
return {
|
|
valido: true,
|
|
mensaje: 'Número decimal válido'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Clase para validar cuentas de alumno (formato específico UNAM - 9 dígitos)
|
|
export class ValidadorCuentaAlumno extends Validador {
|
|
validar(valor: string): ResultadoValidacion {
|
|
if (!valor) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'La cuenta de alumno es requerida'
|
|
};
|
|
}
|
|
|
|
// Formato de cuenta UNAM: 9 dígitos para todos los alumnos
|
|
if (!/^\d{9}$/.test(valor)) {
|
|
return {
|
|
valido: false,
|
|
mensaje: 'La cuenta de alumno debe tener exactamente 9 dígitos numéricos'
|
|
};
|
|
}
|
|
|
|
return {
|
|
valido: true,
|
|
mensaje: 'Cuenta de alumno válida'
|
|
};
|
|
}
|
|
}
|
|
|
|
// Fábrica de validadores para crear el validador apropiado según el tipo
|
|
export class FabricaValidadores {
|
|
static crear(tipo: string): Validador | null {
|
|
switch (tipo?.toLowerCase()) {
|
|
case 'correo':
|
|
return new ValidadorCorreo();
|
|
case 'correo_institucional':
|
|
return new ValidadorCorreo(true);
|
|
case 'telefono':
|
|
return new ValidadorTelefono();
|
|
case 'nombre':
|
|
return new ValidadorNombre();
|
|
case 'entero':
|
|
return new ValidadorEntero();
|
|
case 'decimal':
|
|
return new ValidadorDecimal();
|
|
case 'cuenta_alumno':
|
|
return new ValidadorCuentaAlumno();
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Función auxiliar para validar respuestas basada en el tipo de validación
|
|
export function validarRespuesta(respuesta: string, tipoValidacion: string): ResultadoValidacion {
|
|
const validador = FabricaValidadores.crear(tipoValidacion);
|
|
|
|
if (!validador) {
|
|
return {
|
|
valido: true, // Si no hay validador, consideramos válida la respuesta
|
|
mensaje: 'No se requiere validación específica'
|
|
};
|
|
}
|
|
|
|
return validador.validar(respuesta);
|
|
} |