Skip to content

feat(core): total de descuentos globales en notas de crédito y débito - #277

Closed
gustavoam-asdf wants to merge 1 commit into
thegreenter:masterfrom
gustavoam-asdf:feat/note-global-discount-total
Closed

gustavoam-asdf wants to merge 1 commit into
thegreenter:masterfrom
gustavoam-asdf:feat/note-global-discount-total

Conversation

@gustavoam-asdf

@gustavoam-asdf gustavoam-asdf commented Sep 10, 2026

Copy link
Copy Markdown

Relacionado con #232, la mitad de los descuentos globales en notas.

El problema

Una nota de crédito o débito no puede declarar descuentos hoy. El campo existe en el estándar de SUNAT, pero no hay forma de rellenarlo desde Greenter.

La guía de elaboración de documentos XML de la nota de crédito UBL 2.1, campo 25 «Total de Descuentos»:

A través de este elemento se debe indicar el valor total de los descuentos realizados de ser el caso. Su propósito es permitir consignar en el comprobante de pago la sumatoria de los descuentos de cada línea (descuentos por ítem).

Ubicación: //CreditNote/cac:LegalMonetaryTotal/cbc:AllowanceTotalAmount

Por qué se nota que falta

El hermano ya está: BaseSale tiene sumOtrosCargos y las dos plantillas de nota ya emiten cbc:ChargeTotalAmount. Solo faltaba el gemelo de descuentos.

El cambio

  • sumOtrosDescuentos se mueve de Invoice a BaseSale, justo al lado de sumOtrosCargos. Invoice conserva su API pública por herencia y pierde la propiedad duplicada; el setter pasa a devolver self con docblock @return $this, que es exactamente lo que ya hacía setSumOtrosCargos.
  • notacr2.1 y notadb2.1 emiten cbc:AllowanceTotalAmount antes de cbc:ChargeTotalAmount, que es el orden que exige MonetaryTotalType.
  • Dos tests, uno por tipo de nota, que comprueban el nodo y validan el documento contra el XSD.

Verificación

  • composer run-script lint (phpstan) → [OK] No errors
  • vendor/bin/phpunit packages/xml packages/core → 50/50, incluidos los tests de factura, que son los que caerían si mover el campo hubiera roto algo
  • Los dos tests nuevos pasan por assertSchema()

No pude correr psalm en local: no está en vendor/, lo instala la CI aparte.

Sobre cac:AllowanceCharge

Este PR no añade nodos cac:AllowanceCharge a las notas, aunque el issue pueda sugerirlo. La guía de SUNAT no los contempla en la nota: en toda su tabla de estructura no aparece ese elemento, ni en cac:CreditNoteLine ni en la cabecera, y la línea es ID → CreditedQuantity → LineExtensionAmount → PricingReference → TaxTotal → Item → Price.

Lo comprobé además contra el ambiente de pruebas de SUNAT: un cac:AllowanceCharge dentro de una cac:CreditNoteLine se tolera —CDR 0— pero SUNAT lo ignora, y sigue exigiendo LineExtensionAmount = Price × CreditedQuantity. Rebajar la línea por el descuento, que es lo que sí acepta en una factura, se rechaza con el error 3271. Por eso el único camino que la guía define para esto es el total, y es lo que implementa este PR.

Queda fuera lo otro que menciona #232, el motivo de interés por mora en la nota de débito.

Sobre el Quality Gate de SonarCloud

Marca «19.4% Duplication on New Code». Son las 3 líneas del bloque AllowanceTotalAmount, idénticas en las dos plantillas de nota (6 de 31 líneas nuevas). Es la misma forma que ya tiene el bloque ChargeTotalAmount justo al lado, y que el resto de nodos compartidos entre invoice2.1, notacr2.1 y notadb2.1: son plantillas hermanas, no código factorizable. Extraer un partial de Twig para tres líneas de XML iría contra cómo está escrito todo lo demás, así que lo dejo como está — pero dímelo si prefieres otra cosa.

🤖 Generated with Claude Code

@gustavoam-asdf
gustavoam-asdf force-pushed the feat/note-global-discount-total branch from 5380a99 to f6c9a9d Compare September 10, 2026 00:04
SUNAT's XML guide for the electronic credit note defines a total for global
discounts, but there is no way to set it with Greenter today.

Field 25 of the guide, "Total de Descuentos":

  > A traves de este elemento se debe indicar el valor total de los descuentos
  > realizados de ser el caso. Su proposito es permitir consignar en el
  > comprobante de pago la sumatoria de los descuentos de cada linea
  > (descuentos por item).
  >
  > Ubicacion: //CreditNote/cac:LegalMonetaryTotal/cbc:AllowanceTotalAmount

The sibling field is already there: BaseSale has `sumOtrosCargos` and both note
templates render `cbc:ChargeTotalAmount`. Only the discount twin was missing, so
a note cannot report discounts at all.

- `sumOtrosDescuentos` moves from Invoice to BaseSale, right next to
  `sumOtrosCargos`. Invoice keeps its public API by inheritance and drops the
  duplicated property; the setter now returns `self` with a `@return $this`
  docblock, exactly like `setSumOtrosCargos` already did.
- `notacr2.1` and `notadb2.1` render `cbc:AllowanceTotalAmount` before
  `cbc:ChargeTotalAmount`, which is the order `MonetaryTotalType` requires.
- Two tests, one per note type, asserting the node and validating the document
  against the UBL schema.

Related to thegreenter#232: this covers the global discount half of that report. The debit
note interest reason mentioned there is not addressed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gustavoam-asdf
gustavoam-asdf force-pushed the feat/note-global-discount-total branch from f6c9a9d to a5e540f Compare September 10, 2026 00:07
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
19.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.87%. Comparing base (b51a18e) to head (a5e540f).

Additional details and impacted files
@@            Coverage Diff            @@
##             master     #277   +/-   ##
=========================================
  Coverage     95.87%   95.87%           
  Complexity     1312     1312           
=========================================
  Files           173      173           
  Lines          5477     5477           
=========================================
  Hits           5251     5251           
  Misses          226      226           
Flag Coverage Δ
unittests 95.87% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@giansalex giansalex linked an issue Sep 10, 2026 that may be closed by this pull request
@giansalex

giansalex commented Sep 10, 2026

Copy link
Copy Markdown
Member

Ya no se puede usar el manual en PDF (2017) como referencia, actualmente el excel de validaciones es la fuente para cualquier modificacion, la ultima actualización fue en agosto 2026. Alli no se incluye ese tag, eso inidica que aunque el UBL 2.1 lo soporte, SUNAT no lo validará.

From Claude:

Actualmente como se modela SUNAT el "descuento global" en una NC
No con un tag de descuento, sino con el tipo de nota: Catálogo 09 código 04 "Descuento global" (y 05 "Descuento por ítem"), donde el monto de la nota es el descuento, expresado en sus ítems y totales normales. Greenter ya lo soporta con setCodMotivo('04') más los details.

@giansalex giansalex closed this Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants