Macro SAS: Validar teléfonos (España)

Esta macro que incluyo a continuación permite validar número de teléfonos de España, aunque no se encuentre normalizados, esto es, contengan el prefijo internacional o no, o estén separados por algún tipo de separador o de espacio. Al final se ofrecen los números de teléfonos normalizados y sin prefijo.

Esta macro solo hace una verificación del formato del número del teléfono, pero no sabe distinguir entre números reales o no. Para hacer este tipo de verificaciones tenemos servicios que dan información vía API (protocolo de comunicación entre sistemas) devolviendo un fichero JSON sobre el tipo de teléfono, operador, país, etc. Espero poder poner un ejemplo sobre cómo integrar un servicio de terceros como este en un futuro (Hazme un comentario si estás interesado en ello).

%macro telefono(tabla=,campo=);
data &tabla;
set &tabla;
&campo = compress(&campo,' ','P');
if substr(&campo,1,4) = '0034' then &campo = substr(&campo,5);
if substr(&campo,1,2) = '34' then &campo = substr(&campo,3);
if substr(&campo,1,1) not in ('6','7','8','9') or
substr(&campo,1,2) in ('80','90') or
length(&campo) ne 9 then ind_valido = 0;
else ind_valido = 1;
run;
%mend;

Puedes probar con estos ejemplos la macro:

data clientes;
    format telefono $20.;
    input telefono;
    datalines;
    9851246323
    981122044
    +34666777888
    902123123
    0034-616875794
    +0034 615 789 456
    658.452.187
    001 32157979
    34715845978
    888123456
    (95)6543232
    ;
run;*/

%telefono(tabla=clientes,campo=telefono);

Macro SAS: Algoritmo para validar el código de cuenta bancaria (IBAN)

Aquí tenemos una macro que sirve para validar el código de cuenta bancario, IBAN, que rige en muchos países de Europa, Caribe y Medio Oriente. Se le pasan como parámetros el nombre de la tabla y el nombre del campo que contiene el IBAN en formato electrónico (todos seguido, sin espacios) y devuelve un indicador de si es válido o no y el nomobre de la entidad bancaria a la que pertenece.

El código IBAN tiene tres partes, los dos primeros dígitos corresponden al país, los dos segundos son el código de verificación que se calculan en función de todos los demás y que validan que el código se ha escrito bien; y una última parte que es variable. Cada país define la estructura de esta tercera parte que es un código alfanumérico de hasta 30 caracteres, ya que el código IBAN tiene un máximo de 34 caracteres totales.

En el caso de España, esta tercera parte del IBAN corresponde a 4 dígitos para el código de la entidad bancaria tal y como se lo asigna el Banco de España, otros 4 dígitos para el código de la oficina bancaria, los dos siguientes corresponden al código de verificación del antiguo CCC (el antiguo número de cuenta que teníamos en España), y finalmente 10 caracteres para el número de cuenta bancaria. El total de caracteres para España son 24.

Esta macro valida el código del país, que tiene que estar incluido en la lista de países adjunta a este post, el código de oficina bancaria para los IBAN españoles (también según listado adjunto). Será necesario crear las tabla SAS PAISES y BANCOS respectivamente para que la macro funcione plenamente. Lamentablemente, solo valida códigos IBAN españoles.

%macro normalizar_ccc(tabla=,campo=);
    /* Identificar país */
    data norm1;
        set &tabla;
        &campo = upcase(&campo);
        pais = substr(&campo,1,2);
        verificador = substr(&campo,3,2);
        resto = substr(&campo,5);
    run;

    /* Verificando pais y longitud */
    proc sql;
        create table norm2 as
        select a.*,
               longitud,
               case when length(&campo) ne longitud then 0
                    when b.codigo_pais = '' then 0
                    else 1 end as ind_valido
        from norm1 a
        left join IBAN b
        on a.pais = b.codigo_pais and a.pais = 'ES';
    quit;

    /* Verificar banco */
    proc sql;
        create table norm3 as
        select a.*,
               b.banco
        from norm2 a
        left join BANCOS b
        on substr(&campo,5,4) = input(put(b.codigo_banco,z4.),$4.);
    quit;

    data norm4;
        set norm3;
        if banco = '' then ind_valido = 0;
    run;

    /* validar codigo control */
    data norm5 (drop=calculo: modulo);
        set norm4;
        format calculo1 calculo2 calculo3 32.;
        if ind_valido = 1 and longitud = 24 then do;
            calculo1 = 0;
            calculo2 = 0;
            %do x = 5 %to 17;
                calculo1 = calculo1 * 10;
                if anyalpha(substr(&campo,&x,1)) = 1 then do;
                    calculo1 = calculo1 * 10;
                    calculo1 = calculo1 + rank(substr(&campo,&x,1))-55;
                end;
                else do;
                    calculo1 = calculo1 + put(substr(&campo,&x,1),8.);
                end;
            %end;
            modulo = mod(calculo1,97);
            %do x = 18 %to 24;
                calculo2 = calculo2 * 10;
                if anyalpha(substr(&campo,&x,1)) = 1 then do;
                    calculo2 = calculo2 * 10;
                    calculo2 = calculo2 + rank(substr(&campo,&x,1))-55;
                end;
                else do;
                    calculo2 = calculo2 + put(substr(&campo,&x,1),8.);
                end;
            %end;
            %do x = 1 %to 4;
                calculo2 = calculo2 * 10;
                if anyalpha(substr(&campo,&x,1)) = 1 then do;
                    calculo2 = calculo2 * 10;
                    calculo2 = calculo2 + rank(substr(&campo,&x,1))-55;
                end;
                else do;
                    calculo2 = calculo2 + put(substr(&campo,&x,1),8.);
                end;
            %end;
            calculo3 = (modulo * (10**ceil(log10(round(calculo2))))) + calculo2;
            modulo = mod(calculo3,97);
            if modulo ne 1 then do;
                ind_valido = 0;
                banco = "";
            end;
        end;
    run;

    /* salida */
    proc sql;
        create table &tabla._ as
        select a.*, ind_valido, banco
        from &tabla a
        left join norm5 b
        on a.&campo = b.&campo;
    quit;
%mend normalizar_ccc;

Descárgate este Excel que contiene los datos que debes incluir en la tabla PAISES y BANCOS:

El algoritmo para validar el IBAN partiendo del número de cuenta, consiste en lo siguiente:
1) Colocer los cuatro primeros caracteres detrás de todos los demás.
2) Sustituir todos los caracteres alfanuméricos que pueda haber por un equivalente numérico de 2 cifras de forma que A=10, B=11, C=12, etc.
3) Dividir la cifre restante entre 97 y tomar el resto de esa división.
4) Si el resto es un 1, entonces el IBAN es correcto.

Para validar la macro con algunos ejemplos de prueba, puedes utilizar lo siguiente:

data CUENTAS;
    format ccc $50.;
    length ccc $ 50;
    input ccc $;
    datalines;
ES6621000418401234567891
ES6000491500051234567892
ES9420805801101234567891
ES90002469125012345678910
ES7100302053091234567895
EL71003020530943574567895
ES1000492352082414205416
ES1720852066623456789011
ES4501986746463463463547
ES450198674646D463463547
ES450198674646&463463547
ES450198674646.463463547
ES1346657676431313200000
run;

%normalizar_ccc(tabla=CUENTAS,campo=ccc);

Macro SAS: Validar DNI de Perú

Hoy voy a incluir una macro que sirve para validar con SAS un número de DNI peruano verificando si el dígito verificador es correcto o no.
Hay que empezar diciendo que inicialmente, y hasta 2007, los DNIs sin fecha de caducidad tenían un dígito verificador que correspondía con una de estas letras: A, B, C, D, E, F, G, H, I, J o K. En el resto de casos se asigna un número de 0 a 9 que sirve para verificar el resto de dígitos del DNI a través de una serie de operaciones matemáticas.
Para trabajar con algunos ejemplos alimentaremos la macro con la siguiente tabla con DNIs a evaluar:

/* Ejemplos*/;
data DNIS;
    format DNI $10.;
    input DNI;
    datalines;
    67415321-0
    1657351-A
    31874-1
    671354134
run;

La macro %validarDNI añade dos campos a la tabla que se le pase por parámetro: dni_normal y ind_valido. dni_normal es el valor del campo que contenía el DNI originalmente, pero normalizado. ind_valido toma dos valores posibles: 1 que indica que el DNI es correcto y 0 que indica que es erróneo.
%validarDNI acepta además dos parámetros obligatorios: el nombre de la tabla y el nombre del campo DNI a validar dentro de ella.

/*Macro*/;
%macro validarDNI(tabla=, campo=);
    data DNI1 (drop=a valor resto codigo:);
        set &tabla;
        rename &campo=dni_original;
        dni_normal = upcase(compress(&campo,'-_. '));
        a = 9 - length(dni_normal);
        if a > 0 then dni_normal = compress(repeat('0',a-1) || dni_normal);
        ind_valido = 1;
        if length(dni_normal) > 9 then ind_valido = 0;
        %do i = 1 %to 8;
            if 0 > put(substr(dni_normal,&i,1),8.) or put(substr(dni_normal,&i,1),8.) > 9 then ind_valido = 0;
        %end;
        if substr(dni_normal,9,1) not in ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K') then ind_valido = 0;
        if ind_valido > 0 then do;
            valor = 3*put(substr(dni_normal,1,1),8.) +
                    2*put(substr(dni_normal,2,1),8.) +
                    7*put(substr(dni_normal,3,1),8.) +
                    6*put(substr(dni_normal,4,1),8.) +
                    5*put(substr(dni_normal,5,1),8.) +
                    4*put(substr(dni_normal,6,1),8.) +
                    3*put(substr(dni_normal,7,1),8.) +
                    2*put(substr(dni_normal,8,1),8.);
            resto = mod(valor,11);
            if resto = 0 then resto = 11;
            resto = resto + 1;
            codigo1 = substr('67890112345',resto,1);
            codigo2 = substr('KABCDEFGHIJ',resto,1);
            if substr(dni_normal,9) ne codigo1 and substr(dni_normal,9) ne codigo2 then ind_valido = 0;
        end;
    run;

    data &tabla;
        set DNI1;
        rename dni_original = &campo;
    run;
%mend;
%validarDNI(tabla=DNIS, campo=DNI);

El algoritmo para calcular el dígito verificador del DNI es el siguiente: se multiplica cada número del DNI normalizado (sus 8 primeros dígitos) por el dígito que ocupe la misma posición en la cadena: 1, 7, 8, 0, 1, 1, 4, 6 y luego se suman todos los factores para dar una cifra de la que calcularemos el resto con resto a dividirla entre 11 (aquí, si el resto es 0 tomaremos 11).
Restamos 11 menos la resultante de la operación anterior. Le sumaremos 1 y entonces tomaremos ese valor para buscar el dígito correspondiente a esa posición en la cadena: 6, 7, 8, 9, 0, 1, 1, 2, 3, 4, 5. Este último es el dígito de verificación.

Macro SAS: Validar y normalizar el DNI

Comparto con vosotros esta macro que realiza una validación del campo DNI de una tabla. Admite como parámetros el nombre de la tabla (DNIS) y el nombre del campo con el DNI (DNI). Como salida genera otra tabla que se llama DNIS_ (añade un subrallado al final) y le añade el campo dni_norm. La macro normaliza DNIs y NIEs españoles.

En el primer paso se verifica el formato del DNI y se rechazan los que tengan un formato que no sea compatible. Estos se marcan con el indicador ind_valido. Para aquellos que cumplen con el formato básico de los DNIs, trocea ese DNI en prefix, number y sufix, De forma que se validan por separado.

En el segundo paso se normaliza la parte numérica del DNI dándole tantos dígitos como vaya a necesitar añadiendo ceros por la izquierda. En el tercer paso se calcula la letra del DNI/NIE y se juntan todos los trozos para tener el DNI normalizado. En un último paso se realiza el cruce que la tabla inicial de DNIs.

%macro normalizar_dni(tabla=,campo=);
    /* obtenemos las partes del DNI y localizamos formatos incorrectos */
    data norm1;
        set &tabla;
        &campo = upcase(&campo);
        if anypunct(&campo) > 0 then ind_valido = 0;
        else if length(&campo) > 9 then ind_valido = 0;
        else if 0 < anyalpha(substr(&campo,2)) < length(substr(&campo,2)) then ind_valido = 0;
        else if compress(substr(&campo,1,1),'XYZ','D') ne '' then ind_valido = 0;
        else if length(&campo)=9 and anyalpha(&campo)=0 and substr(&campo,1,1)='0' then do;
            &campo=substr(&campo,2);
            ind_valido = 1;
            prefix = compress(substr(&campo,1,1),'','D');
            number = input(compress(&campo,'','A'),8.);
            sufix = compress(substr(&campo,length(&campo)),'','D');
        end;
        else if length(&campo)=9 and anyalpha(&campo)=0 then ind_valido = 0;
        else do;
            ind_valido = 1;
            prefix = compress(substr(&campo,1,1),'','D');
            number = input(compress(&campo,'','A'),8.);
            sufix = compress(substr(&campo,length(&campo)),'','D');
        end;
    run;

    /* normalizamos el número */
    data norm2;
        set norm1;
        format numero $8.;
        length numero $ 8;
        n = number;
        if anyalpha(prefix)=1 then numero = put(number,z7.);
            else numero = put(number,z8.);
    run;

    /* calculamos la letra del DNI y verificamos si es correcta cuando venga informada */
    data norm3 (drop=prefix number sufix numero n letras resto letra_norm ind_valido);
        set norm2;
        letras = 'TRWAGMYFPDXBNJZSQVHLCKE';
        if prefix='Y' then n=n+10000000;
        if prefix='Z' then n=n+20000000;
        resto = mod(n,23);
        letra_norm = substr(letras,resto+1,1);
        dni_norm = compress(prefix||numero||letra_norm);
    run;

    /* salida */
    proc sql;
        create table &tabla._ as
        select a.*, dni_norm
        from &tabla a
        left join norm3 b
        on a.&campo = b.&campo;
    quit;
%mend normalizar_dni;

Podéis probarla con este set de datos de prueba que os dejo aquí:

data DNIS;
    format dni $20.;
    length dni $ 20;
    input dni $;
    datalines;
    16634732A
    1b
    16634732
    32631459w
    X00123
    Y1234612
    123&134
    Z1224536
    X00z12
    064563314
    6843131058
    1635740.65
    000000315
    G12
run;

%normalizar_dni(tabla=DNIS,campo=dni);