@workflow507/panama-ruc
v0.4.1
Published
Motor ultra rápido de cálculo y validación del Dígito Verificador (DV) del RUC en Panamá. Sin dependencias. Funciona en Node, Bun, Deno, Cloudflare Workers y navegadores.
Maintainers
Readme
@workflow507/panama-ruc
Motor ultra rápido de cálculo y validación del Dígito Verificador (DV) del RUC en Panamá. Sin dependencias. Funciona en Node, Bun, Deno, Cloudflare Workers y navegadores.
🇵🇦 Español
¿Qué hace?
Calcula y valida el Dígito Verificador (DV) del RUC en Panamá según el algoritmo oficial de la DGI. Cubre los 5 tipos de contribuyente: Natural, Natural NT, Jurídica (incl. antiguas), Jurídica NT y Finca.
import { calculateDV, validate, parse, Ruc } from '@workflow507/panama-ruc';
// Natural (cédula): el tipo se detecta solo por el formato
calculateDV("8-783-1657"); // → "23"
// Jurídica (empresas)
calculateDV("2588017-1-831938"); // → "20"
// Finca (inmueble): formato de dos partes
calculateDV("8-30213562"); // → DV oficial
parse("8-783-1657");
// → { type: "natural", dv: "23", fullId: "8-783-1657 DV23",
// provincia: { codigo: "08", nombre: "Panamá" }, folio: "783", asiento: "1657" }
// El motor SIEMPRE devuelve un tipo detectado por el formato. Para el caso raro
// de una empresa antigua con formato de cédula (ej. "2-221-78" = CIA. DE SEGUROS S.A.),
// pasá el tipo y recalcula:
calculateDV("2-221-78", { typeHint: "juridica" }); // → "56"¿Por qué este paquete?
- ⚡ Ultra rápido: ~0.4µs por validación
- 🪶 Liviano: sin dependencias
- 🌐 Universal: Node, Bun, Deno, Cloudflare Workers, Browser, React Native
- 🎯 Tipado completo: TypeScript-first con discriminated unions
- 🛡️ Validado contra la DGI oficial: el DV de cada tipo se cotejó contra el consultador oficial de la DGI (no contra suposiciones). 168 tests.
- 🧮 Los 5 tipos: Natural, Natural NT, Jurídica (+ legacy), Jurídica NT, Finca.
- 🧹 Acepta inputs sucios:
"8 783 1657","8.783.1657"," 8-783-1657 " - 🆕 Actualizado: Incluye Comarca Naso Tjër Di (Ley 656 de 2020)
- 🎛️ Tipo detectado por formato, sobreescribible: cada resultado trae
type; podés forzar otro contypeHinty recalcular (ideal para un selector de tipo en la UI). - 🔧 API funcional + OOP: Usá lo que prefieras
Instalación
npm install @workflow507/panama-ruc
# o
pnpm add @workflow507/panama-ruc
# o
bun add @workflow507/panama-rucUso
API funcional
import {
calculateDV,
validate,
parse,
parseMany,
extractFromText,
generate,
} from '@workflow507/panama-ruc';
// Calcular DV (el tipo se detecta por formato)
calculateDV("8-783-1657"); // "23" (natural)
calculateDV("2588017-1-831938"); // "20" (jurídica)
calculateDV("8-30213562"); // finca (dos partes)
// Validar
validate("8-783-1657", "23"); // { valid: true, type: "natural" }
// Parsear info completa
const data = parse("8-783-1657");
console.log(data.provincia?.nombre); // "Panamá"
// Procesar lote
const result = parseMany(["2588017-1-831938", "8-30213562", ...]);
console.log(`${result.validCount}/${result.total} válidos`);
// Extraer de texto sucio (usa el DV embebido para desambiguar)
const found = extractFromText("Cliente: María, RUC 8-783-1657 DV 23");
// → [{ ruc: "8-783-1657", dv: "23", dvValid: true, position: 17 }]
// Generar RUCs de prueba
const rucs = generate({ type: "natural", count: 10, seed: 42 });API orientada a objetos
import { Ruc } from '@workflow507/panama-ruc';
const ruc = Ruc.from("8-783-1657");
console.log(ruc.dv); // "23"
console.log(ruc.type); // "natural"
console.log(ruc.provincia?.nombre); // "Panamá"
console.log(ruc.toString()); // "8-783-1657 DV23"
console.log(ruc.isValid("23")); // trueManejo de errores
import { parse, RucError, RUC_ERROR_CODES } from '@workflow507/panama-ruc';
try {
parse("BASURA");
} catch (e) {
if (e instanceof RucError) {
if (e.code === RUC_ERROR_CODES.TYPE_UNDETECTABLE) {
console.log("No se pudo detectar el tipo");
}
}
}
// O sin throw
import { safeParse } from '@workflow507/panama-ruc';
const result = safeParse("BASURA");
if (!result.ok) {
console.log(result.error.code);
}RUCs NT (formato ambiguo)
Los RUC de tipo NT (provincia-NT-folio-asiento) pueden ser natural-nt
(extranjero residente) o juridica-nt (sin fines de lucro). El mismo texto
da DV distinto según el subtipo, así que hay que decir cuál es:
import { calculateDV, parse, safeParse } from '@workflow507/panama-ruc';
calculateDV("8-NT-1-13656", { typeHint: "natural-nt" }); // → "02"
calculateDV("8-NT-1-13656", { typeHint: "juridica-nt" }); // → "43"
// Sin typeHint NO adivina: falla fuerte para no devolver un DV incorrecto.
const r = safeParse("8-NT-1-13656");
// → { ok: false, error.code: "AMBIGUOUS_NT_TYPE" }
parse("8-NT-1-24", { typeHint: "natural-nt" }); // okCLI
$ npm install -g @workflow507/panama-ruc
$ panama-ruc dv 8-783-1657
23
$ panama-ruc validate 8-783-1657 23
✓ Válido (tipo: natural)
$ panama-ruc parse 8-783-1657
┌─────────────────────────────────────┐
│ RUC: 8-783-1657 │
│ DV: 23 │
│ Completo: 8-783-1657 DV23 │
│ Tipo: natural │
│ Provincia: Panamá │
│ Folio: 783 │
│ Asiento: 1657 │
└─────────────────────────────────────┘
$ panama-ruc generate --type juridica --count 5
$ panama-ruc batch rucs.csvTipos de RUC soportados
| Tipo | Ejemplo | Soporte |
|------|---------|---------|
| Persona Natural (cédula) | 8-783-1657 | ✅ Completo² |
| Natural con letra E (Extranjero) | E-12-345 | ✅ Completo |
| Natural con letra N (Naturalizado) | N-12-345 | ✅ Completo |
| Natural con letra PE (Panameño Extranjero) | PE-12-345 | ✅ Completo |
| Natural con letra AV (Antes Vigencia) | 8AV-123-45 | ✅ Completo |
| Natural con letra PI (Panameño Indígena) | 4PI-123-45 | ✅ Completo |
| Persona Jurídica | 2588017-1-831938 | ✅ Completo |
| Jurídica antigua (legacy) | 12388-184-921 | ✅ Completo² |
| Natural NT (extranjero residente) | 8-NT-1-24 | ✅ Completo¹ |
| Jurídica NT (sin fines de lucro) | 8-NT-1-13656 | ✅ Completo¹ |
| Finca (inmueble) | 8-30213562 | ✅ Completo (validado vs DGI) |
| Finca con letra (SB/EE) | Variados | ❌ DGI no publica algoritmo |
¹ El formato NT (provincia-NT-folio-asiento) no distingue natural-nt de juridica-nt: pasá typeHint.
² El formato corto (primer grupo de 1-2 dígitos ≤ 14) se interpreta como persona natural (la provincia de la cédula), que es lo correcto casi siempre. Una empresa antigua puede compartir ese formato y dar otro DV (ej. 2-221-78 = CIA. DE SEGUROS S.A. → jurídica): el nombre comercial lo revela y se fuerza con typeHint: "juridica". Si el primer grupo es > 14 no puede ser provincia → se detecta jurídica solo.
¹ Los dos tipos NT comparten el formato textual
provincia-NT-folio-asientopero producen un DV distinto. Como no se pueden distinguir solo por el texto, debés indicar el subtipo contypeHint. Si no lo hacés, la librería falla conAMBIGUOUS_NT_TYPEen vez de devolver un DV adivinado (ver más abajo).
Rendimiento
| Operación | Tiempo |
|-----------|--------|
| calculateDV() | ~0.6µs |
| parseMany(10000) sin cache | ~60ms |
| parseMany(10000) con cache | ~12ms |
| Bundle size (min) | 18.5 KB |
| Bundle size (min+gzip) | ~6 KB |
Compatibilidad
- ✅ Node.js 18+
- ✅ Bun
- ✅ Deno
- ✅ Cloudflare Workers
- ✅ Browser (todos los modernos)
- ✅ React Native
- ✅ ESM y CommonJS
Configuración avanzada
// Override de provincias si DGI agrega nuevas
import { setCustomProvincias } from '@workflow507/panama-ruc';
setCustomProvincias([
...defaultProvincias,
{ codigo: "15", nombre: "Nueva Provincia", tipo: "provincia" },
]);Algoritmo
Esta librería implementa el algoritmo oficial de la Dirección General de Ingresos (DGI) de Panamá, publicado en el documento "Cálculo del DV del RUC".
El cálculo es:
- Se construye un RUCTB (RUC table) de 20 caracteres
- Se aplica módulo 11 con pesos incrementales desde 2
- Para RUCs jurídicos antiguos, se aplica un salto de peso en posición 12
- El DV son 2 dígitos calculados secuencialmente
Validación de correctitud: los DV de todos los tipos soportados (natural sin letra, E, PE, N, AV, PI, jurídica, jurídica legacy, natural-nt y juridica-nt) están verificados en los tests contra los casos publicados por implementaciones independientes de referencia (juancorradine/Panama-RUC-DV-Calculator y apple314159/panama-dv), que a su vez derivan del documento oficial de la DGI. Ver tests/fixtures/known-rucs.json.
Casos de uso
- Validación de formularios web/móvil
- Procesamiento de planillas DGI en bulk
- Sistemas de factura electrónica (validación de RUCs de receptores)
- Importación y limpieza de bases de datos contables
- ETL para sistemas financieros y contables
- Apps de contadores y firmas contables
Contribuir
Issues y PRs son bienvenidos en GitHub.
🇬🇧 English
What it does
Calculates and validates the Check Digit (DV) of Panama's Single Taxpayer Registry (RUC), following the official algorithm published by DGI (Dirección General de Ingresos).
import { calculateDV, validate, parse } from '@workflow507/panama-ruc';
calculateDV("8-783-1657"); // → "23" (natural)
calculateDV("2588017-1-831938"); // → "20" (legal entity)
calculateDV("8-30213562"); // → property (finca)
calculateDV("2-221-78", { typeHint: "juridica" }); // → "56" (old company w/ ID-like format)Why this package?
- ⚡ Ultra fast: ~0.4µs per validation
- 🪶 Lightweight: zero dependencies
- 🌐 Universal: Node, Bun, Deno, Cloudflare Workers, Browser
- 🎯 Fully typed: TypeScript-first with discriminated unions
- 🛡️ Validated against the official DGI lookup: every type's DV was cross-checked against DGI's official checker. 168 tests.
- 🧮 All 5 taxpayer types: Natural, Natural NT, Legal Entity (+ legacy), Legal Entity NT, Property (Finca).
- 🎛️ Type detected from format, overridable: each result carries
type; override withtypeHintand recalculate (great for a UI type selector).
Installation
npm install @workflow507/panama-rucSee Spanish section above for complete API documentation.
Acknowledgments
This library was inspired by previous community implementations such as:
- panama-ruc-dv-calculator (Python) by Juan Corradine
- panama-dv (Python)
The algorithm itself is publicly documented by DGI Panama in their official specification "Cálculo del DV del RUC".
This is an independent TypeScript implementation written from the public specification, with added features (auto-detection, validation, batch processing, text extraction, generation, CLI, etc.).
License
MIT © 2026 Jeziel Leira / Workflow507
Made with ❤️ in 🇵🇦 by Workflow507
