Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions src/ValidadorEc.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,15 @@ private function performPrivateCompanyRucValidation(string $number): bool
$this->assertValidProvinceCode((int) substr($number, 0, 2));
$this->assertValidThirdDigit((int) $number[2], self::TYPE_RUC_PRIVATE);
$this->assertValidEstablishmentCode((int) substr($number, 10, 3));
$this->assertValidModulo11(
substr($number, 0, 9),
(int) $number[9],
self::MODULO_11_PRIVATE_COEFFICIENTS
);

// Skip modulo 11 check for extended sequential (7 digits) per SRI rules
if (!$this->hasExtendedSequential($number)) {
$this->assertValidModulo11(
substr($number, 0, 9),
(int) $number[9],
self::MODULO_11_PRIVATE_COEFFICIENTS
);
}
} catch (InvalidArgumentException $e) {
$this->error = $e->getMessage();

Expand Down Expand Up @@ -328,6 +332,42 @@ private function performPublicCompanyRucValidation(string $number): bool

// ==================== Helper Methods ====================

/**
* Check if private company RUC uses extended sequential (7 digits).
*
* Per SRI official communication, private company RUCs with sequential
* numbers exceeding 6 digits (>999999) do not have a validatable check
* digit. The modulo 11 validation should be skipped for these cases.
*
* Traditional structure (6-digit sequential):
* PP + 9 + SSSSSS + V + EEE
* Positions: 0-1 province, 2 type, 3-8 sequential (000001-999999), 9 check digit, 10-12 establishment
*
* Extended structure (7-digit sequential):
* PP + 9 + SSSSSSS + EEE
* Positions: 0-1 province, 2 type, 3-9 sequential (1000000+), 10-12 establishment
*
* Detection logic: Extended sequentials start at 1000000, meaning position 3 must be '1'-'9'
* AND positions 4-9 must form a value that makes the total 7-digit number >= 1000000.
* If position 3 is '0', the sequential is at most 0XXXXXX which is always < 1000000 (traditional).
*/
private function hasExtendedSequential(string $number): bool
{
// If position 3 is '0', sequential area starts with 0, making it traditional format
// (max value would be 0999999 which is < 1000000)
if ($number[3] === '0') {
return false;
}

// For position 3 >= '1', check if the 7-digit value at positions 3-9 is >= 1000000
// This handles extended sequentials (1000000-9999999)
// Note: Traditional RUCs with high sequential (100000-999999) + check digit
// will also match this condition, but SRI's rule applies to all such cases
$sevenDigitValue = (int) substr($number, 3, 7);

return $sevenDigitValue >= 1000000;
}

private function isValidFormat(string $number): bool
{
if ($number === '') {
Expand Down
41 changes: 40 additions & 1 deletion tests/PrivateCompanyRucValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ public function test_establishment_code_zero_fails(): void

public function test_invalid_check_digit_fails(): void
{
$this->assertFalse($this->validator->validatePrivateCompanyRuc('0992397532001'));
// Use RUC with 6-digit sequential (<=999999) to ensure check digit validation runs
// 0990999996001 is valid (check digit 6), 0990999997001 has wrong check digit
$this->assertFalse($this->validator->validatePrivateCompanyRuc('0990999997001'));
$this->assertEquals('Check digit validation failed', $this->validator->getError());
}

Expand All @@ -101,4 +103,41 @@ public function test_multiple_establishments_are_valid(): void
$this->assertTrue($this->validator->validatePrivateCompanyRuc('0992397535002'));
$this->assertTrue($this->validator->validatePrivateCompanyRuc('0992397535999'));
}

// ==================== Extended Sequential RUC Tests ====================

public function test_valid_private_company_ruc_with_extended_sequential(): void
{
// RUC with 7-digit sequential (>999999)
// Per SRI rules, these RUCs don't have a validatable check digit
$this->assertTrue($this->validator->validatePrivateCompanyRuc('1791000001001'));
}

public function test_extended_sequential_ruc_detected_correctly_by_validate(): void
{
$this->assertTrue($this->validator->validate('1791000001001'));
$this->assertEquals('ruc_private', $this->validator->getDocumentType());
}

public function test_traditional_6digit_sequential_still_validates_check_digit(): void
{
// Traditional RUC with 6-digit sequential (<=999999) should still validate check digit
// 0990999996001 is valid (check digit 6), 0990999998001 has wrong check digit
$this->assertFalse($this->validator->validatePrivateCompanyRuc('0990999998001'));
$this->assertEquals('Check digit validation failed', $this->validator->getError());
}

public function test_traditional_ruc_with_sequential_starting_with_zero(): void
{
// Sequential starting with 0 is always traditional (max 0999999 < 1000000)
// 0990999996001 has sequential 099999, check digit 6
$this->assertTrue($this->validator->validatePrivateCompanyRuc('0990999996001'));
}

public function test_extended_sequential_at_boundary_1000000(): void
{
// Sequential exactly 1000000 (first extended sequential)
// Province 09, type 9, sequential 1000000, establishment 001
$this->assertTrue($this->validator->validatePrivateCompanyRuc('0991000000001'));
}
}