te paso una rutina que usamos en BORSAN hace muchos años, que hemos ido depurando con el tiempo.
Te he puesto un ejemplo rápido.
Creo no haberme equivocado en nada.
El código yo lo refactorizaría completo, pero bueno, eso ya te lo dejo a ti :-)
lcNif = "00000001"
lcNifResultante = CalculaNif(lcNif,.T.)
? lcNifResultante && -> "00000001R
*---------------------------------------------------------
* Método:
* CALCULANIF
* Parameters: tcValor: Valor
* tlSustituirValor: Sustituir el valor a la vuelta, es decir, poner el resultado que nos de en el value del objeto llamante
*
* Objetivo:
* Comprueba el NIF y retorna el nif corecto
*!* CIF.- Si empieza por las letras {N,P,Q,R,S,V}, el dígito es una letra A-J
*!* CIF.- Si empieza por las letras {A-H,J,U,W}, el dígito es un numero del 0-9
*!* CIF.- Si empieza por las letras {K,L,M}, el dígito una letra calculada como el DNI
*!* Ver:
https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal* Programador:
* informática Borsan
* Fecha de Terminación:
* 23/05/01 18:34:40
*---------------------------------------------------------
PROCEDURE CalculaNif
LPARAMETERS tcValor, tlSustituirValor
LOCAL llRetorno
LOCAL lcCadena, lcCaracter, lcNumero, lcLetras, lcSalida, lcEntrada, lcNumeroCalc, lcPrimero
LOCAL lnInd, lnLong, lnValor
LOCAL llAct, llRetorno
LOCAL lcMsg, lcMsgBox, lcMensaje
LOCAL lcLetrasCIF
PRIVATE pnA ,pnB, pnDigito , pcDigito, plError
STORE 0 TO pnA, pnB, pnDigito
pcDigito = ''
plError = .F.
STORE '' TO lcCadena, lcCaracter, lcNumero, lcLetras, lcSalida, lcEntrada, lcNumeroCalc, lcPrimero,lcMsg, lcMsgBox, lcMensaje
STORE 0 TO lnInd, lnLong, lnValor
STORE .F. TO llAct, llRetorno
llRetorno = .T.
tlSustituirValor = IIF(PCOUNT() = 1, .T., tlSustituirValor)
llAct = !EMPTY(NVL(tcValor, ""))
lcLetrasCIF = 'ABCDEFGHIJ'
IF !llAct
llRetorno = .F.
ENDIF
llAct = llAct AND tlSustituirValor
TRY
IF llRetorno
IF LEN(ALLTRIM(tcValor)) < 5
=MESSAGEBOX(txt('NIF/CIF incorrecto. Introdúzcalo de nuevo.'),oCte.ExclamacionIcon + oCte.OkOnly, oCte.Nom_Aplicacion)
llRetorno = .F.
ENDIF
ENDIF
IF llRetorno
tcValor = ALLTRIM(UPPER(tcValor))
lcEntrada = tcValor
* quitar todo lo que no sea letras o numeros.
FOR lnInd = 1 TO LEN(lcEntrada)
lcCaracter = SUBSTR(lcEntrada, lnInd, 1)
IF (ISALPHA(lcCaracter) OR ISDIGIT(lcCaracter))
lcCadena = lcCadena + lcCaracter
ENDIF
ENDFOR
lcEntrada = lcCadena && lcEntrada es tcEntrada sin signos de puntuación y similares
lcPrimero = LEFT(lcEntrada, 1)
IF AT(lcPrimero, "KLMXYZ") > 0 OR ISDIGIT(lcPrimero)
***************************************************************************
* N.I.F. = Número de Identificación Fiscal para persona Físicas *
***************************************************************************
lcLetras = 'TRWAGMYFPDXBNJZSQVHLCKE'
lcCadena = ''
lcSalida = ''
lnLong = LEN(lcEntrada)
lcNumero = ''
lcNumeroCalc = ''
FOR lnInd = 1 TO lnLong
lcCaracter = SUBSTR(lcEntrada, lnInd, 1)
IF ISDIGIT(lcCaracter)
lcNumero = lcNumero + lcCaracter
ENDIF
ENDFOR
lcNumeroCalc = lcNumero
* Desde el 08/10/20008 se le ha agotado la numeración con la X a la AEAT,
* con lo cual desde esa fecha todas las solicitudes que se acepten de NIE empezaran con la "Y".
*!* Modificado : 05/11/2008 por Pedro (PEDRO). Nuevas letras y su control en los N.I.E.
* La Orden INT/2058/2008, crea las nuevas letras de N.I.E. de las personas físicas extranjeras "Y" y "Z",
* en lugar de la "X", al haberse agotado la capacidad de numeración de esta ésta última.
* La única peculiaridad a resaltar referida a esta configuración de NIEs que comiencen por "Y" o "Z"
* consiste en que el algoritmo de cálculo del carácter de control será el mismo que para la letra "X",
* con la diferencia de que la letra inicial (X,Y,Z) tendrá un peso (0,1,2 respectivamente),
* que habrá que concatenar con la parte numérica del NIE para formar el número base a dividir por 23.
* (Nota: la asignación de un peso cero a la letra X equivale a decir que dicha letra no se tiene en cuenta
* en el algoritmo de cálculo de carácter de control).
lcSalida = IIF(AT(lcPrimero, "XYZKLM") > 0, lcPrimero, "")
lcNumeroCalc = PADL(ICASE(AT(lcPrimero, "XKLM") > 0, "0", lcPrimero = "Y", "1", lcPrimero = "Z", "2", "") + lcNumero, 8, "0")
* Cálculo de la letra
lnValor = VAL(lcNumeroCalc)
lnLong = INT(lnValor / 23) * 23
lnInd = (lnValor - lnLong) + 1
lcSalida = lcSalida + lcNumero + SUBSTR(lcLetras, lnInd, 1)
ELSE && This.NIF
***************************************************************************
* C.I.F. *
***************************************************************************
*!* Si empieza por las letras {N,P,Q,R,S,V}, el dígito es una letra A-J
*!* Si empieza por las letras {A-H,J,U,W}, el dígito es un numero del 0-9
*!* Modificado : 26/11/2012 14:12:16 por Rafael (WIN8X64)
*!* Faltaba la 'V'
* Control de la posibles letras por las que empiezan los CIF
IF INLIST(LEFT(lcEntrada,1),'A','B','C','D','E','F','G','H','N','P','Q','S','R', 'J', 'U', 'V', 'W')
* A Sociedades Anónimas
* B Sociedades de Responsabilidad Limitada
* C Sociedades Colectivas
* D Sociedades Comanditarias
* E Comunidades de bienes
* F Sociedades Cooperativas
* G Asociaciones y otros tipos no definidos
* H Comunidades de propietarios en régimen de propiedad horizontal
* K, L, M corresponden al formato antiguo
* N. Entidades no residentes.
* P. Corporaciones locales.
* Q. Organismos autónomos, estatales o no, y asimilados, y congregaciones e instituciones religiosas.
* S. Órganos de la Administración del Estado y comunidades autónomas
* V. Sociedad Agraria de Transformación.
STORE 0 TO pnA, pnB, pnDigito, lnInd
pcDigito = ''
plError = .F.
* Control que las posiciones 2 a 8 deben ser numericas.
FOR lnInd = 2 TO 8
IF !(SUBSTR(lcEntrada, lnInd, 1) >= '0' AND SUBSTR(lcEntrada, lnInd, 1) <= '9')
plError = .T.
ENDIF
NEXT
IF plError
* propone "CIF ERRONEO" como CIF calculado.
* lcSalida = "CIF ERRONEO"
=MESSAGEBOX(txt('NIF/CIF incorrecto. Introdúzcalo de nuevo.'),oCte.ExclamacionIcon + oCte.OkOnly, oCte.Nom_Aplicacion)
*!* NO HACEMOS QUE SEA ERRONEO, para dejar introducir datos de cif extranjeros como portugal
*!* llRetorno = .F.
*!* llAct = .F.
lcSalida = lcEntrada
llRetorno = .T.
ELSE
* Suma las posiciones pares (sin contar la letra inicial)
pnA = VAL(SUBSTR(lcEntrada,3,1)) + VAL(SUBSTR(lcEntrada,5,1)) + VAL(SUBSTR(lcEntrada,7,1))
* Suma las posiciones impares (sin contar la letra inicial) : Multiplica por 2 y suma los 2 dígitos.
pnB = 0
FOR lnInd = 2 TO 8 STEP 2
pnDigito = VAL(SUBSTR(lcEntrada, lnInd, 1)) * 2
pcDigito = PADL(ALLTRIM(STR(pnDigito)), 2, '0')
pnB = pnB + VAL(SUBSTR(pcDigito, 1, 1)) + VAL(SUBSTR(pcDigito, 2, 1))
NEXT
pnDigito = pnA + pnB
pnDigito = VAL(RIGHT(ALLT(STR(pnDigito)), 1))
pnDigito = 10 - pnDigito
*!* Calcular cuál ha de ser el dígito de control
* Si comienza por 'P' (ésto no es muy seguro) debe ser una letra que se corresponde con la
* siguiente relación: A=1, B=2, C=3, D=4, E=5, F=6, G=7, H=8, I=9, J=0
*!* El dígito de control será una letra si empieza por K, P, Q, ó S.
*!* Si empieza por las letras {N,P,Q,R,S,W}, el dígito es una letra A-J
*!* Si empieza por las letras {A-H,J,U,V}, el dígito es un numero del 0-9
IF INLIST(SUBSTR(lcEntrada, 1, 1),'N','P','Q','R','S','W')
*!* Modificado : 19.11.04 por Pedro (PEDRO). Si pnDigito = 10 no le ponía la letra 'J'
* IF pnDigito = 0
IF pnDigito = 0 OR pnDigito = 10
pcDigito = 'J'
ELSE
pcDigito = SUBSTR(lcLetrasCIF, pnDigito, 1)
ENDIF
ELSE
pcDigito = RIGHT(ALLT(STR(pnDigito)), 1) && PARA 10 dejamos 0
ENDIF
lcSalida = LEFT(lcEntrada, 8) + pcDigito
ENDIF
ELSE
IF MESSAGEBOX(txt('El valor introducido no se evalúa como CIF. ¿Desea mantener el valor?'),oCte.wi_icointerrog+oCte.wi_sino,oCte.Nom_Aplicacion) = oCte.wir_si
lcSalida = lcEntrada
llRetorno = .T.
ELSE
lcSalida = ' '
llRetorno = .F.
llAct = .F.
ENDIF
ENDIF
ENDIF
ENDIF && llRetorno
IF llRetorno
IF lcSalida <> tcValor AND llAct AND !EMPTY(lcSalida) AND tlSustituirValor
lcMsg = txt('El NIF/CIF calculado es diferente al introducido.') + CHR(13) + CHR(10) + ;
txt("Valor introducido")+": " + tcValor + CHR(13) + CHR(10) + txt("Valor calculado")+": " + lcSalida + CHR(13) + CHR(10)+ ;
txt("¿Desea aceptar el nuevo valor? ")
IF MESSAGEBOX(lcMsg,oCte.wi_icointerrog+oCte.wi_sino,oCte.Nom_Aplicacion) = oCte.wir_si
*!*
ELSE
llAct = .F.
ENDIF
ENDIF
ENDIF
CATCH TO loErr
oApp.ERROR(loErr)
llRetorno = .F.
FINALLY
ENDTRY
tlSustituirValor = llAct
RETURN lcSalida