Algoritmo de validación de RFC
Un RFC es válido si y solo si cumple las siguientes 7 condiciones:
- El RFC debe tener exactamente 12 o 13 carácteres (12 para personas morales y 13 para personas físicas). Es posible que nuestra interfaz de usuario permita introducir guiones, espacios u otros carácteres separadores. Aquí hablo solo de los carácteres que componen el RFC, los separadores se tratarían a otro nivel.
- Los carácteres del nombre pueden solo contener las letras mayúsculas A-Z, el símbolo & y la Ñ. Para RFC de 12 carácteres el nombre son los 3 primeros y para 13 son los 4 primeros.
- El nombre no es inconveniente. Hay una tabla con 41 nombres inconvenientes en el documento, por ejemplo MOCO, un RFC valido no puede tener ese nombre. Todos los nombres inconvenientes son para personas físicas (son todos de cuatro letras) pero al hacer la comprobación no es necesario comprobar que el RFC sea de 13 carácteres. Podemos comprobar contra los cuatro primeros directamente, si diésemos como incorrecto un RFC de persona moral por este motivo el resultado sería válido porque esto solo puede suceder si el primer dígito de la fecha no es una cifra, que sería un RFC incorrecto.
- La fecha es válida. La fecha son los 6 carácteres que siguen al nombre. En formato AAMMDD ( dos últimas cifras del año, mes 01 a 12 y día 01 a 31). No sería válido ni 331403 (3 del mes 14 de algún año acabado en 33). Tampoco sería válido 010229 (29 de febrero de algún año acabado en 01).
- El primer carácter diferenciador de homonimia es válido.Este es el primero que sigue a la fecha. Debe ser una letra mayúscula de la A a la V, ambas inclusive, o una cifra del 1 al 9 ambas inclusive, la Ñ y el 0 no son válidas. Esto es así porque en el apartado PROCEDIMIENTO PARA OBTENER LA CLAVE DIFERENCIADORA DE HOMONIMIA del documento que figura en la pregunta hay un paso en que se divide un número de 3 cifras entre 34 y se consulta una tabla. 999/34 = 29 . En esa tabla el 29 es la letra V
- El segundo carácter diferenciador de homonimia es válido. Este es el que sigue al anterior. Debido al procedimiento que se sigue este puede ser cualquier carácter de la tabla correspondiente, es decir, de la A a la Z y del 1 al 9. La Ñ y el 0 no son válidos. El documento describe cómo se calculan los 2 carácteres diferenciadores de homonimia, pero esto no nos interesa porque solo disponemos del RFC y no del nombre que dio lugar a esos 2 carácteres diferenciadores de homonimia.
- El dígito Verificador debe ser correcto. El dígito verificador es el último. Solo puede ser una cifra del 0 al 9 o la letra A. Y se calcula en función de los otros carácteres del RFC. Este dígito actúa como un código detector de errores que permite detectar la mayoría de errores cometidos por usuarios al introducir un RFC.
Nótese que por válido nos referimos a un RFC que cumple todas las normas del documento, es decir, existe una combinación de nombre y fecha de nacimiento o creación que genera ese RFC. Mientras que para un RFC inválido no hay ningún nombre y fecha que acabe generando ese RFC. Que sea válido no significa que exista.
Cálculo del dígito de verificación.
Si el RFC tiene 12 carácteres añadimos un espacio en blanco al principio.
Usamos todos los carácteres del RFC excepto el último. Es decir, usamos 12 carácteres siempre.
Cada carácter se corresponde con un valor según la siguiente tabla:
El valor del primer carácter se multiplica por 13, el segundo por 12, el tercero por 11 y así sucesivamente hasta el carácter duodécimo que se multiplica por 2.
Todos estos valores se suman. Y con la suma el documento describe unas operaciones innecesariamente complicadas que son equivalentes a :
- A 11000 le restas la suma. Calculas el módulo 11 de la resta.
- Si el resultado del módulo es 10 el dígito verificador es la A. En otro caso es el mismo número que has obtenido.
Ejemplo para el RFC GODE561231GR8
Carácter Valor Multiplicador Resultado
G 16 * 13 = 208
O 25 * 12 = 300
D 13 * 11 = 143
E 14 * 10 = 140
5 5 * 9 = 45
6 6 * 8 = 48
1 1 * 7 = 7
2 2 * 6 = 12
3 3 * 5 = 15
1 1 * 4 = 4
G 16 * 3 = 48
R 28 * 2 = 56
-------------------------------------
SUMA : 1026
( 11000 - 1026 ) % 11 = 8 -> El dígito verificador es 8
El RFC es Válido.
Expresión Regular
Si fuese a utilizar una expresión regular como paso previo a la validación usaría :
[A-ZÑ&]{3,4}\d{6}[A-V1-9][A-Z1-9][0-9A]
Esta expresión acepta fechas incorrectas como 999999. Ni el día 99 ni el mes 99 existen.
No le veo sentido al complicar el regex, para rechazar los días que empiecen por 9 por ejemplo, puesto que de todas formas se van a quedar fechas no comprobadas por el regex (salvo que use un regex muy complicado). Es mejor dejar el análisis léxico para los regex (ver si los carácteres son válidos) y el semántico para otros niveles (ver si la fecha es correcta). Pero esto es opinable.
Usando Python y una biblioteca.
Con frecuencia la mejor manera de implementar algo es no hacerlo y usar una implementación ya hecha si está disponible y es fiable. Salvo para fines educativos o ciertos requisitos muy especiales.
Hay varias librerías que comprueban un RFC. Por ejemplo stdnum de Arthur de Jong and others.
Disponible en https://github.com/arthurdejong/python-stdnum bajo licencia GNU Lesser General Public License versión 2.1 o posterior.
#!/usr/bin/env python
from stdnum.mx.rfc import (validate,InvalidComponent,InvalidFormat,
InvalidLength,InvalidChecksum)
# Devuelve true si el RFC pasado es valido
# rfc es un string
def esValido(rfc):
try:
validate( rfc, validate_check_digits=True)
return True
except (InvalidComponent,InvalidFormat,InvalidLength,InvalidChecksum):
return False
Poco hay que comentar, ya viene todo hecho por la biblioteca.
Usando programación funcional. En Scala
La función que hace la comprobación es la única pública y también se llama esValido
. Al igual que la de Python devuelve true
si el RFC pasado es válido.
El código incluye comentarios que explican su funcionamiento.
object RFCcheck extends App {
private val caracteres = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
// Estos son los carácteres permitidos en las 3 primeras posiciones (personas
// morales, longitud total RFC es 12) o las 4 primeras posiciones (personas
// físicas, longitud total RFC es 3)
private val caracteresPrimeraParte = caracteres + "Ñ&"
// Cifras permitidas en los 6 carácteres de la fecha
private val cifras = "0123456789"
// Carácteres permitidos en el dígito validador
private val caracteresValidador = cifras + "A"
// Valores permitidos para los carácteres primero y segundo de la clave
// diferenciadora de la homonimia
// Estos valores estan en la tabla del anexo 2
// Nótese que el primer carácter se obtiene dividiendo un numero de 3 cifras
// entre 34
// 999/34 = 29 . Por tanto el máximo carácter en ese caso es 'V'
private val caracteresHomonimia1 = "123456789ABCDEFGHIJKLMNOPQRSTUV"
private val caracteresHomonimia2 = caracteresHomonimia1 + "WXYZ"
// Comprobar que la fecha es valida
// Por ejemplo, no permitir el 97 de octubre o el mes 17
// Nótese que se permite el 29 de febrero de los años acabados en 00
//porque el año 2100 no tendrá ese día pero el 2000 sí lo tuvo
private val maxDiaMes = -1::31::28::31::30::31::30::31::31::30::31::30::31::
Nil
private def fechaValida( s:String ):Boolean = {
val anyo = s.substring( 0, 2).toInt
val mes = s.substring( 2, 4).toInt
val dia = s.substring( 4, 6).toInt
if ( mes<1 || mes>12 ) return false
if ( dia<1 ) return false
if ( anyo%4==0 && mes==2 ) return dia<=29
dia<=maxDiaMes(mes)
}
// Carácteres en el orden de la tabla del anexo 3. Su posición es igual al
// valor que se les asigna en la tabla del Anexo 3
private val tablaAnexo3 = "0123456789ABCDEFGHIJKLMN&OPQRSTUVWXYZ Ñ"
// Cálculo del dígito verificador
private def calculaDigitoVerificador( rfc:String ):Char = {
// Usar el rfc sin el último carácter que es el verificador
// Para las personas morales se añade un espacio al principio
val rfcSinDigitoV = if (rfc.length==13 ) rfc.substring(0,12)
else " " +rfc.substring(0,11)
// Para cada carácter multiplicamos su valor por (13 - posicion)
// Y calculamos la suma de todas esas multiplicaciones
val suma = (0 until 12 zip rfcSinDigitoV) .
foldLeft (0) ( (acu,p) => acu + tablaAnexo3.indexOf(p._2) * (13-p._1) )
// Y calculamos el dígito verificador (11000-suma) módulo 11.
// caracteresValidador tiene los carácteres en el orden que
// necesitamos del 0 al 10 (el décimo es la A)
caracteresValidador( (11000-suma) % 11)
}
// Palabras no permitidas según el anexo 4
private val palabrasInconvenientes = "BUEI"::"BUEY"::"CACA"::"CACO"::"CAGA"::
"CAGO"::"CAKA"::"CAKO"::"COGE"::"COJA"::"COJE"::"COJI"::"COJO"::"CULO"::
"FETO"::"GUEY"::"JOTO"::"KACA"::"KACO"::"KAGA"::"KAGO"::"KOGE"::"COJO"::
"KAKA"::"KULO"::"MAME"::"MAMO"::"MEAR"::"MEAS"::"MEON"::"MION"::"MOCO"::
"MULA"::"PEDA"::"PEDO"::"PENE"::"PUTA"::"PUTO"::"QULO"::"RATA"::"RUIN"::
Nil
// Calcula si es válido un rfc
// El RFC debe tener exáctamente 12 carácteres para personas morales
// Y 13 carácteres para personas físicas
// No se admiten separadores como espacios o guiones
def esValido( rfc:String ):Boolean = {
val nombre = rfc.substring(0, rfc.length - 9) // Primeros 3 o 4 carácteres
val fecha = rfc.substring(rfc.length - 9, rfc.length - 3) // 6 carácteres
val homonimia1 = rfc(rfc.length-3) // 1º char de clave dif de homonimia
val homonimia2 = rfc(rfc.length-2) // 2º char de clave dif de homonimia
val digitoVerificador = rfc(rfc.length-1) // Último carácter
// Un RFC es válido si se cumple todo lo siguiente:
(rfc.length == 12 || rfc.length == 13) && // 12 o 13 de longitud
// Solo carácteres permitidos en nombre
nombre.foldLeft(true)(_ && caracteresPrimeraParte.contains(_)) &&
// Solo carácteres permitidos en fecha
fecha. foldLeft(true)(_ && cifras.contains(_)) &&
// Solo carácteres permitidos en clave diferenciadora de homonimia
caracteresHomonimia1.contains(homonimia1) &&
caracteresHomonimia2.contains(homonimia2) &&
// La fecha es valida
fechaValida(fecha) &&
// Nombre no es una palabra inconveniente
!palabrasInconvenientes.contains(nombre) &&
// El digito verificador es correcto
digitoVerificador == calculaDigitoVerificador(rfc)
}
}
Observaciones
En el documento Algoritmo para generar el RFC con homoclave para personas fisicas y morales.odt en la regla 9ª de la sección 2.2 figuran 39 palabras inconvenientes que no se pueden usar en un RFC. Sin embargo en el Anexo 4 figuran 41 palabras. Las 2 palabras extra son CAKO y MEAS. Yo he supuesto que el anexo es el correcto.
He probado con un millón de RFCs aleatorios, correctos e incorrectos, y las dos implementaciones han dado el mismo resultado en todos los casos. Aunque esto tampoco es garantía de nada.
No doy como buenos XEXX010101000 ni XAXX010101000 porque en el documento no se mencionan. La biblioteca de python hace lo mismo. Caso de que fuese necesario darlos como buenos es trivial hacer la modificación.