Validar DNI y RUC peruanos sin llamar a una API (módulo-11 en TypeScript)

Si tu app pide DNI o RUC en algún punto del flujo (KYC, onboarding, facturación, ecommerce con factura electrónica), tarde o temprano te va a tocar consultar SUNAT, RENIEC o algún proveedor que envuelva ambos.

Pero antes de gastar una llamada API por cada documento que entra, vale la pena validar offline: muchos errores son tan triviales como un dígito de menos, un espacio metido al inicio, o un usuario tipeando 12345678 para ver si pasa.

Validar localmente te ahorra latencia, dinero (si pagas por consulta) y rate limits.

¿Cómo se validan?

DNI

8 dígitos numéricos. RENIEC asigna desde 00000001 hacia arriba. La regla práctica:

  • Exactamente 8 dígitos
  • Sin secuencias triviales (00000000, 11111111, etc.)
function isValidDNI(dni: string): boolean {
  if (!/^d{8}$/.test(dni)) return false;
  if (/^(d)1{7}$/.test(dni)) return false; // todos iguales
  return true;
}

Nota: existe un cálculo módulo-11 para DNI usado en sistemas bancarios peruanos, pero RENIEC no lo expone públicamente como check-digit obligatorio. Para validación pública se acepta el formato, y la verificación real ocurre contra el padrón.

RUC (la parte interesante)

11 dígitos. Inicia con un par que indica el tipo de contribuyente:

Prefijo Tipo
10 Persona natural
15 No domiciliado
17 Sucesión indivisa
20 Persona jurídica

El último dígito es un check-digit calculado con módulo-11 sobre los primeros 10:

  1. Multiplica cada dígito por su peso: [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
  2. Suma todos los productos
  3. expected = (11 - (sum % 11)) % 10
  4. Compara con el último dígito del RUC
function isValidRUC(ruc: string): boolean {
  if (!/^d{11}$/.test(ruc)) return false;
  if (!['10', '15', '17', '20'].includes(ruc.slice(0, 2))) return false;

  const weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
  const sum = weights.reduce((acc, w, i) => acc + w * Number(ruc[i]), 0);
  const expected = (11 - (sum % 11)) % 10;
  return Number(ruc[10]) === expected;
}

Esto rechaza inmediatamente RUC tipeados al azar y secuencias inválidas, sin necesidad de pegarle a SUNAT.

Tipo de contribuyente

Solo con el prefijo ya sabes qué tipo de entidad es:

function tipoContribuyente(ruc: string): 'natural' | 'juridica' | 'no-domiciliado' | 'sucesion' | null {
  if (!isValidRUC(ruc)) return null;
  switch (ruc.slice(0, 2)) {
    case '10': return 'natural';
    case '15': return 'no-domiciliado';
    case '17': return 'sucesion';
    case '20': return 'juridica';
    default:   return null;
  }
}

Útil para:

  • Decidir si pides datos de razón social o nombre completo
  • Aplicar IGV correctamente (algunas modalidades de no-domiciliado tienen tratamiento distinto)
  • Mostrar diferentes flujos de KYC

La librería: dni-validator-peru

Para no copiar-pegar este código en cada proyecto, lo empaqueté como dni-validator-peru — TypeScript ESM, zero dependencies, MIT, 17 tests.

npm install dni-validator-peru
import {
  isValidDNI,
  isValidRUC,
  validateDocumento,
  tipoContribuyente,
} from 'dni-validator-peru';

isValidDNI('72345678');        // true
isValidDNI('00000000');        // false
isValidRUC('20602431216');     // true (Grupo Securex S.A.C.)
tipoContribuyente('20602431216'); // 'juridica'

// Detector unificado: te dice si es DNI, RUC o ninguno
validateDocumento('72345678');     // { tipo: 'DNI', valid: true }
validateDocumento('20602431216');  // { tipo: 'RUC', valid: true, subtipo: 'juridica' }
validateDocumento('abc');          // { tipo: null, valid: false }

Por qué la mantengo

Securex hace KYC en cada onboarding (somos casa de cambio digital regulada por SBS, certificada ISO 37301). Lo hicimos auto-fill: el usuario ingresa su DNI, y validamos módulo-11 + buscamos en padrón SUNAT. El módulo-11 elimina ~5% de submissions inválidas antes de gastar un call de API.

Esa lógica de validación local me parecía suficientemente común y aburrida como para sacarla en open source.

Otras librerías hermanas

Las tres mantengo en github.com/Edsoncame:

  • tipo-cambio-peru — BCRP / SBS / SUNAT en una sola llamada.
  • feriados-peru — Calendario de feriados nacionales con utilidades de días hábiles.

Todas zero-dependency, ESM, MIT.

npm · GitHub · Mantenido por Securex.