DEV Community

Cover image for Cómo validar un RNC, RUC o CUIT antes de emitir una factura electrónica
Josue Garcia
Josue Garcia

Posted on AI-assisted

Cómo validar un RNC, RUC o CUIT antes de emitir una factura electrónica

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:

  1. Localmente, con el dígito verificador: gratis, instantáneo y sin depender de nadie.
  2. 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])
Enter fullscreen mode Exit fullscreen mode

⚠️ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
{
  "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"
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

Tres consejos de producción

  1. 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.
  2. 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.
  3. 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"
Enter fullscreen mode Exit fullscreen mode

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)