Si tu sistema emite facturas electrónicas en Latinoamérica, tarde o temprano te toca validar el identificador fiscal del cliente: el RNC en República Dominicana, el RUC en Ecuador o el CUIT en Argentina. Un número mal escrito o de un contribuyente dado de baja puede hacer que la factura sea rechazada o, peor, que quede emitida con datos incorrectos.
En este artículo te muestro cómo validarlo en dos capas:
- Localmente, con el dígito verificador: gratis, instantáneo y sin depender de nadie.
- Contra el padrón de contribuyentes, para saber si el número existe, cómo se llama la empresa y si está activa.
Aviso: soy el creador de IDFiscal, la API que uso en la segunda parte. La primera parte no la necesita.
Por qué no basta con un formulario
El problema de fondo es que las administraciones tributarias no lo ponen fácil:
- La DGII (República Dominicana) no tiene una API pública de consulta; publica el padrón completo como un archivo descargable que se actualiza a diario.
- El SRI (Ecuador) tiene la consulta web protegida con captcha; el catastro se publica como datos abiertos, un archivo por provincia.
- ARCA (ex AFIP, Argentina) tiene web services, pero requieren certificado digital, autenticación WSAA y SOAP.
Así que o descargas y procesas esos archivos tú mismo, o usas un servicio que lo haga.
Capa 1: el dígito verificador
Los tres identificadores tienen un dígito de control al final. Calcularlo detecta la mayoría de los errores de tipeo (un dígito cambiado, dos dígitos invertidos) sin hacer ninguna consulta.
República Dominicana: RNC y cédula
El RNC de 9 dígitos usa módulo 11 con pesos 7, 9, 8, 6, 5, 4, 3, 2. La cédula de 11 dígitos usa el algoritmo de Luhn.
def rnc_valido(rnc: str) -> bool:
if len(rnc) != 9 or not rnc.isdigit():
return False
pesos = [7, 9, 8, 6, 5, 4, 3, 2]
resto = sum(int(d) * p for d, p in zip(rnc[:8], pesos)) % 11
dv = 2 if resto == 0 else 1 if resto == 1 else 11 - resto
return dv == int(rnc[8])
def cedula_valida(cedula: str) -> bool:
if len(cedula) != 11 or not cedula.isdigit():
return False
suma = 0
for i, d in enumerate(cedula[:10]):
v = int(d) * (1 if i % 2 == 0 else 2)
suma += v if v < 10 else v - 9
return (10 - suma % 10) % 10 == int(cedula[10])
⚠️ Ojo con República Dominicana: probé estos algoritmos contra el padrón completo de la DGII y coinciden en el 99,99 % de los casos, pero hay unos pocos RNC (5 de ~504.000) y cédulas (32 de ~292.000) reales cuyo dígito no sigue la regla. Usa el dígito verificador para advertir al usuario ("¿seguro que está bien escrito?"), no para rechazar el número sin consultarlo.
Argentina: CUIT
Módulo 11 con pesos 5, 4, 3, 2, 7, 6, 5, 4, 3, 2. Si el resultado es 11 el dígito es 0, y si es 10 es 9. Acá sí puedes rechazar con confianza: lo comprobé contra los 6,2 millones de CUIT del padrón de ARCA y coinciden todos.
function cuitValido(cuit) {
cuit = cuit.replace(/\D/g, '');
if (cuit.length !== 11) return false;
const pesos = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
const suma = pesos.reduce((acc, p, i) => acc + p * Number(cuit[i]), 0);
let dv = 11 - (suma % 11);
if (dv === 11) dv = 0;
if (dv === 10) dv = 9;
return dv === Number(cuit[10]);
}
cuitValido('30-50001091-2'); // true
Ecuador: RUC
El RUC tiene 13 dígitos y, para personas naturales, es la cédula seguida de 001. Existen reglas de verificación según el tercer dígito, pero en la práctica hay muchos RUC reales que no las cumplen, así que para Ecuador te recomiendo validar solo el largo y consultar el catastro.
Capa 2: consultar el padrón
El dígito verificador te dice que el número podría existir. Para saber si existe, a nombre de quién está y si está activo, hay que consultar el padrón. Con IDFiscal es una petición HTTP por país:
curl -H "X-API-Key: TU_API_KEY" https://idfiscal.com/v1/do/rnc/101010632
{
"rnc": "101010632",
"razon_social": "BANCO POPULAR DOMINICANO S A BANCO MULTIPLE",
"actividad_economica": "BANCOS MULTIPLES",
"fecha_inicio": "1984-08-15",
"estado": "ACTIVO",
"regimen_pago": "NORMAL"
}
Lo mismo para Ecuador (/v1/ec/ruc/0991462996001) y Argentina (/v1/ar/cuit/30500010912). Para Argentina, la respuesta incluye condicion_iva, que te dice directamente si corresponde factura A (RESPONSABLE_INSCRIPTO) o factura B (monotributistas, exentos y no inscriptos):
{
"cuit": "30500010912",
"denominacion": "BANCO DE LA NACION ARGENTINA",
"tipo_persona": "JURIDICA",
"condicion_iva": "RESPONSABLE_INSCRIPTO",
"iva": "AC",
"monotributo": "NI",
"empleador": true
}
Juntándolo todo antes de facturar
Este es el flujo que recomiendo, en Python:
import os
import requests
API = "https://idfiscal.com/v1"
HEADERS = {"X-API-Key": os.environ["IDFISCAL_API_KEY"]}
def validar_cliente(pais: str, numero: str) -> dict:
"""pais: 'do', 'ec' o 'ar'. Devuelve el contribuyente o lanza ValueError."""
numero = "".join(c for c in numero if c.isdigit())
recurso = {"do": "rnc", "ec": "ruc", "ar": "cuit"}[pais]
r = requests.get(f"{API}/{pais}/{recurso}/{numero}", headers=HEADERS, timeout=5)
if r.status_code == 400: # formato inválido o dígito verificador incorrecto
raise ValueError(r.json()["error"])
if r.status_code == 404: # no está en el padrón
raise ValueError("El número no está registrado como contribuyente")
if r.status_code == 429: # límite de consultas; Retry-After dice cuánto esperar
raise RuntimeError(f"Reintentar en {r.headers.get('Retry-After')} s")
r.raise_for_status()
contribuyente = r.json()
if contribuyente.get("estado") not in (None, "ACTIVO"):
raise ValueError(f"El contribuyente está {contribuyente['estado']}")
return contribuyente
cliente = validar_cliente("do", "1-01-01063-2")
print(cliente["razon_social"]) # BANCO POPULAR DOMINICANO S A BANCO MULTIPLE
Y en JavaScript (Node 18+ o el backend que uses; no pongas la API key en el navegador):
const recursos = { do: 'rnc', ec: 'ruc', ar: 'cuit' };
async function validarCliente(pais, numero) {
numero = numero.replace(/\D/g, '');
const res = await fetch(`https://idfiscal.com/v1/${pais}/${recursos[pais]}/${numero}`, {
headers: { 'X-API-Key': process.env.IDFISCAL_API_KEY },
});
const body = await res.json();
if (!res.ok) throw new Error(body.error);
return body;
}
const c = await validarCliente('ar', '30-50001091-2');
const tipoFactura = c.condicion_iva === 'RESPONSABLE_INSCRIPTO' ? 'A' : 'B';
Tres consejos de producción
- Cachea el resultado. Los padrones se actualizan como mucho una vez al día (RD diario, Argentina semanal, Ecuador mensual), así que guardar la respuesta 24 horas te ahorra consultas sin perder precisión.
- No bloquees la venta si la consulta falla por red. Si la API no responde, deja continuar y marca la factura para revisar después. Un 404 o un 400 sí son motivo para pedirle al usuario que revise el número.
- Autocompleta. Ya que consultas, rellena la razón social en el formulario: menos errores de tipeo y menos fricción para el cliente.
Buscar por nombre
A veces el cliente no recuerda su RNC pero sí el nombre de la empresa. La API también busca por razón social (solo entre personas jurídicas):
curl -H "X-API-Key: TU_API_KEY" "https://idfiscal.com/v1/do/rnc?q=banco%20popular&limit=5"
Conclusión
- El dígito verificador atrapa los errores de tipeo gratis y al instante. En Argentina es confiable al 100 %; en República Dominicana úsalo como advertencia.
- La consulta al padrón confirma que el contribuyente existe, te da su nombre y su estado, y en Argentina te dice qué tipo de factura emitir.
IDFiscal es gratis hasta 1.000 consultas por día, para los tres países. La documentación completa está en idfiscal.com/docs, con una página por país: RNC de República Dominicana, RUC de Ecuador y CUIT de Argentina.
Top comments (0)