/** * 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); }