Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/eclipxe13/CfdiUtils/llms.txt

Use this file to discover all available pages before exploring further.

When building a CFDI, the SubTotal, Total, Descuento, and the aggregate tax amounts (TotalImpuestosTrasladados, TotalImpuestosRetenidos) must match the sum of values across every Concepto node in the document. Doing this by hand is error-prone and tedious. CfdiUtils\SumasConceptos\SumasConceptos solves the problem by reading the Conceptos tree from a Comprobante node and computing all the required totals for you — including federal taxes (IVA, ISR, IEPS), exempt (Exento) entries, and local taxes from the ImpuestosLocales complement. Once the totals are computed, the companion class SumasConceptosWriter writes them back into the appropriate XML attributes and child nodes. When using CfdiCreator40 (or CfdiCreator33), both steps are wrapped in the single convenience method addSumasConceptos().

Constructor

<?php

use CfdiUtils\SumasConceptos\SumasConceptos;

// Basic usage: default precision of 2 decimal places
$sumas = new SumasConceptos($comprobante);

// Custom precision
$sumas = new SumasConceptos($comprobante, 6);
comprobante
NodeInterface
required
The root Comprobante node (any NodeInterface implementation). SumasConceptos searches for cfdi:Conceptos/cfdi:Concepto children automatically.
precision
int
default:"2"
Number of decimal places used when rounding each intermediate and final total. A precision of 2 is correct for standard MXN invoices. Use 6 when you need extra accuracy (e.g. when passing values to SumasConceptosWriter which defaults to 6-decimal formatting).

Available Getters

After construction, the following read-only values are available:
getSubTotal(): float
float
Sum of all Concepto/@Importe values, rounded to $precision decimal places.
getDescuento(): float
float
Sum of all Concepto/@Descuento values, rounded to $precision. Returns 0.0 when no concepto carries a discount.
getTotal(): float
float
The final invoice total: SubTotal − Descuento + TotalImpuestosTrasladados − TotalImpuestosRetenidos + LocalesTrasladados − LocalesRetenidos, rounded to $precision.
getImpuestosTrasladados(): float
float
Sum of all federal transfer-tax (Traslado) amounts, rounded to $precision. Exempt (Exento) entries are not included.
getImpuestosRetenidos(): float
float
Sum of all federal withholding-tax (Retencion) amounts, rounded to $precision.
getTraslados(): array
array
Associative array of aggregated transfer-tax entries keyed by Impuesto:TipoFactor:TasaOCuota. Each entry contains Impuesto, TipoFactor, TasaOCuota, Importe (float), and Base (float).
getExentos(): array
array
Associative array of exempt transfer-tax entries (where TipoFactor = 'Exento'). Each entry contains Impuesto, TipoFactor, and Base (float).
getRetenciones(): array
array
Associative array of aggregated withholding-tax entries. Each entry contains Impuesto and Importe (float).
getLocalesImpuestosTrasladados(): float
float
Total local transfer-tax amount from the ImpuestosLocales complement.
getLocalesImpuestosRetenidos(): float
float
Total local withholding-tax amount from the ImpuestosLocales complement.
getLocalesTraslados(): array
array
Array of local transfer-tax entries. Each entry contains Impuesto (string), Tasa (float), and Importe (float).
getLocalesRetenciones(): array
array
Array of local withholding-tax entries. Each entry contains Impuesto (string), Tasa (float), and Importe (float).
getPrecision(): int
int
Returns the precision value the object was constructed with.
foundAnyConceptWithDiscount(): bool
bool
Returns true if at least one Concepto node had a Descuento attribute present, even if its value is zero. Used by SumasConceptosWriter to decide whether to write or omit the Comprobante/@Descuento attribute.

Has-checkers

MethodReturns true when…
hasTraslados(): boolAt least one non-exempt transfer tax exists
hasExentos(): boolAt least one exempt transfer-tax entry exists
hasRetenciones(): boolAt least one federal withholding tax exists
hasLocalesTraslados(): boolAt least one local transfer tax from ImpuestosLocales exists
hasLocalesRetenciones(): boolAt least one local withholding tax from ImpuestosLocales exists

Using with CfdiCreator40

The easiest way to apply SumasConceptos is through the addSumasConceptos() method available on both CfdiCreator40 and CfdiCreator33. It creates a SumasConceptos instance internally and passes the results to SumasConceptosWriter, which writes all totals back into the XML in one step.
<?php

use CfdiUtils\CfdiCreator40;

$creator = new CfdiCreator40(
    ['Folio' => 'A-001', 'Fecha' => '2024-01-15T10:00:00', /* ... */],
    $certificado
);

$comprobante = $creator->comprobante();
$comprobante->addConcepto([
    'ClaveProdServ' => '84111506',
    'Cantidad'      => '1',
    'ClaveUnidad'   => 'E48',
    'Descripcion'   => 'Servicios de consultoría',
    'ValorUnitario' => '10000.00',
    'Importe'       => '10000.00',
])->addTraslado([
    'Base'       => '10000.00',
    'Impuesto'   => '002',
    'TipoFactor' => 'Tasa',
    'TasaOCuota' => '0.160000',
    'Importe'    => '1600.00',
]);

// Calculate and write all totals into the Comprobante node
$creator->addSumasConceptos();

// Optionally specify precision (default is 2)
$creator->addSumasConceptos(null, 2);
When you pass null as the first argument, addSumasConceptos() builds a new SumasConceptos from the current state of the Comprobante. You can also pass a pre-built instance if you need to inspect the calculated values before writing them.

Inspecting values before writing

<?php

// Build SumasConceptos first, inspect, then write
$sumas = $creator->buildSumasConceptos(2);

echo 'SubTotal: ', $sumas->getSubTotal(), PHP_EOL;   // e.g. 10000.00
echo 'IVA:      ', $sumas->getImpuestosTrasladados(), PHP_EOL; // e.g. 1600.00
echo 'Total:    ', $sumas->getTotal(), PHP_EOL;       // e.g. 11600.00

// Now write the calculated values into the XML
$creator->addSumasConceptos($sumas, 2);

Using Standalone

You can use SumasConceptos independently of any creator, for example to validate an existing CFDI or to read computed totals without modifying the XML.
<?php

use CfdiUtils\SumasConceptos\SumasConceptos;
use CfdiUtils\SumasConceptos\SumasConceptosWriter;
use CfdiUtils\CfdiCreator40;

// --- Reading totals only ---
$sumas = new SumasConceptos($comprobante, 2);

printf('SubTotal : %.2f%s', $sumas->getSubTotal(), PHP_EOL);
printf('Descuento: %.2f%s', $sumas->getDescuento(), PHP_EOL);
printf('IVA      : %.2f%s', $sumas->getImpuestosTrasladados(), PHP_EOL);
printf('Total    : %.2f%s', $sumas->getTotal(), PHP_EOL);

// --- Reading individual tax lines ---
foreach ($sumas->getTraslados() as $traslado) {
    printf(
        'Impuesto: %s | TipoFactor: %s | TasaOCuota: %s | Base: %.2f | Importe: %.2f%s',
        $traslado['Impuesto'],
        $traslado['TipoFactor'],
        $traslado['TasaOCuota'],
        $traslado['Base'],
        $traslado['Importe'],
        PHP_EOL
    );
}

// --- Writing totals back with custom precision ---
$writer = new SumasConceptosWriter($comprobante40, $sumas, 6);
$writer->put();

Precision

The $precision parameter controls the number of decimal places used at every rounding step during the aggregation. This affects:
  • Each grouped Traslado and Retencion importe and base.
  • The aggregated getImpuestosTrasladados() and getImpuestosRetenidos() values.
  • The individual components of getTotal() before the final sum.
SumasConceptosWriter has its own $precision parameter (defaulting to 6) that controls how the float values are formatted as strings when written into XML attributes. You can pass different values to SumasConceptos (for aggregation rounding) and SumasConceptosWriter (for string formatting) independently.
The SAT Anexo 20 requires amounts to be expressed with up to 6 decimal places. Using a precision lower than 2 is unusual, and precision higher than 6 is capped by the XML schema. For most invoices, precision = 2 for aggregation and precision = 6 for formatting is the recommended combination (which is what addSumasConceptos() uses by default).
<?php

// precision = 2 for SumasConceptos (rounding during aggregation)
// precision = 6 for SumasConceptosWriter (formatting into XML attributes)
$sumas  = new \CfdiUtils\SumasConceptos\SumasConceptos($comprobante, 2);
$writer = new \CfdiUtils\SumasConceptos\SumasConceptosWriter($comprobante40, $sumas, 6);
$writer->put();

Static Helper

<?php

// Build a unique key for a tax combination (used internally; exposed publicly)
$key = \CfdiUtils\SumasConceptos\SumasConceptos::impuestoKey('002', 'Tasa', '0.160000');
// Returns: '002:Tasa:0.160000'
impuestoKey(string $impuesto, string $tipoFactor = '', string $tasaOCuota = ''): string generates the string key used to group identical tax entries in the getTraslados() and getRetenciones() arrays.

Build docs developers (and LLMs) love