Skip to content

Instantly share code, notes, and snippets.

@donal56
Last active February 8, 2024 19:52
Show Gist options
  • Save donal56/bf43f0a2cef5eaeeb6af3e7ba378fc8d to your computer and use it in GitHub Desktop.
Save donal56/bf43f0a2cef5eaeeb6af3e7ba378fc8d to your computer and use it in GitHub Desktop.
Validar una CURP con base en datos personales
_debug = true;
/*
* Validar una curp con base en datos personales
*
* @param {Object} persona - Datos de la persona
* nombre {String}
* apellidoPaterno {String}
* apellidoMaterno {String|null}
* fechaNacimiento {Date}
* entidadFederativaNacimiento {String} - De acuerdo al acuerdo del DOF Torno DXVII No. 17 del 23/10/1996
* sexo {String} - M o F
* @param {String} curp - CURP a comparar
* @param {Boolean} validarHomoclave - Si es falso, se calcularan los primeros 16 carácteres y se verificara que coincidan con la curp provista.
* Si es verdadero, despues del anterior paso se comprobara la homoclave de la curp provista con el algoritmo del dígito verificador.
* @return {Boolean} valido o no
*/
function validarCurp(persona = {}, curp = '', validarHomoclave = true) {
// Constantes
// Algunos datos recuperados del anexo 2 y 4 de http://www.ordenjuridico.gob.mx/Federal/PE/APF/APC/SEGOB/Instructivos/InstructivoNormativo.pdf
const tamanioCurp = 18;
const vocales = ['A', 'E', 'I', 'O', 'U'];
const entidadExtranjera = 'NE';
const excepcionesNombresMujeres = ['MARIA'];
const excepcionesNombresHombres = ['JOSE'];
const preposicionesComunes = ["DE LA", "DE LOS", "DEL", "DE"];
const palabrasInconvenientes = ['BACA', 'LOCO', 'LOCA', 'BAKA', 'LOKA', 'BUEI', 'LOKO', 'BUEY', 'MAME', 'CACA', 'MAMO', 'CACO', 'MEAR', 'CAGA', 'MEAS', 'CAGO', 'MEON', 'CAKA', 'MIAR', 'CAKO', 'MION', 'COGE', 'MOCO', 'COGI', 'MOKO', 'COJA', 'MULA', 'COJE', 'MULO', 'COJI', 'NACA', 'COJO', 'NACO', 'COLA', 'PEDA', 'CULO', 'PEDO', 'FALO', 'PENE', 'FETO', 'PIPI', 'GETA', 'PITO', 'GUEI', 'POPO', 'GUEY', 'PUTA', 'JETA', 'PUTO', 'JOTO', 'QULO', 'KACA', 'RATA', 'KACO', 'ROBA', 'KAGA', 'ROBE', 'KAGO', 'ROBO', 'KAKA', 'RUIN', 'KAKO', 'SENO', 'KOGE', 'TETA', 'KOGI', 'VACA', 'KOJA', 'VAGA', 'KOJE', 'VAGO', 'KOJI', 'VAKA', 'KOJO', 'VUEI', 'KOLA', 'VUEY', 'KULO', 'WUEI', 'LILO', 'WUEY'];
const entidadesFederativas = ['AS', 'BC', 'BS', 'CC', 'CL', 'CM', 'CS', 'CH', 'DF', 'DG', 'GT', 'GR', 'HG', 'JC', 'MC', 'MN', 'MS', 'NT', 'NL', 'OC', 'PL', 'QT', 'QR', 'SP', 'SL', 'SR', 'TC', 'TS', 'TL', 'VZ', 'YN', 'ZS'];
// Funciones
// La letra Ñ se reemplaza por una X
const estaVacio = cadena => cadena == null || cadena.toString().trim() == '';
const limpiar = cadena => cadena.toUpperCase().replace('Ñ', 'X').normalize("NFD").replace(/[\u0300-\u036f]/g, "");
const primeraVocalInterna = function(cadena) {
const auxCadena = cadena.toUpperCase().substring(1, cadena.length);
for(indice in auxCadena.split('')) {
const letra = auxCadena[indice];
if(vocales.includes(letra)) return letra;
}
return 'X';
};
const primerConsonanteInterna = function(cadena) {
const auxCadena = cadena.toUpperCase().substring(1, cadena.length);
for(indice in auxCadena.split('')) {
const letra = auxCadena[indice];
if(!vocales.includes(letra)) return letra;
}
return 'X';
};
const quitarPrimerNombre = function(nombreCompuesto, listaNegra = []) {
let nombres = nombreCompuesto.trim().split(/\s+/);
// No es un nombre compuesto
if(nombres.length <= 1) return nombreCompuesto;
let primerNombre = nombres[0].toUpperCase().trim();
listaNegra.forEach(function(nombreLista) {
if(primerNombre == nombreLista) {
nombres = nombres.splice(1, nombres.length);
nombreCompuesto = nombres.join(" ");
}
});
return nombreCompuesto;
};
const quitarPreposiciones = function(cadena, listaNegra = []) {
cadena = cadena.trim().toUpperCase();
listaNegra.forEach(function(nombreLista) {
if(cadena.startsWith(nombreLista + " ")) {
cadena = cadena.substring(nombreLista.length + 1, cadena.length);
}
});
return cadena;
}
// Validación de entradas
let {nombre, apellidoPaterno, apellidoMaterno, fechaNacimiento, entidadFederativaNacimiento, sexo} = persona;
if(estaVacio(nombre)) throw 'El nombre de la persona es un parámetro requerido';
if(estaVacio(apellidoPaterno)) throw 'El apellido paterno de la persona es un parámetro requerido';
if(estaVacio(entidadFederativaNacimiento)) throw 'La entidad federativa de nacimiento de la persona es un parámetro requerido';
if(estaVacio(sexo) || !['M', 'F'].includes(sexo.toUpperCase())) throw 'El sexo de la persona es un parámetro requerido (M o F)';
if(fechaNacimiento == null || !['Date', 'Moment'].includes(fechaNacimiento.constructor.name)) throw 'La fecha de nacimiento de la persona es un parámetro requerido';
// Limpieza de entradas
// Cuando una persona no cuenta con segundo apellido se utliza X en donde se requiere
nombre = quitarPrimerNombre(limpiar(nombre), sexo == 'M' ? excepcionesNombresHombres : excepcionesNombresMujeres);
apellidoPaterno = quitarPreposiciones(limpiar(apellidoPaterno), preposicionesComunes);
apellidoMaterno = estaVacio(apellidoMaterno) ? 'XXXXXXXX' : quitarPreposiciones(limpiar(apellidoMaterno), preposicionesComunes);
entidadFederativaNacimiento = entidadFederativaNacimiento.trim();
sexo = sexo.toUpperCase();
fechaNacimiento = fechaNacimiento.constructor.name == 'Moment' ? fechaNacimiento.toDate() : fechaNacimiento;
nombre = nombre.split(" ").filter(x => !preposicionesComunes.includes(x)).join(" ");
// Variables auxiliares
let curpComputada = new Array(tamanioCurp - 2);
curpComputada.fill('');
Object.seal(curpComputada);
// 1.- Primera letra del primer apellido
curpComputada[0] = apellidoPaterno[0];
// 2.- Primera vocal del primer apellido despues de la primer letra
curpComputada[1] = primeraVocalInterna(apellidoPaterno);
// 3.- Primera letra del segundo apellido
curpComputada[2] = apellidoMaterno[0];
// 4.- Primera letra del nombre de pila. Se tomara en cuenta el segundo nombre si es un nombre compuesto y el primer nombre forma parte de la lista de excepciones
curpComputada[3] = nombre[0];
// 5-10.- Fecha de nacimiento sin espacios en orden de año (dos dígitos), mes y día
const fechaComprimida = (fechaNacimiento.getYear() > 99 ? fechaNacimiento.getYear() -100 : fechaNacimiento.getYear()).toString().padStart(2, '0') + (fechaNacimiento.getMonth() + 1).toString().padStart(2, '0') + fechaNacimiento.getDate().toString().padStart(2, '0');
curpComputada[4] = fechaComprimida[0];
curpComputada[5] = fechaComprimida[1];
curpComputada[6] = fechaComprimida[2];
curpComputada[7] = fechaComprimida[3];
curpComputada[8] = fechaComprimida[4];
curpComputada[9] = fechaComprimida[5];
// 11.- Letra del sexo o género (H para Hombre, o M para Mujer)
curpComputada[10] = sexo == 'M' ? 'H' : 'M';
// 12-13.- Dos letras correspondientes a la entidad federativa de nacimiento (en caso de haber nacido fuera del país, se marca como NE, «Nacido en el Extranjero»
const claveEntidadNacimiento = entidadesFederativas.includes(entidadFederativaNacimiento) ? entidadFederativaNacimiento : entidadExtranjera;
curpComputada[11] = claveEntidadNacimiento[0];
curpComputada[12] = claveEntidadNacimiento[1];
// 13.-Primera consonante interna (después de la primera letra) del primer apellido.
curpComputada[13] = primerConsonanteInterna(apellidoPaterno);
// 14.- Primera consonante interna del segundo apellido.
curpComputada[14] = primerConsonanteInterna(apellidoMaterno);
// 15.- Primera consonante interna del nombre de pila.
// Solo tomar el primer nombre. P/E: El nombre 'Noé Matías' debe retornar X, no M
curpComputada[15] = primerConsonanteInterna(nombre.split(" ")[0]);
// 16.- Diferenciador de homonimia. Dígito del 0 al 9 para fechas de nacimiento hasta el año 1999 y de la A a la Z para fechas de nacimiento a partir del 2000. Primera mitad de la homoclave
// 17.- Dígito verificador, para comprobar la integridad. Segunda mitad de la homoclave.
// Excepciones
let curpComputadaFinal = curpComputada.join('').toUpperCase();
// Se remplaza la segunda letra si las primeras 4 letras forman una palabra altisonante o 'inconveniente'
if(palabrasInconvenientes.includes(curpComputadaFinal.substr(0, 4))) {
curpComputadaFinal = curpComputadaFinal.charAt(0) + 'X' + curpComputadaFinal.substr(2, tamanioCurp - 4);
}
// Primera validación
if(curpComputadaFinal.length != (tamanioCurp - 2)) throw 'Ocurrio un error durante la validación de la CURP';
const supuestaCurp = curp.substring(0, 16).toUpperCase();
const primeraValidacion = curpComputadaFinal === supuestaCurp && curp.length == tamanioCurp;
if(_debug) console.debug('CURP generada: ' + curpComputadaFinal, primeraValidacion);
if(!validarHomoclave || !primeraValidacion) return primeraValidacion;
// Segunda validación
const regexHomonimia = fechaNacimiento.getFullYear() >= 2000 ? /[A-Z]/m : /[0-9]/m;
const identificadorHomonimia = curp.charAt(tamanioCurp - 2);
const segundaValidacion = regexHomonimia.exec(identificadorHomonimia) !== null
if(_debug) console.debug('Identificador de homonimía: ' + identificadorHomonimia, segundaValidacion);
if(!segundaValidacion) return segundaValidacion;
// Tercera validación
const caracteres = '0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ';
const suma = (supuestaCurp + identificadorHomonimia).split('').reduce((acc, el, ix, arr) => (tamanioCurp - ix) * caracteres.indexOf(el) + acc, 0);
const modulo = suma % 10;
const digitoVerificador = modulo != 0 ? 10 - modulo : modulo;
const terceraValidacion = curp.charAt(tamanioCurp - 1) == digitoVerificador;
if(_debug) console.debug('Dígito verificador: ' + digitoVerificador, terceraValidacion);
return terceraValidacion;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment