Si construyes formularios, onboarding o sistemas de nómina para usuarios en México, tarde o temprano te topas con la CURP. Y la primera reacción de muchos devs es la misma: "le pongo un regex de 18 caracteres alfanuméricos y listo".
El problema: un regex solo verifica la forma. Una CURP con un error de dedo (una O en lugar de un 0, dos letras intercambiadas) pasa un regex sin problema — y termina en un trámite rechazado. La CURP tiene un mecanismo de autoverificación incorporado: el dígito verificador. Si lo implementas, tu validador detecta la mayoría de errores de captura sin consultar ninguna base de datos.
La estructura: cada posición significa algo
La CURP tiene 18 caracteres, en este orden:
1–4: iniciales — inicial y primera vocal interna del primer apellido, inicial del segundo apellido, inicial del nombre.
5–10: fecha de nacimiento en formato AAMMDD.
11: sexo — H o M (RENAPO también contempla X para personas no binarias).
12–13: clave de la entidad de nacimiento (32 estados; NE = nacido en el extranjero).
14–16: primeras consonantes internas (no iniciales) del primer apellido, segundo apellido y nombre.
17: diferenciador de homonimia — un dígito (0–9) para nacidos antes del 2000, una letra para nacidos a partir del 2000.
18: dígito verificador.
El dígito verificador: suma ponderada
Aquí está la parte que hace que la validación valga la pena. Para los primeros 17 caracteres:
Convierte cada carácter a un número: 0–9 valen 0–9, A–N valen 10–23, Ñ vale 24, O–Z valen 25–36.
Multiplica cada valor por un peso descendente: el primer carácter por 18, el segundo por 17, …, el decimoséptimo por 2.
Suma todo. El dígito verificador es (10 − (suma mod 10)) mod 10.
En JavaScript:
// Mapa de valores: 0–9 → 0–9, A–N → 10–23, Ñ → 24, O–Z → 25–36
const VALOR = { '0': 0, /* ... */ 'A': 10, 'B': 11, /* ... */ 'Ñ': 24, 'O': 25, /* ... */ 'Z': 36 };
function digitoVerificador(primeros17) {
let suma = 0;
for (let i = 0; i < 17; i++) {
suma += VALOR[primeros17[i]] * (18 - i); // pesos 18, 17, …, 2
}
return (10 - (suma % 10)) % 10;
}
// La CURP es válida si:
digitoVerificador(curp.slice(0, 17)) === Number(curp[17]);
Cambia un solo carácter y el dígito deja de coincidir. Por eso este chequeo detecta casi cualquier error de captura.
Los casos borde que rompen los validadores ingenuos
Nombres con María o José: si el nombre empieza con María o José, se usa el siguiente nombre para la inicial. Un validador que no contempla esto rechazará CURP válidas.
La Ñ: tiene su propio valor (24) en la tabla. Si tu mapa la ignora o la trata como N, el dígito sale mal.
Sin segundo apellido: la posición 3 se llena con X. Lo mismo con las consonantes internas: si no existen, va X.
Nacidos en el extranjero: la entidad es NE.
El diferenciador (posición 17): un validador que exige dígito ahí rechazará a todos los nacidos a partir del 2000, cuya posición 17 es una letra.
Fecha imposible: 31 de febrero, mes 13, etc. Valida que la fecha exista de verdad.
Lo que la validación NO demuestra
Que la estructura sea válida no significa que la CURP esté registrada en RENAPO ni que pertenezca a quien la presenta. La validación de formato detecta errores de captura, no identidades falsas. Para confirmar el registro hay que consultar el portal oficial gob.mx/curp.
Si no quieres implementarlo
Si solo necesitas verificar una CURP sin escribir código, hay un Validador de CURP que aplica todas estas reglas en el navegador: revisa las 18 posiciones, calcula el dígito verificador, te dice exactamente qué posición falla y te muestra los datos que contiene la clave (fecha, sexo, estado, edad). Y como todo corre del lado del cliente, la CURP nunca se envía ni se guarda.
Top comments (0)