From 21838c0c3ce43d7e10101d406a13b26a69c44ff3 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 12:47:28 -0600 Subject: [PATCH 01/25] Fix link syntax --- CODE_OF_CONDUCT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 2992f411..57975307 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,8 +55,8 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at eclipxe13@gmail.com. All -complaints will be reviewed and investigated and will result in a response that +reported by contacting the project team at [eclipxe13@gmail.com](mailto:eclipxe13@gmail.com). +All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. From d06b46d0c7e736d570fdba8460201bd60a09cd16 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 12:48:32 -0600 Subject: [PATCH 02/25] Fix precfdi term to *Pre-CFDI* --- docs/leer/leer-cfdi-retenciones.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/leer/leer-cfdi-retenciones.md b/docs/leer/leer-cfdi-retenciones.md index 768b7ac2..b77ba2ef 100644 --- a/docs/leer/leer-cfdi-retenciones.md +++ b/docs/leer/leer-cfdi-retenciones.md @@ -40,8 +40,8 @@ a un PAC para poderlos emitir. Por otro lado, el SAT no les ha exigido a los PAC que cuenten con herramientas gratuitas para elaborarlos, por lo que generalmente se requerirá contratar el uso de una aplicación o bien contratar los servicios de timbrado. -Con `CfdiUtils` podrás generar el *precfdi* y mandarlo timbrar con tu PAC habitual, el PAC te devolverá el *cfdi* -incluyendo el timbre fiscal digital y este es el comprobante legal. +Con `CfdiUtils` podrás generar el *Pre-CFDI* y mandarlo timbrar con tu PAC habitual, el PAC te devolverá el *CFDI* +incluyendo el *Timbre Fiscal Digital* y este es el comprobante legal. ### Más información From 4e78b10c63ca81ac519a41f24bbf2305bb3bbc4c Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 12:48:46 -0600 Subject: [PATCH 03/25] Update markdownlint-cli tool --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce94dc16..f24ed0c9 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "devDependencies": { - "markdownlint-cli": "^0.31.1" + "markdownlint-cli": "^0.34.0" } } From b84a381abaeb44c83e82cd38085345638dabe118 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 13:21:07 -0600 Subject: [PATCH 04/25] =?UTF-8?q?Add=20complement=20Servicios=20parciales?= =?UTF-8?q?=20de=20construcci=C3=B3n=201.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parcialescontruccion10.json | 12 +++++++ .../ParcialesContruccion10/Inmueble.php | 13 +++++++ .../ParcialesConstruccion.php | 36 +++++++++++++++++++ .../ParcialesContruccion10Test.php | 30 ++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 development/ElementsMaker/specifications/parcialescontruccion10.json create mode 100644 src/CfdiUtils/Elements/ParcialesContruccion10/Inmueble.php create mode 100644 src/CfdiUtils/Elements/ParcialesContruccion10/ParcialesConstruccion.php create mode 100644 tests/CfdiUtilsTests/Elements/ParcialesContruccion10/ParcialesContruccion10Test.php diff --git a/development/ElementsMaker/specifications/parcialescontruccion10.json b/development/ElementsMaker/specifications/parcialescontruccion10.json new file mode 100644 index 00000000..b0391606 --- /dev/null +++ b/development/ElementsMaker/specifications/parcialescontruccion10.json @@ -0,0 +1,12 @@ +{ + "php-namespace": "CfdiUtils\\Elements\\ParcialesContruccion10", + "prefix": "servicioparcial", + "xml-namespace": "http://www.sat.gob.mx/servicioparcialconstruccion", + "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/servicioparcialconstruccion/servicioparcialconstruccion.xsd", + "version-attribute": "Version", + "version-value": "1.0", + "root-element": "parcialesconstruccion", + "structure": { + "Inmueble": {} + } +} diff --git a/src/CfdiUtils/Elements/ParcialesContruccion10/Inmueble.php b/src/CfdiUtils/Elements/ParcialesContruccion10/Inmueble.php new file mode 100644 index 00000000..84fcfe85 --- /dev/null +++ b/src/CfdiUtils/Elements/ParcialesContruccion10/Inmueble.php @@ -0,0 +1,13 @@ + 'http://www.sat.gob.mx/servicioparcialconstruccion', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/servicioparcialconstruccion' + . ' http://www.sat.gob.mx/sitio_internet/cfd' + . '/servicioparcialconstruccion/servicioparcialconstruccion.xsd', + 'Version' => '1.0', + ]; + } + + public function getInmueble(): Inmueble + { + return $this->helperGetOrAdd(new Inmueble()); + } + + public function addInmueble(array $attributes = []): Inmueble + { + $subject = $this->getInmueble(); + $subject->addAttributes($attributes); + return $subject; + } +} diff --git a/tests/CfdiUtilsTests/Elements/ParcialesContruccion10/ParcialesContruccion10Test.php b/tests/CfdiUtilsTests/Elements/ParcialesContruccion10/ParcialesContruccion10Test.php new file mode 100644 index 00000000..511271ee --- /dev/null +++ b/tests/CfdiUtilsTests/Elements/ParcialesContruccion10/ParcialesContruccion10Test.php @@ -0,0 +1,30 @@ +assertElementHasName($element, 'servicioparcial:parcialesconstruccion'); + $this->assertElementHasFixedAttributes($element, [ + 'xmlns:servicioparcial' => 'http://www.sat.gob.mx/servicioparcialconstruccion', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/servicioparcialconstruccion' + . ' http://www.sat.gob.mx/sitio_internet/cfd' + . '/servicioparcialconstruccion/servicioparcialconstruccion.xsd', + 'Version' => '1.0', + ]); + $this->assertElementHasChildSingle($element, Inmueble::class); + } + + public function testInmueble(): void + { + $element = new Inmueble(); + $this->assertElementHasName($element, 'servicioparcial:Inmueble'); + } +} From 8c61af6326e664b4607471e20e729704ed1fbcc0 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 12:24:36 -0600 Subject: [PATCH 05/25] Add bcmath extension as a suggestion and development --- .github/workflows/build.yml | 6 +++--- composer.json | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cef5463d..de662e1b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,7 +85,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '8.2' - extensions: soap, intl, xsl, fileinfo + extensions: soap, intl, xsl, fileinfo, bcmath coverage: none tools: composer:v2, phpstan env: @@ -128,7 +128,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: soap, intl, xsl, fileinfo + extensions: soap, intl, xsl, fileinfo, bcmath coverage: xdebug tools: composer:v2 env: @@ -175,7 +175,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: soap, intl, xsl, fileinfo + extensions: soap, intl, xsl, fileinfo, bcmath coverage: none tools: composer:v2 env: diff --git a/composer.json b/composer.json index 0c256849..73ba353e 100644 --- a/composer.json +++ b/composer.json @@ -39,9 +39,11 @@ "eclipxe/xmlschemavalidator": "^3.0.2" }, "suggest": { + "ext-bcmath": "Allows calculate totals and taxes on Pagos 2.0", "genkgo/xsl": "Allows usage of Genkgo/Xsl transformations" }, "require-dev": { + "ext-bcmath": "*", "genkgo/xsl": "^1.0.8", "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.6", From ebf316361c217be26e6dcf9e8c5270afa969b48c Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 12:34:32 -0600 Subject: [PATCH 06/25] Add Pagos 2.0 Calculator --- src/CfdiUtils/SumasPagos20/Calculator.php | 141 ++++++++++++++++ src/CfdiUtils/SumasPagos20/Currencies.php | 22 +++ src/CfdiUtils/SumasPagos20/Decimal.php | 67 ++++++++ .../SumasPagos20/DoctoRelacionado.php | 31 ++++ src/CfdiUtils/SumasPagos20/Impuesto.php | 158 +++++++++++++++++ src/CfdiUtils/SumasPagos20/Impuestos.php | 118 +++++++++++++ src/CfdiUtils/SumasPagos20/Pago.php | 59 +++++++ src/CfdiUtils/SumasPagos20/Pagos.php | 44 +++++ src/CfdiUtils/SumasPagos20/Totales.php | 140 +++++++++++++++ .../SumasPagos20/CalculatorTest.php | 159 ++++++++++++++++++ 10 files changed, 939 insertions(+) create mode 100644 src/CfdiUtils/SumasPagos20/Calculator.php create mode 100644 src/CfdiUtils/SumasPagos20/Currencies.php create mode 100644 src/CfdiUtils/SumasPagos20/Decimal.php create mode 100644 src/CfdiUtils/SumasPagos20/DoctoRelacionado.php create mode 100644 src/CfdiUtils/SumasPagos20/Impuesto.php create mode 100644 src/CfdiUtils/SumasPagos20/Impuestos.php create mode 100644 src/CfdiUtils/SumasPagos20/Pago.php create mode 100644 src/CfdiUtils/SumasPagos20/Pagos.php create mode 100644 src/CfdiUtils/SumasPagos20/Totales.php create mode 100644 tests/CfdiUtilsTests/SumasPagos20/CalculatorTest.php diff --git a/src/CfdiUtils/SumasPagos20/Calculator.php b/src/CfdiUtils/SumasPagos20/Calculator.php new file mode 100644 index 00000000..1b29c25b --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/Calculator.php @@ -0,0 +1,141 @@ +paymentTaxesTruncate = min(6, max(0, $paymentTaxesTruncate)); + $this->currencies = $currencies ?? new Currencies(['MXN' => 2, 'USD' => 2]); + } + + public function calculate(NodeInterface $nodePagos): Pagos + { + $pagos = []; + foreach ($nodePagos->searchNodes('pago20:Pago') as $nodePago) { + $pagos[] = $this->buildPago($nodePago); + } + + $totales = $this->buildTotales($pagos); + return new Pagos($totales, ...$pagos); + } + + private function buildPago(NodeInterface $nodePago): Pago + { + $sumMonto = new Decimal('0'); + $impuestos = new Impuestos(); + foreach ($nodePago->searchNodes('pago20:DoctoRelacionado') as $nodeDoctoRelacionado) { + $doctoRelacionado = $this->buildDoctoRelacionado($nodeDoctoRelacionado); + $sumMonto = $sumMonto->sum($doctoRelacionado->getImpPagado()); + $impuestos = $impuestos->aggregate($doctoRelacionado->getImpuestos()); + } + $montoMinimo = $sumMonto->truncate($this->currencies->get($nodePago['MonedaP'])); + $monto = (isset($nodePago['Monto'])) ? new Decimal($nodePago['Monto']) : $montoMinimo; + $impuestos = $impuestos->truncate($this->paymentTaxesTruncate); + $tipoCambioP = new Decimal($nodePago['TipoCambioP']); + return new Pago($monto, $montoMinimo, $tipoCambioP, $impuestos); + } + + private function buildDoctoRelacionado(NodeInterface $nodeDoctoRelacionado): DoctoRelacionado + { + $equivalenciaDr = new Decimal($nodeDoctoRelacionado['EquivalenciaDR']); + + $impPagado = new Decimal($nodeDoctoRelacionado['ImpPagado']); + $impPagado = $impPagado->divide($equivalenciaDr); + + $traslados = $this->processImpuestosTraslados( + $equivalenciaDr, + $nodeDoctoRelacionado->searchNodes('pago20:ImpuestosDR', 'pago20:TrasladosDR', 'pago20:TrasladoDR') + ); + $retenciones = $this->processImpuestosRetenciones( + $equivalenciaDr, + $nodeDoctoRelacionado->searchNodes('pago20:ImpuestosDR', 'pago20:RetencionesDR', 'pago20:RetencionDR') + ); + $impuestos = new Impuestos(...$traslados, ...$retenciones); + + return new DoctoRelacionado($impPagado, $impuestos); + } + + /** @return array */ + private function processImpuestosTraslados(Decimal $equivalenciaDr, Nodes $nodeImpuestos): array + { + $impuestos = []; + foreach ($nodeImpuestos as $nodeImpuesto) { + $impuesto = new Impuesto( + 'Traslado', + $nodeImpuesto['ImpuestoDR'], + $nodeImpuesto['TipoFactorDR'], + $nodeImpuesto['TasaOCuotaDR'], + new Decimal($nodeImpuesto['BaseDR']), + new Decimal($nodeImpuesto['ImporteDR']) + ); + $impuesto = $impuesto->divide($equivalenciaDr); + $impuestos[] = $impuesto; + } + return $impuestos; + } + + /** @return array */ + private function processImpuestosRetenciones(Decimal $equivalenciaDr, Nodes $nodeImpuestos): array + { + $impuestos = []; + foreach ($nodeImpuestos as $nodeImpuesto) { + $impuesto = new Impuesto( + 'Retencion', + $nodeImpuesto['ImpuestoDR'], + '', + '', + new Decimal('0'), + new Decimal($nodeImpuesto['ImporteDR']) + ); + $impuesto = $impuesto->divide($equivalenciaDr); + $impuestos[] = $impuesto; + } + return $impuestos; + } + + /** @param Pago[] $pagos */ + private function buildTotales(array $pagos): Totales + { + $total = new Decimal('0'); + $impuestos = new Impuestos(); + foreach ($pagos as $pago) { + $tipoCambioP = $pago->getTipoCambioP(); + $impuestos = $impuestos->aggregate($pago->getImpuestos()->multiply($tipoCambioP)); + $total = $total->sum($pago->getMonto()->multiply($tipoCambioP)); + } + $impuestos = $impuestos->round(2); // MXN + + $retencionIva = $impuestos->find('Retencion', '002'); + $retencionIsr = $impuestos->find('Retencion', '001'); + $retencionIeps = $impuestos->find('Retencion', '003'); + $trasladoIva16 = $impuestos->find('Traslado', '002', 'Tasa', '0.160000'); + $trasladoIva08 = $impuestos->find('Traslado', '002', 'Tasa', '0.080000'); + $trasladoIva00 = $impuestos->find('Traslado', '002', 'Tasa', '0.000000'); + $trasladoIvaEx = $impuestos->find('Traslado', '002', 'Exento'); + + return new Totales( + $retencionIva ? $retencionIva->getImporte() : null, + $retencionIsr ? $retencionIsr->getImporte() : null, + $retencionIeps ? $retencionIeps->getImporte() : null, + $trasladoIva16 ? $trasladoIva16->getBase() : null, + $trasladoIva16 ? $trasladoIva16->getImporte() : null, + $trasladoIva08 ? $trasladoIva08->getBase() : null, + $trasladoIva08 ? $trasladoIva08->getImporte() : null, + $trasladoIva00 ? $trasladoIva00->getBase() : null, + $trasladoIva00 ? $trasladoIva00->getImporte() : null, + $trasladoIvaEx ? $trasladoIvaEx->getBase() : null, + $total->round(2) // MXN + ); + } +} diff --git a/src/CfdiUtils/SumasPagos20/Currencies.php b/src/CfdiUtils/SumasPagos20/Currencies.php new file mode 100644 index 00000000..398f1044 --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/Currencies.php @@ -0,0 +1,22 @@ + */ + private $currencyAllowedDecimals; + + /** @param array $currencyAllowedDecimals */ + public function __construct(array $currencyAllowedDecimals) + { + foreach ($currencyAllowedDecimals as $currency => $decimals) { + $this->currencyAllowedDecimals[$currency] = min(4, max(0, $decimals)); + } + } + + public function get($currency): int + { + return $this->currencyAllowedDecimals[$currency] ?? 2; + } +} diff --git a/src/CfdiUtils/SumasPagos20/Decimal.php b/src/CfdiUtils/SumasPagos20/Decimal.php new file mode 100644 index 00000000..82be2ee7 --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/Decimal.php @@ -0,0 +1,67 @@ +value = $value; + } + + public function getValue(): string + { + return $this->value; + } + + public function sum(self $other, int $scale = self::SCALE): self + { + return new self(bcadd($this->value, $other->value, $scale)); + } + + public function multiply(self $other, int $scale = self::SCALE): self + { + return new self(bcmul($this->value, $other->value, $scale)); + } + + public function divide(self $other, int $scale = self::SCALE): self + { + return new self(bcdiv($this->value, $other->value, $scale)); + } + + public function round(int $decimals): self + { + $exp = bcpow('10', strval($decimals + 1)); + $offset = (bccomp($this->value, '0', $decimals) < 0) ? '-5' : '5'; + return new self(bcdiv(bcadd(bcmul($this->value, $exp, 0), $offset), $exp, $decimals)); + } + + public function truncate(int $decimals): self + { + return new self(bcadd($this->value, '0', $decimals)); + } + + public function __toString(): string + { + return $this->value; + } + + public function jsonSerialize(): string + { + return $this->value; + } + + public function compareTo(self $other): int + { + return bccomp($this->value, $other->value, self::SCALE); + } +} diff --git a/src/CfdiUtils/SumasPagos20/DoctoRelacionado.php b/src/CfdiUtils/SumasPagos20/DoctoRelacionado.php new file mode 100644 index 00000000..ec468f6e --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/DoctoRelacionado.php @@ -0,0 +1,31 @@ +impPagado = $impPagado; + $this->impuestos = $impuestos; + } + + public function getImpPagado(): Decimal + { + return $this->impPagado; + } + + public function getImpuestos(): Impuestos + { + return $this->impuestos; + } +} diff --git a/src/CfdiUtils/SumasPagos20/Impuesto.php b/src/CfdiUtils/SumasPagos20/Impuesto.php new file mode 100644 index 00000000..153aa69c --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/Impuesto.php @@ -0,0 +1,158 @@ +tipo = $tipo; + $this->impuesto = $impuesto; + $this->tipoFactor = $tipoFactor; + $this->tasaCuota = $tasaCuota; + $this->base = $base; + $this->importe = $importe; + } + + public static function buildKey(string $tipo, string $impuesto, string $tipoFactor, string $tasaCuota): string + { + if ('Retencion' === $tipo) { + return sprintf('T:%s|I:%s', $tipo, $impuesto); + } + return sprintf('T:%s|I:%s|F:%s|C:%s', $tipo, $impuesto, $tipoFactor, $tasaCuota); + } + + public function getKey(): string + { + return $this->buildKey($this->tipo, $this->impuesto, $this->tipoFactor, $this->tasaCuota); + } + + public function getTipo(): string + { + return $this->tipo; + } + + public function getImpuesto(): string + { + return $this->impuesto; + } + + public function getTipoFactor(): string + { + return $this->tipoFactor; + } + + public function getTasaCuota(): string + { + return $this->tasaCuota; + } + + public function getBase(): Decimal + { + return $this->base; + } + + public function getImporte(): Decimal + { + return $this->importe; + } + + public function add(self $other): self + { + return new self( + $this->tipo, + $this->impuesto, + $this->tipoFactor, + $this->tasaCuota, + $this->base->sum($other->base), + $this->importe->sum($other->importe), + ); + } + + public function truncate(int $decimals): self + { + return new self( + $this->tipo, + $this->impuesto, + $this->tipoFactor, + $this->tasaCuota, + $this->base->truncate($decimals), + $this->importe->truncate($decimals), + ); + } + + public function multiply(Decimal $factor): self + { + return new self( + $this->tipo, + $this->impuesto, + $this->tipoFactor, + $this->tasaCuota, + $this->base->multiply($factor), + $this->importe->multiply($factor), + ); + } + + public function divide(Decimal $factor): self + { + return new self( + $this->tipo, + $this->impuesto, + $this->tipoFactor, + $this->tasaCuota, + $this->base->divide($factor), + $this->importe->divide($factor), + ); + } + + public function round(int $decimals): self + { + return new self( + $this->tipo, + $this->impuesto, + $this->tipoFactor, + $this->tasaCuota, + $this->base->round($decimals), + $this->importe->round($decimals), + ); + } + + /** @return array */ + public function jsonSerialize(): array + { + return [ + 'tipo' => $this->tipo, + 'impuesto' => $this->impuesto, + 'tipoFactor' => $this->tipoFactor, + 'tasaCuota' => $this->tasaCuota, + 'base' => $this->base, + 'importe' => $this->importe, + ]; + } +} diff --git a/src/CfdiUtils/SumasPagos20/Impuestos.php b/src/CfdiUtils/SumasPagos20/Impuestos.php new file mode 100644 index 00000000..794f1230 --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/Impuestos.php @@ -0,0 +1,118 @@ + */ + private $impuestos = []; + + public function __construct(Impuesto ...$impuestos) + { + foreach ($impuestos as $impuesto) { + $this->impuestos[$impuesto->getKey()] = $impuesto; + } + } + + public function find(string $tipo, string $impuesto, string $tipoFactor = '', string $tasaCuota = ''): ?Impuesto + { + $key = Impuesto::buildKey($tipo, $impuesto, $tipoFactor, $tasaCuota); + if (! isset($this->impuestos[$key])) { + return null; + } + return $this->impuestos[$key]; + } + + public function get(string $tipo, string $impuesto, string $tipoFactor = '', string $tasaCuota = ''): Impuesto + { + $impuesto = $this->find($tipo, $impuesto, $tipoFactor, $tasaCuota); + if (null === $impuesto) { + throw new LogicException(sprintf( + 'No se pudo encontrar el %s impuesto "%s", tipo factor "%s", tasa o cuota "%s"', + $tipo, + $impuesto, + $tipoFactor, + $tasaCuota + )); + } + return $impuesto; + } + + public function getTraslado(string $impuesto, string $tipoFactor, string $tasaCuota): Impuesto + { + return $this->get('Traslado', $impuesto, $tipoFactor, $tasaCuota); + } + + /** @return Impuesto[] */ + public function getRetenciones(): array + { + return $this->filterByTipo('Retencion'); + } + + /** @return Impuesto[] */ + public function getTraslados(): array + { + return $this->filterByTipo('Traslado'); + } + + /** @return Impuesto[] */ + private function filterByTipo(string $tipo): array + { + return array_values(array_filter( + $this->impuestos, + function (Impuesto $impuesto) use ($tipo): bool { + return $impuesto->getTipo() === $tipo; + } + )); + } + + public function getRetencion(string $impuesto): Impuesto + { + return $this->get('Retencion', $impuesto); + } + + public function aggregate(self $other): self + { + $impuestos = $this->impuestos; + foreach ($other->impuestos as $key => $impuesto) { + $impuestos[$key] = (isset($impuestos[$key])) ? $impuesto->add($impuestos[$key]) : $impuesto; + } + return new self(...$impuestos); + } + + public function truncate(int $decimals): self + { + $impuestos = $this->impuestos; + foreach ($impuestos as $key => $impuesto) { + $impuestos[$key] = $impuesto->truncate($decimals); + } + return new self(...$impuestos); + } + + public function multiply(Decimal $value): self + { + $impuestos = $this->impuestos; + foreach ($impuestos as $key => $impuesto) { + $impuestos[$key] = $impuesto->multiply($value); + } + return new self(...$impuestos); + } + + public function round(int $decimals): self + { + $impuestos = $this->impuestos; + foreach ($impuestos as $key => $impuesto) { + $impuestos[$key] = $impuesto->round($decimals); + } + return new self(...$impuestos); + } + + /** @return array */ + public function jsonSerialize(): array + { + return $this->impuestos; + } +} diff --git a/src/CfdiUtils/SumasPagos20/Pago.php b/src/CfdiUtils/SumasPagos20/Pago.php new file mode 100644 index 00000000..7e261cfe --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/Pago.php @@ -0,0 +1,59 @@ +monto = $monto; + $this->montoMinimo = $montoMinimo; + $this->tipoCambioP = $tipoCambioP; + $this->impuestos = $impuestos; + } + + public function getMonto(): Decimal + { + return $this->monto; + } + + public function getMontoMinimo(): Decimal + { + return $this->montoMinimo; + } + + public function getTipoCambioP(): Decimal + { + return $this->tipoCambioP; + } + + public function getImpuestos(): Impuestos + { + return $this->impuestos; + } + + /** @return array */ + public function jsonSerialize(): array + { + return [ + 'monto' => $this->monto, + 'montoMinimo' => $this->montoMinimo, + 'tipoCambioP' => $this->tipoCambioP, + 'impuestos' => $this->impuestos, + ]; + } +} diff --git a/src/CfdiUtils/SumasPagos20/Pagos.php b/src/CfdiUtils/SumasPagos20/Pagos.php new file mode 100644 index 00000000..a8b322e4 --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/Pagos.php @@ -0,0 +1,44 @@ + */ + private $pagos; + + public function __construct(Totales $totales, Pago ...$pago) + { + $this->totales = $totales; + $this->pagos = array_values($pago); + } + + public function getTotales(): Totales + { + return $this->totales; + } + + /** @return list */ + public function getPagos(): array + { + return $this->pagos; + } + + public function getPago(int $index): Pago + { + return $this->pagos[$index]; + } + + public function jsonSerialize(): array + { + return [ + 'totales' => $this->totales, + 'pagos' => $this->pagos, + ]; + } +} diff --git a/src/CfdiUtils/SumasPagos20/Totales.php b/src/CfdiUtils/SumasPagos20/Totales.php new file mode 100644 index 00000000..7ad846f0 --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/Totales.php @@ -0,0 +1,140 @@ +retencionIva = $retencionIva; + $this->retencionIsr = $retencionIsr; + $this->retencionIeps = $retencionIeps; + $this->trasladoIva16Base = $trasladoIva16Base; + $this->trasladoIva16Importe = $trasladoIva16Importe; + $this->trasladoIva08Base = $trasladoIva08Base; + $this->trasladoIva08Importe = $trasladoIva08Importe; + $this->trasladoIva00Base = $trasladoIva00Base; + $this->trasladoIva00Importe = $trasladoIva00Importe; + $this->trasladoIvaExento = $trasladoIvaExento; + $this->total = $total; + } + + public function getRetencionIva(): ?Decimal + { + return $this->retencionIva; + } + + public function getRetencionIsr(): ?Decimal + { + return $this->retencionIsr; + } + + public function getRetencionIeps(): ?Decimal + { + return $this->retencionIeps; + } + + public function getTrasladoIva16Base(): ?Decimal + { + return $this->trasladoIva16Base; + } + + public function getTrasladoIva16Importe(): ?Decimal + { + return $this->trasladoIva16Importe; + } + + public function getTrasladoIva08Base(): ?Decimal + { + return $this->trasladoIva08Base; + } + + public function getTrasladoIva08Importe(): ?Decimal + { + return $this->trasladoIva08Importe; + } + + public function getTrasladoIva00Base(): ?Decimal + { + return $this->trasladoIva00Base; + } + + public function getTrasladoIva00Importe(): ?Decimal + { + return $this->trasladoIva00Importe; + } + + public function getTrasladoIvaExento(): ?Decimal + { + return $this->trasladoIvaExento; + } + + public function getTotal(): Decimal + { + return $this->total; + } + + /** @return array */ + public function jsonSerialize(): array + { + return array_filter([ + 'retencionIva' => $this->retencionIva, + 'retencionIsr' => $this->retencionIsr, + 'retencionIeps' => $this->retencionIeps, + 'trasladoIva16Base' => $this->trasladoIva16Base, + 'trasladoIva16Importe' => $this->trasladoIva16Importe, + 'trasladoIva08Base' => $this->trasladoIva08Base, + 'trasladoIva08Importe' => $this->trasladoIva08Importe, + 'trasladoIva00Base' => $this->trasladoIva00Base, + 'trasladoIva00Importe' => $this->trasladoIva00Importe, + 'trasladoIvaExento' => $this->trasladoIvaExento, + 'total' => $this->total, + ]); + } +} diff --git a/tests/CfdiUtilsTests/SumasPagos20/CalculatorTest.php b/tests/CfdiUtilsTests/SumasPagos20/CalculatorTest.php new file mode 100644 index 00000000..09655f27 --- /dev/null +++ b/tests/CfdiUtilsTests/SumasPagos20/CalculatorTest.php @@ -0,0 +1,159 @@ + + + + + + + + + + + + XML; + $pagos = XmlNodeUtils::nodeFromXmlString($xml); + + $calculator = new Calculator(); + $result = $calculator->calculate($pagos); + + $this->assertSame('0.14', (string) $result->getTotales()->getTotal()); + $this->assertSame('0.12', (string) $result->getTotales()->getTrasladoIva16Base()); + $this->assertSame('0.02', (string) $result->getTotales()->getTrasladoIva16Importe()); + + $impuesto = $result->getPago(0)->getImpuestos()->getTraslado('002', 'Tasa', '0.160000'); + $this->assertSame('0.123456', (string) $impuesto->getBase()); + $this->assertSame('0.019753', (string) $impuesto->getImporte()); + } + + public function testCalculateTwoDocuments(): void + { + $xml = <<< XML + + + + + + + + + + + + + + + + + + + XML; + $pagos = XmlNodeUtils::nodeFromXmlString($xml); + + $calculator = new Calculator(); + $result = $calculator->calculate($pagos); + + $this->assertSame('1.57', (string) $result->getTotales()->getTotal()); + $this->assertSame('1.36', (string) $result->getTotales()->getTrasladoIva16Base()); + $this->assertSame('0.22', (string) $result->getTotales()->getTrasladoIva16Importe()); + + $impuesto = $result->getPago(0)->getImpuestos()->getTraslado('002', 'Tasa', '0.160000'); + $this->assertSame('1.358024', (string) $impuesto->getBase()); + $this->assertSame('0.217283', (string) $impuesto->getImporte()); + } + + /** + * In the following case, also Pago::monto is greater than Pago::montoMinimo + */ + public function testCalculatePaymentUsdDoctosMxnAndUsd(): void + { + $xml = <<< XML + + + + + + + + + + + + + + + + + + + + + + + + + + + XML; + $pagos = XmlNodeUtils::nodeFromXmlString($xml); + + $calculator = new Calculator(); + $result = $calculator->calculate($pagos); + + $expectedJson = <<< JSON + { + "totales": { + "trasladoIva16Base": "63.22", + "trasladoIva16Importe": "10.11", + "trasladoIva00Base": "0.12", + "trasladoIva00Importe": "0.00", + "total": "89.47" + }, + "pagos": [ + { + "monto": "5.00", + "montoMinimo": "4.09", + "tipoCambioP": "17.8945", + "impuestos": { + "T:Traslado|I:002|F:Tasa|C:0.160000": { + "tipo": "Traslado", + "impuesto": "002", + "tipoFactor": "Tasa", + "tasaCuota": "0.160000", + "base": "3.532679", + "importe": "0.565228" + }, + "T:Traslado|I:002|F:Tasa|C:0.000000": { + "tipo": "Traslado", + "impuesto": "002", + "tipoFactor": "Tasa", + "tasaCuota": "0.000000", + "base": "0.006899", + "importe": "0.000000" + } + } + } + ] + } + JSON; + $this->assertJsonStringEqualsJsonString($expectedJson, json_encode($result)); + } +} From 45fcdd233d72cb3552d9965ed261009c65c29a46 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 12:44:37 -0600 Subject: [PATCH 07/25] Add Pagos 2.0 Writer (writes calculations) --- src/CfdiUtils/SumasPagos20/PagosWriter.php | 112 +++++++++++++++ .../CreateComprobantePagos40CaseTest.php | 133 ++++++++++++++++++ tests/assets/created-cfdi40-pago20-valid.xml | 34 +++++ 3 files changed, 279 insertions(+) create mode 100644 src/CfdiUtils/SumasPagos20/PagosWriter.php create mode 100644 tests/CfdiUtilsTests/CreateComprobantePagos40CaseTest.php create mode 100644 tests/assets/created-cfdi40-pago20-valid.xml diff --git a/src/CfdiUtils/SumasPagos20/PagosWriter.php b/src/CfdiUtils/SumasPagos20/PagosWriter.php new file mode 100644 index 00000000..320699a8 --- /dev/null +++ b/src/CfdiUtils/SumasPagos20/PagosWriter.php @@ -0,0 +1,112 @@ +pagos = $pagos; + } + + public static function calculateAndPut(ElementPagos $complementoPagos): Pagos + { + $calculator = new Calculator(); + $result = $calculator->calculate($complementoPagos); + + $writer = new self($complementoPagos); + $writer->put($result); + + return $result; + } + + public function put(Pagos $result): void + { + $this->writeTotales($result); + $this->writePagos($result); + } + + private function writeTotales(Pagos $pagoElement): void + { + $this->removeNodeIfExists($this->pagos, 'pago20:Totales'); + $totales = $pagoElement->getTotales(); + $this->pagos->addTotales([ + 'MontoTotalPagos' => $totales->getTotal(), + 'TotalRetencionesIVA' => $totales->getRetencionIva(), + 'TotalRetencionesISR' => $totales->getRetencionIsr(), + 'TotalRetencionesIEPS' => $totales->getRetencionIeps(), + 'TotalTrasladosBaseIVA16' => $totales->getTrasladoIva16Base(), + 'TotalTrasladosImpuestoIVA16' => $totales->getTrasladoIva16Importe(), + 'TotalTrasladosBaseIVA8' => $totales->getTrasladoIva08Base(), + 'TotalTrasladosImpuestoIVA8' => $totales->getTrasladoIva08Importe(), + 'TotalTrasladosBaseIVA0' => $totales->getTrasladoIva00Base(), + 'TotalTrasladosImpuestoIVA0' => $totales->getTrasladoIva00Importe(), + 'TotalTrasladosBaseIVAExento' => $totales->getTrasladoIvaExento(), + ]); + } + + private function writePagos(Pagos $pagos): void + { + foreach ($this->pagos->searchNodes('pago20:Pago') as $index => $pagoElement) { + if (! $pagoElement instanceof ElementPago) { + throw new LogicException( + sprintf('Cannot work with a pago20:Pago of class %s', get_class($pagoElement)) + ); + } + $pagoData = $pagos->getPago($index); + $this->writePago($pagoElement, $pagoData); + } + } + + public function writePago(ElementPago $pagoElement, Pago $pagoData): void + { + if (! isset($pagoElement['Monto'])) { + $pagoElement['Monto'] = $pagoData->getMontoMinimo(); + } + + $this->removeNodeIfExists($pagoElement, 'pago20:ImpuestosP'); + + $retenciones = $pagoData->getImpuestos()->getRetenciones(); + if ([] !== $retenciones) { + $retencionesElement = $pagoElement->getImpuestosP()->getRetencionesP(); + $retencionesElement->clear(); + foreach ($retenciones as $retencion) { + $retencionesElement->addRetencionP([ + 'ImpuestoP' => $retencion->getImpuesto(), + 'ImporteP' => $retencion->getImporte(), + ]); + } + } + + $traslados = $pagoData->getImpuestos()->getTraslados(); + if ([] !== $traslados) { + $trasladosElement = $pagoElement->getImpuestosP()->getTrasladosP(); + $trasladosElement->clear(); + foreach ($traslados as $traslado) { + $trasladosElement->addTrasladoP([ + 'ImpuestoP' => $traslado->getImpuesto(), + 'TipoFactorP' => $traslado->getTipoFactor(), + 'TasaOCuotaP' => $traslado->getTasaCuota(), + 'BaseP' => $traslado->getBase(), + 'ImporteP' => ('Exento' === $traslado->getTipoFactor()) ? null : $traslado->getImporte(), + ]); + } + } + } + + private function removeNodeIfExists(NodeInterface $node, string ...$searchPath): void + { + $elements = $node->searchNodes(...$searchPath); + foreach ($elements as $element) { + $node->children()->remove($element); + } + } +} diff --git a/tests/CfdiUtilsTests/CreateComprobantePagos40CaseTest.php b/tests/CfdiUtilsTests/CreateComprobantePagos40CaseTest.php new file mode 100644 index 00000000..5059b5ec --- /dev/null +++ b/tests/CfdiUtilsTests/CreateComprobantePagos40CaseTest.php @@ -0,0 +1,133 @@ +utilAsset('certs/EKU9003173C9.cer'); + $keyfile = $this->utilAsset('certs/EKU9003173C9.key.pem'); + $certificado = new Certificado($cerfile); + $fecha = strtotime('2023-06-13 14:15:16'); + $fechaPago = strtotime('2023-06-12 17:18:19'); + + $creator = new CfdiCreator40(); + $comprobante = $creator->comprobante(); + $comprobante->addAttributes([ + 'Fecha' => Format::datetime($fecha), + 'TipoDeComprobante' => 'P', // pago + 'LugarExpedicion' => '52000', + 'Moneda' => 'XXX', + 'Exportacion' => '01', + ]); + $creator->putCertificado($certificado, false); + + $comprobante->addEmisor([ + 'Nombre' => 'ESCUELA KEMPER URGATE', + 'Rfc' => 'EKU9003173C9', + 'RegimenFiscal' => '601', + ]); + $comprobante->addReceptor([ + 'Rfc' => 'COSC8001137NA', + 'Nombre' => 'CARLOS CORTES SOTO', + 'RegimenFiscalReceptor' => '605', + 'UsoCFDI' => 'CP01', + 'DomicilioFiscalReceptor' => '52000', + ]); + // The concepto *must* have this content + $comprobante->addConcepto([ + 'ClaveProdServ' => '84111506', + 'Cantidad' => '1', + 'ClaveUnidad' => 'ACT', + 'Descripcion' => 'Pago', + 'ValorUnitario' => '0', + 'Importe' => '0', + 'ObjetoImp' => '01', + ]); + + $complementoPagos = new Pagos(); + $pago = $complementoPagos->addPago([ + 'FechaPago' => Format::datetime($fechaPago), + 'FormaDePagoP' => '03', // transferencia + 'MonedaP' => 'MXN', + 'TipoCambioP' => '1', + 'Monto' => '15000.00', + 'NumOperacion' => '963852', + 'RfcEmisorCtaOrd' => 'BMI9704113PA', + 'CtaOrdenante' => '0001970000', + 'RfcEmisorCtaBen' => 'BBA830831LJ2', + 'CtaBeneficiario' => '0198005000', + ]); + + $pago->addDoctoRelacionado([ + 'IdDocumento' => '00000000-1111-2222-3333-00000000000A', + 'MonedaDR' => 'MXN', + 'EquivalenciaDR' => '1', + 'NumParcialidad' => '2', + 'ImpSaldoAnt' => '12000.00', + 'ImpPagado' => '12000.00', + 'ImpSaldoInsoluto' => '0', + 'ObjetoImpDR' => '02', + ])->getImpuestosDR()->getTrasladosDR()->addTrasladoDR([ + 'ImpuestoDR' => '002', + 'TipoFactorDR' => 'Tasa', + 'TasaOCuotaDR' => '0.160000', + 'BaseDR' => '10344.83', + 'ImporteDR' => '1655.17', + ]); + + $pago->addDoctoRelacionado([ + 'IdDocumento' => '00000000-1111-2222-3333-00000000000B', + 'MonedaDR' => 'MXN', + 'EquivalenciaDR' => '1', + 'NumParcialidad' => '1', + 'ImpSaldoAnt' => '10000.00', + 'ImpPagado' => '3000.00', + 'ImpSaldoInsoluto' => '7000.00', + 'ObjetoImpDR' => '02', + ])->getImpuestosDR()->getTrasladosDR()->addTrasladoDR([ + 'ImpuestoDR' => '002', + 'TipoFactorDR' => 'Tasa', + 'TasaOCuotaDR' => '0.160000', + 'BaseDR' => '2586.21', + 'ImporteDR' => '413.79', + ]); + + // add calculated values to pagos (totales, pagos montos y pagos impuestos) + PagosWriter::calculateAndPut($complementoPagos); + + // add the "complemento de pagos" ($complementoPagos) to the $comprobante + $comprobante->addComplemento($complementoPagos); + + // use this method (with 0 decimals) to add attributes + $creator->addSumasConceptos(null, 0); + + // add sello and validate to assert that the specimen does not have any errors + $creator->addSello('file://' . $keyfile, ''); + + // this is after add sello to probe that it did not change the cadena origen or the sello + $creator->moveSatDefinitionsToComprobante(); + + // perform validations, it should not have any error nor warnings + $findings = $creator->validate(); + + // print_r(['validation' => ['errors' => $findings->errors(), 'warnings' => $findings->warnings()]]); + + $this->assertFalse( + $findings->hasErrors() || $findings->hasWarnings(), + 'Created document must not contain errors, fix your test specimen' + ); + + // test that the file is the same as expected + /** @see tests/assets/created-cfdi40-pago20-valid.xml */ + $expectedFile = $this->utilAsset('created-cfdi40-pago20-valid.xml'); + $this->assertXmlStringEqualsXmlFile($expectedFile, $creator->asXml()); + } +} diff --git a/tests/assets/created-cfdi40-pago20-valid.xml b/tests/assets/created-cfdi40-pago20-valid.xml new file mode 100644 index 00000000..b6081889 --- /dev/null +++ b/tests/assets/created-cfdi40-pago20-valid.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6e66d14c6c0beb970a8fb6c504522c22508e42df Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 12:46:54 -0600 Subject: [PATCH 08/25] Add 8 cases from well known pagos values to test calculations --- .../CalculateFromCfdiCasesTest.php | 165 +++++++++ tests/assets/pagos20-calculator/001.xml | 68 ++++ tests/assets/pagos20-calculator/002.xml | 36 ++ tests/assets/pagos20-calculator/003.xml | 50 +++ tests/assets/pagos20-calculator/004.xml | 321 ++++++++++++++++++ tests/assets/pagos20-calculator/005.xml | 16 + tests/assets/pagos20-calculator/006.xml | 63 ++++ tests/assets/pagos20-calculator/007.xml | 287 ++++++++++++++++ tests/assets/pagos20-calculator/008.xml | 64 ++++ 9 files changed, 1070 insertions(+) create mode 100644 tests/CfdiUtilsTests/SumasPagos20/CalculateFromCfdiCasesTest.php create mode 100644 tests/assets/pagos20-calculator/001.xml create mode 100644 tests/assets/pagos20-calculator/002.xml create mode 100644 tests/assets/pagos20-calculator/003.xml create mode 100644 tests/assets/pagos20-calculator/004.xml create mode 100644 tests/assets/pagos20-calculator/005.xml create mode 100644 tests/assets/pagos20-calculator/006.xml create mode 100644 tests/assets/pagos20-calculator/007.xml create mode 100644 tests/assets/pagos20-calculator/008.xml diff --git a/tests/CfdiUtilsTests/SumasPagos20/CalculateFromCfdiCasesTest.php b/tests/CfdiUtilsTests/SumasPagos20/CalculateFromCfdiCasesTest.php new file mode 100644 index 00000000..9730a793 --- /dev/null +++ b/tests/CfdiUtilsTests/SumasPagos20/CalculateFromCfdiCasesTest.php @@ -0,0 +1,165 @@ +getNode()->searchNode('cfdi:Complemento', 'pago20:Pagos'); + if (null === $nodePagos) { + throw new LogicException(sprintf('File %s does not have a pago20:Pagos node', $cfdiFile)); + } + + $calculator = new Calculator(); + $pagos = $calculator->calculate($nodePagos); + // echo PHP_EOL, json_encode($pagos, JSON_PRETTY_PRINT); + + // totales + $nodeTotales = $nodePagos->searchNode('pago20:Totales'); + $totales = $pagos->getTotales(); + $this->assertSame($nodeTotales['MontoTotalPagos'], (string) $totales->getTotal()); + $this->checkAttributeDecimal($nodeTotales, 'TotalRetencionesIVA', $totales->getRetencionIva()); + $this->checkAttributeDecimal($nodeTotales, 'TotalRetencionesISR', $totales->getRetencionIsr()); + $this->checkAttributeDecimal($nodeTotales, 'TotalRetencionesIEPS', $totales->getRetencionIeps()); + $this->checkAttributeDecimal($nodeTotales, 'TotalTrasladosBaseIVA16', $totales->getTrasladoIva16Base()); + $this->checkAttributeDecimal($nodeTotales, 'TotalTrasladosImpuestoIVA16', $totales->getTrasladoIva16Importe()); + $this->checkAttributeDecimal($nodeTotales, 'TotalTrasladosBaseIVA8', $totales->getTrasladoIva08Base()); + $this->checkAttributeDecimal($nodeTotales, 'TotalTrasladosImpuestoIVA8', $totales->getTrasladoIva08Importe()); + $this->checkAttributeDecimal($nodeTotales, 'TotalTrasladosBaseIVA0', $totales->getTrasladoIva00Base()); + $this->checkAttributeDecimal($nodeTotales, 'TotalTrasladosImpuestoIVA0', $totales->getTrasladoIva00Importe()); + $this->checkAttributeDecimal($nodeTotales, 'TotalTrasladosBaseIVAExento', $totales->getTrasladoIvaExento()); + + $processedPagos = []; + // pago@monto, pago/impuestos + foreach ($nodePagos->searchNodes('pago20:Pago') as $index => $nodePago) { + $pago = $pagos->getPago($index); + $processedPagos[] = $pago; + $this->assertTrue($pago->getMontoMinimo()->compareTo(new Decimal($nodePago['Monto'])) <= 0); + $nodeRetenciones = $nodePago->searchNodes('pago20:ImpuestosP', 'pago20:RetencionesP', 'pago20:RetencionP'); + foreach ($nodeRetenciones as $nodeRetencion) { + $retencion = $pago->getImpuestos()->getRetencion($nodeRetencion['ImpuestoP']); + $this->checkDecimalEquals(new Decimal($nodeRetencion['ImporteP']), $retencion->getImporte(), ''); + } + $nodeTraslados = $nodePago->searchNodes('pago20:ImpuestosP', 'pago20:TrasladosP', 'pago20:TrasladoP'); + foreach ($nodeTraslados as $nodeTraslado) { + $traslado = $pago->getImpuestos()->getTraslado( + $nodeTraslado['ImpuestoP'], + $nodeTraslado['TipoFactorP'], + $nodeTraslado['TasaOCuotaP'] + ); + $this->checkDecimalEquals(new Decimal($nodeTraslado['BaseP']), $traslado->getBase(), ''); + $this->checkDecimalEquals(new Decimal($nodeTraslado['ImporteP']), $traslado->getImporte(), ''); + } + } + + // check the result does not have additional pago elements + $missingPagos = array_filter( + $pagos->getPagos(), + function (Pago $pago) use ($processedPagos): bool { + return ! in_array($pago, $processedPagos, true); + } + ); + $this->assertSame([], $missingPagos, 'The result contains pagos that has not been processed'); + } + + private function checkAttributeDecimal(NodeInterface $node, string $attribute, ?Decimal $value): void + { + if (! isset($node[$attribute])) { + $this->assertNull($value, "Since attribute $attribute exists, then value must exists"); + } else { + $this->checkDecimalEquals( + new Decimal($node[$attribute]), + $value, + "Attribute $attribute does not match with value" + ); + } + } + + private function checkDecimalEquals(Decimal $expected, Decimal $value, string $message = ''): void + { + $this->assertTrue(0 === $expected->compareTo($value), $message); + } +} diff --git a/tests/assets/pagos20-calculator/001.xml b/tests/assets/pagos20-calculator/001.xml new file mode 100644 index 00000000..31995bf1 --- /dev/null +++ b/tests/assets/pagos20-calculator/001.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/assets/pagos20-calculator/002.xml b/tests/assets/pagos20-calculator/002.xml new file mode 100644 index 00000000..1c46e39d --- /dev/null +++ b/tests/assets/pagos20-calculator/002.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/assets/pagos20-calculator/003.xml b/tests/assets/pagos20-calculator/003.xml new file mode 100644 index 00000000..8731bd43 --- /dev/null +++ b/tests/assets/pagos20-calculator/003.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/assets/pagos20-calculator/004.xml b/tests/assets/pagos20-calculator/004.xml new file mode 100644 index 00000000..aab254ac --- /dev/null +++ b/tests/assets/pagos20-calculator/004.xml @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/assets/pagos20-calculator/005.xml b/tests/assets/pagos20-calculator/005.xml new file mode 100644 index 00000000..209c784d --- /dev/null +++ b/tests/assets/pagos20-calculator/005.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/tests/assets/pagos20-calculator/006.xml b/tests/assets/pagos20-calculator/006.xml new file mode 100644 index 00000000..fec397fe --- /dev/null +++ b/tests/assets/pagos20-calculator/006.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/assets/pagos20-calculator/007.xml b/tests/assets/pagos20-calculator/007.xml new file mode 100644 index 00000000..423c370b --- /dev/null +++ b/tests/assets/pagos20-calculator/007.xml @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/assets/pagos20-calculator/008.xml b/tests/assets/pagos20-calculator/008.xml new file mode 100644 index 00000000..fd583528 --- /dev/null +++ b/tests/assets/pagos20-calculator/008.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 50e58767ada1e053f4bec927a46c68e3eabcd6c1 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 13:06:08 -0600 Subject: [PATCH 09/25] Add documentation for Pagos 2.0 calculator and writer --- docs/index.md | 1 + docs/utilerias/calculo-pagos20.md | 122 ++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 124 insertions(+) create mode 100644 docs/utilerias/calculo-pagos20.md diff --git a/docs/index.md b/docs/index.md index bd813524..6bba1f18 100644 --- a/docs/index.md +++ b/docs/index.md @@ -79,6 +79,7 @@ Solo hay métodos específicos para CFDI 3.3 y CFDI 4.0. ## Utilerías - [OpenSSL](utilerias/openssl.md) +- [Cálculo de CFDI con complemento de pagos 2.0](utilerias/calculo-pagos20.md) ## Contribuciones diff --git a/docs/utilerias/calculo-pagos20.md b/docs/utilerias/calculo-pagos20.md new file mode 100644 index 00000000..7562a7e4 --- /dev/null +++ b/docs/utilerias/calculo-pagos20.md @@ -0,0 +1,122 @@ +# Cálculo de CFDI con complemento de pagos 2.0 + +Es frecuente tener conflictos para hacer los cálculos de los atributos que van en el +*Complemento para recepción de Pagos versión 2.0*, para ello se ha creado la utilería +`CfdiUtils\SumasPagos20\Calculator`. + +En resumen, los datos calculados son: `Totales`, `Pago\Impuestos` y `Pago@MontoMínimo`. + +También se provee de un escritor de los datos de los cálculos `CfdiUtils\SumasPagos20\PagosWriter` +para escribir en el nodo del complemento de pagos. + +Importante: Para poder utilizar esta herramienta, es importante tener instalada la extensión +[`BCMath`](https://www.php.net/manual/en/book.bc.php). + +## Ejemplo de uso + +```php + 2, 'USD' => '2', 'EUR' => 2]) // Monedas con decimales +); +$result = $pagosCalculator->calculate($pagos); +$pagosWriter = new PagosWriter($pagos); +$pagosWriter->writePago($result); +``` + +## Origen de los datos + +Para hacer los cálculos, se require de forma general un *Pre-CFDI* con algunos datos armados: + +- `Pagos`: Nodo del complemento + - `Pago`: Nodo de un pago + - `@Monto`: Atributo que establece el monto del pago (opcional para el cálculo). + - `@MonedaP`: Moneda del pago (para saber los dígitos soportados). + - `@TipoCambioP`: Factor de conversión de la moneda del pago a MXN. + - `DocumentoRelacionado`: Nodo del documento relacionado. + - `@ImpPagado`: Monto pagado. + - `@EquivalenciaDR`: Factor de equivalencia de la moneda del documento a la moneda del pago. + - `ImpuestosDR`: Nodo de impuestos del documento relacionado, **con todos sus hijos y atributos**. + +## Cálculo y resultado + +La herramienta de cálculo obtiene los valores del *Pre-CFDI* y procesa cada elemento `Pago`, +cada elemento hijo `DocumentoRelacionado` y cada elemento hijo `ImpuestosDR\RetencionesDR\RetencionDR` +e `ImpuestosDR\TrasladosDR\TrasladoDR` para calcular *Totales* e *Información de pagos*. + +Los objetos devueltos son inmutables, en futuras versiones será reforzada esta característica. +Adicionalmente, los objetos son exportables a formato JSON para proveer una mejor experiencia de desarrollo. + +El resultado es un objeto `CfdiUtils\SumasPagos20\Pagos` que contiene: + +- `Pagos::getTotales(): Totales`: Información de totales. +- `Pagos::getPagos(): Pago[]`: Arreglo de pagos. +- `Pagos::getPago(int $index): Pago`: Obtiene un pago. + +El objeto `CfdiUtils\SumasPagos20\Totales` contiene: + +- `Totales::getRetencionIva(): Decimal|null`. +- `Totales::getRetencionIsr(): Decimal|null`. +- `Totales::getRetencionIeps(): Decimal|null`. +- `Totales::getTrasladoIva16Base(): Decimal|null`. +- `Totales::getTrasladoIva16Importe(): Decimal|null`. +- `Totales::getTrasladoIva08Base(): Decimal|null`. +- `Totales::getTrasladoIva08Importe(): Decimal|null`. +- `Totales::getTrasladoIva00Base(): Decimal|null`. +- `Totales::getTrasladoIva00Importe(): Decimal|null`. +- `Totales::getTrasladoIvaExento(): Decimal|null`. +- `Totales::getTotal(): Decimal`. + +En caso de devolver `null` significa que no existe información para establecer en el total. + +El objeto `Pago` contiene: + +- `Pago::getMonto(): Decimal`: Monto obtenido del nodo, o bien, el monto mínimo. +- `Pago::getMontoMinimo(): Decimal`: Monto mínimo que debe existir en el atributo `Pago@Monto`. +- `Pago::getTipoCambioP(): Decimal`: Tipo de cambio para MXN obtenido del atributo `Pago@Monto`. +- `Pago::getImpuestos(): Impuestos`: Conjunto de impuestos que deben existir en el nodo `Pago\ImpuestosP`. + +El objeto `Impuestos` contiene: + +- `Impuestos::getRetencion(string $impuesto): Impuesto`: Método para obtener una retención según su clave de impuesto. +- `Impuestos::getTraslado(string $impuesto, string $tipoFactor, string $tasaCuota): Impuesto`: + Método para obtener un traslado según su clave de impuesto, clave de tipo de factor y valor de tasa o cuota. + +El objeto `Impuesto` contiene: + +- `Impuesto::getBase(): Decimal`: Valor del monto base. +- `Impuesto::getImporte(): Decimal`: Valor del importe. + +El objeto `Decimal`: + +Este es un objeto especial para realizar operaciones matemáticas con precisión. +Requiere de la librería `BCMath`. + +- `strval(Decimal)`: `Decimal` es un `Stringable`, por lo que puede ser convertido a cadena de caracteres. +- `Decimal::round(int $decimals): Decimal`: obtiene el valor redondeado a un número de decimales. + +## Nota de `BCMath` + +Trabajar con números de punto flotante no es sencillo. +Para el 99% de los casos con simplemente tener cuidado al leer, escribir, comparar, y redondear es suficiente. +Sin embargo, este proceso resulta complicado para la función de truncado. + +Por ello, es mejor usar la estrategia de trabajar con los números como cadenas de caracteres y delegar las operaciones +matemáticas a la extensión `BCMath` *Arbitrary Precision Mathematics*. + +Existen otras extensiones y otras formas de hacer esta tarea, sin embargo, esta estrategia es de las más aceptadas +entre los desarrollos de PHP. diff --git a/mkdocs.yml b/mkdocs.yml index f40f3dc3..025cb91b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,6 +40,7 @@ nav: - componentes/certificado.md - "Utilerías": - utilerias/openssl.md + - utilerias/calculo-pagos20.md - "Contribuir": - contribuir/guia-desarrollador.md - contribuir/guia-documentador.md From 95bf0bad17897d61170dda5e78fd6da570dcd0a5 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 13:53:11 -0600 Subject: [PATCH 10/25] Fix PHP 7.3 compatibility (1st try) --- src/CfdiUtils/SumasPagos20/Calculator.php | 4 ++-- src/CfdiUtils/SumasPagos20/Impuestos.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/CfdiUtils/SumasPagos20/Calculator.php b/src/CfdiUtils/SumasPagos20/Calculator.php index 1b29c25b..e46bb401 100644 --- a/src/CfdiUtils/SumasPagos20/Calculator.php +++ b/src/CfdiUtils/SumasPagos20/Calculator.php @@ -66,7 +66,7 @@ private function buildDoctoRelacionado(NodeInterface $nodeDoctoRelacionado): Doc return new DoctoRelacionado($impPagado, $impuestos); } - /** @return array */ + /** @return list */ private function processImpuestosTraslados(Decimal $equivalenciaDr, Nodes $nodeImpuestos): array { $impuestos = []; @@ -85,7 +85,7 @@ private function processImpuestosTraslados(Decimal $equivalenciaDr, Nodes $nodeI return $impuestos; } - /** @return array */ + /** @return list */ private function processImpuestosRetenciones(Decimal $equivalenciaDr, Nodes $nodeImpuestos): array { $impuestos = []; diff --git a/src/CfdiUtils/SumasPagos20/Impuestos.php b/src/CfdiUtils/SumasPagos20/Impuestos.php index 794f1230..c3b7f3e3 100644 --- a/src/CfdiUtils/SumasPagos20/Impuestos.php +++ b/src/CfdiUtils/SumasPagos20/Impuestos.php @@ -80,7 +80,7 @@ public function aggregate(self $other): self foreach ($other->impuestos as $key => $impuesto) { $impuestos[$key] = (isset($impuestos[$key])) ? $impuesto->add($impuestos[$key]) : $impuesto; } - return new self(...$impuestos); + return new self(...array_values($impuestos)); } public function truncate(int $decimals): self @@ -89,7 +89,7 @@ public function truncate(int $decimals): self foreach ($impuestos as $key => $impuesto) { $impuestos[$key] = $impuesto->truncate($decimals); } - return new self(...$impuestos); + return new self(...array_values($impuestos)); } public function multiply(Decimal $value): self @@ -98,7 +98,7 @@ public function multiply(Decimal $value): self foreach ($impuestos as $key => $impuesto) { $impuestos[$key] = $impuesto->multiply($value); } - return new self(...$impuestos); + return new self(...array_values($impuestos)); } public function round(int $decimals): self @@ -107,7 +107,7 @@ public function round(int $decimals): self foreach ($impuestos as $key => $impuesto) { $impuestos[$key] = $impuesto->round($decimals); } - return new self(...$impuestos); + return new self(...array_values($impuestos)); } /** @return array */ From 71a1602d354d25ffd51af04c8a6082688f65671b Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 14:00:01 -0600 Subject: [PATCH 11/25] Refactor CfdiUtils\SumasPagos20\Impuestos --- src/CfdiUtils/SumasPagos20/Impuestos.php | 52 ++++++++++++++---------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/CfdiUtils/SumasPagos20/Impuestos.php b/src/CfdiUtils/SumasPagos20/Impuestos.php index c3b7f3e3..e27df423 100644 --- a/src/CfdiUtils/SumasPagos20/Impuestos.php +++ b/src/CfdiUtils/SumasPagos20/Impuestos.php @@ -17,6 +17,14 @@ public function __construct(Impuesto ...$impuestos) } } + /** @param array $impuestos */ + private static function withImpuestos(array $impuestos): self + { + $object = new self(); + $object->impuestos = $impuestos; + return $object; + } + public function find(string $tipo, string $impuesto, string $tipoFactor = '', string $tasaCuota = ''): ?Impuesto { $key = Impuesto::buildKey($tipo, $impuesto, $tipoFactor, $tasaCuota); @@ -41,34 +49,23 @@ public function get(string $tipo, string $impuesto, string $tipoFactor = '', str return $impuesto; } + /** @return list */ + public function getTraslados(): array + { + return $this->filterByTipo('Traslado'); + } + public function getTraslado(string $impuesto, string $tipoFactor, string $tasaCuota): Impuesto { return $this->get('Traslado', $impuesto, $tipoFactor, $tasaCuota); } - /** @return Impuesto[] */ + /** @return list */ public function getRetenciones(): array { return $this->filterByTipo('Retencion'); } - /** @return Impuesto[] */ - public function getTraslados(): array - { - return $this->filterByTipo('Traslado'); - } - - /** @return Impuesto[] */ - private function filterByTipo(string $tipo): array - { - return array_values(array_filter( - $this->impuestos, - function (Impuesto $impuesto) use ($tipo): bool { - return $impuesto->getTipo() === $tipo; - } - )); - } - public function getRetencion(string $impuesto): Impuesto { return $this->get('Retencion', $impuesto); @@ -80,7 +77,7 @@ public function aggregate(self $other): self foreach ($other->impuestos as $key => $impuesto) { $impuestos[$key] = (isset($impuestos[$key])) ? $impuesto->add($impuestos[$key]) : $impuesto; } - return new self(...array_values($impuestos)); + return self::withImpuestos($impuestos); } public function truncate(int $decimals): self @@ -89,7 +86,7 @@ public function truncate(int $decimals): self foreach ($impuestos as $key => $impuesto) { $impuestos[$key] = $impuesto->truncate($decimals); } - return new self(...array_values($impuestos)); + return self::withImpuestos($impuestos); } public function multiply(Decimal $value): self @@ -98,7 +95,7 @@ public function multiply(Decimal $value): self foreach ($impuestos as $key => $impuesto) { $impuestos[$key] = $impuesto->multiply($value); } - return new self(...array_values($impuestos)); + return self::withImpuestos($impuestos); } public function round(int $decimals): self @@ -107,7 +104,7 @@ public function round(int $decimals): self foreach ($impuestos as $key => $impuesto) { $impuestos[$key] = $impuesto->round($decimals); } - return new self(...array_values($impuestos)); + return self::withImpuestos($impuestos); } /** @return array */ @@ -115,4 +112,15 @@ public function jsonSerialize(): array { return $this->impuestos; } + + /** @return list */ + private function filterByTipo(string $tipo): array + { + return array_values(array_filter( + $this->impuestos, + function (Impuesto $impuesto) use ($tipo): bool { + return $impuesto->getTipo() === $tipo; + } + )); + } } From 2931dc638d693741b971af572f45731e845bb99e Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 14:05:55 -0600 Subject: [PATCH 12/25] Remove assets folder from GitHub code language detection --- .gitattributes | 3 +++ docs/CHANGELOG.md | 1 + 2 files changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index 89e0b42e..04118d18 100644 --- a/.gitattributes +++ b/.gitattributes @@ -27,3 +27,6 @@ /phpcs.xml.dist export-ignore /phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore + +# Do not count these files on github code language +/tests/assets/** linguist-detectable=false diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 013c2b5d..14cfc0aa 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -43,6 +43,7 @@ and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. Changes on development environment and documentation: +- Remove GitHub code language detection on `tests/assets/`. - Update `markdownlint-cli` tool. - Fix issues found by `markdownlint-cli`. From 7517676d0151dffd603713b9b31be90a78584155 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 14:15:42 -0600 Subject: [PATCH 13/25] Fix typo on classes and namespace s/Contruccion/Construccion/ --- .../specifications/parcialescontruccion10.json | 2 +- .../Inmueble.php | 2 +- .../ParcialesConstruccion.php | 2 +- .../ParcialesConstruccion10Test.php} | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/CfdiUtils/Elements/{ParcialesContruccion10 => ParcialesConstruccion10}/Inmueble.php (78%) rename src/CfdiUtils/Elements/{ParcialesContruccion10 => ParcialesConstruccion10}/ParcialesConstruccion.php (94%) rename tests/CfdiUtilsTests/Elements/{ParcialesContruccion10/ParcialesContruccion10Test.php => ParcialesConstruccion10/ParcialesConstruccion10Test.php} (74%) diff --git a/development/ElementsMaker/specifications/parcialescontruccion10.json b/development/ElementsMaker/specifications/parcialescontruccion10.json index b0391606..d475c3d1 100644 --- a/development/ElementsMaker/specifications/parcialescontruccion10.json +++ b/development/ElementsMaker/specifications/parcialescontruccion10.json @@ -1,5 +1,5 @@ { - "php-namespace": "CfdiUtils\\Elements\\ParcialesContruccion10", + "php-namespace": "CfdiUtils\\Elements\\ParcialesConstruccion10", "prefix": "servicioparcial", "xml-namespace": "http://www.sat.gob.mx/servicioparcialconstruccion", "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/servicioparcialconstruccion/servicioparcialconstruccion.xsd", diff --git a/src/CfdiUtils/Elements/ParcialesContruccion10/Inmueble.php b/src/CfdiUtils/Elements/ParcialesConstruccion10/Inmueble.php similarity index 78% rename from src/CfdiUtils/Elements/ParcialesContruccion10/Inmueble.php rename to src/CfdiUtils/Elements/ParcialesConstruccion10/Inmueble.php index 84fcfe85..d3a63067 100644 --- a/src/CfdiUtils/Elements/ParcialesContruccion10/Inmueble.php +++ b/src/CfdiUtils/Elements/ParcialesConstruccion10/Inmueble.php @@ -1,6 +1,6 @@ assertElementHasName($element, 'servicioparcial:parcialesconstruccion'); From 405f49d95f40b19b4835ffeb1902ae0d57b6d0e9 Mon Sep 17 00:00:00 2001 From: Blake Date: Mon, 20 Feb 2023 14:32:29 -0600 Subject: [PATCH 14/25] Se agrega maker y especificacioes --- .gitignore | 2 +- .../ConsumoDeCombustibles11.json | 19 ++++ .../specifications/Donatarias11.json | 11 +++ .../ElementsMaker/specifications/Iedu10.json | 11 +++ .../ElementsMaker/specifications/Ine11.json | 11 +++ .../specifications/LeyendasFiscales10.json | 14 +++ .../specifications/NotariosPublicos10.json | 22 +++++ .../ParcialesConstruccion10.json | 13 +++ development/maker.sh | 25 +++++ .../ConceptoConsumoDeCombustibles.php | 25 +++++ .../ConsumoDeCombustibles11/Conceptos.php | 28 ++++++ .../ConsumoDeCombustibles.php | 35 +++++++ .../ConsumoDeCombustibles11/Determinado.php | 13 +++ .../ConsumoDeCombustibles11/Determinados.php | 25 +++++ .../Elements/Donatarias11/Donatarias.php | 23 +++++ .../Elements/Iedu10/InstEducativas.php | 23 +++++ src/CfdiUtils/Elements/Ine11/Ine.php | 23 +++++ .../Elements/LeyendasFiscales10/Leyenda.php | 13 +++ .../LeyendasFiscales10/LeyendasFiscales.php | 38 ++++++++ .../NotariosPublicos10/DatosAdquiriente.php | 25 +++++ .../NotariosPublicos10/DatosEnajenante.php | 25 +++++ .../NotariosPublicos10/DatosNotario.php | 13 +++ .../NotariosPublicos10/DatosOperacion.php | 13 +++ .../NotariosPublicos10/DatosUnAdquiriente.php | 13 +++ .../NotariosPublicos10/DatosUnEnajenante.php | 13 +++ .../NotariosPublicos10/DescInmueble.php | 13 +++ .../NotariosPublicos10/DescInmuebles.php | 25 +++++ .../NotariosPublicos10/NotariosPublicos.php | 93 +++++++++++++++++++ 28 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 development/ElementsMaker/specifications/ConsumoDeCombustibles11.json create mode 100644 development/ElementsMaker/specifications/Donatarias11.json create mode 100644 development/ElementsMaker/specifications/Iedu10.json create mode 100644 development/ElementsMaker/specifications/Ine11.json create mode 100644 development/ElementsMaker/specifications/LeyendasFiscales10.json create mode 100644 development/ElementsMaker/specifications/NotariosPublicos10.json create mode 100644 development/ElementsMaker/specifications/ParcialesConstruccion10.json create mode 100644 development/maker.sh create mode 100644 src/CfdiUtils/Elements/ConsumoDeCombustibles11/ConceptoConsumoDeCombustibles.php create mode 100644 src/CfdiUtils/Elements/ConsumoDeCombustibles11/Conceptos.php create mode 100644 src/CfdiUtils/Elements/ConsumoDeCombustibles11/ConsumoDeCombustibles.php create mode 100644 src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinado.php create mode 100644 src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinados.php create mode 100644 src/CfdiUtils/Elements/Donatarias11/Donatarias.php create mode 100644 src/CfdiUtils/Elements/Iedu10/InstEducativas.php create mode 100644 src/CfdiUtils/Elements/Ine11/Ine.php create mode 100644 src/CfdiUtils/Elements/LeyendasFiscales10/Leyenda.php create mode 100644 src/CfdiUtils/Elements/LeyendasFiscales10/LeyendasFiscales.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquiriente.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenante.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosNotario.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosOperacion.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosUnAdquiriente.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosUnEnajenante.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DescInmueble.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DescInmuebles.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/NotariosPublicos.php diff --git a/.gitignore b/.gitignore index 94faf6bd..09c1d78b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ /composer.lock /node_modules /package-lock.json - +.DS_Store diff --git a/development/ElementsMaker/specifications/ConsumoDeCombustibles11.json b/development/ElementsMaker/specifications/ConsumoDeCombustibles11.json new file mode 100644 index 00000000..a6372851 --- /dev/null +++ b/development/ElementsMaker/specifications/ConsumoDeCombustibles11.json @@ -0,0 +1,19 @@ +{ + "php-namespace": "CfdiUtils\\Elements\\ConsumoDeCombustibles11", + "prefix": "consumodecombustibles11", + "xml-namespace": "http://www.sat.gob.mx/ConsumoDeCombustibles11", + "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/consumodecombustibles/consumodeCombustibles11.xsd", + "version-attribute": "version", + "version-value": "1.1", + "root-element": "ConsumoDeCombustibles", + "structure": { + "Conceptos": { + "ConceptoConsumoDeCombustibles": { + "multiple": true, + "Determinados": { + "Determinado": {} + } + } + } + } +} diff --git a/development/ElementsMaker/specifications/Donatarias11.json b/development/ElementsMaker/specifications/Donatarias11.json new file mode 100644 index 00000000..9c05f684 --- /dev/null +++ b/development/ElementsMaker/specifications/Donatarias11.json @@ -0,0 +1,11 @@ +{ + "php-namespace": "CfdiUtils\\Elements\\Donatarias11", + "prefix": "donat", + "xml-namespace": "http://www.sat.gob.mx/donat", + "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd", + "version-attribute": "version", + "version-value": "1.1", + "root-element": "Donatarias", + "structure": { + } +} diff --git a/development/ElementsMaker/specifications/Iedu10.json b/development/ElementsMaker/specifications/Iedu10.json new file mode 100644 index 00000000..d925df55 --- /dev/null +++ b/development/ElementsMaker/specifications/Iedu10.json @@ -0,0 +1,11 @@ +{ + "php-namespace": "CfdiUtils\\Elements\\Iedu10", + "prefix": "iedu", + "xml-namespace": "http://www.sat.gob.mx/iedu", + "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd", + "version-attribute": "version", + "version-value": "1.0", + "root-element": "InstEducativas", + "structure": { + } +} diff --git a/development/ElementsMaker/specifications/Ine11.json b/development/ElementsMaker/specifications/Ine11.json new file mode 100644 index 00000000..3402c382 --- /dev/null +++ b/development/ElementsMaker/specifications/Ine11.json @@ -0,0 +1,11 @@ +{ + "php-namespace": "CfdiUtils\\Elements\\Ine11", + "prefix": "ine", + "xml-namespace": "http://www.sat.gob.mx/ine", + "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd", + "version-attribute": "Version", + "version-value": "1.1", + "root-element": "Ine", + "structure": { + } +} diff --git a/development/ElementsMaker/specifications/LeyendasFiscales10.json b/development/ElementsMaker/specifications/LeyendasFiscales10.json new file mode 100644 index 00000000..d731a3fc --- /dev/null +++ b/development/ElementsMaker/specifications/LeyendasFiscales10.json @@ -0,0 +1,14 @@ +{ + "php-namespace": "CfdiUtils\\Elements\\LeyendasFiscales10", + "prefix": "leyendasFisc", + "xml-namespace": "http://www.sat.gob.mx/leyendasFiscales", + "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd", + "version-attribute": "version", + "version-value": "1.0", + "root-element": "LeyendasFiscales", + "structure": { + "Leyenda": { + "multiple": true + } + } +} diff --git a/development/ElementsMaker/specifications/NotariosPublicos10.json b/development/ElementsMaker/specifications/NotariosPublicos10.json new file mode 100644 index 00000000..88319c4e --- /dev/null +++ b/development/ElementsMaker/specifications/NotariosPublicos10.json @@ -0,0 +1,22 @@ +{ + "php-namespace": "CfdiUtils\\Elements\\NotariosPublicos10", + "prefix": "notariospublicos", + "xml-namespace": "http://www.sat.gob.mx/notariospublicos", + "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/notariospublicos/notariospublicos.xsd", + "version-attribute": "Version", + "version-value": "1.0", + "root-element": "NotariosPublicos", + "structure": { + "DescInmuebles": { + "DescInmueble": {} + }, + "DatosOperacion": {}, + "DatosNotario": {}, + "DatosEnajenante": { + "DatosUnEnajenante": {} + }, + "DatosAdquiriente": { + "DatosUnAdquiriente": {} + } + } +} diff --git a/development/ElementsMaker/specifications/ParcialesConstruccion10.json b/development/ElementsMaker/specifications/ParcialesConstruccion10.json new file mode 100644 index 00000000..8e4e1c68 --- /dev/null +++ b/development/ElementsMaker/specifications/ParcialesConstruccion10.json @@ -0,0 +1,13 @@ +{ + "php-namespace": "CfdiUtils\\Elements\\ParcialesConstruccion10", + "prefix": "servicioparcial", + "xml-namespace": "http://www.sat.gob.mx/implocal", + "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd", + "version-attribute": "Version", + "version-value": "1.0", + "root-element": "ParcialesConstruccion", + "structure": { + "Inmueble": { + } + } +} diff --git a/development/maker.sh b/development/maker.sh new file mode 100644 index 00000000..77a9e68b --- /dev/null +++ b/development/maker.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Usage example: +# Need composer install on the main project before +# Omit extension .json in the arg +# sh maker.sh Donatarias11 +# sh maker.sh ConsumoDeCombustibles11 + +nameFile=$1 +mainPath=$(pwd) +dirPath=$(dirname $mainPath) +if [ -z "$nameFile" ] +then + echo 'Need name of the file as arg' +else + # echo $dirPath + echo "\nStart ElementsMaker $nameFile" + echo rm -rf $dirPath/src/CfdiUtils/Elements/$nameFile + mkdir -p $dirPath/src/CfdiUtils/Elements/$nameFile + php $dirPath/development/bin/elements-maker.php $dirPath/development/ElementsMaker/specifications/$nameFile.json $dirPath/src/CfdiUtils/Elements/$nameFile + echo composer dev:fix-style + # vendor/bin/php-cs-fixer fix --verbose + # bin/phpcbf --colors -sp src/ tests/ + echo "End ElementsMaker $nameFile\n" +fi diff --git a/src/CfdiUtils/Elements/ConsumoDeCombustibles11/ConceptoConsumoDeCombustibles.php b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/ConceptoConsumoDeCombustibles.php new file mode 100644 index 00000000..90e43062 --- /dev/null +++ b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/ConceptoConsumoDeCombustibles.php @@ -0,0 +1,25 @@ +helperGetOrAdd(new Determinados()); + } + + public function addDeterminados(array $attributes = []): Determinados + { + $subject = $this->getDeterminados(); + $subject->addAttributes($attributes); + return $subject; + } +} diff --git a/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Conceptos.php b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Conceptos.php new file mode 100644 index 00000000..15731986 --- /dev/null +++ b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Conceptos.php @@ -0,0 +1,28 @@ +addChild($subject); + return $subject; + } + + public function multiConceptoConsumoDeCombustibles(array ...$elementAttributes): self + { + foreach ($elementAttributes as $attributes) { + $this->addConceptoConsumoDeCombustibles($attributes); + } + return $this; + } +} diff --git a/src/CfdiUtils/Elements/ConsumoDeCombustibles11/ConsumoDeCombustibles.php b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/ConsumoDeCombustibles.php new file mode 100644 index 00000000..c646f5d0 --- /dev/null +++ b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/ConsumoDeCombustibles.php @@ -0,0 +1,35 @@ + 'http://www.sat.gob.mx/ConsumoDeCombustibles11', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/ConsumoDeCombustibles11' + . ' http://www.sat.gob.mx/sitio_internet/cfd/consumodecombustibles/consumodeCombustibles11.xsd', + 'version' => '1.1', + ]; + } + + public function getConceptos(): Conceptos + { + return $this->helperGetOrAdd(new Conceptos()); + } + + public function addConceptos(array $attributes = []): Conceptos + { + $subject = $this->getConceptos(); + $subject->addAttributes($attributes); + return $subject; + } +} diff --git a/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinado.php b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinado.php new file mode 100644 index 00000000..ad8a905f --- /dev/null +++ b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinado.php @@ -0,0 +1,13 @@ +helperGetOrAdd(new Determinado()); + } + + public function addDeterminado(array $attributes = []): Determinado + { + $subject = $this->getDeterminado(); + $subject->addAttributes($attributes); + return $subject; + } +} diff --git a/src/CfdiUtils/Elements/Donatarias11/Donatarias.php b/src/CfdiUtils/Elements/Donatarias11/Donatarias.php new file mode 100644 index 00000000..d42c7b37 --- /dev/null +++ b/src/CfdiUtils/Elements/Donatarias11/Donatarias.php @@ -0,0 +1,23 @@ + 'http://www.sat.gob.mx/donat', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/donat' + . ' http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd', + 'version' => '1.1', + ]; + } +} diff --git a/src/CfdiUtils/Elements/Iedu10/InstEducativas.php b/src/CfdiUtils/Elements/Iedu10/InstEducativas.php new file mode 100644 index 00000000..315d2cbd --- /dev/null +++ b/src/CfdiUtils/Elements/Iedu10/InstEducativas.php @@ -0,0 +1,23 @@ + 'http://www.sat.gob.mx/iedu', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/iedu' + . ' http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd', + 'version' => '1.0', + ]; + } +} diff --git a/src/CfdiUtils/Elements/Ine11/Ine.php b/src/CfdiUtils/Elements/Ine11/Ine.php new file mode 100644 index 00000000..d19b9b6d --- /dev/null +++ b/src/CfdiUtils/Elements/Ine11/Ine.php @@ -0,0 +1,23 @@ + 'http://www.sat.gob.mx/ine', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/ine' + . ' http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd', + 'Version' => '1.1', + ]; + } +} diff --git a/src/CfdiUtils/Elements/LeyendasFiscales10/Leyenda.php b/src/CfdiUtils/Elements/LeyendasFiscales10/Leyenda.php new file mode 100644 index 00000000..af994116 --- /dev/null +++ b/src/CfdiUtils/Elements/LeyendasFiscales10/Leyenda.php @@ -0,0 +1,13 @@ + 'http://www.sat.gob.mx/leyendasFiscales', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/leyendasFiscales' + . ' http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd', + 'version' => '1.0', + ]; + } + + public function addLeyenda(array $attributes = []): Leyenda + { + $subject = new Leyenda($attributes); + $this->addChild($subject); + return $subject; + } + + public function multiLeyenda(array ...$elementAttributes): self + { + foreach ($elementAttributes as $attributes) { + $this->addLeyenda($attributes); + } + return $this; + } +} diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquiriente.php b/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquiriente.php new file mode 100644 index 00000000..7b572cbb --- /dev/null +++ b/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquiriente.php @@ -0,0 +1,25 @@ +helperGetOrAdd(new DatosUnAdquiriente()); + } + + public function addDatosUnAdquiriente(array $attributes = []): DatosUnAdquiriente + { + $subject = $this->getDatosUnAdquiriente(); + $subject->addAttributes($attributes); + return $subject; + } +} diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenante.php b/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenante.php new file mode 100644 index 00000000..aef662ef --- /dev/null +++ b/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenante.php @@ -0,0 +1,25 @@ +helperGetOrAdd(new DatosUnEnajenante()); + } + + public function addDatosUnEnajenante(array $attributes = []): DatosUnEnajenante + { + $subject = $this->getDatosUnEnajenante(); + $subject->addAttributes($attributes); + return $subject; + } +} diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/DatosNotario.php b/src/CfdiUtils/Elements/NotariosPublicos10/DatosNotario.php new file mode 100644 index 00000000..8f942e4e --- /dev/null +++ b/src/CfdiUtils/Elements/NotariosPublicos10/DatosNotario.php @@ -0,0 +1,13 @@ +helperGetOrAdd(new DescInmueble()); + } + + public function addDescInmueble(array $attributes = []): DescInmueble + { + $subject = $this->getDescInmueble(); + $subject->addAttributes($attributes); + return $subject; + } +} diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/NotariosPublicos.php b/src/CfdiUtils/Elements/NotariosPublicos10/NotariosPublicos.php new file mode 100644 index 00000000..7098cbb6 --- /dev/null +++ b/src/CfdiUtils/Elements/NotariosPublicos10/NotariosPublicos.php @@ -0,0 +1,93 @@ + 'http://www.sat.gob.mx/notariospublicos', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/notariospublicos' + . ' http://www.sat.gob.mx/sitio_internet/cfd/notariospublicos/notariospublicos.xsd', + 'Version' => '1.0', + ]; + } + + public function getDescInmuebles(): DescInmuebles + { + return $this->helperGetOrAdd(new DescInmuebles()); + } + + public function addDescInmuebles(array $attributes = []): DescInmuebles + { + $subject = $this->getDescInmuebles(); + $subject->addAttributes($attributes); + return $subject; + } + + public function getDatosOperacion(): DatosOperacion + { + return $this->helperGetOrAdd(new DatosOperacion()); + } + + public function addDatosOperacion(array $attributes = []): DatosOperacion + { + $subject = $this->getDatosOperacion(); + $subject->addAttributes($attributes); + return $subject; + } + + public function getDatosNotario(): DatosNotario + { + return $this->helperGetOrAdd(new DatosNotario()); + } + + public function addDatosNotario(array $attributes = []): DatosNotario + { + $subject = $this->getDatosNotario(); + $subject->addAttributes($attributes); + return $subject; + } + + public function getDatosEnajenante(): DatosEnajenante + { + return $this->helperGetOrAdd(new DatosEnajenante()); + } + + public function addDatosEnajenante(array $attributes = []): DatosEnajenante + { + $subject = $this->getDatosEnajenante(); + $subject->addAttributes($attributes); + return $subject; + } + + public function getDatosAdquiriente(): DatosAdquiriente + { + return $this->helperGetOrAdd(new DatosAdquiriente()); + } + + public function addDatosAdquiriente(array $attributes = []): DatosAdquiriente + { + $subject = $this->getDatosAdquiriente(); + $subject->addAttributes($attributes); + return $subject; + } +} From 3d6fc8e170a82ee7a47b12e25a816f3ac0a90f80 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 14:19:54 -0600 Subject: [PATCH 15/25] Remove duplicated (and incorrect) spec file --- .../specifications/ParcialesConstruccion10.json | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 development/ElementsMaker/specifications/ParcialesConstruccion10.json diff --git a/development/ElementsMaker/specifications/ParcialesConstruccion10.json b/development/ElementsMaker/specifications/ParcialesConstruccion10.json deleted file mode 100644 index 8e4e1c68..00000000 --- a/development/ElementsMaker/specifications/ParcialesConstruccion10.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "php-namespace": "CfdiUtils\\Elements\\ParcialesConstruccion10", - "prefix": "servicioparcial", - "xml-namespace": "http://www.sat.gob.mx/implocal", - "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd", - "version-attribute": "Version", - "version-value": "1.0", - "root-element": "ParcialesConstruccion", - "structure": { - "Inmueble": { - } - } -} From 23acf5128b0f7c46024bb452d77b3165d1dc9b39 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 16:07:32 -0600 Subject: [PATCH 16/25] Fix elements ConsumoDeCombustibles11 --- .../ConsumoDeCombustibles11.json | 4 +- docs/CHANGELOG.md | 1 + .../ConsumoDeCombustibles11/Determinados.php | 15 +++--- .../ConsumoDeCombustiblesTest.php | 53 +++++++++++++++++++ 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 tests/CfdiUtilsTests/Elements/ConsumoDeCombustibles11/ConsumoDeCombustiblesTest.php diff --git a/development/ElementsMaker/specifications/ConsumoDeCombustibles11.json b/development/ElementsMaker/specifications/ConsumoDeCombustibles11.json index a6372851..295ca832 100644 --- a/development/ElementsMaker/specifications/ConsumoDeCombustibles11.json +++ b/development/ElementsMaker/specifications/ConsumoDeCombustibles11.json @@ -11,7 +11,9 @@ "ConceptoConsumoDeCombustibles": { "multiple": true, "Determinados": { - "Determinado": {} + "Determinado": { + "multiple": true + } } } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 14cfc0aa..6bb733d1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -40,6 +40,7 @@ This tool takes a *Pre-CFDI* elements to write `Pagos\Totales`, `Pagos\Pago[]\Im and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. - Add element helpers for complement *Servicios parciales de construcción 1.0*. +- Add element helpers for complement *Consumo de combustibles 1.1*. Changes on development environment and documentation: diff --git a/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinados.php b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinados.php index 938622a1..17dbf257 100644 --- a/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinados.php +++ b/src/CfdiUtils/Elements/ConsumoDeCombustibles11/Determinados.php @@ -11,15 +11,18 @@ public function getElementName(): string return 'consumodecombustibles11:Determinados'; } - public function getDeterminado(): Determinado + public function addDeterminado(array $attributes = []): Determinado { - return $this->helperGetOrAdd(new Determinado()); + $subject = new Determinado($attributes); + $this->addChild($subject); + return $subject; } - public function addDeterminado(array $attributes = []): Determinado + public function multiDeterminado(array ...$elementAttributes): self { - $subject = $this->getDeterminado(); - $subject->addAttributes($attributes); - return $subject; + foreach ($elementAttributes as $attributes) { + $this->addDeterminado($attributes); + } + return $this; } } diff --git a/tests/CfdiUtilsTests/Elements/ConsumoDeCombustibles11/ConsumoDeCombustiblesTest.php b/tests/CfdiUtilsTests/Elements/ConsumoDeCombustibles11/ConsumoDeCombustiblesTest.php new file mode 100644 index 00000000..ad8e2893 --- /dev/null +++ b/tests/CfdiUtilsTests/Elements/ConsumoDeCombustibles11/ConsumoDeCombustiblesTest.php @@ -0,0 +1,53 @@ +assertElementHasName($element, 'consumodecombustibles11:ConsumoDeCombustibles'); + $this->assertElementHasFixedAttributes($element, [ + 'xmlns:consumodecombustibles11' => 'http://www.sat.gob.mx/ConsumoDeCombustibles11', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/ConsumoDeCombustibles11' + . ' http://www.sat.gob.mx/sitio_internet/cfd/consumodecombustibles/consumodeCombustibles11.xsd', + 'version' => '1.1', + ]); + $this->assertElementHasChildSingle($element, Conceptos::class); + } + + public function testConceptos(): void + { + $element = new Conceptos(); + $this->assertElementHasName($element, 'consumodecombustibles11:Conceptos'); + $this->assertElementHasChildMultiple($element, ConceptoConsumoDeCombustibles::class); + } + + public function testConceptoConsumoDeCombustibles(): void + { + $element = new ConceptoConsumoDeCombustibles(); + $this->assertElementHasName($element, 'consumodecombustibles11:ConceptoConsumoDeCombustibles'); + $this->assertElementHasChildSingle($element, Determinados::class); + } + + public function testDeterminados(): void + { + $element = new Determinados(); + $this->assertElementHasName($element, 'consumodecombustibles11:Determinados'); + $this->assertElementHasChildMultiple($element, Determinado::class); + } + + public function testDeterminado(): void + { + $element = new Determinado(); + $this->assertElementHasName($element, 'consumodecombustibles11:Determinado'); + } +} From fa0ca5e998206783ac3282227c947b39e6ce609c Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 18:41:00 -0600 Subject: [PATCH 17/25] Review Donatarias11 --- docs/CHANGELOG.md | 1 + .../Elements/Donatarias11/DonatariasTest.php | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/CfdiUtilsTests/Elements/Donatarias11/DonatariasTest.php diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6bb733d1..ee655268 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -41,6 +41,7 @@ and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. - Add element helpers for complement *Servicios parciales de construcción 1.0*. - Add element helpers for complement *Consumo de combustibles 1.1*. +- Add element helpers for complement *Donatarias 1.1*. Changes on development environment and documentation: diff --git a/tests/CfdiUtilsTests/Elements/Donatarias11/DonatariasTest.php b/tests/CfdiUtilsTests/Elements/Donatarias11/DonatariasTest.php new file mode 100644 index 00000000..9375755d --- /dev/null +++ b/tests/CfdiUtilsTests/Elements/Donatarias11/DonatariasTest.php @@ -0,0 +1,21 @@ +assertElementHasName($element, 'donat:Donatarias'); + $this->assertElementHasFixedAttributes($element, [ + 'xmlns:donat' => 'http://www.sat.gob.mx/donat', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/donat' + . ' http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd', + 'version' => '1.1', + ]); + } +} From 7d3057011c193869c90eac73dfefe43bc27198b0 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 18:48:12 -0600 Subject: [PATCH 18/25] Fix IEDU 1.0 --- .../ElementsMaker/specifications/Iedu10.json | 2 +- docs/CHANGELOG.md | 1 + .../Elements/Iedu10/InstEducativas.php | 2 +- .../Elements/Iedu10/IeduTest.php | 21 +++++++++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 tests/CfdiUtilsTests/Elements/Iedu10/IeduTest.php diff --git a/development/ElementsMaker/specifications/Iedu10.json b/development/ElementsMaker/specifications/Iedu10.json index d925df55..8fbf2ce4 100644 --- a/development/ElementsMaker/specifications/Iedu10.json +++ b/development/ElementsMaker/specifications/Iedu10.json @@ -5,7 +5,7 @@ "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd", "version-attribute": "version", "version-value": "1.0", - "root-element": "InstEducativas", + "root-element": "instEducativas", "structure": { } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ee655268..d3a0dfb5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -42,6 +42,7 @@ and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. - Add element helpers for complement *Servicios parciales de construcción 1.0*. - Add element helpers for complement *Consumo de combustibles 1.1*. - Add element helpers for complement *Donatarias 1.1*. +- Add element helpers for complement *Instituciones educativas privadas 1.0*. Changes on development environment and documentation: diff --git a/src/CfdiUtils/Elements/Iedu10/InstEducativas.php b/src/CfdiUtils/Elements/Iedu10/InstEducativas.php index 315d2cbd..8b628e27 100644 --- a/src/CfdiUtils/Elements/Iedu10/InstEducativas.php +++ b/src/CfdiUtils/Elements/Iedu10/InstEducativas.php @@ -8,7 +8,7 @@ class InstEducativas extends AbstractElement { public function getElementName(): string { - return 'iedu:InstEducativas'; + return 'iedu:instEducativas'; } public function getFixedAttributes(): array diff --git a/tests/CfdiUtilsTests/Elements/Iedu10/IeduTest.php b/tests/CfdiUtilsTests/Elements/Iedu10/IeduTest.php new file mode 100644 index 00000000..cd359b6b --- /dev/null +++ b/tests/CfdiUtilsTests/Elements/Iedu10/IeduTest.php @@ -0,0 +1,21 @@ +assertElementHasName($element, 'iedu:instEducativas'); + $this->assertElementHasFixedAttributes($element, [ + 'xmlns:iedu' => 'http://www.sat.gob.mx/iedu', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/iedu' + . ' http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd', + 'version' => '1.0', + ]); + } +} From 4008f7918233686254529eec0c6cf489362cf29a Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 18:54:30 -0600 Subject: [PATCH 19/25] Fix INE 1.1 --- .../ElementsMaker/specifications/Ine11.json | 2 +- docs/CHANGELOG.md | 1 + src/CfdiUtils/Elements/Ine11/Ine.php | 2 +- .../CfdiUtilsTests/Elements/Ine11/IneTest.php | 21 +++++++++++++++++++ .../LeyendasFiscalesTest.php | 8 +++++++ 5 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/CfdiUtilsTests/Elements/Ine11/IneTest.php create mode 100644 tests/CfdiUtilsTests/Elements/LeyendasFiscales10/LeyendasFiscalesTest.php diff --git a/development/ElementsMaker/specifications/Ine11.json b/development/ElementsMaker/specifications/Ine11.json index 3402c382..58fa831a 100644 --- a/development/ElementsMaker/specifications/Ine11.json +++ b/development/ElementsMaker/specifications/Ine11.json @@ -5,7 +5,7 @@ "xml-schemalocation": "http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd", "version-attribute": "Version", "version-value": "1.1", - "root-element": "Ine", + "root-element": "INE", "structure": { } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d3a0dfb5..6c15c094 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -43,6 +43,7 @@ and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. - Add element helpers for complement *Consumo de combustibles 1.1*. - Add element helpers for complement *Donatarias 1.1*. - Add element helpers for complement *Instituciones educativas privadas 1.0*. +- Add element helpers for complement *INE 1.1*. Changes on development environment and documentation: diff --git a/src/CfdiUtils/Elements/Ine11/Ine.php b/src/CfdiUtils/Elements/Ine11/Ine.php index d19b9b6d..8b5196b1 100644 --- a/src/CfdiUtils/Elements/Ine11/Ine.php +++ b/src/CfdiUtils/Elements/Ine11/Ine.php @@ -8,7 +8,7 @@ class Ine extends AbstractElement { public function getElementName(): string { - return 'ine:Ine'; + return 'ine:INE'; } public function getFixedAttributes(): array diff --git a/tests/CfdiUtilsTests/Elements/Ine11/IneTest.php b/tests/CfdiUtilsTests/Elements/Ine11/IneTest.php new file mode 100644 index 00000000..576262e7 --- /dev/null +++ b/tests/CfdiUtilsTests/Elements/Ine11/IneTest.php @@ -0,0 +1,21 @@ +assertElementHasName($element, 'ine:INE'); + $this->assertElementHasFixedAttributes($element, [ + 'xmlns:ine' => 'http://www.sat.gob.mx/ine', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/ine' + . ' http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd', + 'Version' => '1.1', + ]); + } +} diff --git a/tests/CfdiUtilsTests/Elements/LeyendasFiscales10/LeyendasFiscalesTest.php b/tests/CfdiUtilsTests/Elements/LeyendasFiscales10/LeyendasFiscalesTest.php new file mode 100644 index 00000000..d301acc9 --- /dev/null +++ b/tests/CfdiUtilsTests/Elements/LeyendasFiscales10/LeyendasFiscalesTest.php @@ -0,0 +1,8 @@ + Date: Wed, 14 Jun 2023 19:01:30 -0600 Subject: [PATCH 20/25] Review Leyendas Fiscales 1.0 --- docs/CHANGELOG.md | 1 + .../LeyendasFiscalesTest.php | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6c15c094..6f15e8e6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -44,6 +44,7 @@ and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. - Add element helpers for complement *Donatarias 1.1*. - Add element helpers for complement *Instituciones educativas privadas 1.0*. - Add element helpers for complement *INE 1.1*. +- Add element helpers for complement *Leyendas Fiscales 1.0*. Changes on development environment and documentation: diff --git a/tests/CfdiUtilsTests/Elements/LeyendasFiscales10/LeyendasFiscalesTest.php b/tests/CfdiUtilsTests/Elements/LeyendasFiscales10/LeyendasFiscalesTest.php index d301acc9..1dd695bb 100644 --- a/tests/CfdiUtilsTests/Elements/LeyendasFiscales10/LeyendasFiscalesTest.php +++ b/tests/CfdiUtilsTests/Elements/LeyendasFiscales10/LeyendasFiscalesTest.php @@ -2,7 +2,28 @@ namespace CfdiUtilsTests\Elements\LeyendasFiscales10; -final class LeyendasFiscalesTest +use CfdiUtils\Elements\LeyendasFiscales10\Leyenda; +use CfdiUtils\Elements\LeyendasFiscales10\LeyendasFiscales; +use CfdiUtilsTests\Elements\ElementTestCase; + +final class LeyendasFiscalesTest extends ElementTestCase { + public function testLeyendasFiscales(): void + { + $element = new LeyendasFiscales(); + $this->assertElementHasName($element, 'leyendasFisc:LeyendasFiscales'); + $this->assertElementHasFixedAttributes($element, [ + 'xmlns:leyendasFisc' => 'http://www.sat.gob.mx/leyendasFiscales', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/leyendasFiscales' + . ' http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd', + 'version' => '1.0', + ]); + $this->assertElementHasChildMultiple($element, Leyenda::class); + } + public function testLeyenda(): void + { + $element = new Leyenda(); + $this->assertElementHasName($element, 'leyendasFisc:Leyenda'); + } } From 212e4c3e3a2535da7feff51a3af7fccd166ee141 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 20:36:51 -0600 Subject: [PATCH 21/25] =?UTF-8?q?Fix=20Notarios=20P=C3=BAblicos=201.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../specifications/NotariosPublicos10.json | 18 ++- docs/CHANGELOG.md | 3 + .../NotariosPublicos10/DatosAdquiriente.php | 19 +++ .../DatosAdquirienteCopSC.php | 13 ++ .../DatosAdquirientesCopSC.php | 28 +++++ .../NotariosPublicos10/DatosEnajenante.php | 19 +++ .../DatosEnajenanteCopSC.php | 13 ++ .../DatosEnajenantesCopSC.php | 28 +++++ .../NotariosPublicos10/DescInmuebles.php | 15 ++- .../NotariosPublicosTest.php | 117 ++++++++++++++++++ 10 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquirienteCopSC.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquirientesCopSC.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenanteCopSC.php create mode 100644 src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenantesCopSC.php create mode 100644 tests/CfdiUtilsTests/Elements/NotariosPublicos10/NotariosPublicosTest.php diff --git a/development/ElementsMaker/specifications/NotariosPublicos10.json b/development/ElementsMaker/specifications/NotariosPublicos10.json index 88319c4e..5bb5ae15 100644 --- a/development/ElementsMaker/specifications/NotariosPublicos10.json +++ b/development/ElementsMaker/specifications/NotariosPublicos10.json @@ -8,15 +8,27 @@ "root-element": "NotariosPublicos", "structure": { "DescInmuebles": { - "DescInmueble": {} + "DescInmueble": { + "multiple": true + } }, "DatosOperacion": {}, "DatosNotario": {}, "DatosEnajenante": { - "DatosUnEnajenante": {} + "DatosUnEnajenante": {}, + "DatosEnajenantesCopSC": { + "DatosEnajenanteCopSC": { + "multiple": true + } + } }, "DatosAdquiriente": { - "DatosUnAdquiriente": {} + "DatosUnAdquiriente": {}, + "DatosAdquirientesCopSC": { + "DatosAdquirienteCopSC": { + "multiple": true + } + } } } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6f15e8e6..ad721227 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -45,6 +45,9 @@ and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. - Add element helpers for complement *Instituciones educativas privadas 1.0*. - Add element helpers for complement *INE 1.1*. - Add element helpers for complement *Leyendas Fiscales 1.0*. +- Add element helpers for complement *Notarios Públicos 10*. + +Thanks `@BlakePro` for the initial work on these improvements. Changes on development environment and documentation: diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquiriente.php b/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquiriente.php index 7b572cbb..9534dc14 100644 --- a/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquiriente.php +++ b/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquiriente.php @@ -11,6 +11,13 @@ public function getElementName(): string return 'notariospublicos:DatosAdquiriente'; } + public function getChildrenOrder(): array + { + return [ + 'notariospublicos:DatosUnAdquiriente', + 'notariospublicos:DatosAdquirientesCopSC']; + } + public function getDatosUnAdquiriente(): DatosUnAdquiriente { return $this->helperGetOrAdd(new DatosUnAdquiriente()); @@ -22,4 +29,16 @@ public function addDatosUnAdquiriente(array $attributes = []): DatosUnAdquirient $subject->addAttributes($attributes); return $subject; } + + public function getDatosAdquirientesCopSC(): DatosAdquirientesCopSC + { + return $this->helperGetOrAdd(new DatosAdquirientesCopSC()); + } + + public function addDatosAdquirientesCopSC(array $attributes = []): DatosAdquirientesCopSC + { + $subject = $this->getDatosAdquirientesCopSC(); + $subject->addAttributes($attributes); + return $subject; + } } diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquirienteCopSC.php b/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquirienteCopSC.php new file mode 100644 index 00000000..cc8c1834 --- /dev/null +++ b/src/CfdiUtils/Elements/NotariosPublicos10/DatosAdquirienteCopSC.php @@ -0,0 +1,13 @@ +addChild($subject); + return $subject; + } + + public function multiDatosAdquirienteCopSC(array ...$elementAttributes): self + { + foreach ($elementAttributes as $attributes) { + $this->addDatosAdquirienteCopSC($attributes); + } + return $this; + } +} diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenante.php b/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenante.php index aef662ef..a3e65bfa 100644 --- a/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenante.php +++ b/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenante.php @@ -11,6 +11,13 @@ public function getElementName(): string return 'notariospublicos:DatosEnajenante'; } + public function getChildrenOrder(): array + { + return [ + 'notariospublicos:DatosUnEnajenante', + 'notariospublicos:DatosEnajenantesCopSC']; + } + public function getDatosUnEnajenante(): DatosUnEnajenante { return $this->helperGetOrAdd(new DatosUnEnajenante()); @@ -22,4 +29,16 @@ public function addDatosUnEnajenante(array $attributes = []): DatosUnEnajenante $subject->addAttributes($attributes); return $subject; } + + public function getDatosEnajenantesCopSC(): DatosEnajenantesCopSC + { + return $this->helperGetOrAdd(new DatosEnajenantesCopSC()); + } + + public function addDatosEnajenantesCopSC(array $attributes = []): DatosEnajenantesCopSC + { + $subject = $this->getDatosEnajenantesCopSC(); + $subject->addAttributes($attributes); + return $subject; + } } diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenanteCopSC.php b/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenanteCopSC.php new file mode 100644 index 00000000..fb4743b2 --- /dev/null +++ b/src/CfdiUtils/Elements/NotariosPublicos10/DatosEnajenanteCopSC.php @@ -0,0 +1,13 @@ +addChild($subject); + return $subject; + } + + public function multiDatosEnajenanteCopSC(array ...$elementAttributes): self + { + foreach ($elementAttributes as $attributes) { + $this->addDatosEnajenanteCopSC($attributes); + } + return $this; + } +} diff --git a/src/CfdiUtils/Elements/NotariosPublicos10/DescInmuebles.php b/src/CfdiUtils/Elements/NotariosPublicos10/DescInmuebles.php index a3294a08..456cdcf9 100644 --- a/src/CfdiUtils/Elements/NotariosPublicos10/DescInmuebles.php +++ b/src/CfdiUtils/Elements/NotariosPublicos10/DescInmuebles.php @@ -11,15 +11,18 @@ public function getElementName(): string return 'notariospublicos:DescInmuebles'; } - public function getDescInmueble(): DescInmueble + public function addDescInmueble(array $attributes = []): DescInmueble { - return $this->helperGetOrAdd(new DescInmueble()); + $subject = new DescInmueble($attributes); + $this->addChild($subject); + return $subject; } - public function addDescInmueble(array $attributes = []): DescInmueble + public function multiDescInmueble(array ...$elementAttributes): self { - $subject = $this->getDescInmueble(); - $subject->addAttributes($attributes); - return $subject; + foreach ($elementAttributes as $attributes) { + $this->addDescInmueble($attributes); + } + return $this; } } diff --git a/tests/CfdiUtilsTests/Elements/NotariosPublicos10/NotariosPublicosTest.php b/tests/CfdiUtilsTests/Elements/NotariosPublicos10/NotariosPublicosTest.php new file mode 100644 index 00000000..256531c6 --- /dev/null +++ b/tests/CfdiUtilsTests/Elements/NotariosPublicos10/NotariosPublicosTest.php @@ -0,0 +1,117 @@ +assertElementHasName($element, 'notariospublicos:NotariosPublicos'); + $this->assertElementHasFixedAttributes($element, [ + 'xmlns:notariospublicos' => 'http://www.sat.gob.mx/notariospublicos', + 'xsi:schemaLocation' => 'http://www.sat.gob.mx/notariospublicos' + . ' http://www.sat.gob.mx/sitio_internet/cfd/notariospublicos/notariospublicos.xsd', + 'Version' => '1.0', + ]); + $this->assertElementHasChildSingle($element, DescInmuebles::class); + $this->assertElementHasChildSingle($element, DatosOperacion::class); + $this->assertElementHasChildSingle($element, DatosNotario::class); + $this->assertElementHasChildSingle($element, DatosEnajenante::class); + $this->assertElementHasChildSingle($element, DatosAdquiriente::class); + } + + public function testDescInmuebles(): void + { + $element = new DescInmuebles(); + $this->assertElementHasName($element, 'notariospublicos:DescInmuebles'); + $this->assertElementHasChildMultiple($element, DescInmueble::class); + } + + public function testDescInmueble(): void + { + $element = new DescInmueble(); + $this->assertElementHasName($element, 'notariospublicos:DescInmueble'); + } + + public function testDatosOperacion(): void + { + $element = new DatosOperacion(); + $this->assertElementHasName($element, 'notariospublicos:DatosOperacion'); + } + + public function testDatosNotario(): void + { + $element = new DatosNotario(); + $this->assertElementHasName($element, 'notariospublicos:DatosNotario'); + } + + public function testDatosEnajenante(): void + { + $element = new DatosEnajenante(); + $this->assertElementHasName($element, 'notariospublicos:DatosEnajenante'); + $this->assertElementHasChildSingle($element, DatosUnEnajenante::class); + $this->assertElementHasChildSingle($element, DatosEnajenantesCopSC::class); + } + + public function testDatosUnEnajenante(): void + { + $element = new DatosUnEnajenante(); + $this->assertElementHasName($element, 'notariospublicos:DatosUnEnajenante'); + } + + public function testDatosEnajenantesCopSC(): void + { + $element = new DatosEnajenantesCopSC(); + $this->assertElementHasName($element, 'notariospublicos:DatosEnajenantesCopSC'); + $this->assertElementHasChildMultiple($element, DatosEnajenanteCopSC::class); + } + + public function testDatosEnajenanteCopSC(): void + { + $element = new DatosEnajenanteCopSC(); + $this->assertElementHasName($element, 'notariospublicos:DatosEnajenanteCopSC'); + } + + public function testDatosAdquiriente(): void + { + $element = new DatosAdquiriente(); + $this->assertElementHasName($element, 'notariospublicos:DatosAdquiriente'); + $this->assertElementHasChildSingle($element, DatosUnAdquiriente::class); + $this->assertElementHasChildSingle($element, DatosAdquirientesCopSC::class); + } + + public function testDatosUnAdquiriente(): void + { + $element = new DatosUnAdquiriente(); + $this->assertElementHasName($element, 'notariospublicos:DatosUnAdquiriente'); + } + + public function testDatosAdquirientesCopSC(): void + { + $element = new DatosAdquirientesCopSC(); + $this->assertElementHasName($element, 'notariospublicos:DatosAdquirientesCopSC'); + $this->assertElementHasChildMultiple($element, DatosAdquirienteCopSC::class); + } + + public function testDatosAdquirienteCopSC(): void + { + $element = new DatosAdquirienteCopSC(); + $this->assertElementHasName($element, 'notariospublicos:DatosAdquirienteCopSC'); + } +} From cfef60158151fd8e6a0b56d5bf918315b4705131 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Wed, 14 Jun 2023 20:38:24 -0600 Subject: [PATCH 22/25] Remove script, use instructions in README --- development/maker.sh | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 development/maker.sh diff --git a/development/maker.sh b/development/maker.sh deleted file mode 100644 index 77a9e68b..00000000 --- a/development/maker.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# Usage example: -# Need composer install on the main project before -# Omit extension .json in the arg -# sh maker.sh Donatarias11 -# sh maker.sh ConsumoDeCombustibles11 - -nameFile=$1 -mainPath=$(pwd) -dirPath=$(dirname $mainPath) -if [ -z "$nameFile" ] -then - echo 'Need name of the file as arg' -else - # echo $dirPath - echo "\nStart ElementsMaker $nameFile" - echo rm -rf $dirPath/src/CfdiUtils/Elements/$nameFile - mkdir -p $dirPath/src/CfdiUtils/Elements/$nameFile - php $dirPath/development/bin/elements-maker.php $dirPath/development/ElementsMaker/specifications/$nameFile.json $dirPath/src/CfdiUtils/Elements/$nameFile - echo composer dev:fix-style - # vendor/bin/php-cs-fixer fix --verbose - # bin/phpcbf --colors -sp src/ tests/ - echo "End ElementsMaker $nameFile\n" -fi From d8c3780bedd6ecd4b22c1ae057ba5089e0962f71 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 15 Jun 2023 11:33:04 -0600 Subject: [PATCH 23/25] Documentation review - Move CFDI creation from 3.3 to 4.0. - Review all files and fix almost all typos and phrasing errors, thanks PhpStorm. - Add usage of phpcfdi/credentials. - Add support for PHP 8.2 on README.md --- README.md | 8 +- docs/CHANGELOG.md | 8 +- docs/componentes/cadena-de-origen.md | 6 +- docs/componentes/certificado.md | 4 +- docs/componentes/elements.md | 19 +- docs/componentes/estado-sat.md | 29 +- docs/componentes/xmlresolver.md | 5 +- docs/contribuir/guia-desarrollador.md | 4 +- docs/contribuir/guia-documentador.md | 4 +- docs/contribuir/guia-windows.md | 25 +- ...i-de-retenciones-e-informacion-de-pagos.md | 2 +- docs/crear/complemento-nomina12b.md | 6 +- .../complementos-aun-no-implementados.md | 2 +- docs/crear/crear-cfdi-33.md | 10 + docs/crear/crear-cfdi-40.md | 261 +++++++++++++++++- docs/crear/crear-cfdi.md | 204 -------------- docs/crear/elements-cfdi-40.md | 165 +++++++++++ docs/crear/elements-cfdi33.md | 164 ----------- docs/index.md | 4 +- docs/instalar/instalacion.md | 5 +- docs/leer/leer-cfdi.md | 9 +- docs/leer/limpieza-cfdi.md | 26 +- docs/leer/quickreader.md | 4 +- docs/problemas/contradicciones-pagos.md | 28 +- docs/problemas/descarga-certificados.md | 4 +- docs/problemas/multiples-complementos.md | 8 +- docs/utilerias/openssl.md | 16 +- docs/validar/validaciones-40.md | 34 +-- docs/validar/validaciones-estandar.md | 12 +- mkdocs.yml | 4 +- 30 files changed, 568 insertions(+), 512 deletions(-) create mode 100644 docs/crear/crear-cfdi-33.md delete mode 100644 docs/crear/crear-cfdi.md create mode 100644 docs/crear/elements-cfdi-40.md delete mode 100644 docs/crear/elements-cfdi33.md diff --git a/README.md b/README.md index 0ea04389..7dd61bd0 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,11 @@ CFDI y herramientas del SAT. Y próximamente el lugar donde publicaremos la vers ## Main features -- Create CFDI version 3.3 & 4.0 based on a friendly extendable non-xml objects (`nodes`). +- Create CFDI version 3.3 & 4.0 based on a friendly extendable non XML objects (`nodes`). - Read CFDI version 3.2, 3.3 & 4.0. -- Validate CFDI version 3.3 & 4.0 against schemas, cfdi signature (`Sello`) and custom rules. +- Validate CFDI version 3.3 & 4.0 against schemas, CFDI signature (`Sello`) and custom rules. - Validate that the Timbre Fiscal Digital signature match with the CFDI 3.3 & CFDI 4.0, if not then the document has been modified after signature. -- Validates the "Complemento de recepción de pagos". - Helper objects to deal with: - `Cadena de origen` generation. - Extract information from CER files or `Certificado` attribute. @@ -40,7 +39,7 @@ CFDI y herramientas del SAT. Y próximamente el lugar donde publicaremos la vers - Retrieve the CFDI version information. - Keep a local copy of the tree of XSD and XSLT file dependencies from SAT. - Keep a local copy of certificates to avoid downloads them each time. -- Check the SAT WebService to get the status of a CFDI (*Estado*, *EsCancelable* and *EstatusCancelacion*) without WSDL. +- Check the SAT WebService to get the status of a CFDI (*`Estado`*, *`EsCancelable`*, *`EstatusCancelacion`* and *`EFOS`*) without WSDL. ## Installation @@ -78,6 +77,7 @@ See for more details. | 2.12.7 | 7.0, 7.1, 7.2, 7.3, 7.4 | 2019-12-04 | | 2.15.0 | 7.3, 7.4, 8.0 | 2021-03-17 | | 2.20.1 | 7.3, 7.4, 8.0, 8.1 | 2022-03-08 | +| 2.23.5 | 7.3, 7.4, 8.0, 8.1, 8.2 | 2023-05-26 | ## Contributing diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ad721227..7237885a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -34,7 +34,7 @@ ## Version 2.24.0 2023-06-14 -- Add a calculator and writer for complement *Pagos 2.0*. +Add a calculator and writer for complement *Pagos 2.0*. This tool takes a *Pre-CFDI* elements to write `Pagos\Totales`, `Pagos\Pago[]\ImpuestosP` and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. @@ -45,12 +45,16 @@ and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. - Add element helpers for complement *Instituciones educativas privadas 1.0*. - Add element helpers for complement *INE 1.1*. - Add element helpers for complement *Leyendas Fiscales 1.0*. -- Add element helpers for complement *Notarios Públicos 10*. +- Add element helpers for complement *Notarios Públicos 1.0*. Thanks `@BlakePro` for the initial work on these improvements. Changes on development environment and documentation: +- Move CFDI creation from 3.3 to 4.0. +- Review all files and fix almost all typos and phrasing errors, thanks PhpStorm. +- Add usage of phpcfdi/credentials. +- Add support for PHP 8.2 on README.md - Remove GitHub code language detection on `tests/assets/`. - Update `markdownlint-cli` tool. - Fix issues found by `markdownlint-cli`. diff --git a/docs/componentes/cadena-de-origen.md b/docs/componentes/cadena-de-origen.md index 3964e51b..6fe1eaf5 100644 --- a/docs/componentes/cadena-de-origen.md +++ b/docs/componentes/cadena-de-origen.md @@ -85,11 +85,11 @@ $location = $resolver->resolveCadenaOrigenLocation('3.3'); // fabricar la cadena de origen $builder = new DOMBuilder(); -$cadenaorigen = $builder->build($xmlContent, $location); +$cadenaOrigen = $builder->build($xmlContent, $location); ``` Sin embargo, en la práctica es poco probable que desees generar la cadena de origen. -Básicamente porque si estás creando un CFDI esta será generada automáticamente. +Básicamente, porque si estás creando un CFDI esta será generada automáticamente. Si estás leyendo o validando también será generada automáticamente por los validadores. @@ -137,7 +137,7 @@ $tfdXmlString = \CfdiUtils\Nodes\XmlNodeUtils::nodeToXmlString($tfd); ## PHP y XSLT versión 2 Es importante notar que hasta el momento (enero/2019) no es posible en PHP -procesar XSLT versión 2.0. Sin embargo el procesador que sí tiene PHP genera +procesar XSLT versión 2.0. Sin embargo, el procesador que sí tiene PHP genera las cadenas de origen a pesar de la versión. Esto no garantiza que si el SAT modifica los archivos XSLT utilizando características incompatibles se producirá el resultado correcto. diff --git a/docs/componentes/certificado.md b/docs/componentes/certificado.md index 1cea25b2..c1857bf9 100644 --- a/docs/componentes/certificado.md +++ b/docs/componentes/certificado.md @@ -15,7 +15,7 @@ Una vez cargado el certificado permite obtener los siguientes datos utilizando * - Llave pública - Nombre del archivo cargado -Adicionalmente cuenta con los métodos: +Adicionalmente, cuenta con los métodos: - Permite verificar si una llave privada corresponde a este certificado: @@ -64,7 +64,7 @@ var_dump($certificate->getRfc()); // algo como COSC8001137NA ## Números de serie del certificado -En el número de serie requerido en los CFDI se utiliza una representación ASCII y no hexadecimal, sin embargo +En el número de serie requerido en los CFDI se utiliza una representación ASCII y no hexadecimal, sin embargo, en algunas ocasiones se podría necesitar el número en formato hexadecimal de dos dígitos o la representación decimal. El objeto `Certificado` contiene internamente un objeto de tipo `SerialNumber` que del que se puede obtener **una copia** diff --git a/docs/componentes/elements.md b/docs/componentes/elements.md index 6215a3c3..1abce713 100644 --- a/docs/componentes/elements.md +++ b/docs/componentes/elements.md @@ -47,16 +47,16 @@ entonces `Comprobante::addEmisor(['RegimenFiscal' => '601'])` tiene este comport - Se obtiene el elemento `Emisor`, si no existe se crea uno vacío. - Se escriben los atributos pasados al elemento obtenido. -- Se devuelve el elemento. +- Se devuelve el elemento creado `Emisor`. -Por el contrario, como puede haber varios Cfdi Relacionados, entonces -`CfdiRelacionados::addCfdiRelacionado(['UUID' => $uuid])` tiene este comportamiento: +Por el contrario, como puede haber varios conceptos, entonces +`Conceptos::addConcepto([/* attributes */])` tiene este comportamiento: -- Se crea un elemento de tipo `CfdiRelacionado` con los atributos pasados. -- Se agrega el elemento recién creado a los hijos de `CfdiRelacionados`. -- Se devuelve el elemento creado. +- Se crea un elemento de tipo `Concepto` con los atributos pasados. +- Se agrega el elemento recién creado a los hijos de `Conceptos`. +- Se devuelve el elemento creado `Concepto`. -Existe un caso donde lo que se espera entregar como atributo al prefijo `add*` es en realidad un hijo. +Existe algunos casos donde, lo que se espera entregar como argumento al prefijo `add*`, es en realidad un hijo. Esto sucede en `addComplemento` y `addAddenda`. @@ -66,7 +66,8 @@ La nomenclatura con el prefijo `multi*` se escribe la forma `ElementoPadre::mult y se espera crear múltiples una instancia de `ElementoHijo` con los atributos datos, agregarla a los hijos de `ElementoPadre` y la instancia de `ElementoPadre` creada. -Otra forma de decirlo: es como los métodos `add*` pero se le pueden mandar varios arreglos de atributos y se creará un elemento para cada parámetro enviado. +Otra forma de decirlo: es como los métodos `add*` pero se le pueden mandar varios arreglos de atributos y +se creará un elemento para cada parámetro enviado. Por lo anterior, `CfdiRelacionados::multiCfdiRelacionado([ ['UUID' => $uuid1], ['UUID' => $uuid2] ])` agregará dos hijos -y devolverá la misma instancia del objeto llamado. +y devolverá la misma instancia del objeto `CfdiRelacionados` llamado. diff --git a/docs/componentes/estado-sat.md b/docs/componentes/estado-sat.md index 3e19bdce..46734f2d 100644 --- a/docs/componentes/estado-sat.md +++ b/docs/componentes/estado-sat.md @@ -89,7 +89,7 @@ Se refiere a que si en el momento de la consulta el CFDI se puede cancelar. - `No cancelable`: No se puede cancelar, tal vez ya hay documentos relacionados. - `Cancelable sin aceptación`: Se puede cancelar de inmediato. -- `Cancelable con aceptación`: Se puede cancelar pero se va a tener que esperar respuesta. +- `Cancelable con aceptación`: Se puede cancelar, pero se va a tener que esperar respuesta. ### EstatusCancelacion (estado de cancelación) @@ -106,7 +106,7 @@ Se refiere al estado de la cancelación solicitada previamente. El WebService del SAT devuelve dos códigos que asumimos se refieren al emisor del CFDI: -- 200: No se enctró en el listado de EFOS. +- 200: No se encontró en el listado de EFOS. - 100: Se encontró en el listado de EFOS. Desconocemos si el código se refiere a si estaba listado en el momento de la emisión del CFDI, @@ -114,18 +114,17 @@ al momento de ser reportado el CFDI al SAT o al momento de consulta. ## Estados mutuamente excluyentes -CodigoEstatus | Estado | EsCancelable | EstatusCancelacion | Explicación -------------- | ------------- | ------------------------- | ------------------------ | ----------------------------------------------------- -N - ... | * | * | * | El SAT no sabe del CFDI con los datos ofrecidos -S - ... | Cancelado | * | Plazo vencido | Cancelado por plazo vencido -S - ... | Cancelado | * | Cancelado con aceptación | Cancelado con aceptación del receptor -S - ... | Cancelado | * | Cancelado sin aceptación | No fue requerido preguntarle al receptor y se canceló -S - ... | Vigente | No cancelable | * | No se puede cancelar -S - ... | Vigente | Cancelable sin aceptación | * | Se puede cancelar pero no se ha realizado solicitud -S - ... | Vigente | Cancelable con aceptación | (ninguno) | Se puede cancelar pero no se ha realizado solicitud -S - ... | Vigente | Cancelable con aceptación | En proceso | Se hizo la solicitud y se está en espera -S - ... | Vigente | Cancelable con aceptación | Solicitud rechazada | Se hizo la solicitud y fue rechazada - +| `CodigoEstatus` | `Estado` | `EsCancelable` | `EstatusCancelacion` | `Explicación` | +|-----------------|-----------|---------------------------|--------------------------|-------------------------------------------------------| +| N - ... | * | * | * | El SAT no sabe del CFDI con los datos ofrecidos | +| S - ... | Cancelado | * | Plazo vencido | Cancelado por plazo vencido | +| S - ... | Cancelado | * | Cancelado con aceptación | Cancelado con aceptación del receptor | +| S - ... | Cancelado | * | Cancelado sin aceptación | No fue requerido preguntarle al receptor y se canceló | +| S - ... | Vigente | No cancelable | * | No se puede cancelar | +| S - ... | Vigente | Cancelable sin aceptación | * | Se puede cancelar pero no se ha realizado solicitud | +| S - ... | Vigente | Cancelable con aceptación | (ninguno) | Se puede cancelar pero no se ha realizado solicitud | +| S - ... | Vigente | Cancelable con aceptación | En proceso | Se hizo la solicitud y se está en espera | +| S - ... | Vigente | Cancelable con aceptación | Solicitud rechazada | Se hizo la solicitud y fue rechazada | ## Ejemplo de uso a partir de un archivo @@ -183,7 +182,7 @@ Hasta antes de la versión 2.10 se necesitaba un archivo WSDL, a partir de 2.10 ya no se necesita y la llamada SOAP se hace correctamente. -## Posibles futuros cambios +## Futuros cambios Usar alguna librería como o en lugar de la extensión SOAP de PHP. diff --git a/docs/componentes/xmlresolver.md b/docs/componentes/xmlresolver.md index 6b0954ed..5b08d0e2 100644 --- a/docs/componentes/xmlresolver.md +++ b/docs/componentes/xmlresolver.md @@ -12,7 +12,8 @@ los recursos más usuales son: que una firma es válida con respecto a un emisor. La firma es lo que el sat llama sello y el emisor se distingue por un certificado. -Estos recursos están disponibles en internet, pero son grandes y tienen cambios esporádicos. Por ejemplo, el archivo de catálogos del SAT mide 6.3 MB. +Estos recursos están disponibles en internet, pero son grandes y tienen cambios esporádicos. +Por ejemplo, el archivo de catálogos del SAT mide 6.3 MB. Por ello es conveniente tener una copia local de los recursos. El problema viene cuando esos recursos no se pueden simplemente descargar y almacenar. @@ -93,7 +94,7 @@ La librería por defecto no puede obtener los recursos que necesita. Sin embargo, para ello existe la interface `\XmlResourceRetriever\Downloader\DownloaderInterface` (esta interface no pertenece a este proyecto, pertenece a `XmlResourceRetriever`). -Tu puedes implementar el `DownloaderInterface` en una clase que utilice `curl` o `guzzle` +Tú puedes implementar la interface `DownloaderInterface` en una clase que utilice `curl` o `guzzle` o ejecute un comando en la shell como `wget` y luego crear tu objeto `XmlResolver` con este descargador. ```php diff --git a/docs/contribuir/guia-desarrollador.md b/docs/contribuir/guia-desarrollador.md index cffb839f..fb8b2f41 100644 --- a/docs/contribuir/guia-desarrollador.md +++ b/docs/contribuir/guia-desarrollador.md @@ -4,7 +4,7 @@ Esta es una guía rápida que pretende guiarte para que puedas desarrollar la li ## Código de conducta -Revisa nuesto [COC][] y nuestra página de [CONTRIBUTING][]. +Revisa nuestro [COC][] y nuestra página de [CONTRIBUTING][]. En resumen: @@ -21,7 +21,7 @@ Requieres tener instalado y disponible `git` `composer` y `php`. Opcionalmente podrías tener instalado `saxonb-xslt`. El proyecto es compatible con PHP 7.0. -Respeta esta compatilibilidad, no agregues características de versiones superiores. +Respeta esta compatibilidad, no agregues características de versiones superiores. ## Primeros pasos diff --git a/docs/contribuir/guia-documentador.md b/docs/contribuir/guia-documentador.md index 14fb2127..0de2f91d 100644 --- a/docs/contribuir/guia-documentador.md +++ b/docs/contribuir/guia-documentador.md @@ -46,7 +46,7 @@ Estas herramientas te ayudarán para realizar la documentación y no tener probl La documentación del proyecto se encuentra en el [repositorio de CfdiUtils](https://github.com/eclipxe13/cfdiutils) en la carpeta `docs/` y sus cambios serán aprobados usando un *pull request* tradicional. -Si va a agregar nuevas páginas debe agregarlas a el archivo `mkdocs.yml` en la carpeta base del proyecto. +Si va a agregar nuevas páginas debe agregarlas al archivo `mkdocs.yml` en la carpeta base del proyecto. ```shell git clone https://github.com/eclipxe13/cfdiutils @@ -107,7 +107,7 @@ node node_modules/markdownlint-cli/markdownlint.js !!! note "mkdocs" Herramienta de `python` para crear la documentación en formato html -Revisa la página de . +Revisa la página . En Debian GNU/Linux y derivados lo puedes instalar usando: diff --git a/docs/contribuir/guia-windows.md b/docs/contribuir/guia-windows.md index 7fb3770f..bcc6edba 100644 --- a/docs/contribuir/guia-windows.md +++ b/docs/contribuir/guia-windows.md @@ -1,24 +1,15 @@ # Guía para trabajar en MS Windows -Microsoft windows no es el entorno de desarrollo primario, sin embargo aquí unos consejos +Microsoft windows no es el entorno de desarrollo primario, sin embargo, aquí unos consejos para poder desarrollar (código o documentación) en esta plataforma. Recuerda consultar la [Guía del desarrollador](guia-desarrollador.md) y la [Guía del documentador](guia-documentador.md) como primeros pasos. -## AppVeyor - -[AppVeyor] es una plataforma de integración continua con sistema operativo MS Windows. -Esta librería es construida en esta plataforma para garantizar la compatibilidad. - -Gracias a la integración hecha en 2018-07-17 se pudieron encontrar algunos bugs menores -y hacer las reparaciones necesarias, en especial en el paquete [XmlSchemaValidator]. - - ## Chocolatey La forma más conveniente de preparar un entorno de desarrollo dentro de MS Windows es -utilizar [Chocolatey]. Este es un gestor de paquetes (tipo `apt` o `yum`) que permite la +utilizar [`Chocolatey`][chocolatey]. Este es un gestor de paquetes (tipo `apt` o `yum`) que permite la instalación y actualización de software de manera ágil. Recuerda que para instalar, desinstalar o actualizar paquetes requieres privilegios administrativos. @@ -34,7 +25,6 @@ choco install -y git php saxonhe Para evitar problemas con git y los finales de línea, es importante que configures tu entorno de desarrollo de la siguiente forma. -Al momento de que [AppVeyor] hace el clon del proyecto está trabajando de esta misma manera. ```shell git config --global core.autocrlf input @@ -43,7 +33,6 @@ git config --global core.autocrlf input > Referencias: > > * -> * ## Ejecución de pruebas locales @@ -67,7 +56,7 @@ vendor\bin\phpunit vendor\bin\phpstan.bat --no-progress analyse --level max src tests ``` -Lamentablemente no se puede ejecutar `composer dev:build` o alguno de los comandos personalizados +Lamentablemente, no se puede ejecutar `composer dev:build` o alguno de los comandos personalizados definidos `composer.json` porque no funcionan correctamente. @@ -95,8 +84,8 @@ vendor\bin\phpunit ## Documentación -En teoría, si tienes instalado [nodejs] y [python] ya sea usando [chocolatey] o por algún instalador -deberías de poder ejecutar las herramientas de construcción de paquetes sin mayor complicación +En teoría, si tienes instalado [`nodejs`][nodejs] y [`python`][python], ya sea usando [`chocolatey`][chocolatey] o +por algún instalador, deberías de poder ejecutar las herramientas de construcción de paquetes sin mayor complicación siguiendo los pasos generales de la [Guía del documentador](guia-documentador.md). ```shell @@ -116,14 +105,12 @@ mkdocs serve ## GNU/Linux en MS Windows -Con las últimas versiones de MS Windows es podible ejecutar en una máquina virtual interna +Con las últimas versiones de MS Windows es posible ejecutar en una máquina virtual interna alguna versión de GNU/Linux como Ubuntu o SUSE. Si sigues este camino, solo ten en cuenta que, aunque estés en un sistema operativo MS Windows en realidad los comandos se ejecutan en otra *máquina virtual* por lo que las pruebas y comandos que ejecutes será como Linux, no como MS Windows. -[appveyor]: https://www.appveyor.com/ [chocolatey]: https://chocolatey.org/ -[XmlSchemaValidator]: https://github.com/eclipxe13/XmlSchemaValidator [nodejs]: https://nodejs.org/es/ [python]: https://www.python.org/ diff --git a/docs/crear/cfdi-de-retenciones-e-informacion-de-pagos.md b/docs/crear/cfdi-de-retenciones-e-informacion-de-pagos.md index 29ba75a4..e88a2d9f 100644 --- a/docs/crear/cfdi-de-retenciones-e-informacion-de-pagos.md +++ b/docs/crear/cfdi-de-retenciones-e-informacion-de-pagos.md @@ -5,7 +5,7 @@ ahí encontrarás más información de referencia. En esta sección encontrarás el ejemplo para poder crear un CFDI de este tipo. -La estrategia para crear este un CFDI de retenciones es la misma que para [crear un CFDI 3.3](../crear/crear-cfdi.md). +La estrategia para crear este un CFDI de retenciones es la misma que para [crear un CFDI 4.0](../crear/crear-cfdi-40.md). Consulta la información relacionada con el uso de [elementos](../componentes/elements.md), [nodos](../componentes/nodes.md) y [complementos no implementados](../crear/complementos-aun-no-implementados.md). diff --git a/docs/crear/complemento-nomina12b.md b/docs/crear/complemento-nomina12b.md index 97ec84f5..24616b45 100644 --- a/docs/crear/complemento-nomina12b.md +++ b/docs/crear/complemento-nomina12b.md @@ -6,10 +6,10 @@ vigente a partir del 01 de enero del 2020. La documentación del complemento la puedes encontrar en el sitio oficial del SAT: -- Recibo de nómina +- Recibo de nómina: y . -- Estándar técnico . -- Catálogos . +- Estándar técnico: . +- Catálogos: . Según la documentación técnica el XML **debe cumplir** con la siguiente especificación: diff --git a/docs/crear/complementos-aun-no-implementados.md b/docs/crear/complementos-aun-no-implementados.md index 1cdc932d..4acf41e2 100644 --- a/docs/crear/complementos-aun-no-implementados.md +++ b/docs/crear/complementos-aun-no-implementados.md @@ -63,7 +63,7 @@ Dado el ejemplo anterior, el comprobante contendrá la siguiente información: - + diff --git a/docs/crear/crear-cfdi-33.md b/docs/crear/crear-cfdi-33.md new file mode 100644 index 00000000..c6bc4d1f --- /dev/null +++ b/docs/crear/crear-cfdi-33.md @@ -0,0 +1,10 @@ +# Creación de CFDI 3.3 + +Por favor, lee la documentación de cómo [crear un CFDI 4.0](crear-cfdi-40.md). +Todas las reglas generales de creación aplican para CFDI 3.3. + +Utiliza los elementos de ayuda del espacio de nombres `CfdiUtils\Elements\Cfdi33` +y el objeto de creación `CfdiUtils\CfdiCreator33`. + +Dado que el SAT suspendió el uso de la versión 3.3, las clases dedicadas a la creación de +CFDI en estas versiones quedará sin soporte y desaparecerá en la siguiente versión mayor. diff --git a/docs/crear/crear-cfdi-40.md b/docs/crear/crear-cfdi-40.md index 717a56a2..f2ad6ce9 100644 --- a/docs/crear/crear-cfdi-40.md +++ b/docs/crear/crear-cfdi-40.md @@ -1,7 +1,212 @@ # Creación de CFDI 4.0 -Por favor, lee la documentación de cómo [crear un CFDI 3.3](crear-cfdi.md). -Todas las reglas generales de creación aplican para CFDI 4.0. +Para crear un CFDI versión 4.0 se ofrece el objeto `CfdiUtils\CfdiCreator40`. + +Este objeto trabaja directamente con la estructura `CfdiUtils\Elements\Cfdi40\Comprobante` +para facilitar la manipulación de la estructura y los datos, y contiene métodos que ayudan +a establecer el certificado, generar el sello, generar o almacenar el XML, y validar la estructura recién creada. + +Esta clase es una especie de pegamento de todas las pequeñas utilerías y estructuras de datos. + +## Métodos de ayuda + +- `comprobante(): Comprobante`: Obtiene el nodo raíz `Comprobante`. Todos los métodos utilizan este objeto. + +- `putCertificado(Certificado $certificado, bool $putEmisorRfcNombre = true)`: Establece el valor de los atributos + `NoCertificado` y `Certificado`, y si `$putEmisorRfcNombre` es verdadero entonces también establece el valor + de `Rfc` y `Nombre` en el nodo `Emisor`. + +- `asXml(): string`: Genera y devuelve el contenido XML de la exportación del nodo `Comprobante`. + +- `saveXml(string $filename): bool`: Genera y almacena el contenido XML. + +- `buildCadenaDeOrigen(): string`: Construye la cadena de origen siempre que exista un resolvedor de recursos XML. + +- `buildSumasConceptos(int $precision = 2): SumasConceptos`: Genera un objeto de tipo `SumasConceptos` + según los datos de los `Conceptos`. + +- `addSumasConceptos(SumasConceptos $sumasConceptos = null, int $precision = 2)`: Establece los valores de `$sumasConceptos` + en el comprobante, si no se pasó el objeto entonces lo fabrica con `buildSumasConceptos()`. Las sumas en cuestión son + los valores del comprobante `SubTotal`, `Total` y `Descuento`, nodo de impuestos del comprobante, y también + los totales `TotaldeRetenciones` y `TotaldeTraslados` del complemento de impuestos locales. + +- `addSello(string $key, string $passPhrase = '')`: Realiza el procedimiento de firma con la llave primaria y + almacena el valor de dicha llave en base64 en el atributo `Sello`. + Si el certificado existe como un objeto `Certificado` entonces este método también verifica que + la llave primaria pertenece al certificado y genera una excepción si no es así. + +- `validate(): Asserts`: Crea un validador que verifica la estructura XML contra su archivo XSD + y realiza validaciones adicionales. + Consulta la [documentación de validaciones](../validar/validacion-cfdi.md) para más información. + +- `moveSatDefinitionsToComprobante(): void`: Mueve las declaraciones de espacios de nombres `xmlns:*` + y las declaraciones de ubicación de esquemas `xsi:schemaLocation` al nodo raíz. + + +## Pasos básicos de creación de un CFDI + +No hay una sola forma de hacer las cosas, pero la receta de creación sería algo como: + +```php + 'XXX', + 'Folio' => '0000123456', + // y otros atributos más... +]; +$creator = new \CfdiUtils\CfdiCreator40($comprobanteAtributos, $certificado); + +$comprobante = $creator->comprobante(); + +// No agrego (aunque puedo) el Rfc y Nombre porque uso los que están establecidos en el certificado +$comprobante->addEmisor([ + 'Nombre' => 'ESCUELA KEMPER URGATE', + 'RegimenFiscal' => '601', // General de Ley Personas Morales +]); + +$comprobante->addReceptor([/* Atributos del receptor */]); + +$comprobante->addConcepto([ + /* Atributos del concepto */ +])->addTraslado([ + /* Atributos del impuesto trasladado */ +]); + +// método de ayuda para establecer las sumas del comprobante e impuestos +// con base en la suma de los conceptos y la agrupación de sus impuestos +$creator->addSumasConceptos(null, 2); + +// método de ayuda para generar el sello (obtener la cadena de origen y firmar con la llave privada) +$creator->addSello('file:// ... ruta para mi archivo key convertido a PEM ...', 'contraseña de la llave'); + +// método de ayuda para mover las declaraciones de espacios de nombre al nodo raíz +$creator->moveSatDefinitionsToComprobante(); + +// método de ayuda para validar usando las validaciones estándar de creación de la librería +$asserts = $creator->validate(); +if ($asserts->hasErrors()) { // contiene si hay o no errores + print_r(['errors' => $asserts->errors()]); + return; +} + +// método de ayuda para generar el xml y guardar los contenidos en un archivo +$creator->saveXml('... lugar para almacenar el cfdi ...'); + +// método de ayuda para generar el xml y retornarlo como un string +$creator->asXml(); +``` + +En el ejemplo anterior en la línea que dice `$comprobante = $creator->comprobante();` +se está obteniendo el **elemento** `CfdiUtils\Elements\Cfdi40\Comprobante`. + +Todos los [elementos](../componentes/elements.md) son una especialización de los [nodos](../componentes/nodes.md). +A diferencia de los nodos, los elementos contienen métodos de ayuda que permiten entender los hijos que manejan, +por ejemplo `CfdiUtils\Elements\Cfdi40\Comprobante` contiene un método llamado `addReceptor()` +con el que se puede insertar en el lugar correcto el nodo "Receptor" incluyendo un arreglo de atributos. + +## Acerca de las definiciones de espacios de nombre + +A partir de la versión `2.12.0` se agregó el método `moveSatDefinitionsToComprobante()` que ayuda a mover las +definiciones de espacios de nombres al nodo principal `cfdi:Comprobante`. + +Si no se llama a este método, las definiciones de espacios de nombres quedarán en el nodo que las utiliza, +por ejemplo: + +```xml + + + + + + + + + +``` + +Y si aplica este método las definiciones cambiarán de lugar, quedando como: + +```xml + + + + + + + + + +``` + +En realidad, esto no es una regla importarte e incluso se podría decir que sale de la práctica común de XML. +Sin embargo, en la documentación técnica del SAT lo documenta como *obligatorio*. + +Si no creaste tus CFDI con esta estructura malamente obligada por el SAT, no te preocupes, en caso de ser +necesario podrías hasta modificar tus CFDI anteriores (aun cuando tengan sello) porque la ubicación de las +definiciones de los espacios de nombres no participan en la formación de la cadena de origen. + + +## Formación del texto de los códigos QR + +La formación del texto que se incluye en los códigos QR tiene reglas específicas +y puede utilizarse el objeto `\CfdiUtils\ConsultaCfdiSat\RequestParameters` +para obtener el texto contenido en el código QR. + +Este es un ejemplo para obtener la URL directamente de un contenido XML. + +```php +$xmlContents = '...'; +$cfdi = \CfdiUtils\Cfdi::newFromString($xmlContents); +$parameters = \CfdiUtils\ConsultaCfdiSat\RequestParameters::createFromCfdi($cfdi); + +echo $parameters->expression(); // https://verificacfdi.facturaelectronica.sat.gob.mx/... +``` + + +## Orden de los nodos de un CFDI + +A pesar de tratarse de una estructura XML el SAT por las reglas impuestas en los +archivos XSD ha puesto reglas de orden de aparición de nodos. + +Por lo anterior **esta estructura presentará error** porque el nodo `Receptor` +debe ir después del nodo `Emisor`: + +```xml + + + + +``` + +Cuando se está usando el espacio de nombres `CfdiUtils\Elements` las estructuras conocen el +orden en el que deben existir los nodos, por lo que no es necesario preocuparse por el orden de aparición. +Esta mejora fue introducida en la versión 2.4.0. + +Si se está utilizando `CfdiUtils\Nodes` de forma independiente a `CfdiUtils\Elements` entonces será necesario +establecer el orden de los nodos con el método `CfdiUtils\Nodes\Nodes::setOrder(array $order)`. +O simplemente insertar los nodos en el orden correcto. + + +## Resolvedor de recursos XML + +Los archivos XSD necesarios para validar la estructura XML de un CFDI y +los archivos XSLT necesarios para generar la cadena de origen +son almacenados localmente y reutilizados cada vez que se require. + +Para establecer dicha configuración diferente a la predeterminada establezca el objeto `XmlResolver` +usando el método `CfdiCreator40::setXmlResolver(XmlResolver $xmlResolver = null)`. + +Si establece el valor a nulo (`CfdiCreator40::hasXmlResolver()` es `false`) entonces no se podrá +crear la cadena de origen (necesario para obtener la ruta de los archivos XSLT) y tampoco se podrá abastecer +a los objetos de validación que requieran de un resolvedor con el objeto apropiado resultando en varias revisiones +sin ejecutar. + +Si lo que desea es no almacenar localmente los recursos entonces lo que debe hacer es establecer +una cadena de caracteres vacía mediante el método `XmlResolver::setLocalPath`. ## Migrar de CFDI 3.3 a CFDI 4.0 @@ -14,5 +219,55 @@ de CFDI 3.3 a CFDI 4.0 lo menos dolorosa posible. Ten en cuenta que tendrás que modificar la información que pasas a los elementos porque se han agregado varios de ellos, sin embargo, la *compatibilidad* ofrecida permite que te concentres en solo un problema: -*Aplicar los cambios del SAT a CFDI 4.0.*. +*Aplicar los cambios del SAT a CFDI 4.0*. + + +## Uso de `phpcfdi/credentials` + +Esta librería implementa objetos para trabajar con Certificados y Llaves primarias. +Sin embargo, se recomienda usar [`phpcfdi/credentials`](https://github.com/phpcfdi/credentials) +dado que es una librería especializada para trabajar con archivos FIEL y CSD. + +El siguiente ejemplo muestra cómo se puede usar `phpcfdi/credentials` para abrir +el certificado y llave privada tal como son entregadas por el SAT. + +```php +putCertificado( + new Certificado($csd->certificate()->pem()), + false // no establecer el RFC ni el nombre del emisor +); + +// se construye el pre-cfdi ... + +// sellado del cfdi +$creator->addSello($csd->privateKey()->pem(), $csd->privateKey()->passPhrase()); +``` + +El CSD también se puede crear usando el contenido del certificado y llave privada. + +```php + 'XXX', - 'Folio' => '0000123456', - // y otros atributos más... -]; -$creator = new \CfdiUtils\CfdiCreator33($comprobanteAtributos, $certificado); - -$comprobante = $creator->comprobante(); - -// No agrego (aunque puedo) el Rfc y Nombre porque uso los que están establecidos en el certificado -$comprobante->addEmisor([ - 'RegimenFiscal' => '601', // General de Ley Personas Morales -]); - -$comprobante->addReceptor([/* Atributos del receptor */]); - -$comprobante->addConcepto([ - /* Atributos del concepto */ -])->addTraslado([ - /* Atributos del impuesto trasladado */ -]); - -// método de ayuda para establecer las sumas del comprobante e impuestos -// con base en la suma de los conceptos y la agrupación de sus impuestos -$creator->addSumasConceptos(null, 2); - -// método de ayuda para generar el sello (obtener la cadena de origen y firmar con la llave privada) -$creator->addSello('file:// ... ruta para mi archivo key convertido a PEM ...', 'contraseña de la llave'); - -// método de ayuda para mover las declaraciones de espacios de nombre al nodo raíz -$creator->moveSatDefinitionsToComprobante(); - -// método de ayuda para validar usando las validaciones estándar de creación de la librería -$asserts = $creator->validate(); -$asserts->hasErrors(); // contiene si hay o no errores - -// método de ayuda para generar el xml y guardar los contenidos en un archivo -$creator->saveXml('... lugar para almacenar el cfdi ...'); - -// método de ayuda para generar el xml y retornarlo como un string -$creator->asXml(); -``` - -En el ejemplo anterior en la línea que dice `$comprobante = $creator->comprobante();` -se está obteniendo el **elemento** `CfdiUtils\Elements\Cfdi33\Comprobante`. - -Todos los [elementos](../componentes/elements.md) son una especialización de los [nodos](../componentes/nodes.md). -A diferencia de los nodos, los elementos contienen métodos de ayuda que pemiten entender los hijos que manejan, -por ejemplo `CfdiUtils\Elements\Cfdi33\Comprobante` contiene un método llamado `addReceptor()` -con el que se puede insertar en el lugar correcto el nodo "Receptor" incluyendo un arreglo de atributos. - -## Acerca de las definiciones de espacios de nombre - -A partir de la versión `2.12.0` se agregó el método `moveSatDefinitionsToComprobante()` que ayuda a mover las -definiciones de espacios de nombres al nodo principal `cfdi:Comprobante`. - -Si no se llama a este método, las definciones de espacios de nombres quedarán en el nodo que las utiliza, por -ejemplo: - -```xml - - - - - - - - - -``` - -Y si aplica este método las definiciones cambiarán de lugar, quedando como: - -```xml - - - - - - - - - -``` - -En realidad, esto no es una regla importarte e incluso se podría decir que sale de la práctica común de XML. -Sin embargo, en la documentación técnica del SAT lo documenta como *mandatorio*. Es decir, se está obligado -a seguir esta definición. - -Si no creaste tus CFDI con esta estructura malamente requerida por el SAT, no te preocupes, en caso de ser -necesario podrías hasta modificar tus CFDI anteriores (aun cuando tengan sello) porque la ubicación de las -definiciones de los espacios de nombres no participan en la formación de la cadena de origen. - - -## Formación del texto de los códigos QR - -La formación del texto que se incluye en los códigos QR tiene reglas específicas -y puede utilizarse el objeto `\CfdiUtils\ConsultaCfdiSat\RequestParameters` -para obtener el texto contenido en el código QR. - -Este es un ejemplo para la obtener la URL directamente de un contenido XML. - -```php -$xmlContents = '...'; -$cfdi = \CfdiUtils\Cfdi::newFromString($xmlContents); -$parameters = \CfdiUtils\ConsultaCfdiSat\RequestParameters::createFromCfdi($cfdi); - -echo $parameters->expression(); // https://verificacfdi.facturaelectronica.sat.gob.mx/... -``` - - -## Orden de los nodos de un CFDI - -A pesar de tratarse de una estructura XML el SAT por las reglas impuestas en los -archivos XSD ha puesto reglas de orden de aparición de nodos. - -Por lo anterior **esta estructura presentará error** porque el nodo `Receptor` -debe ir después del nodo `Emisor`: - -```xml - - - - -``` - -Cuando se está usando el espacio de nombres `CfdiUtils\Elements` las estructuras conocen el -orden en el que deben existir los nodos, por lo que no es necesario preocuparse por el orden de aparición. -Esta mejora fue introducida en la versión 2.4.0. - -Si se está utilizando `CfdiUtils\Nodes` de forma independiente a `CfdiUtils\Elements` entonces será necesario -establecer el orden de los nodos con el método `CfdiUtils\Nodes\Nodes::setOrder(array $order)`. -O simplemente insertar los nodos en el orden correcto. - - -## Resolvedor de recursos XML - -Los archivos XSD necesarios para validar la estructura XML de un CFDI y -los archivos XSLT necesarios para generar la cadena de origen -son almacenados localmente y reutilizados cada vez que se require. - -Para establecer dicha configuración diferente a la predeterminada establezca el objeto `XmlResolver` -usando el método `CfdiCreator33::setXmlResolver(XmlResolver $xmlResolver = null)`. - -Si establece el valor a nulo (`CfdiCreator33::hasXmlResolver()` es `false`) entonces no se podrá -crear la cadena de origen (necesario para obtener la ruta de los archivos XSLT) y tampoco se podrá abastecer -a los objetos de validación que requieran de un resolvedor con el objeto apropiado resultando en varias revisiones -sin ejecutar. - -Si lo que desea es no almacenar localmente los recursos entonces lo que debe hacer es establecer -una cadena de caracteres vacía mediante el método `XmlResolver::setLocalPath`. diff --git a/docs/crear/elements-cfdi-40.md b/docs/crear/elements-cfdi-40.md new file mode 100644 index 00000000..ce09aaab --- /dev/null +++ b/docs/crear/elements-cfdi-40.md @@ -0,0 +1,165 @@ +# Elementos de CFDI versión 4.0 + +El espacio de nombres de `CfdiUtils\Elements\Cfdi40` permite trabajar en forma más fácil +con los nodos con nombres y acciones específicas y es la base de la creación de un CFDI 4.0. + +Es la implementación de [elementos](../componentes/elements.md), +que son [nodos](../componentes/nodes.md) con métodos de ayuda. + +## `Comprobante` + +Representa el nodo raíz `Comprobante`. +Contiene los siguientes métodos de ayuda: + +- `getInformacionGlobal(): InformacionGlobal`: Crea (si no existe) y obtiene el nodo único `InformacionGlobal`. +- `addInformacionGlobal(array $attributes = []): InformacionGlobal`: Agrega y devuelve el único nodo `InformacionGlobal`. +- `addCfdiRelacionados(array $attributes = []): CfdiRelacionados`: Agrega y devuelve un nuevo nodo `CfdiRelacionados`. +- `multiCfdiRelacionados(array ...$elementAttributes): Comprobante`: Agrega nuevos nodos `CfdiRelacionados`, es una forma rápida de llamar al método `multiTraslado` múltiples veces. +- `getEmisor(): Emisor`: Crea (si no existe) y obtiene el nodo único `Emisor`. +- `addEmisor(array $attributes = []): Emisor`: Agrega y devuelve el único nodo `Emisor`. +- `getReceptor(): Receptor`: Crea (si no existe) y obtiene el nodo único `Receptor`. +- `addReceptor(array $attributes = []): Receptor`: Agrega y devuelve el único nodo `Receptor`. +- `getConceptos(): Conceptos`: Crea (si no existe) y obtiene el nodo único `Conceptos`. +- `addConcepto(array $attributes = [], array $children = []): Concepto`: Agrega y devuelve un nuevo nodo `Concepto`. +- `getImpuestos(): Impuestos`: Crea (si no existe) y obtiene el nodo único `Impuestos`. +- `addImpuestos(array $attributes = []): Impuestos`: Agrega y devuelve el único nodo `Impuestos`. +- `addTraslado(array $attributes = []): Traslado`: Agrega y devuelve un nuevo nodo `Traslado` (en `Impuestos/Traslados`). +- `multiTraslado(array ...$elementAttributes): Comprobante`: Agrega nuevos nodo `Traslado`, es una forma rápida de llamar al método `addTraslado` múltiples veces. +- `addRetencion(array $attributes = []): Retencion`: Agrega y devuelve un nuevo nodo `Retencion` (en `Impuestos/Retenciones`). +- `multiRetencion(array ...$elementAttributes): Comprobante`: Agrega nuevos nodos de `Retencion`, es una forma rápida de llamar al método `addRetencion` múltiples veces. +- `getComplemento(): Complemento`: Crea (si no existe) y obtiene el nodo único `Complemento`. +- `addComplemento(NodeInterface $children): Comprobante`: Agrega el nodo `$children` dentro del único nodo `Complemento`. +- `getAddenda(): Addenda`: Crea (si no existe) y obtiene el nodo único `Addenda`. +- `addAddenda(NodeInterface $children): Comprobante`: Agrega el nodo `$children` dentro del único nodo `Addenda`. + + +## `CfdiRelacionados` + +Representa el nodo `Comprobante/CfdiRelacionados`. + +- `addCfdiRelacionado(array $attributes = []): CfdiRelacionado`: Agrega y devuelve un nuevo nodo `CfdiRelacionado`. +- `multiCfdiRelacionado(array ...$elementAttributes): CfdiRelacionados`: Agrega nuevos nodos de `CfdiRelacionado`, es una forma rápida de llamar al método `addCfdiRelacionado` múltiples veces. + + +## `CfdiRelacionado` + +Representa el nodo `Comprobante/CfdiRelacionados/CfdiRelacionado`. + + +## `Emisor` + +Representa el nodo `Comprobante/Emisor`. + + +## `Receptor` + +Representa el nodo `Comprobante/Receptor`. + + +## `Conceptos` + +Representa el nodo `Comprobante/Conceptos`. + +- `addConcepto(array $attributes = []): Concepto`: Agrega y devuelve un nuevo nodo `Conceptos`. +- `multiConcepto(array ...$elementAttributes): Conceptos`: Agrega nuevos nodos de `Concepto`, es una forma rápida de llamar al método `addConcepto` múltiples veces. + + +## `Concepto` + +Representa el nodo `Comprobante/Conceptos/Concepto`. + +- `getImpuestos(): ConceptoImpuestos`: Crea (si no existe) y obtiene el nodo único `ConceptoImpuestos`. +- `addTraslado(array $attributes = []): Traslado`: Agrega y devuelve un nuevo nodo `Traslado` (en `Impuestos/Traslados`). +- `multiTraslado(array ...$elementAttributes): Concepto`: Agrega nuevos nodos `Traslado`, es una forma rápida de llamar al método `addTraslado` múltiples veces. +- `addRetencion(array $attributes = []): Retencion`: Agrega y devuelve un nuevo nodo `Retencion` (en `Impuestos/Retenciones`). +- `multiRetencion(array ...$elementAttributes): Concepto`: Agrega nuevos nodos `Retencion`, es una forma rápida de llamar al método `addRetencion` múltiples veces. +- `addInformacionAduanera(array $attributes = []): InformacionAduanera`: Agrega y devuelve un nuevo nodo `InformacionAduanera`. +- `multiInformacionAduanera(array ...$elementAttributes): Concepto`: Agrega nuevos nodos `InformacionAduanera`, es una forma rápida de llamar al método `addInformacionAduanera` múltiples veces. +- `addCuentaPredial(array $attributes = []): CuentaPredial`: Agrega y devuelve el único nodo `CuentaPredial`. +- `getComplementoConcepto(): ComplementoConcepto`: Crea (si no existe) y obtiene el nodo único `ComplementoConcepto`. +- `addComplementoConcepto(array $attributes = [], array $children = []): ComplementoConcepto`: Agrega y devuelve el único nodo `Complementoconcepto`. +- `addParte(array $attributes = [], array $children = []): Parte`: Agrega y devuelve un nuevo nodo `Parte`. +- `multiParte(array ...$elementAttributes)`: Agrega nuevos nodos `Parte`, es una forma rápida de llamar al método `addParte` múltiples veces. + + +### `ConceptoImpuestos` + +La clase `ConceptoImpuestos` es igual a la clase `Impuestos` con la única diferencia de orden: + +El primer nodo de `Impuestos` dentro de `Concepto` debe ser `Traslados`. +Mientras que el primer nodo de `Impuestos` dentro de `Comprobante` debe ser `Retenciones`. + +- `Comprobante` + - `Conceptos` + - `Impuestos` + - `Traslados` + - `Retenciones` + - `Impuestos` + - `Retenciones` + - `Traslados` + +También puedes notar que no existe el método `Concepto::addImpuestos(array $attributes = [])` +porque este nodo por definición no tiene atributos propios y, por lo tanto, no es necesario. + + +## `InformacionAduanera` + +Representa el nodo `Comprobante/Conceptos/Concepto/InformacionAduanera` +y `Comprobante/Conceptos/Concepto/Parte/InformacionAduanera`. + + +## `CuentaPredial` + +Representa el nodo `Comprobante/Conceptos/Concepto/CuentaPredial`. + + +## `ComplementoConcepto` + +Representa el nodo `Comprobante/Conceptos/Concepto/ComplementoConcepto`. + + +## `Parte` + +Representa el nodo `Comprobante/Conceptos/Concepto/Parte`. + +- `addInformacionAduanera(array $attributes = []): InformacionAduanera`: Agrega y devuelve un nuevo nodo `InformacionAduanera`. +- `multiInformacionAduanera(array ...$elementAttributes): Parte`: Agrega nuevos nodos `InformacionAduanera`, es una forma rápida de llamar al método `addInformacionAduanera` múltiples veces. + + +## `Impuestos` + +Representa el nodo `Comprobante/Impuestos` y también `Comprobante/Conceptos/Concepto/Impuestos`. + +- `getTraslados(): Traslados`: Crea (si no existe) y obtiene el nodo único `Traslados`. +- `getRetenciones(): Retenciones`: Crea (si no existe) y obtiene el nodo único `Retenciones`. + +Aunque el nodo `Impuestos` (hijo de `Comprobante`) es diferente que el nodo `Impuestos` (hijo de `Concepto`) +se puede utilizar la misma estructura de datos, porque los cambios se dan en atributos y no en hijos. + + +## `Traslados` + +Representa el nodo `Comprobante/Impuestos/Traslados` y `Comprobante/Conceptos/Concepto/Impuestos/Traslados`. + +- `addTraslado(array $attributes = []): Traslado`: Agrega y devuelve un nuevo nodo `Traslado`. +- `multiTraslado(array ...$elementAttributes): Traslados`: Agrega nuevos nodos `Traslado`, es una forma rápida de llamar al método `addTraslado` múltiples veces. + + +## `Retenciones` + +Representa el nodo `Comprobante/Impuestos/Retenciones` y `Comprobante/Conceptos/Concepto/Impuestos/Retenciones`. + +- `addRetencion(array $attributes = []): Retencion`: Agrega y devuelve un nuevo nodo `Retencion`. +- `multiRetencion(array ...$elementAttributes): Retenciones`: Agrega nuevos nodos `Retencion`, es una forma rápida de llamar al método `addRetencion` múltiples veces. + + +## `Traslado` + +Representa el nodo `Comprobante/Impuestos/Retenciones/Traslado` +y `Conceptos/Concepto/Impuestos/Retenciones/Traslado`. + + +## `Retencion` + +Representa el nodo `Comprobante/Impuestos/Retenciones/Retencion` +y `Conceptos/Concepto/Impuestos/Retenciones/Retencion`. diff --git a/docs/crear/elements-cfdi33.md b/docs/crear/elements-cfdi33.md deleted file mode 100644 index 9fe8bd18..00000000 --- a/docs/crear/elements-cfdi33.md +++ /dev/null @@ -1,164 +0,0 @@ -# Elementos de Cfdi versión 33 - -El espacio de nombres de `CfdiUtils\Elements\Cfdi33` permite trabajar en forma más fácil -con los nodos con nombres y acciones específicas y es la base de la creación de un CFDI 3.3. - -Es la implementación de [elementos](../componentes/elements.md), -que son [nodos](../componentes/nodes.md) con métodos de ayuda. - -## Comprobante `cfdi:Comprobante` - -Representa el nodo raiz Comprobante. -Contiene los siguientes métodos de ayuda: - -- `getCfdiRelacionados(): CfdiRelacionados`: Crea (si no existe) y obtiene el nodo único CfdiRelacionados -- `addCfdiRelacionados(array $attributes = []): CfdiRelacionados`: Agrega y devuelve el único nodo CfdiRelacionados -- `addCfdiRelacionado(array $attributes = []): CfdiRelacionado`: Agrega y devuelve un nuevo nodo CfdiRelacionado -- `addCfdiRelacionados(array ...$attributes): Comprobante`: Agrega nuevos nodos CfdiRelacionado, es una forma rápida de llamar al método `addCfdiRelacionado` múltiples veces -- `getEmisor(): Emisor`: Crea (si no existe) y obtiene el nodo único Emisor -- `addEmisor(array $attributes = []): Emisor`: Agrega y devuelve el único nodo Emisor -- `getReceptor(): Receptor`: Crea (si no existe) y obtiene el nodo único Receptor -- `addReceptor(array $attributes = []): Receptor`: Agrega y devuelve el único nodo Receptor -- `getConceptos(): Conceptos`: Crea (si no existe) y obtiene el nodo único Conceptos -- `addConcepto(array $attributes = [], array $children = []): Concepto`: Agrega y devuelve un nuevo nodo Concepto -- `getImpuestos(): Impuestos`: Crea (si no existe) y obtiene el nodo único Impuestos -- `addImpuestos(array $attributes = []): Impuestos`: Agrega y devuelve el único nodo Impuestos -- `addTraslado(array $attributes = []): Traslado`: Agrega y devuelve un nuevo nodo Traslado (en Impuestos / Traslados) -- `multiTraslado(array ...$elementAttributes): Comprobante`: Agrega nuevos nodo Traslado, es una forma rápida de llamar al método `addTraslado` múltiples veces -- `addRetencion(array $attributes = []): Retencion`: Agrega y devuelve un nuevo nodo Retencion (en Impuestos / Retenciones) -- `multiRetencion(array ...$elementAttributes): Comprobante`: Agrega nuevos nodo Retencion, es una forma rápida de llamar al método `addRetencion` múltiples veces -- `getComplemento(): Complemento`: Crea (si no existe) y obtiene el nodo único Complemento -- `addComplemento(NodeInterface $children): Comprobante`: Agrega el nodo $children dentro del único nodo Complemento -- `getAddenda(): Addenda`: Crea (si no existe) y obtiene el nodo único Addenda -- `addAddenda(NodeInterface $children): Comprobante`: Agrega el nodo $children dentro del único nodo Addenda - - -## CfdiRelacionados `cfdi:CfdiRelacionados` - -Representa el nodo Comprobante / CfdiRelacionados. - -- `addCfdiRelacionado(array $attributes = []): CfdiRelacionado`: Agrega y devuelve un nuevo nodo CfdiRelacionado - - -## CfdiRelacionado `cfdi:CfdiRelacionado` - -Representa el nodo Comprobante / CfdiRelacionados / CfdiRelacionado. - - -## Emisor `cfdi:Emisor` - -Representa el nodo Comprobante / Emisor. - - -## Receptor `cfdi:Receptor` - -Representa el nodo Comprobante / Receptor. - - -## Conceptos `cfdi:Conceptos` - -Representa el nodo Comprobante / Conceptos. - -- `addConcepto(array $attributes = []): Concepto`: Agrega y devuelve un nuevo nodo Conceptos - - -## Conceptos `cfdi:Concepto` - -Representa el nodo Comprobante / Conceptos / Concepto. - -- `getImpuestos(): ConceptoImpuestos`: Crea (si no existe) y obtiene el nodo único ConceptoImpuestos -- `addTraslado(array $attributes = []): Traslado`: Agrega y devuelve un nuevo nodo Traslado (en Impuestos / Traslados) -- `multiTraslado(array ...$elementAttributes): Concepto`: Agrega nuevos nodo Traslado, es una forma rápida de llamar al método `addTraslado` múltiples veces -- `addRetencion(array $attributes = []): Retencion`: Agrega y devuelve un nuevo nodo Retencion (en Impuestos / Retenciones) -- `multiRetencion(array ...$elementAttributes): Concepto`: Agrega nuevos nodo Retencion, es una forma rápida de llamar al método `addRetencion` múltiples veces -- `addInformacionAduanera(array $attributes = []): InformacionAduanera`: Agrega y devuelve un nuevo nodo InformacionAduanera -- `multiInformacionAduanera(array ...$elementAttributes): Concepto`: Agrega nuevos nodo InformacionAduanera, es una forma rápida de llamar al método `addInformacionAduanera` múltiples veces -- `addCuentaPredial(array $attributes = []): CuentaPredial`: Agrega y devuelve el único nodo CuentaPredial -- `getComplementoConcepto(): ComplementoConcepto`: Crea (si no existe) y obtiene el nodo único ComplementoConcepto -- `addComplementoConcepto(array $attributes = [], array $children = []): ComplementoConcepto`: Agrega y devuelve el único nodo Complementoconcepto -- `addParte(array $attributes = [], array $children = []): Parte`: Agrega y devuelve un nuevo nodo Parte -- `multiParte(array ...$elementAttributes)`: Agrega nuevos nodo Parte, es una forma rápida de llamar al método `addParte` múltiples veces - - -### ConceptoImpuestos - -La clase `ConceptoImpuestos` es igual a la clase `Impuestos` con la única diferencia de orden: - -El primer nodo de `Impuestos` dentro de `Concepto` debe ser `Traslados`. -Mientras que el primer nodo de `Impuestos` dentro de `Comprobante` debe ser `Retenciones`. - -- Comprobante - - Conceptos - - Impuestos - - Traslados - - Retenciones - - Impuestos - - Retenciones - - Traslados - -También puedes notar que no existe el método `Concepto::addImpuestos(array $attributes = [])` -porque este nodo por definición no tiene atributos propios y por lo tanto no es necesario. - - -## InformacionAduanera `cfdi:InformacionAduanera` - -Representa el nodo Comprobante / Conceptos / Concepto / InformacionAduanera -y Comprobante / Conceptos / Concepto / Parte / InformacionAduanera. - - -## CuentaPredial `cfdi:CuentaPredial` - -Representa el nodo Comprobante / Conceptos / Concepto / CuentaPredial. - - -## ComplementoConcepto `cfdi:ComplementoConcepto` - -Representa el nodo Comprobante / Conceptos / Concepto / ComplementoConcepto. - - -## Parte `cfdi:Parte` - -Representa el nodo Comprobante / Conceptos / Concepto / Parte. - -- `addInformacionAduanera(array $attributes = []): InformacionAduanera`: Agrega y devuelve un nuevo nodo InformacionAduanera -- `multiInformacionAduanera(array ...$elementAttributes): Parte`: Agrega nuevos nodo InformacionAduanera, es una forma rápida de - llamar al método `addInformacionAduanera` múltiples veces - - -## Impuestos `cfdi:Impuestos` - -Representa el nodo Comprobante / Impuestos y también Comprobante / Conceptos / Concepto / Impuestos. - -- `getTraslados(): Traslados`: Crea (si no existe) y obtiene el nodo único Traslados. -- `getRetenciones(): Retenciones`: Crea (si no existe) y obtiene el nodo único Retenciones. - -Aunque el nodo impuestos (hijo de comprobante) es diferente que el nodo impuestos (hijo de concepto) -se puede utilizar la misma estructura de datos porque los cambios se dan a nivel de atributos y no de hijos. - - -## Traslados `cfdi:Traslados` - -Representa el nodo Comprobante / Impuestos / Traslados y Comprobante / Conceptos / Concepto / Impuestos / Traslados. - -- `addTraslado(array $attributes = []): Traslado`: Agrega y devuelve un nuevo nodo Traslado. -- `multiTraslado(array ...$elementAttributes): Traslados`: Agrega nuevos nodo Traslado, es una forma rápida de llamar al método `addTraslado` múltiples veces - - -## Retenciones `cfdi:Retenciones` - -Representa el nodo Comprobante / Impuestos / Retenciones y Comprobante / Conceptos / Concepto / Impuestos / Retenciones. - -- `addRetencion(array $attributes = []): Retencion`: Agrega y devuelve un nuevo nodo Retencion. -- `multiRetencion(array ...$elementAttributes): Retenciones`: Agrega nuevos nodo Retencion, es una forma rápida de llamar al método `addRetencion` múltiples veces - - -## Traslado `cfdi:Traslado` - -Representa el nodo Comprobante / Impuestos / Retenciones / Traslado -y Conceptos / Concepto / Impuestos / Retenciones / Traslado. - - -## Retencion `cfdi:Retencion` - -Representa el nodo Comprobante / Impuestos / Retenciones / Retencion -y Conceptos / Concepto / Impuestos / Retenciones / Retencion. diff --git a/docs/index.md b/docs/index.md index 6bba1f18..c197861f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -58,9 +58,9 @@ Validadores para CFDI 3.3 y CFDI 4.0. Solo hay métodos específicos para CFDI 3.3 y CFDI 4.0. -- [Crear un CFDI 3.3](crear/crear-cfdi.md) -- [Elementos de CFDI 3.3](crear/elements-cfdi33.md) +- [Crear un CFDI 3.3](crear/crear-cfdi-33.md) - [Crear un CFDI 4.0](crear/crear-cfdi-40.md) +- [Elementos de CFDI 3.3](crear/elements-cfdi-40.md) - [Elementos de Nómina 1.2 revisión B](crear/complemento-nomina12b.md) - [Agregar complementos](crear/complementos-aun-no-implementados.md) - [CFDI Retenciones](crear/cfdi-de-retenciones-e-informacion-de-pagos.md) diff --git a/docs/instalar/instalacion.md b/docs/instalar/instalacion.md index f973c549..0e850d0e 100644 --- a/docs/instalar/instalacion.md +++ b/docs/instalar/instalacion.md @@ -16,7 +16,7 @@ Las ventajas de usar composer son: ## Qué incluye el paquete de distribución No es lo mismo el proyecto de la librería que el paquete publicado en composer, esto es porque en el -proyecto se excluyen componentes relacionados con el desarrollo del proyecto, integración contínua, tests +proyecto se excluyen componentes relacionados con el desarrollo del proyecto, integración continua, pruebas y dependencias de desarrollo. Lo que encontrarás en `vendor/eclipxe/cfdiutils/` es: @@ -35,8 +35,7 @@ Si tu proyecto no utiliza composer, te puede convenir usar este truco: ```shell cd mi_proyecto mkdir cfdiutils -cd cfdiutils -composer require eclipxe/cfdiutils +composer require --working-dir=cfdiutils eclipxe/cfdiutils ``` Dentro del script de PHP donde deseas incluir php incluye el archivo autoload generado: diff --git a/docs/leer/leer-cfdi.md b/docs/leer/leer-cfdi.md index 498be339..8c37badd 100644 --- a/docs/leer/leer-cfdi.md +++ b/docs/leer/leer-cfdi.md @@ -44,7 +44,7 @@ $comprobante = $cfdi->getNode(); // Nodo de trabajo del nodo cfdi:Comprobante El método estático `CfdiUtils\Cfdi::newFromString` verifica que el contenido XML no esté vacío y no contenga errores (se pueda crear un `DOMDocument` a partir de este contenido). -Posteriormente invoca la creación de un objeto de tipo `CfdiUtils\Cfdi` pasando +Posteriormente, invoca la creación de un objeto de tipo `CfdiUtils\Cfdi` pasando el objeto `DOMDocument` como parámetro. @@ -53,9 +53,10 @@ el objeto `DOMDocument` como parámetro. Al crear un objeto de tipo `CfdiUtils\Cfdi` se verifican las siguientes reglas del objeto `DOMDocument`: -1. el documento implementa el espacio de nombres del cfdi `http://www.sat.gob.mx/cfd/3` -1. con el prefijo `cfdi` -1. en el elemento raíz `` +- El documento implementa el espacio de nombres del CFDI `http://www.sat.gob.mx/cfd/3` o `http://www.sat.gob.mx/cfd/4`. +- El espacio de nombres tiene el prefijo `cfdi`. +- El elemento raíz tiene el nombre `cfdi:Comprobante`. +- El atributo de versión coincide con el espacio de nombres. No realiza ninguna validación. La validación de un CFDI está fuera de los límites de esta clase. diff --git a/docs/leer/limpieza-cfdi.md b/docs/leer/limpieza-cfdi.md index e41baa28..fb2f090b 100644 --- a/docs/leer/limpieza-cfdi.md +++ b/docs/leer/limpieza-cfdi.md @@ -1,9 +1,9 @@ # Limpieza de un CFDI -Frecuentemente se reciben archivos de CFDI que fueron firmados y son válidos pero contienen errores. +Frecuentemente, se reciben archivos de CFDI que fueron firmados y son válidos pero contienen errores. Sucede que, después de que el SAT (o el PAC en nombre del SAT) ha firmado un CFDI estos suelen ser alterados -con información que no pertenece a la cadena de origen. Lamentablemente esto es permitido por el SAT. +con información que no pertenece a la cadena de origen. Lamentablemente, esto es permitido por el SAT. Un caso común de alteración es agregar más nodos al nodo `cfdi:Addenda`, como la información contenida no pertenece a la cadena de origen entonces no se considera que el documento ha sido alterado. @@ -16,8 +16,8 @@ Algunos de estos errores son: - Existen espacios de nombres XML definidos que no pertenecen al SAT y no está disponible su archivo XSD - La especificación XSD no puede ser obtenida - Los datos en el nodo `cfdi:Addenda` no cumplen con la especificación XSD -- Múltiples nodos `cfdi:Complemento`, en el Anexo 20 está especificado que solo puede haber uno pero - en el archivo XSD está especificado que pueden haber muchos. +- Múltiples nodos `cfdi:Complemento`, en el Anexo 20 está especificado que solo puede haber uno, + pero en el archivo XSD está especificado que pueden existir muchos. Estos errores comunes terminan en un error de validación. @@ -26,16 +26,16 @@ Estos errores comunes terminan en un error de validación. Para evitar estos errores se puede usar el objeto `CfdiUtils\Cleaner\Cleaner`. Este objeto requiere una cadena de texto con XML válido. Y limpia el XML siguiendo estos pasos: -1. Cambiar la defición incorrecta en algunos CFDI del SAT `xmlns:schemaLocation` por `xsi:schemaLocation`. -1. Remover la definición de CFDI 3 si no tiene prefijo `xmlns="http://www.sat.gob.mx/cfd/3"` siempre que la definición +1. Cambiar la definición incorrecta en algunos CFDI del SAT `xmlns:schemaLocation` por `xsi:schemaLocation`. +2. Remover la definición de CFDI 3 si no tiene prefijo `xmlns="http://www.sat.gob.mx/cfd/3"` siempre que la definición con prefijo `xmlns:cfdi="http://www.sat.gob.mx/cfd/3"` sí esté presente. -1. Remueve el nodo `cfdi:Addenda`. -1. Remueve dentro de las locaciones de espacios de nombre `xsi:schemaLocation` los namespaces que no tengan +3. Remueve el nodo `cfdi:Addenda`. +4. Remueve dentro de las locaciones de espacios de nombre `xsi:schemaLocation` los namespaces que no tengan a continuación una uri que termine en `.xsd`. -1. Remueve todos los nodos que no tengan relación con el SAT (los que no contengan `http://www.sat.gob.mx/`). -1. Remueve todos los pares de espacio de nombre y archivo xsd de los `xsi:schemaLocation` que no tengan relación con el SAT. -1. Remueve todos los espacios de nombres listados que no están en uso. -1. Colapsa los nodos `cfdi:Complemento` en uno solo, respetando el mismo orden de aparición para que se genere +5. Remueve todos los nodos que no tengan relación con el SAT (los que no contengan `http://www.sat.gob.mx/`). +6. Remueve todos los pares de espacio de nombre y archivo xsd de los `xsi:schemaLocation` que no tengan relación con el SAT. +7. Remueve todos los espacios de nombres listados que no están en uso. +8. Colapsa los nodos `cfdi:Complemento` en uno solo, respetando el mismo orden de aparición para que se genere exactamente la misma cadena de origen. Las primeras dos formas no trabajan con el CFDI como XML, lo trabajan como una cadena de texto. @@ -50,7 +50,7 @@ $possibleDirty = '... el xml del cfdi ...'; $cleanContent = CfdiUtils\Cleaner\Cleaner::staticClean($possibleDirty); ``` -También se puede instanciar un objeto de la clase `CfdiUtils\Cleaner\Cleaner` y usar estos métodos: +También se puede crear un objeto de la clase `CfdiUtils\Cleaner\Cleaner` y usar estos métodos: - `load(string $content)`: Carga un contenido XML "sucio" - `clean()`: Realiza la limpieza diff --git a/docs/leer/quickreader.md b/docs/leer/quickreader.md index 8a0554cf..b8e08986 100644 --- a/docs/leer/quickreader.md +++ b/docs/leer/quickreader.md @@ -3,7 +3,7 @@ El lector rápido ofrece una forma simple y rápida de acceder a los contenidos de un CFDI. -El `QuickReader` permite poder acceder a los atributos y los elementos sin importar +El objeto `QuickReader` permite poder acceder a los atributos y los elementos sin importar las mayúsculas y minúsculas y también omite la información del namespace XML. * Atributos: Se accede a su información usando la notación de arreglo. @@ -17,7 +17,7 @@ Si accedes a una propiedad o elemento que no existe **no habrá ningún error**. El lector rápido fue creado para casos en donde requieres información rápida y lo que más necesitas es una navegación ágil dentro de la estructura de un CFDI, por ejemplo, en la exportación de los datos a una estructura -específica de JSON o bien en la exportación de los datos a un template para luego crear un PDF. +específica de JSON o bien en la exportación de los datos a una plantilla para luego crear un PDF. El lector rápido es una transformación con pérdida de datos, para empezar se pierde en espacio de nombres XML, así como la diferenciación de mayúsculas y minúsculas. Es por eso que no debes pensar en este objeto como una forma fácil de escribir un XML, fue creado expresamente para lectura. También ten en cuenta que no puede interpretar todo el contenido del XML, solo los elementos (tags) y sus atributos, no puede interpretar nodos de tipo texto o comentarios. diff --git a/docs/problemas/contradicciones-pagos.md b/docs/problemas/contradicciones-pagos.md index 1d4f4d5b..d14a15a7 100644 --- a/docs/problemas/contradicciones-pagos.md +++ b/docs/problemas/contradicciones-pagos.md @@ -25,13 +25,13 @@ Guía de llenado: La suma de los valores registrados en el nodo DoctoRelacionado debe ser menor o igual que el valor de este campo. - Se debe considerar la conversión a la moneda del pago registrada en el campo MonedaP - y el margen de variación por efecto de redondeo de acuerdo a la siguiente formula: + y el margen de variación por efecto de redondeo de acuerdo a la siguiente fórmula: - Calcular el límite inferior como: `(ImportePagado - (10^-NumDecimalesImportePagado/2) / (TipoCambioDR + (10^-NumDecimalesTipoCambioDR)/2-0.0000000001)` - Calcular el límite superior como: `(ImportePagado + (10^-NumDecimalesImportePagado ) / 2-0.0000000001) / (TipoCambioDR - (10^-NumDecimalesTipoCambioDR /2)` -...por lo visto el SAT no sabe abrir y cerrar paréntesis, ni precedencia de operadores, ni precisiones de números. +... Por lo visto el SAT no sabe abrir y cerrar paréntesis, ni precedencia de operadores, ni precisiones de números. ### Entendimiento de la fórmula @@ -62,7 +62,7 @@ Pero bien sabemos que no se pueden cumplir ambas condiciones en todos los casos. Haz este ejercicio: Te pagan una factura por un monto de 5,137.42 USD, en tu banco entraron 96,426.29 MXN al TC de operaciones comerciales según el DOF de 18.7694. -El `DoctoRelacionado@TipoCambioDR` debe ser `1/18.7694`, es decir `0.0532782081`. +El atributo `DoctoRelacionado@TipoCambioDR` debe ser `1/18.7694`, es decir `0.0532782081`. ¿Qué valor se pone, dado que `TipoCambioDR` solo admite 6 decimales? `0.053278` o `0.053279`. Si se pone `0.053278` en la suma de los importes pagados traducidos a la moneda del pago @@ -151,19 +151,19 @@ Según la [Matriz de validaciones para el Comprobante fiscal digital por Interne > CFDI33136 - Para registrar el campo NumRegIdTrib, el CFDI debe contener el complemento de comercio exterior > y el RFC del receptor debe ser un RFC genérico extranjero. -La primer inconsistencia existe en la propia regla, dado que en el primer parte establece: +La primera inconsistencia existe en la propia regla, dado que en el primer parte establece: `Si A y B entonces debe existir C` y en la segunda parte establece: `Para que exista C entonces A y B`. -Este es un error lógico pues en la `regla` establece que una obligatoriedad a partir de que dos condiciones sean verdaderas. -En cambio, en el `error` establece una obligatoriedad de condiciones si el resultado está presente. +Este es un error lógico, pues en la *regla* establece que una obligatoriedad a partir de que dos condiciones sean verdaderas. +En cambio, en el *error* establece una obligatoriedad de condiciones si el resultado está presente. -Con la primer parte, `Si A y B entonces debe existir C`, `C` puede o no existir en otros escenarios, +Con la primera parte, `Si A y B entonces debe existir C`, `C` puede o no existir en otros escenarios, pero si se cumple `A y B` entonces su existencia está obligada. Con la segunda parte. `Para que exista C entonces A y B`, `C` solamente puede existir en los escenarios donde se cumpla `A y B` y en ningún otro. -Esto era una insonsistencia, aunque no un problema tangible dado que en ningún lugar se exigía establecer +Esto era una inconsistencia, aunque no un problema tangible dado que en ningún lugar se exigía establecer el atributo de Residencia Fiscal con excepción de la *Guía de llenado del comprobante al que se le incorpore el complemento para comercio exterior*. Sin embargo, esto cambió en la guía de llenado del complemento de pagos, donde en la página 14 establece: @@ -172,7 +172,7 @@ Sin embargo, esto cambió en la guía de llenado del complemento de pagos, donde > país de residencia para efectos fiscales del receptor del comprobante. > Este campo es obligatorio cuando se registre una clave en el RFC genérica extranjera. > -> Se captura el número de registro de identidad fiscal del receptor del comprobante fiscal cuando éste +> Se captura el número de registro de identidad fiscal del receptor del comprobante fiscal cuando este > sea residente en el extranjero. La inconsistencia se presenta en que: @@ -183,14 +183,14 @@ La inconsistencia se presenta en que: Posibles soluciones: 1. Que el SAT aclare el error 38 de la matriz y establezca que lo que se debe validar es la `Regla` y no el mensaje de error. -2. Que el SAT aclare que tiene más peso la matriz de errores a las guía de llenado, a pesar de sus fechas de publicación. +2. Que el SAT aclare que tiene más peso la matriz de errores a las guías de llenado, a pesar de sus fechas de publicación. Mientras tanto, en la librería no existe ninguna validación en su versión original que marque algún error al respecto. Si tu PAC no te deja timbrar un CFDI con `NumRegIdTrib` exígele el sustento legal o la aclaración del SAT al respecto. -Al 2018-10-01 [FinkOk](https://www.finkok.com/) no marca error de timbrado si se agrega el `NumRegIdTrib`. -Está interpretando la `Reglas de validación` y no el `Error`. +Al 2018-10-01 [FinkOk](https://www.finkok.com/) no marca error de timbrado si se agrega el atributo `NumRegIdTrib`. +Está interpretando la *Regla de validación* y no el *Error*. -Al 2018-10-01 [Facturaxion](https://www.facturaxion.com/) marca error de timbrado si se agrega el `NumRegIdTrib`. -Está interpretando el `Error` y no la `Regla de validación`. +Al 2018-10-01 [Facturaxion](https://www.facturaxion.com/) marca error de timbrado si se agrega el atributo `NumRegIdTrib`. +Está interpretando el *Error* y no la *Regla de validación*. diff --git a/docs/problemas/descarga-certificados.md b/docs/problemas/descarga-certificados.md index a599c193..acfbc5cb 100644 --- a/docs/problemas/descarga-certificados.md +++ b/docs/problemas/descarga-certificados.md @@ -13,7 +13,7 @@ para el protocolo `https` vigentes y expirados: - Expirado: `4D:CE:6C:8E:0D:C6:4C:E3` vigente hasta `2018-09-22 16:07:04 GMT` - Vigente: `00:A7:06:AA:42:44:4E:E4:E9:00:00:00:00:58:08:91:5B` vigente hasta `2020-09-12 16:41:28 GMT` -No hay una tendencia, de una muestra de 1,000 descargas realizada el `2018-10-17 17:30 GMT-5` el resultado fue +No hay una tendencia, de una muestra de 1,000 descargas realizada el `2018-10-17 17:30 GMT-5` el resultado fue: incorrectas 2,853 (28.53%) y correctas 7,147 (71.47%) por lo que la posibilidad de obtener un certificado incorrecto es cercana al 30%. @@ -49,7 +49,7 @@ do { // $asserts podría tener el código 'TFDSELLO01' con estado de error después de haberlo intentado 10 veces ``` -Personalmente no recomiendo desabilitar la seguridad del protocolo HTTPS, pero es una posible solución. +Personalmente, no recomiendo deshabilitar la seguridad del protocolo HTTPS, pero es una posible solución. Se puede desactivar usando el downloader genérico `XmlResourceRetriever\Downloader\PhpDownloader` y estableciendo un contexto que desactive la verificación de la siguiente manera. diff --git a/docs/problemas/multiples-complementos.md b/docs/problemas/multiples-complementos.md index c29c34af..b265b825 100644 --- a/docs/problemas/multiples-complementos.md +++ b/docs/problemas/multiples-complementos.md @@ -18,7 +18,7 @@ Sin embargo, en el archivo de definición de esquema XSD de CFDI 3.3 ubicado en ``` Por lo tanto, el problema es que las dos definiciones técnicas publicadas por la autoridad se contradicen. -Por un lado el Anexo 20 define una cardinalidad de `(0, 1)` y el esquema XSD define `(0, N)`. +Por un lado, el Anexo 20 define una cardinalidad de `(0, 1)` y el esquema XSD define `(0, N)`. Dado lo anterior, y que no existe ninguna regla en la Matriz de errores que prevenga esta situación, es posible que existan comprobantes con múltiples `cfdi:Complemento` y una controversia de si son o no correctos. @@ -36,12 +36,12 @@ esta recomendación, además, al llamar al método `clean()` también se colapsa * **Complemento de concepto** -El otro lugar donde se pueden poner complementos en en el nodo +El otro lugar donde se pueden poner complementos en el nodo `cfdi:Comprobante/cfdi:Conceptos/cfdi:Concepto/cfdi:ComplementoConcepto`, -sin embargo este nodo no tiene el mismo problema de cardinalidad, en Anexo 20 y XSD esta definido como `(0, 1)`. +sin embargo este nodo no tiene el mismo problema de cardinalidad, en Anexo 20 y XSD está definido como `(0, 1)`. * **Complemento de retención e información de pagos** Los documentos de CFDI de Retención e información de pagos también admiten complementos en el nodo `cfdi:Comprobante/cfdi:Complemento`, -sin embargo este nodo no tiene el mismo problema de cardinalidad, en Anexo 20 y XSD esta definido como `(0, 1)`. +sin embargo este nodo no tiene el mismo problema de cardinalidad, en Anexo 20 y XSD está definido como `(0, 1)`. diff --git a/docs/utilerias/openssl.md b/docs/utilerias/openssl.md index b74e10ad..4109b395 100644 --- a/docs/utilerias/openssl.md +++ b/docs/utilerias/openssl.md @@ -12,7 +12,7 @@ Para la mayoría de comandos ejecuta externamente openssl para poder funcionar. ### Archivos de certificado CER Los archivos de certificado provistos por el SAT se encuentran en formato X509 DER. -PHP no puede trabajar con estos archivos en la forma original pues requiere el formato PEM. +PHP no puede trabajar con estos archivos en la forma original, pues requiere el formato PEM. Cambiar a formato PEM solo requiere codificar en base 64 y cierto formato. Por lo tanto, se puede hacer utilizando solamente PHP o bien utilizando `openssl`. @@ -25,7 +25,7 @@ Esta utilería le entrega ambas formas de hacerlo. ### Archivos de llave privada KEY Los archivos de llave privada provistos por el SAT se encuentran en formato PKCS#8 DER. -PHP no puede trabajar con estos archivos en la forma original pues requiere el formato PEM. +PHP no puede trabajar con estos archivos en la forma original, pues requiere el formato PEM. Convertir archivos PKCS#8 DER a PEM no es una actividad que pueda hacer PHP. Por esta razón estamos obligados a utilizar el comando externo `openssl`. @@ -40,7 +40,7 @@ Este es un ejemplo válido del contenido en formato PEM. ```text -----BEGIN MY INFORMATION----- -UXXDqSBjdXJpb3NvIHNvcyB2b3MhCg== +UXXDqSBjdXJpb3NvIHNvCyB2b3MhCg== -----END MY INFORMATION----- ``` @@ -51,7 +51,7 @@ Para el caso de certificados, llaves públicas y llaves privadas: - El contenido de una sección debería ser un texto en [base 64](https://en.wikipedia.org/wiki/Base64#Base64_table). -- También puede incluir `LF` o `CRLF` como finales de línea de el contenido a 64 caracteres. +- También puede incluir `LF` o `CRLF` como finales de línea del contenido a 64 caracteres. - Para `RSA PRIVATE KEY` también incluye texto que **no es base 64**: @@ -64,7 +64,7 @@ WqPzvGCc ... - Un archivo o contenido PEM puede incluir (entre otras cosas): - el certificado, en la sección `CERTIFICATE`, - - la llave publica, en la sección `PUBLIC KEY`, + - la llave pública, en la sección `PUBLIC KEY`, - la llave privada, en la sección `PRIVATE KEY`, `RSA PRIVATE KEY` o `ENCRYPTED PRIVATE KEY`. Aunque su uso más frecuente es que un archivo PEM contenga solamente un contenido y no múltiples, @@ -89,8 +89,8 @@ que devuelven un objeto `PemContainer` con finales de línea normalizados y sin De forma general, tenga en cuenta estas consideraciones: -- Cuando se trabaja con un archivo de entrada se valida que exista y que su tamaño sea mayor a cero. -- Cuando se trabaja con un archivo de salida se valida que no exista pero que sí exista su directorio. +- Cuando se trabaja con un archivo de entrada se verifica que exista y que su tamaño sea mayor a cero. +- Cuando se trabaja con un archivo de salida se verifica que no exista, pero que sí exista su directorio. En caso de que exista su tamaño debe ser cero. - Ninguna ejecución con el comando `openssl` puede contener caracteres de control excepto `CR` y `LF`. - Las contraseñas pasadas al comando `openssl` se pasan por el entorno y no por la línea de comandos. @@ -166,7 +166,7 @@ e información específica de `RSA PRIVATE KEY`. El extractor **no verifica** si el contenido en Base 64 está correctamente codificado. -Por ejemplo, el siguiente contenido generará la extración de `"FOO-BAR"` como el contenido de `certificate()`, +Por ejemplo, el siguiente contenido generará la extracción de `"FOO-BAR"` como el contenido de `certificate()`, una cadena vacía para `publicKey()` por tener caracteres indebidos como espacios en blanco y una cadena vacía para `privateKey()` porque no se encuentra. diff --git a/docs/validar/validaciones-40.md b/docs/validar/validaciones-40.md index 3bf780a7..f624a74d 100644 --- a/docs/validar/validaciones-40.md +++ b/docs/validar/validaciones-40.md @@ -9,26 +9,28 @@ Valida que el archivo XML tiene las especificaciones necesarias para 4.0. - XML01: El XML implementa el namespace %s con el prefijo cfdi. - XML02: El nodo principal se llama cfdi:Comprobante. -- XML03: La versión es 4.0 +- XML03: La versión es 4.0. ## SelloDigitalCertificado Valida el Sello del comprobante y el Certificado -- SELLO01: Se puede obtener el certificado del comprobante -- SELLO02: El número de certificado del comprobante igual al encontrado en el certificado -- SELLO03: El RFC del comprobante igual al encontrado en el certificado -- SELLO04: El nombre del emisor del comprobante igual al encontrado en el certificado -- SELLO05: La fecha del documento es mayor o igual a la fecha de inicio de vigencia del certificado -- SELLO06: La fecha del documento menor o igual a la fecha de fin de vigencia del certificado -- SELLO07: El sello del comprobante está en base 64 -- SELLO08: El sello del comprobante coincide con el certificado y la cadena de origen generada +- SELLO01: Se puede obtener el certificado del comprobante. +- SELLO02: El número de certificado del comprobante igual al encontrado en el certificado. +- SELLO03: El RFC del comprobante igual al encontrado en el certificado. +- SELLO04: El nombre del emisor del comprobante igual al encontrado en el certificado, + en el caso de una persona moral se omite la validación, + en el caso de una persona física se genera un error. +- SELLO05: La fecha del documento es mayor o igual a la fecha de inicio de vigencia del certificado. +- SELLO06: La fecha del documento menor o igual a la fecha de fin de vigencia del certificado. +- SELLO07: El sello del comprobante está en base 64. +- SELLO08: El sello del comprobante coincide con el certificado y la cadena de origen generada. ## TimbreFiscalDigitalSello -Posiblemente este es el **validador más importante** porque se encarga de comprobar que +Posiblemente, este es el **validador más importante** porque se encarga de comprobar que el CFDI no fue modificado después de haber sido sellado. - `TFDSELLO01`: El Sello SAT del Timbre Fiscal Digital corresponde al certificado SAT @@ -36,13 +38,13 @@ el CFDI no fue modificado después de haber sido sellado. Esto lo hace de la siguiente forma: 1. Obtiene el TimbreFiscalDigital, si no existe entonces no hay qué validar. -1. Corrobora que sea versión 1.1, si no lo es entonces no hay qué validar -1. Se asegura que cuente con SelloCFD y que coincida con el Sello del comprobante. -1. Se asegura que NoCertificadoSAT contenga un número válido. -1. Obtiene el certificado con el que fue sellado desde el sitio del SAT `https://rdc.sat.gob.mx/`. +2. Corrobora que sea versión 1.1, si no lo es entonces no hay qué validar +3. Se asegura que cuente con SelloCFD y que coincida con el Sello del comprobante. +4. Se asegura que NoCertificadoSAT contenga un número válido. +5. Obtiene el certificado con el que fue sellado desde el sitio del SAT `https://rdc.sat.gob.mx/`. Si no se pudo obtener entonces el resultado será de error. -1. Fabrica la cadena de origen del TimbreFiscalDigital. -1. Verifica que el sello corresponde con la cadena de origen usando el certificado. +6. Fabrica la cadena de origen del TimbreFiscalDigital. +7. Verifica que el sello corresponde con la cadena de origen usando el certificado. Es posible que un emisor intente modificar el comprobante, simplemente debe alterar el contenido sin modificar el TimbreFiscalDigital ni el atributo Sello del comprobante. diff --git a/docs/validar/validaciones-estandar.md b/docs/validar/validaciones-estandar.md index 647755aa..3911833c 100644 --- a/docs/validar/validaciones-estandar.md +++ b/docs/validar/validaciones-estandar.md @@ -164,13 +164,13 @@ el CFDI no fue modificado después de haber sido sellado. Esto lo hace de la siguiente forma: 1. Obtiene el TimbreFiscalDigital, si no existe entonces no hay qué validar. -1. Corrobora que sea versión 1.1, si no lo es entonces no hay qué validar -1. Se asegura que cuente con SelloCFD y que coincida con el Sello del comprobante. -1. Se asegura que NoCertificadoSAT contenga un número válido. -1. Obtiene el certificado con el que fue sellado desde el sitio del SAT `https://rdc.sat.gob.mx/`. +2. Corrobora que sea versión 1.1, si no lo es entonces no hay qué validar +3. Se asegura que cuente con SelloCFD y que coincida con el Sello del comprobante. +4. Se asegura que NoCertificadoSAT contenga un número válido. +5. Obtiene el certificado con el que fue sellado desde el sitio del SAT `https://rdc.sat.gob.mx/`. Si no se pudo obtener entonces el resultado será de error. -1. Fabrica la cadena de origen del TimbreFiscalDigital. -1. Verifica que el sello corresponde con la cadena de origen usando el certificado. +6. Fabrica la cadena de origen del TimbreFiscalDigital. +7. Verifica que el sello corresponde con la cadena de origen usando el certificado. Es posible que un emisor intente modificar el comprobante, simplemente debe alterar el contenido sin modificar el TimbreFiscalDigital ni el atributo Sello del comprobante. diff --git a/mkdocs.yml b/mkdocs.yml index 025cb91b..a475f2ed 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,9 +25,9 @@ nav: - validar/validacion-cfdi-40.md - validar/validaciones-40.md - "Creación": - - crear/crear-cfdi.md + - crear/crear-cfdi-33.md - crear/crear-cfdi-40.md - - crear/elements-cfdi33.md + - crear/elements-cfdi-40.md - crear/complemento-nomina12b.md - crear/complementos-aun-no-implementados.md - crear/cfdi-de-retenciones-e-informacion-de-pagos.md From f5a46da503119f5d81a11fdf36e9dcd25e569e97 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 15 Jun 2023 11:40:47 -0600 Subject: [PATCH 24/25] Move soap extension requirement to suggest --- composer.json | 3 ++- docs/CHANGELOG.md | 2 ++ src/CfdiUtils/ConsultaCfdiSat/WebService.php | 2 ++ .../ConsultaCfdiSat/WebServiceConsumingTest.php | 6 +++--- tests/CfdiUtilsTests/ConsultaCfdiSat/WebServiceTest.php | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 73ba353e..d6d09bcb 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,6 @@ "ext-simplexml": "*", "ext-mbstring": "*", "ext-openssl": "*", - "ext-soap": "*", "ext-iconv": "*", "ext-json": "*", "symfony/process": "^3.4|^4.2|^5.0|^6.0", @@ -40,10 +39,12 @@ }, "suggest": { "ext-bcmath": "Allows calculate totals and taxes on Pagos 2.0", + "ext-soap": "Allows consume the CFDI Status SAT Web Service", "genkgo/xsl": "Allows usage of Genkgo/Xsl transformations" }, "require-dev": { "ext-bcmath": "*", + "ext-soap": "*", "genkgo/xsl": "^1.0.8", "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.6", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7237885a..9e3c9d7a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -39,6 +39,8 @@ Add a calculator and writer for complement *Pagos 2.0*. This tool takes a *Pre-CFDI* elements to write `Pagos\Totales`, `Pagos\Pago[]\ImpuestosP` and (if not found) `Pagos\Pago[]@Monto`. This feature is also documented. +Make SOAP extension requirement optional. + - Add element helpers for complement *Servicios parciales de construcción 1.0*. - Add element helpers for complement *Consumo de combustibles 1.1*. - Add element helpers for complement *Donatarias 1.1*. diff --git a/src/CfdiUtils/ConsultaCfdiSat/WebService.php b/src/CfdiUtils/ConsultaCfdiSat/WebService.php index f475d6a2..e32dbf12 100644 --- a/src/CfdiUtils/ConsultaCfdiSat/WebService.php +++ b/src/CfdiUtils/ConsultaCfdiSat/WebService.php @@ -1,5 +1,7 @@ getMockBuilder(WebService::class) ->setMethodsExcept(['request']) ->setMethods(['doRequestConsulta']) From 85f3a14fc66b40a11dc9fe386da21d8a31d49044 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Thu, 15 Jun 2023 11:43:57 -0600 Subject: [PATCH 25/25] Reduce wait for code coverage from 10 mins to 2 minutes. --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 344c91c1..16583c04 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -18,4 +18,4 @@ build: tools: external_code_coverage: - timeout: 600 # default is 300 seconds + timeout: 120 # default is 300 seconds