Ir al contenido

Comprobante de Retención

El Comprobante de Retención Electrónico (tipo 20) registra la retención de IGV aplicada a compras o pagos a proveedores. Es un documento adicional a la factura de compra.

CampoTipoDescripciónValidación
seriestringSerie del comprobantePatrón ^R\d{3}$. Ej: R001, R002.
numerointegerCorrelativoMayor a 0.
fechaEmisiondateFecha de emisiónYYYY-MM-DD.
proveedorProveedorDatos del agente de retención (emisor)ruc de 11 dígitos y razonSocial obligatorios.
clienteClienteDatos del retenido (proveedor)Campos obligatorios del cliente.
importeTotalRetenidoDecimalTotal retenidoDebe ser positivo.
importeTotalPagadoDecimalTotal pagado (monto neto)Debe ser positivo.
tipoRegimenstringRégimen de retenciónCatálogo N.° 23: 01, 02, 03.
tipoRegimenPorcentajeDecimalPorcentaje del régimenEj: 0.03 para 3%.
operacionesPercepcionRetencionOperacion[]Operaciones retenidasAl menos una operación.

El modelo Retention no tiene campos opcionales a nivel raíz.

Operación de retención (PercepcionRetencionOperacion)

Sección titulada «Operación de retención (PercepcionRetencionOperacion)»
CampoTipoDescripciónValidación
numeroOperacionintNúmero de operaciónMayor a 0.
fechaOperaciondateFecha de la operaciónYYYY-MM-DD.
importeOperacionDecimalImporte retenido en esta operaciónDebe ser positivo.
comprobanteComprobanteAfectadoComprobante afectadoVer detalle abajo.
CampoTipoDescripción
tipoComprobantestringTipo del comprobante afectado (Catálogo N.° 01).
serieNumerostringSerie-número del comprobante.
fechaEmisiondateFecha de emisión.
importeTotalDecimalImporte total del comprobante.
monedastringMoneda del comprobante.
Código / ReglaDescripción
2074 (Perception/Retention)UBLVersionID debe ser 2.0.
2072 (Perception/Retention)CustomizationID debe ser 1.0.
1001El ID debe tener formato serie-número.
1007schemeID del agente de retención debe ser 6 (RUC).
1008RUC del agente de retención debe tener 11 dígitos.
1037RegistrationName del agente es obligatorio.
SerieDebe iniciar con R.
TotalimporteTotalPagado = importeTotalComprobante - importeTotalRetenido (debe cuadrar por operación).

Regímenes de retención (Catálogo N.° 23)

Sección titulada «Regímenes de retención (Catálogo N.° 23)»
CódigoDescripciónTasa típica
01Tasa 3%3%
02Tasa 6%6%
03Tasa mixtaVariable
from openubl.models import (
Retention, PercepcionRetencionOperacion,
ComprobanteAfectado, Proveedor, Cliente,
)
from openubl.renderer import render_retention
from openubl.validator import SunatValidator
from decimal import Decimal
from datetime import date
retention = Retention(
serie="R001",
numero=1,
fechaEmision=date(2025, 6, 10),
proveedor=Proveedor(
ruc="20100100100",
razonSocial="Mi Empresa S.A.C.",
),
cliente=Cliente(
nombre="Comercial del Sur S.A.",
numeroDocumentoIdentidad="20100200200",
tipoDocumentoIdentidad="6", # RUC
),
importeTotalRetenido=Decimal("18.00"),
importeTotalPagado=Decimal("282.00"),
tipoRegimen="01", # Tasa 3%
tipoRegimenPorcentaje=Decimal("0.03"),
operaciones=[
PercepcionRetencionOperacion(
numeroOperacion=1,
fechaOperacion=date(2025, 6, 10),
importeOperacion=Decimal("18.00"),
comprobante=ComprobanteAfectado(
tipoComprobante="01",
serieNumero="F005-20",
fechaEmision=date(2025, 6, 10),
importeTotal=Decimal("300.00"),
moneda="PEN",
),
),
],
)
xml = render_retention(retention)
errors = SunatValidator().validate_schema(
xml,
"sunat_schemas/xsd_2.1/2.0/maindoc/UBLPE-Retention-1.0.xsd",
)
assert errors == []
print(xml)

ContentEnricher no enriquece Retention. Los totales y los importes por operación deben proporcionarse explícitamente.

from openubl.models import (
Retention, PercepcionRetencionOperacion,
ComprobanteAfectado, Proveedor, Cliente,
)
from openubl.renderer import render_retention
from decimal import Decimal
from datetime import date
retention = Retention(
serie="R001",
numero=1,
fechaEmision=date(2025, 6, 10),
proveedor=Proveedor(
ruc="20100100100",
razonSocial="Mi Empresa S.A.C.",
),
cliente=Cliente(
nombre="Comercial del Sur S.A.",
numeroDocumentoIdentidad="20100200200",
tipoDocumentoIdentidad="6",
),
importeTotalRetenido=Decimal("18.00"),
importeTotalPagado=Decimal("282.00"),
tipoRegimen="01",
tipoRegimenPorcentaje=Decimal("0.03"),
operaciones=[
PercepcionRetencionOperacion(
numeroOperacion=1,
fechaOperacion=date(2025, 6, 10),
importeOperacion=Decimal("18.00"),
comprobante=ComprobanteAfectado(
tipoComprobante="01",
serieNumero="F005-20",
fechaEmision=date(2025, 6, 10),
importeTotal=Decimal("300.00"),
moneda="PEN",
),
),
],
)
xml = render_retention(retention)
print(xml)

Un comprobante de retención puede incluir varias operaciones sobre facturas de compra distintas.

from openubl.models import (
Retention, PercepcionRetencionOperacion,
ComprobanteAfectado, Proveedor, Cliente,
)
from openubl.renderer import render_retention
from openubl.validator import SunatValidator
from decimal import Decimal
from datetime import date
retention = Retention(
serie="R001",
numero=2,
fechaEmision=date(2025, 6, 10),
proveedor=Proveedor(ruc="20100100100", razonSocial="Mi Empresa S.A.C."),
cliente=Cliente(
nombre="Comercial del Sur S.A.",
numeroDocumentoIdentidad="20100200200",
tipoDocumentoIdentidad="6",
),
importeTotalRetenido=Decimal("33.00"),
importeTotalPagado=Decimal("967.00"),
tipoRegimen="01",
tipoRegimenPorcentaje=Decimal("0.03"),
operaciones=[
PercepcionRetencionOperacion(
numeroOperacion=1,
fechaOperacion=date(2025, 6, 10),
importeOperacion=Decimal("18.00"),
comprobante=ComprobanteAfectado(
tipoComprobante="01",
serieNumero="F005-20",
fechaEmision=date(2025, 6, 10),
importeTotal=Decimal("300.00"),
moneda="PEN",
),
),
PercepcionRetencionOperacion(
numeroOperacion=2,
fechaOperacion=date(2025, 6, 10),
importeOperacion=Decimal("15.00"),
comprobante=ComprobanteAfectado(
tipoComprobante="01",
serieNumero="F005-21",
fechaEmision=date(2025, 6, 10),
importeTotal=Decimal("250.00"),
moneda="PEN",
),
),
],
)
xml = render_retention(retention)
errors = SunatValidator().validate_schema(
xml,
"sunat_schemas/xsd_2.1/2.0/maindoc/UBLPE-Retention-1.0.xsd",
)
assert errors == [], errors
print(xml)
{
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>..."
}

Si validate=true y el cuerpo no cumple las reglas, la API devuelve un error 422 con el detalle de la validación.