Skip to content

Commit f9f77b8

Browse files
authored
Merge pull request #126 from Ahuahuachi/master
A new utility `CfdiUtils\Utils\RegimenCapitalRemover` has been created to remove the *Régimen de Capital* from a string. The most common case is to remove it from a certificate name, but you can use it anywhere. This release includes a change to `Certificado::getName()` method. The argument `$trimSuffix` was added, with default `false`. When you pass `$trimSuffix: true`, it will try to remove the *Régimen de capital* suffix from the name detected on the certificate. For example: `EMPRESA PATITO SA DE CV` will return `EMPRESA PATITO`. The method `CfdiCreator40::putCertificado()` now evaluates if the certificate belongs to a *Persona Moral*, and the root node has attribute `Version` equal to `4.0`. If that is the case then it will set up the `Emisor@Name` attribute removing the suffix. That means that is no longer necessary to override the `Emisor@Name` later. The assertion `SELLO04: El nombre del emisor del comprobante es igual al encontrado en el certificado` has been changed, previously, if the RFC belongs to a *Persona Moral* then it returns status `none`. Now, the assertion uses the "remove the *Régimen de capital*" capability to perform the validation. Thanks @Ahuahuachi for the contribution.
2 parents 77bfaa7 + 12f224d commit f9f77b8

25 files changed

+332
-54
lines changed

docs/CHANGELOG.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,28 @@
3333
- Remove deprecated constant `CfdiUtils\Retenciones\Retenciones::RET_NAMESPACE`.
3434
- Remove deprecated class `CfdiUtils\Utils\Crp20277Fixer`.
3535

36-
## Version 2.30.0 2024-06-18
36+
## Version 2.31.0 2025-02-19
37+
38+
A new utility `CfdiUtils\Utils\RegimenCapitalRemover` has been created to remove the *Régimen de Capital*
39+
from a string. The most common case is to remove it from a certificate name, but you can use it anywhere.
40+
41+
This release includes a change to `Certificado::getName()` method.
42+
The argument `$trimSuffix` was added, with default `false`.
43+
When you pass `$trimSuffix: true`, it will try to remove the *Régimen de capital* suffix from the
44+
name detected on the certificate. For example: `EMPRESA PATITO SA DE CV` will return `EMPRESA PATITO`.
45+
46+
The method `CfdiCreator40::putCertificado()` now evaluates if the certificate belongs to a *Persona Moral*,
47+
and the root node has attribute `Version` equal to `4.0`.
48+
If that is the case then it will set up the `Emisor@Name` attribute removing the suffix.
49+
That means that is no longer necessary to override the `Emisor@Name` later.
50+
51+
The assertion `SELLO04: El nombre del emisor del comprobante es igual al encontrado en el certificado`
52+
has been changed, previously, if the RFC belongs to a *Persona Moral* then it returns status `none`.
53+
Now, the assertion uses the "remove the *Régimen de capital*" capability to perform the validation.
54+
55+
Thanks `@ahuahuachi` for the contribution.
56+
57+
## Version 2.30.0 2025-02-14
3758

3859
This is a maintenance release to fix continuous integration.
3960

docs/crear/crear-cfdi-40.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Esta clase es una especie de pegamento de todas las pequeñas utilerías y estru
1515
- `putCertificado(Certificado $certificado, bool $putEmisorRfcNombre = true)`: Establece el valor de los atributos
1616
`NoCertificado` y `Certificado`, y si `$putEmisorRfcNombre` es verdadero entonces también establece el valor
1717
de `Rfc` y `Nombre` en el nodo `Emisor`.
18+
- En el caso del `Emisor`, se quita el prefijo relacionado al régimen de capital.
1819

1920
- `asXml(): string`: Genera y devuelve el contenido XML de la exportación del nodo `Comprobante`.
2021

@@ -61,7 +62,6 @@ $comprobante = $creator->comprobante();
6162

6263
// No agrego (aunque puedo) el Rfc y Nombre porque uso los que están establecidos en el certificado
6364
$comprobante->addEmisor([
64-
'Nombre' => 'ESCUELA KEMPER URGATE',
6565
'RegimenFiscal' => '601', // General de Ley Personas Morales
6666
]);
6767

@@ -246,10 +246,10 @@ $csd = Credential::openFiles($cerfile, $keyfile, $passPhrase);
246246
// helper de creación del cfdi
247247
$creator = new CfdiCreator40();
248248

249-
// poner el certificado y número del certificado
249+
// poner el certificado y número del certificado junto con los datos del emisor
250250
$creator->putCertificado(
251251
new Certificado($csd->certificate()->pem()),
252-
false // no establecer el RFC ni el nombre del emisor
252+
true // establecer RFC y el nombre del emisor sin régimen de capital
253253
);
254254

255255
// se construye el pre-cfdi ...

src/CfdiUtils/Certificado/Certificado.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use CfdiUtils\OpenSSL\OpenSSL;
66
use CfdiUtils\OpenSSL\OpenSSLPropertyTrait;
7+
use CfdiUtils\Utils\RegimenCapitalRemover;
78

89
class Certificado
910
{
@@ -36,6 +37,9 @@ class Certificado
3637
/** @var string */
3738
private $pemContents;
3839

40+
/** @var string|null */
41+
private $nameWithoutRegimenCapitalSuffix;
42+
3943
/**
4044
* Certificado constructor.
4145
*
@@ -80,6 +84,7 @@ public function __construct(string $filename, OpenSSL $openSSL = null)
8084
$this->certificateName = strval($data['name'] ?? '');
8185
$this->rfc = (string) strstr(($data['subject']['x500UniqueIdentifier'] ?? '') . ' ', ' ', true);
8286
$this->name = strval($data['subject']['name'] ?? '');
87+
$this->nameWithoutRegimenCapitalSuffix = null;
8388
$serial = new SerialNumber('');
8489
if (isset($data['serialNumberHex'])) {
8590
$serial->loadHexadecimal($data['serialNumberHex']);
@@ -180,9 +185,22 @@ public function getCertificateName(): string
180185
* Name (Razón Social) set when certificate was created
181186
* @return string
182187
*/
183-
public function getName(): string
188+
public function getName($trimSuffix = false): string
189+
{
190+
return (! $trimSuffix) ? $this->name : $this->getNameWithoutRegimenCapitalSuffix();
191+
}
192+
193+
/**
194+
* Name (Razón Social) set when certificate was created without *régimen de capital* suffix
195+
*/
196+
public function getNameWithoutRegimenCapitalSuffix(): string
184197
{
185-
return $this->name;
198+
if (null === $this->nameWithoutRegimenCapitalSuffix) {
199+
$remover = RegimenCapitalRemover::createDefault();
200+
$this->nameWithoutRegimenCapitalSuffix = $remover->remove($this->name);
201+
}
202+
203+
return $this->nameWithoutRegimenCapitalSuffix;
186204
}
187205

188206
/**

src/CfdiUtils/CfdiCreatorTrait.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ public function putCertificado(Certificado $certificado, bool $putEmisorRfcNombr
6363
if (null === $emisor) {
6464
$emisor = $this->comprobante->getEmisor();
6565
}
66+
$trimSuffix = 12 === mb_strlen($certificado->getRfc()) && '4.0' === $this->comprobante['Version'];
6667
$emisor->addAttributes([
67-
'Nombre' => $certificado->getName(),
68+
'Nombre' => $certificado->getName($trimSuffix),
6869
'Rfc' => $certificado->getRfc(),
6970
]);
7071
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php
2+
3+
namespace CfdiUtils\Utils;
4+
5+
use LogicException;
6+
7+
final class RegimenCapitalRemover
8+
{
9+
/**
10+
* @var list<string> List of "Régimen de Capital", taken from IMSS
11+
* @see https://altapatronalpresencial.imss.gob.mx/sapi/plantillaPatrones.do?method=initCapturaMoral
12+
*/
13+
private const SUFFIXES = [
14+
'A EN P',
15+
'AA',
16+
'ABP',
17+
'AC',
18+
'AF',
19+
'AFORE',
20+
'AG',
21+
'ALPR',
22+
'APL',
23+
'APN',
24+
'AR DE IC DE CV DE RL',
25+
'AR DE IC DE CV',
26+
'AR DE IC DE RI',
27+
'AR DE IC DE RL DE CV',
28+
'AR DE IC DE RL',
29+
'AR DE IC',
30+
'AR',
31+
'ART',
32+
'C POR A',
33+
'CEL',
34+
'EPE',
35+
'EPS',
36+
'FA',
37+
'FC DE RL',
38+
'I A S',
39+
'IAP',
40+
'IBP',
41+
'INSTITUCION DE ASISTENCIA SOCIAL PRIVADA',
42+
'INSTITUCION DE BANCA MULTIPLE',
43+
'INSTITUTO DE ASISTENCIA SOCIAL',
44+
'L',
45+
'MI',
46+
'ORGANIZACIONES AUXILIARES DE CREDITO',
47+
'P EN C',
48+
'RI',
49+
'RL Y CV',
50+
'S C DE P S DE R L DE C V',
51+
'S C P R DE R L',
52+
'S DE CV',
53+
'S DE I DE OL',
54+
'S DE P DE RL',
55+
'S DE PR DE RI',
56+
'S DE PR DE RS',
57+
'S DE RL ART',
58+
'S DE RL DE CV MI',
59+
'S DE RL DE CV',
60+
'S DE RL DE IP DE CV',
61+
'S DE RL DE IP',
62+
'S DE RL MI ART',
63+
'S DE RL MI DE CV',
64+
'S DE RL MI',
65+
'S DE RL',
66+
'S DE SS DE R L',
67+
'S DE SS DE RI',
68+
'S DE SS',
69+
'S EN C DE CV',
70+
'S EN C DE RI DE CV',
71+
'S EN C DE RI',
72+
'S EN C DE RL DE CV',
73+
'S EN C DE RL',
74+
'S EN C POR A DE CV',
75+
'S EN C POR A DE RI DE CV',
76+
'S EN C POR A DE RI',
77+
'S EN C POR A DE RL DE CV',
78+
'S EN C POR A DE RL',
79+
'S EN C POR A',
80+
'S EN C',
81+
'S EN CS DE CV',
82+
'S EN NC DE CV',
83+
'S EN NC DE RI DE CV',
84+
'S EN NC DE RI',
85+
'S EN NC DE RL DE CV',
86+
'S EN NC DE RL',
87+
'SA DE CV DE RL',
88+
'SA DE CV S DE I DE C',
89+
'SA DE CV SFOL',
90+
'SA DE CV SFP',
91+
'SA DE CV SIID PM',
92+
'SA DE CV SIID',
93+
'SA DE CV SIRV',
94+
'SA DE CV SOFOM ENR',
95+
'SA DE CV SOFOM ER',
96+
'SA DE CV',
97+
'SA DE RI DE CV',
98+
'SA DE RI',
99+
'SA DE RL DE CV',
100+
'SA DE RL',
101+
'SA SOFOM EN R',
102+
'SA SOFOM ER',
103+
'SA',
104+
'SAB DE CV',
105+
'SAB',
106+
'SAPI DE CV',
107+
'SAPI DE CV,SOFOM,ENR',
108+
'SAPI',
109+
'SAS DE CV',
110+
'SAS',
111+
'SC DE C DE B Y S DE RL DE CV',
112+
'SC DE C DE RL DE CV',
113+
'SC DE C DE RL',
114+
'SC DE C DE RS DE CV',
115+
'SC DE C DE RS',
116+
'SC DE CV DE RL',
117+
'SC DE CV',
118+
'SC DE P DE RL DE CV',
119+
'SC DE P DE RL',
120+
'SC DE P DE RS DE CV',
121+
'SC DE P DE RS',
122+
'SC DE RI',
123+
'SC DE RL DE CV',
124+
'SC DE RL DE IP Y CV',
125+
'SC DE RL MI',
126+
'SC DE RL',
127+
'SC DE RS DE CV',
128+
'SC DE RS',
129+
'SC DE S DE RL DE CV',
130+
'SC PBS RL',
131+
'SC',
132+
'SCAPRL de CV',
133+
'SCL (LIMITADA)',
134+
'SCL DE CV',
135+
'SCP',
136+
'SCP',
137+
'SCPC DE RL DE CV',
138+
'SCPR DE RL DE C V',
139+
'SCS (SUPLEMENTADA)',
140+
'SCU',
141+
'SGC',
142+
'SIEFORE',
143+
'SIID',
144+
'SIN TIPO DE SOCIEDAD',
145+
'SL',
146+
'SNC',
147+
'SOCIEDAD CIVIL UNIVERSAL',
148+
'SOCIEDAD COOPERATIVA DE RESPONSABILIDAD LIMITADA',
149+
'SOCIEDAD COOPERATIVA',
150+
'SOCIEDAD EN NOMBRE COLECTIVO',
151+
'SOCIEDAD MUTUALISTA DE SEGUROS DE VIDA O DE DAÑOS',
152+
'SOCIEDADES DE INVERSION COMUNES',
153+
'SOCIEDADES DE INVERSION DE CAPITALES',
154+
'SOCIEDADES DE INVERSIÓN',
155+
'SOFOL',
156+
'SP DE RL DE CV',
157+
'SPA DE RL DE CV',
158+
'SPR DE CV',
159+
'SPR DE RI DE CV',
160+
'SPR DE RI',
161+
'SPR DE RL DE CV',
162+
'SPR DE RL',
163+
'SPR DE RS DE CV',
164+
'SPR',
165+
'U DE C',
166+
'U DE E DE R L',
167+
'U DE E',
168+
'UE',
169+
'USPR DE RI',
170+
'USPR DE RL',
171+
];
172+
173+
/** @var string */
174+
private $regularExpression;
175+
176+
public function __construct(string ...$sufixes)
177+
{
178+
$sufixesPattern = implode('|', $sufixes);
179+
if ('' === $sufixesPattern) {
180+
throw new LogicException('No se han establecido sufijos para remover');
181+
}
182+
183+
$this->regularExpression = '/ +(' . $sufixesPattern . ')$/i';
184+
}
185+
186+
public static function createDefault(): self
187+
{
188+
return new self(...self::SUFFIXES);
189+
}
190+
191+
public function remove(string $name): string
192+
{
193+
return preg_replace($this->regularExpression, '', $name);
194+
}
195+
}

src/CfdiUtils/Validate/Cfdi40/Standard/SelloDigitalCertificado.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,12 @@ protected function validateNombre(string $emisorNombre, string $rfc)
3636
return;
3737
}
3838

39-
$isMoralPerson = 12 === mb_strlen($rfc);
40-
if ($isMoralPerson) {
41-
$explanation = 'No es posible realizar la validación en Personas Morales';
42-
$this->asserts->putStatus('SELLO04', Status::none(), $explanation);
43-
return;
44-
}
39+
// Remove régimen de capital from name when is "Persona Moral" only.
40+
$removeSuffixFromName = 12 === mb_strlen($rfc);
4541

4642
$this->asserts->putStatus(
4743
'SELLO04',
48-
Status::when($this->certificado->getName() === $emisorNombre),
44+
Status::when($this->certificado->getName($removeSuffixFromName) === $emisorNombre),
4945
sprintf('Nombre certificado: %s, Nombre comprobante: %s.', $this->certificado->getName(), $emisorNombre)
5046
);
5147
}

tests/CfdiUtilsTests/Certificado/CertificadoTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public function testConstructWithValidExample()
3939
]);
4040
$this->assertSame($certificateName, str_replace('\/', '/', $certificado->getCertificateName()));
4141
$this->assertSame('ESCUELA KEMPER URGATE SA DE CV', $certificado->getName());
42+
$this->assertSame('ESCUELA KEMPER URGATE', $certificado->getName(true));
4243
$this->assertSame('EKU9003173C9', $certificado->getRfc());
4344
$this->assertSame('30001000000500003416', $certificado->getSerial());
4445
$this->assertSame(

tests/CfdiUtilsTests/CfdiCreator40FromExistentTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ public function testPutCertificadoFromCreatorUsingNode()
2929
$xmlSource = strval(file_get_contents($this->utilAsset('cfdi40-real.xml')));
3030
$nodeSource = XmlNodeUtils::nodeFromXmlString($xmlSource);
3131
$creator = CfdiCreator40::newUsingNode($nodeSource);
32-
$creator->putCertificado(new Certificado($this->utilAsset('certs/EKU9003173C9.cer')), true);
32+
$creator->putCertificado(new Certificado($this->utilAsset('certs/EKU9003173C9.cer')));
3333

3434
$comprobante = $creator->comprobante();
3535
$this->assertCount(1, $comprobante->searchNodes('cfdi:Emisor'));
36-
$this->assertSame('ESCUELA KEMPER URGATE SA DE CV', $comprobante->searchAttribute('cfdi:Emisor', 'Nombre'));
36+
$this->assertSame('ESCUELA KEMPER URGATE', $comprobante->searchAttribute('cfdi:Emisor', 'Nombre'));
3737
$this->assertSame('EKU9003173C9', $comprobante->searchAttribute('cfdi:Emisor', 'Rfc'));
3838
}
3939
}

tests/CfdiUtilsTests/CfdiValidator40Test.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ public function testValidateWithCorrectData()
4141
$cfdi = Cfdi::newFromString(strval(file_get_contents($cfdiFile)));
4242

4343
// install PAC testing certificate
44-
$this->installCertificate($this->utilAsset('certs/30001000000400002495.cer'));
44+
$this->installCertificate($this->utilAsset('certs/30001000000500003456.cer'));
4545

4646
$validator = new CfdiValidator40();
4747
$asserts = $validator->validate($cfdi->getSource(), $cfdi->getNode());
48+
print_r($asserts->errors());
4849
$this->assertFalse(
4950
$asserts->hasErrors(),
5051
'The validation of an expected cfdi40 valid file fails,'
@@ -66,7 +67,7 @@ public function testValidateCfdi40Real()
6667
$cfdi = Cfdi::newFromString(strval(file_get_contents($cfdiFile)));
6768

6869
// install PAC certificate, prevent if SAT service is down
69-
$this->installCertificate($this->utilAsset('00001000000504465028.cer'));
70+
$this->installCertificate($this->utilAsset('certs/00001000000708361114.cer'));
7071

7172
$validator = new CfdiValidator40($this->newResolver());
7273
$asserts = $validator->validate($cfdi->getSource(), $cfdi->getNode());

tests/CfdiUtilsTests/ConsultaCfdiSat/RequestParametersTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ public function testCreateFromCfdiVersion40()
9898
$parameters = RequestParameters::createFromCfdi($cfdi);
9999

100100
$this->assertSame('4.0', $parameters->getVersion());
101-
$this->assertSame('CSM190311AH6', $parameters->getRfcEmisor());
102-
$this->assertSame('MCI7306249Y1', $parameters->getRfcReceptor());
103-
$this->assertSame('04BF2854-FE7D-4377-9196-71248F060ABB', $parameters->getUuid());
104-
$this->assertStringEndsWith('Ggwa5tSZhA==', $parameters->getSello());
105-
$this->assertEqualsWithDelta(459.36, $parameters->getTotalFloat(), 0.001);
101+
$this->assertSame('ISD950921HE5', $parameters->getRfcEmisor());
102+
$this->assertSame('COSC8001137NA', $parameters->getRfcReceptor());
103+
$this->assertSame('C2832671-DA6D-11EF-A83D-00155D012007', $parameters->getUuid());
104+
$this->assertStringEndsWith('FoYRhNjeNw==', $parameters->getSello());
105+
$this->assertEqualsWithDelta(1000.00, $parameters->getTotalFloat(), 0.001);
106106
}
107107

108108
/**

0 commit comments

Comments
 (0)