36

Pregunta: ¿Cómo puedo verificar que el formato de una CURP mexicana es válido?

¿Qué es la CURP? La Clave Única de Registro de Población (CURP) es un código alfanumérico único de identidad de 18 caracteres utilizado para identificar oficialmente tanto a residentes como a ciudadanos mexicanos, expedido por el RENAPO.

La misma, se forma a partir de las letras de los nombres y apellidos, la fecha y la entidad federativa de nacimiento, y el sexo. Además, el caracter 17 es para evitar duplicados, y el último caracter es un dígito que se utiliza como detector-corrector de errores. La sintaxis está detallada en el Instructivo Normativo para la Asignación de la Clave Única de Registro de Población .

Contexto: Sólo me interesa validar que una clave podría ser válida. No me interesa en este punto ver si esa CURP existe o no.

Hasta ahora, sólo estoy verificando que sean 18 caracteres alfanuméricos con el regex:

/^[A-Z\d]{18}$/

pero me interesa también considerar que los caracteres sean válidos, y que el dígito verificador coincida.

Mariano
  • 23,777
  • 20
  • 70
  • 102
  • Muy buena Mariano, solo una duda, para que usas el tag pcre? – jasilva Nov 01 '16 at 17:25
  • Gracias @jasilva. Todas las preguntas de [regex] [tienen que especificar el lenguaje/dialecto utilizado](http://es.stackoverflow.com/tags/regex/info). Utilicé [PCRE](http://www.pcre.org/) ya que es uno de los más populares y menos restrictivos. – Mariano Nov 01 '16 at 17:29
  • Relacionada: [Cómo validar un RFC de México y su digito verificador](http://es.stackoverflow.com/q/31713/127) – Mariano Nov 04 '16 at 08:35
  • Relacionada: [Cómo validar un Número de Seguridad Social (NSS) de México](http://es.stackoverflow.com/q/32023/127) – Mariano Nov 05 '16 at 13:20

9 Answers9

44

Expresión regular

La siguiente expresión regular verifica:

  • Las posiciones donde se esperan letras, vocales y consonantes de los nombres y apellidos.
  • Fecha válida (aunque para simplificarlo, no se están validando meses con menos de 31 días).
  • Listado válido de entidades federativas.
  • Y genera referencias para separar los primeros 17 dígitos (grupo 1) del último dígito (grupo 2).
/^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/


Validación completa

Publico el código en JavaScript para poder correrlo acá, pero estoy seguro de que es muy sencillo de llevar a cualquier otro lenguaje.

//Función para validar una CURP
function curpValida(curp) {
    var re = /^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/,
        validado = curp.match(re);
 
    if (!validado)  //Coincide con el formato general?
     return false;
    
    //Validar que coincida el dígito verificador
    function digitoVerificador(curp17) {
        //Fuente https://consultas.curp.gob.mx/CurpSP/
        var diccionario  = "0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ",
            lngSuma      = 0.0,
            lngDigito    = 0.0;
        for(var i=0; i<17; i++)
            lngSuma = lngSuma + diccionario.indexOf(curp17.charAt(i)) * (18 - i);
        lngDigito = 10 - lngSuma % 10;
        if (lngDigito == 10) return 0;
        return lngDigito;
    }
  
    if (validado[2] != digitoVerificador(validado[1])) 
     return false;
        
    return true; //Validado
}


//Handler para el evento cuando cambia el input
//Lleva la CURP a mayúsculas para validarlo
function validarInput(input) {
    var curp = input.value.toUpperCase(),
        resultado = document.getElementById("resultado"),
        valido = "No válido";
        
    if (curpValida(curp)) { // ⬅️ Acá se comprueba
     valido = "Válido";
        resultado.classList.add("ok");
    } else {
     resultado.classList.remove("ok");
    }
        
    resultado.innerText = "CURP: " + curp + "\nFormato: " + valido;
}
#resultado {
    background-color: red;
    color: white;
    font-weight: bold;
}
#resultado.ok {
    background-color: green;
}
<label>CURP:
    <input type="text" id="curp_input" oninput="validarInput(this)" style="width:100%;" placeholder="Ingrese su CURP">
</label>
<pre id="resultado"></pre>


Descripción

Tomando en cuenta la estructura con la que se forma una CURP:

Descripción de cómo se forma cada caracter

  • El primer caracter es la inicial del primer apellido [A-Z]
    (si fuese otra letra, se usa una X).
  • Seguido por la primera vocal interna del apellido [AEIOUX]
    (o una X si no tuviese).
  • Y las iniciales del segundo apellido y del nombre [A-Z]{2}
  • La fecha de nacimiento.
    • \d{2} año.
    • (?:0[1-9]|1[0-2]) mes.
    • (?:0[1-9]|[12]\d|3[01]) día.
  • El sexo (Hombre o Mujer) [HM]
  • La entidad federativa en la que nació (listadas en el pdf)
    (?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)
  • Las primeras consonantes internas de apellidos y nombre [B-DF-HJ-NP-TV-Z]{3}
  • La homoclave (homonimia y siglo) [A-Z\d]
  • El dígito verificador (capturado en el grupo 2) (\d)


Luego de ver si coincide con el regex, comprobamos que el dígito verificador sea válido. Es decir, si coincide con el calculado de los primeros 17 caracteres:

if (validado[2] != digitoVerificador(validado[1]))

Para calcular el digito, primero se suma el valor de cada uno de los 17 caracteres, los cuales tienen un valor de 0 a 36 según este orden (diccionario):

0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ
for(var i=0; i<17; i++)
    lngSuma = lngSuma + diccionario.indexOf(curp17.charAt(i)) * (18 - i);

Y se toma el complemento a 10 del último dígito de esta suma (o 0 si da 10).

lngDigito = 10 - lngSuma % 10;
if (lngDigito == 10) return 0;
return lngDigito;
Mariano
  • 23,777
  • 20
  • 70
  • 102
  • 2
    Buena pregunta y respuesta. Sólo una nota: la expresión regular que pones admite fechas incorrectas (aparte de la nota con meses de menos de 31 días) porque acepta el 00 como mes y como día válido cuando no lo son. Aunque comprendo que eso haría la expresión regular algo más complicada – Alvaro Montoro Nov 01 '16 at 13:31
  • 2
    @Alvaro Es cierto, había querido ser laxo en esa validación. Agregué la validación para que no acepte 00 (no hace mucho más complicado al regex). Sin embargo, si se quisiera validar correctamente la fecha y los meses con menos de 31 días, recomendaría hacerlo con las funciones del lenguaje utilizado (aunque también podría hacerse con regex). Cabe aclarar que, en estos casos, un error de ingreso en la fecha estaría siendo validado por el dígito verificador. – Mariano Nov 01 '16 at 17:18
  • Hace falta un or | para el día en la fecha. Esta es la correcta: /^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/ – user2930137 May 17 '18 at 23:10
  • Muchas gracias @user2930137. Estaba mal en el texto de descripción. Corregido. En el código estaba bien. – Mariano May 17 '18 at 23:17
  • Hola. Tu expresión regular es casi correcta, pero fallaría en apellidos como Íñigo, ya que va acentuada la primera letra, así mismo, no se concidera la Ñ. Yo me basé en su RegEx pero le agregé acentos y ñ's y quedó asi: `/^([A-ZÑ][AEIOUXÁÉÍÓÚ][A-ZÑ]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/` – OfficeYA Apr 04 '22 at 23:54
4

Un poco tarde mi respuesta, pero hace tiempo hice este método para validar los RFC, tanto para personas físicas como personas morales. Los regex los saqué del sitio oficial del SAT para validar RFC. Espero a alguien le sirva. La función está hecha en JS

function validateRFC(rfc) {

        var patternPM = "^(([A-ZÑ&]{3})([0-9]{2})([0][13578]|[1][02])(([0][1-9]|[12][\\d])|[3][01])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{3})([0-9]{2})([0][13456789]|[1][012])(([0][1-9]|[12][\\d])|[3][0])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{3})([02468][048]|[13579][26])[0][2]([0][1-9]|[12][\\d])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{3})([0-9]{2})[0][2]([0][1-9]|[1][0-9]|[2][0-8])([A-Z0-9]{3}))$";
        var patternPF = "^(([A-ZÑ&]{4})([0-9]{2})([0][13578]|[1][02])(([0][1-9]|[12][\\d])|[3][01])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{4})([0-9]{2})([0][13456789]|[1][012])(([0][1-9]|[12][\\d])|[3][0])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{4})([02468][048]|[13579][26])[0][2]([0][1-9]|[12][\\d])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{4})([0-9]{2})[0][2]([0][1-9]|[1][0-9]|[2][0-8])([A-Z0-9]{3}))$";

        if (rfc.match(patternPM) || rfc.match(patternPF)) {
            return true;
        } else {
            alert("La estructura de la clave de RFC es incorrecta.");
            return false;
        }
    }
ArCiGo
  • 59
  • 4
2

Muchas gracias. Esto me ayudó a crear un validador para PHP, dejo el código por si a alguien le sirve.

    function is_curp( $string = '' ){
// By @JorhelR
// TRANSFORMARMOS EN STRING EN MAYÚSCULAS RESPETANDO LAS Ñ PARA EVITAR ERRORES
        $string = mb_strtoupper($string, "UTF-8");
// EL REGEX POR @MARIANO
        $pattern = "/^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/";
        $validate = preg_match($pattern, $string, $match);
        if( $validate === false ){
// SI EL STRING NO CUMPLE CON EL PATRÓN REQUERIDO RETORNA FALSE
            return false;
        }
// ASIGNAMOS VALOR DE 0 A 36 DIVIDIENDO EL STRING EN UN ARRAY
        $ind = preg_split( '//u', '0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ', null, PREG_SPLIT_NO_EMPTY );
// REVERTIMOS EL CURP Y LE COLOCAMOS UN DÍGITO EXTRA PARA QUE EL VALOR DEL PRIMER CARACTER SEA 0 Y EL DEL PRIMER DIGITO DE LA CURP (INVERSA) SEA 1
        $vals = str_split( strrev( $match[0]."?" ) );
// ELIMINAMOS EL CARACTER ADICIONAL Y EL PRIMER DIGITO DE LA CURP (INVERSA)
        unset( $vals[0] );
        unset( $vals[1] );
        $tempSum = 0;
        foreach( $vals as $v => $d ){
// SE BUSCA EL DÍGITO DE LA CURP EN EL INDICE DE LETRAS Y SU CLAVE(VALOR) SE MULTIPLICA POR LA CLAVE(VALOR) DEL DÍGITO. TODO ESTO SE SUMA EN $tempSum
            $tempSum = (array_search( $d, $ind ) * $v)+$tempSum;
        }
// ESTO ES DE @MARIANO NO SUPE QUE ERA
        $digit = 10 - $tempSum % 10;
// SI EL DIGITO CALCULADO ES 10 ENTONCES SE REASIGNA A 0
        $digit = $digit == 10 ? 0 : $digit;
// SI EL DIGITO COINCIDE CON EL ÚLTIMO DÍGITO DE LA CURP RETORNA TRUE, DE LO CONTRARIO FALSE
        return $match[2] == $digit;
    }
Jorhel Reyes
  • 173
  • 8
  • No se mucho de regexp, de hecho, siempre las saco de stackoverflow, asi que no se si estas validando que un curp debe tener 18 caracteres, yo hice test con el curp: 'MCCTEST' y me da true, esta mal, por lo que solo agregue un trim al inicio de la function y un if strlen != 18, saludos – Martin Cisneros Capistrán Feb 22 '21 at 18:43
  • Hay otro detalle, hay que validar si $match contiene resultado, asi quedo al final: https://paiza.io/projects/3NjCP4LT8T3OUq97x5M8Fg – Martin Cisneros Capistrán Feb 22 '21 at 19:11
1

Una API REST para validar CURP y obtener información básica de la persona. La API es Gratis (peticiones ilimitadas), Rápida (latencia de 200 ms en promedio) y Estandarizada de acuerdo a ISO. Más información usando Postman

Ejemplo de petición:

GET https://domain.com/api/curp/CAHF620818HMNLNL09?apiKey=bpT32rai

Ejemplo de respuesta exitosa:

{
    "curp": "CAHF620818HMNLNL09",
    "fatherName": "CALDERON",
    "motherName": "HINOJOSA",
    "name": "FELIPE DE JESUS",
    "gender": "1",
    "birthday": "1962-01-01T00:08:00.000Z",
    "birthState": "MX-MIC"
}

Documentación

1

Aquí les dejo una forma de ejecutarlo en PHP para que creen una función que les devuelve true or false asado completamente en el algoritmo que dejo @Mariano

function validarCURP($cadena) {
    $curp = mb_strtoupper($cadena, "UTF-8");
    $pattern = "/^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/";
    $validacionRegex = preg_match($pattern, $curp);
    if ($validacionRegex === 0) {
        #No cumple con un formato válido
        return false;
    }
    #De aquí en adelante se verifica el dígito verificador
    $diccionario = "0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ";
    $lngSuma = 0.0;
    $lngDigito = 0.0;
    $curp17 = substr($curp, 0, 17);
    for ($i = 0; $i < 17; $i++) {
        //lngSuma = lngSuma + diccionario.indexOf(curp17.charAt(i)) * (18 - i);
        //lngDigito = 10 - lngSuma % 10;
        $lngSuma = $lngSuma + mb_strpos($diccionario, mb_substr($curp17, $i, 1)) * (18 - $i);
    }
    $lngDigito = 10 - ($lngSuma % 10);
    if ($lngDigito == 10) {
        $digitoVerificador = 0;
    }
    $digitoVerificador = $lngDigito;
    return $digitoVerificador == $curp[17];
}

Es muy importante que utilicen las funcion mb_ de los strings ya que me dio un dolor de cabeza ya que no recordaba que la Ñ toma dos caracteres internamete, las funciones de strings que no usan mb_ al inicio utilizan posiciones por byte, no por caracter

Noé
  • 78
  • 6
0

Puedes validar una CURP y la información de la persona usando este API https://rapidapi.com/acrogenesis/api/curp-renapo.

Por ejemplo para usarla en php sería así:

<?php

$client = new http\Client;
$request = new http\Client\Request;

$request->setRequestUrl('https://curp-renapo.p.rapidapi.com/v1/curp/CURP_DE_TU_USUARIO');
$request->setRequestMethod('GET');

$request->setHeaders(array(
    'x-rapidapi-host' => 'curp-renapo.p.rapidapi.com',
    'x-rapidapi-key' => 'api-key'
));

$client->enqueue($request)->send();
$response = $client->getResponse();

echo $response->getBody();

Y te regresaría la información de esta forma:

{
  "curp":"CURP_DE_TU_USUARIO"
  "renapo_valid":true
  "paternal_surname":"CALDERON"
  "mothers_maiden_name":"HINOJOSA"
  "names":"FELIPE DE JESUS"
  "sex":"HOMBRE"
  "birthdate":"18/08/1962"
  "nationality":"MEXICO"
  "entity_birth":"MN"
  "probation_document":"ACTA DE NACIMIENTO"
  "probation_document_data":{
    "Entidad":"16"
    "Municipio":"053"
    "Año":"1963"
    "Número de acta":"00145"
  }
  "rfc":"RFC_DE_TU_USUARIO"
  "sat_valid":true
}
acrogenesis
  • 128
  • 3
  • @Aprendiz es un ex presidente de México y todo la anterior es información pública.. – acrogenesis Dec 13 '19 at 03:25
  • 1
    Me parece que una validación con un API es bastante más valiosa que una validación con regex. Esta validación te dice si realmente existe o no dicho CURP y los datos reales de ese CURP – acrogenesis Dec 13 '19 at 03:28
  • @Aprendiz Ya censure el CURP y RFC y di un ejemplo de como usar el API – acrogenesis Dec 13 '19 at 03:36
0

Puedes usar la libreria curp que está en NPM

npm install --save curp

Si no usas NPM agregarlo con bundle así.

<script src="https://bundle.run/curp"></script>

Ejemplo funcional:

var validarCurp = document.getElementById("validarCurp");

var validarBoton = document.getElementById("validarBoton");

validarBoton.addEventListener("click", function(evt) {
    evt.preventDefault();
    if(curp.validar(validarCurp.value)){
        alert('Es valido ')
    }else{
      alert('No es valido')
    }
});
<script src="https://bundle.run/curp"></script>
<h2 id="valida">Validar CURP</h2>

<p><strong>CURP:</strong>
<input id="validarCurp" type="text" /></p>

<p><input id="validarBoton" type="button" value="Validar" /></p>

Una alternativa más, sin utilizar javascript, validando solo el formato con la propiedad pattern, tiene el detalle que no podemos revisar el dígito verificador.

<form>
 <label for="curp">CURP:</label>
 <input id="curp" 
  type="text"
  required="true" 
  placeholder="Ingrese su CURP" 
  pattern="([A-Z]{4}([0-9]{2})(0[1-9]|1[0-2])(0[1-9]|1[0-9]|2[0-9]|3[0-1])[HM](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|NE)[A-Z]{3}[0-9A-Z]\d)"
/>
  <button>Guardar</button>
</form >
Israel Perales
  • 291
  • 2
  • 8
0

En python puedes utilizar la librería CURPSuite, que realiza validación del formato de la CURP.

Para instalarla:

python3 -m pip install curpsuite

Ejemplo:

from curp import CURP, CURPValueError
try:
  c = CURP("SABC560626MDFLRN01")
except CURPValueError as e:
  # El formato de la CURP es erróneo
  print(e)

Después de crear un objeto CURP, también puedes obtener sus propiedades si así se desea.

>>> c.fecha_nacimiento
datetime.date(1956, 6, 26)

Documentación.

Disclaimer: Soy el desarrollador de CURPSuite.

-1

Según un documento emitido por el SAT, mencionan en la página 3 el siguiente patrón de REGEX, el cual no válida el contenido, sino sólo el formato de la cadena de texto:

[A-Z][A,E,I,O,U,X][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][M,H][AZ]{2}[B,C,D,F,G,H,J,K,L,M,N,Ñ,P,Q,R,S,T,V,W,X,Y,Z]{3}[0-9,AZ][0-9]

No obstante, es incorrecto en la penúltima parte [0-9,AZ] , la cual debería estar así [0-9,A-Z]

A nivel código (Java), podría quedar algo así su validación en una clase de Utilería:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Utils {

    public final static String REGEX_CURP = 
        "[A-Z][A,E,I,O,U,X][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][M,H][A-Z]{2}[B,C,D,F,G,H,J,K,L,M,N,Ñ,P,Q,R,S,T,V,W,X,Y,Z]{3}[0-9,A-Z][0-9]";

    public static boolean validarCurp(String textoCurp){
        boolean curpValido = false;

        Pattern pattern = Pattern.compile(REGEX_CURP;
        Matcher matcher = pattern.matcher(textoCurp);
        curpValido = matcher.find();

        matcher = null;
        pattern = null;

        return curpValido;
    }
}
Nekio
  • 32
  • 5
  • 1
    No es lo único incorrecto. Todas las comas están mal y deberías eliminarlas. Además, intenta validar las fechas pero acepta días hasta 39 y meses hasta 19, y no verifica que sea una entidad federativa válida. Personalmente tomaría cualquiera de las otras expresiones publicadas antes que la emitida por el SAT. – Mariano Oct 13 '17 at 19:14
  • Este código da como CURP válida a `INVÁLIDA,EI000000,AA,,,,0!!!!!!` – Pollo Nov 08 '20 at 04:15