Skip to content

Commit 846b26c

Browse files
authored
Merge pull request #1 from bayasdev/add-is-ruc
Add isRUC validator
2 parents 029de44 + 8bbd13d commit 846b26c

File tree

8 files changed

+200
-54
lines changed

8 files changed

+200
-54
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# validator-ec
22

3-
**validator-ec** es una colección de validadores de datos para Ecuador desarrollada en TypeScript. Actualmente, solo cuenta con un validador para cédulas de identidad ecuatorianas pero se espera agregar más validadores en el futuro.
3+
**validator-ec** es una colección de validadores de datos para Ecuador desarrollada en TypeScript. Actualmente, cuenta con validadores para cédulas de identidad ecuatorianas y números de RUC (Registro Único de Contribuyentes).
44

55
## Características
66

@@ -32,9 +32,10 @@ npm install validator-ec
3232

3333
## Validadores
3434

35-
| Validador | Descripción | Ejemplo |
36-
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------ |
37-
| `isCedula(cedula: string): boolean` | Valida una cédula ecuatoriana de identidad. Devuelve `true` si la cédula es válida, de lo contrario `false`. | `isCedula('1710034065')` |
35+
| Validador | Descripción | Ejemplo |
36+
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
37+
| `isCedula(cedula: string): boolean` | Valida una cédula de identidad ecuatoriana. Devuelve `true` si la cédula es válida, de lo contrario `false`. | `isCedula('1710034065')` |
38+
| `isRUC(ruc: string): boolean` | Valida un número de RUC (Registro Único de Contribuyentes). Devuelve `true` si el RUC es válido, de lo contrario `false`. | `isRUC('1790016919001')` |
3839

3940
## Uso
4041

package.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "validator-ec",
3-
"version": "1.0.3",
3+
"version": "1.1.0",
44
"type": "module",
55
"author": "Victor Bayas",
66
"license": "Apache-2.0",
@@ -11,11 +11,19 @@
1111
"bugs": {
1212
"url": "https://github.com/bayasdev/validator-ec/issues"
1313
},
14+
"keywords": [
15+
"validator",
16+
"validador",
17+
"ecuador",
18+
"cedula",
19+
"ci",
20+
"identidad",
21+
"ruc"
22+
],
1423
"main": "./dist/index.cjs",
1524
"module": "./dist/index.js",
1625
"types": "./dist/index.d.ts",
1726
"exports": {
18-
"./package.json": "./package.json",
1927
".": {
2028
"import": {
2129
"types": "./dist/index.d.ts",
@@ -25,7 +33,8 @@
2533
"types": "./dist/index.d.cts",
2634
"default": "./dist/index.cjs"
2735
}
28-
}
36+
},
37+
"./package.json": "./package.json"
2938
},
3039
"files": [
3140
"dist"

src/helpers/isDigits.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Helper para validar que una cadena contenga únicamente dígitos.
2+
export default function isDigits(value: string): boolean {
3+
return /^\d+$/.test(value);
4+
}

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import isCedula from "./lib/isCedula";
2+
import isRUC from "./lib/isRUC";
23

3-
export { isCedula };
4+
export { isCedula, isRUC };

src/lib/isCedula.ts

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,62 @@
1-
export default function isCedula(cedula: string): boolean {
2-
// Validamos que la cédula solo contenga 10 dígitos
3-
if (cedula.length === 10) {
4-
// Definimos el último dígito o tambien llamado dígito verificador
5-
const lastDigit = parseInt(cedula[cedula.length - 1], 10);
6-
7-
// Definimos las variables a utilizar
8-
let evens = 0;
9-
let odds = 0;
10-
let sum = 0;
11-
12-
// Iteramos cada item excluyendo el último digito, aplicando el Algoritmo de Luhn
13-
for (let i = 1; i <= cedula.length - 1; i++) {
14-
if (i % 2 === 0) {
15-
evens += parseInt(cedula[i - 1]);
16-
} else {
17-
let x = parseInt(cedula[i - 1]) * 2;
18-
x > 9 ? (odds += x - 9) : (odds += x);
19-
}
20-
}
1+
import isDigits from "../helpers/isDigits";
212

22-
sum += evens + odds;
3+
export default function isCedula(cedula: string): boolean {
4+
// Validamos que la cédula tenga 10 dígitos
5+
if (cedula.length !== 10) {
6+
return false;
7+
}
238

24-
// Extraemos el primer digito de la suma
25-
const firstDigit = parseInt(sum.toString()[0], 10);
9+
// Verificamos que todos los caracteres sean dígitos
10+
if (!isDigits(cedula)) {
11+
return false;
12+
}
2613

27-
// Obtenemos la decena
28-
const dozen = (firstDigit + 1) * 10;
14+
// Definimos el último dígito o tambien llamado dígito verificador
15+
const lastDigit = parseInt(cedula[cedula.length - 1], 10);
2916

30-
// Obtenemos el dígito validador
31-
let validatorDigit = dozen - sum;
17+
// Definimos las variables a utilizar
18+
let evens = 0;
19+
let odds = 0;
20+
let sum = 0;
3221

33-
// Si el dígito verificador es mayor a 10 lo igualamos a 0
34-
if (validatorDigit >= 10) {
35-
validatorDigit = 0;
22+
// Iteramos cada item excluyendo el último digito, aplicando el Algoritmo de Luhn
23+
for (let i = 1; i <= cedula.length - 1; i++) {
24+
if (i % 2 === 0) {
25+
evens += parseInt(cedula[i - 1]);
26+
} else {
27+
let x = parseInt(cedula[i - 1]) * 2;
28+
x > 9 ? (odds += x - 9) : (odds += x);
3629
}
30+
}
3731

38-
// Codigo de provincia
39-
// Validamos si la cedula pertenece a alguna provincia
40-
const provinceCode = parseInt(cedula[0] + cedula[1]);
32+
sum += evens + odds;
4133

42-
// Valida cédulas locales y de Ecuatorianos en el exterior
43-
if (provinceCode > 24 && provinceCode != 30) {
44-
return false;
45-
}
34+
// Extraemos el primer digito de la suma
35+
const firstDigit = parseInt(sum.toString()[0], 10);
4636

47-
if (validatorDigit == lastDigit) {
48-
return true;
49-
} else {
50-
return false;
51-
}
52-
} else {
37+
// Obtenemos la decena
38+
const dozen = (firstDigit + 1) * 10;
39+
40+
// Obtenemos el dígito validador
41+
let validatorDigit = dozen - sum;
42+
43+
// Si el dígito verificador es mayor a 10 lo igualamos a 0
44+
if (validatorDigit >= 10) {
45+
validatorDigit = 0;
46+
}
47+
48+
// Codigo de provincia
49+
// Validamos si la cedula pertenece a alguna provincia
50+
const provinceCode = parseInt(cedula[0] + cedula[1]);
51+
52+
// Valida cédulas locales y de Ecuatorianos en el exterior
53+
if (provinceCode > 24 && provinceCode != 30) {
5354
return false;
5455
}
56+
57+
if (validatorDigit == lastDigit) {
58+
return true;
59+
}
60+
61+
return false;
5562
}

src/lib/isRUC.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import isDigits from "../helpers/isDigits";
2+
import isCedula from "./isCedula";
3+
4+
export default function isRUC(ruc: string): boolean {
5+
// Verificamos que el RUC tenga 13 dígitos
6+
if (ruc.length !== 13) {
7+
return false;
8+
}
9+
10+
// Verificamos que todos los caracteres sean dígitos
11+
if (!isDigits(ruc)) {
12+
return false;
13+
}
14+
15+
// Extraemos los primeros 10 dígitos (cédula o código base)
16+
const baseCode = ruc.substring(0, 10);
17+
18+
// Extraemos los últimos 3 dígitos (código de establecimiento)
19+
const establishmentCode = ruc.substring(10, 13);
20+
21+
// Verificamos que el código de establecimiento sea 001
22+
if (establishmentCode !== "001") {
23+
return false;
24+
}
25+
26+
// Obtenemos el tercer dígito para determinar el tipo de entidad
27+
// Fuente: https://www.sri.gob.ec/ruc-personas-naturales
28+
const thirdDigit = parseInt(ruc[2], 10);
29+
30+
// Validación para personas naturales (tercer dígito entre 0 y 5)
31+
if (thirdDigit >= 0 && thirdDigit <= 5) {
32+
// Validamos que los primeros 10 dígitos sean una cédula válida
33+
return isCedula(baseCode);
34+
}
35+
36+
// Validación para personas jurídicas (tercer dígito es 9)
37+
if (thirdDigit === 9) {
38+
return validateLegalEntityRUC(ruc);
39+
}
40+
41+
// Validación para entidades públicas (tercer dígito es 6)
42+
if (thirdDigit === 6) {
43+
return validatePublicEntityRUC(ruc);
44+
}
45+
46+
// Si el tercer dígito no es válido, el RUC es inválido
47+
return false;
48+
}
49+
50+
// Función para validar RUC de personas jurídicas
51+
function validateLegalEntityRUC(ruc: string): boolean {
52+
// Coeficientes para personas jurídicas
53+
const coefficients = [4, 3, 2, 7, 6, 5, 4, 3, 2];
54+
const baseCode = ruc.substring(0, 9);
55+
const validatorDigit = parseInt(ruc[9], 10);
56+
57+
let sum = 0;
58+
for (let i = 0; i < coefficients.length; i++) {
59+
const value = parseInt(baseCode[i], 10) * coefficients[i];
60+
sum += value;
61+
}
62+
63+
const remainder = sum % 11;
64+
const result = remainder === 0 ? 0 : 11 - remainder;
65+
66+
return result === validatorDigit;
67+
}
68+
69+
// Función para validar RUC de entidades públicas
70+
function validatePublicEntityRUC(ruc: string): boolean {
71+
// Coeficientes para entidades públicas
72+
const coefficients = [3, 2, 7, 6, 5, 4, 3, 2];
73+
const baseCode = ruc.substring(0, 8);
74+
const validatorDigit = parseInt(ruc[8], 10);
75+
76+
let sum = 0;
77+
for (let i = 0; i < coefficients.length; i++) {
78+
const value = parseInt(baseCode[i], 10) * coefficients[i];
79+
sum += value;
80+
}
81+
82+
const remainder = sum % 11;
83+
const result = remainder === 0 ? 0 : 11 - remainder;
84+
85+
return result === validatorDigit;
86+
}

tests/index.test.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, it, expect } from "vitest";
2-
import { isCedula } from "../src";
2+
import { isCedula, isRUC } from "../src";
33

4-
describe("Función isCedula", () => {
4+
describe("Validador isCedula", () => {
55
it("debería retornar true para una cédula válida", () => {
66
expect(isCedula("0151314416")).toBe(true); // Provincia 01 (Azuay)
77
expect(isCedula("0258192483")).toBe(true); // Provincia 02 (Bolívar)
@@ -62,3 +62,40 @@ describe("Función isCedula", () => {
6262
expect(isCedula("1710034069")).toBe(false);
6363
});
6464
});
65+
66+
describe("Validador isRUC", () => {
67+
it("debería retornar true para un RUC válido de persona natural", () => {
68+
expect(isRUC("1710034065001")).toBe(true);
69+
});
70+
71+
it("debería retornar true para un RUC válido de persona jurídica", () => {
72+
expect(isRUC("1790016919001")).toBe(true);
73+
expect(isRUC("0990004196001")).toBe(true);
74+
expect(isRUC("0190072002001")).toBe(true);
75+
expect(isRUC("1390012949001")).toBe(true);
76+
});
77+
78+
it("debería retornar true para un RUC válido de entidad pública", () => {
79+
expect(isRUC("1760001550001")).toBe(true);
80+
});
81+
82+
it("debería retornar false para un RUC inválido", () => {
83+
expect(isRUC("1710034065000")).toBe(false);
84+
expect(isRUC("0990001099000")).toBe(false);
85+
expect(isRUC("1760001550000")).toBe(false);
86+
});
87+
88+
it("debería retornar false si el RUC no tiene 13 dígitos", () => {
89+
expect(isRUC("17100340650")).toBe(false);
90+
expect(isRUC("17100340650011")).toBe(false);
91+
});
92+
93+
it("debería retornar false para RUCs con caracteres no numéricos", () => {
94+
expect(isRUC("17A0034065001")).toBe(false);
95+
expect(isRUC("17100X4065001")).toBe(false);
96+
});
97+
98+
it("debería retornar false si el código de establecimiento no es 001", () => {
99+
expect(isRUC("1710034065000")).toBe(false);
100+
});
101+
});

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@
2323
"noUnusedLocals": false,
2424
"noUnusedParameters": false,
2525
"noPropertyAccessFromIndexSignature": false
26-
}
26+
},
27+
"exclude": ["dist"]
2728
}

0 commit comments

Comments
 (0)