Comprobante de Percepción
El Comprobante de Percepción Electrónico (tipo 40) registra la percepción de IGV aplicada a ventas internas o adquisición de combustible. Es un documento adicional a la factura.
Campos requeridos
Sección titulada «Campos requeridos»| Campo | Tipo | Descripción | Validación |
|---|---|---|---|
serie | string | Serie del comprobante | Patrón ^P\d{3}$. Ej: P001, P002. |
numero | integer | Correlativo | Mayor a 0. |
fechaEmision | date | Fecha de emisión | YYYY-MM-DD. |
proveedor | Proveedor | Datos del agente de percepción (emisor) | ruc de 11 dígitos y razonSocial obligatorios. |
cliente | Cliente | Datos del perceptor (adquirente) | Campos obligatorios del cliente. |
importeTotalPercibido | Decimal | Total percibido | Debe ser positivo. |
importeTotalCobrado | Decimal | Total cobrado (incluye percepción) | Debe ser positivo. |
tipoRegimen | string | Régimen de percepción | Catálogo N.° 22: 01, 02, 03. |
tipoRegimenPorcentaje | Decimal | Porcentaje del régimen | Ej: 0.02 para 2%. |
operaciones | PercepcionRetencionOperacion[] | Operaciones percibidas | Al menos una operación. |
Campos opcionales
Sección titulada «Campos opcionales»El modelo Perception no tiene campos opcionales a nivel raíz.
Operación de percepción (PercepcionRetencionOperacion)
Sección titulada «Operación de percepción (PercepcionRetencionOperacion)»| Campo | Tipo | Descripción | Validación |
|---|---|---|---|
numeroOperacion | int | Número de operación | Mayor a 0. |
fechaOperacion | date | Fecha de la operación | YYYY-MM-DD. |
importeOperacion | Decimal | Importe percibido en esta operación | Debe ser positivo. |
comprobante | ComprobanteAfectado | Comprobante afectado | Ver detalle abajo. |
ComprobanteAfectado
Sección titulada «ComprobanteAfectado»| Campo | Tipo | Descripción |
|---|---|---|
tipoComprobante | string | Tipo del comprobante afectado (Catálogo N.° 01). |
serieNumero | string | Serie-número del comprobante. |
fechaEmision | date | Fecha de emisión. |
importeTotal | Decimal | Importe total del comprobante. |
moneda | string | Moneda del comprobante. |
Reglas SUNAT aplicables
Sección titulada «Reglas SUNAT aplicables»| Código / Regla | Descripción |
|---|---|
2074 (Perception/Retention) | UBLVersionID debe ser 2.0. |
2072 (Perception/Retention) | CustomizationID debe ser 1.0. |
1001 | El ID debe tener formato serie-número. |
1007 | schemeID del agente de percepción debe ser 6 (RUC). |
1008 | RUC del agente de percepción debe tener 11 dígitos. |
1037 | RegistrationName del agente es obligatorio. |
| Serie | Debe iniciar con P. |
| Total | importeTotalCobrado = importeTotalComprobante + importeTotalPercibido (debe cuadrar por operación). |
Regímenes de percepción (Catálogo N.° 22)
Sección titulada «Regímenes de percepción (Catálogo N.° 22)»| Código | Descripción | Tasa típica |
|---|---|---|
01 | Venta interna | 2% |
02 | Adquisición de combustible | Variable |
03 | Tasa 3% | 3% |
Ejemplo completo
Sección titulada «Ejemplo completo»from openubl.models import ( Perception, PercepcionRetencionOperacion, ComprobanteAfectado, Proveedor, Cliente,)from openubl.renderer import render_perceptionfrom openubl.validator import SunatValidatorfrom decimal import Decimalfrom datetime import date
perception = Perception( serie="P001", numero=1, fechaEmision=date(2025, 6, 10), proveedor=Proveedor( ruc="20100100100", razonSocial="Mi Empresa S.A.C.", ), cliente=Cliente( nombre="Juan Pérez", numeroDocumentoIdentidad="46779327", tipoDocumentoIdentidad="1", ), importeTotalPercibido=Decimal("53.10"), importeTotalCobrado=Decimal("353.10"), tipoRegimen="01", tipoRegimenPorcentaje=Decimal("0.02"), operaciones=[ PercepcionRetencionOperacion( numeroOperacion=1, fechaOperacion=date(2025, 6, 10), importeOperacion=Decimal("53.10"), comprobante=ComprobanteAfectado( tipoComprobante="01", serieNumero="F001-45", fechaEmision=date(2025, 6, 10), importeTotal=Decimal("300.00"), moneda="PEN", ), ), ],)
xml = render_perception(perception)errors = SunatValidator().validate_schema( xml, "sunat_schemas/xsd_2.1/2.0/maindoc/UBLPE-Perception-1.0.xsd",)assert errors == []print(xml)import { createPerception } from "@openubl/sdk";import { zPerception } from "@openubl/sdk/zod.gen";
const perception = zPerception.parse({ serie: "P001", numero: 1, fechaEmision: "2025-06-10", proveedor: { ruc: "20100100100", razonSocial: "Mi Empresa S.A.C." }, cliente: { nombre: "Juan Pérez", numeroDocumentoIdentidad: "46779327", tipoDocumentoIdentidad: "1" }, importeTotalPercibido: "53.10", importeTotalCobrado: "353.10", tipoRegimen: "01", tipoRegimenPorcentaje: "0.02", operaciones: [{ numeroOperacion: 1, fechaOperacion: "2025-06-10", importeOperacion: "53.10", comprobante: { tipoComprobante: "01", serieNumero: "F001-45", fechaEmision: "2025-06-10", importeTotal: "300.00", moneda: "PEN" } }]});
const { data, error } = await createPerception({ query: { validate: true }, body: perception });
if (error) { throw new Error(JSON.stringify(error));}
console.log(data.xml);curl -X POST "http://localhost:8000/api/v1/perception/create?validate=true" \ -H "Content-Type: application/json" \ -d '{ "serie": "P001", "numero": 1, "fechaEmision": "2025-06-10", "proveedor": { "ruc": "20100100100", "razonSocial": "Mi Empresa S.A.C." }, "cliente": { "nombre": "Juan Pérez", "numeroDocumentoIdentidad": "46779327", "tipoDocumentoIdentidad": "1" }, "importeTotalPercibido": "53.10", "importeTotalCobrado": "353.10", "tipoRegimen": "01", "tipoRegimenPorcentaje": "0.02", "operaciones": [{ "numeroOperacion": 1, "fechaOperacion": "2025-06-10", "importeOperacion": "53.10", "comprobante": { "tipoComprobante": "01", "serieNumero": "F001-45", "fechaEmision": "2025-06-10", "importeTotal": "300.00", "moneda": "PEN" } }] }'Nota sobre enriquecimiento
Sección titulada «Nota sobre enriquecimiento»ContentEnricher no enriquece Perception. Los totales y los importes por operación deben proporcionarse explícitamente.
Endpoint
Sección titulada «Endpoint»from openubl.models import ( Perception, PercepcionRetencionOperacion, ComprobanteAfectado, Proveedor, Cliente,)from openubl.renderer import render_perceptionfrom decimal import Decimalfrom datetime import date
perception = Perception( serie="P001", numero=1, fechaEmision=date(2025, 6, 10), proveedor=Proveedor( ruc="20100100100", razonSocial="Mi Empresa S.A.C.", ), cliente=Cliente( nombre="Juan Pérez", numeroDocumentoIdentidad="46779327", tipoDocumentoIdentidad="1", ), importeTotalPercibido=Decimal("53.10"), importeTotalCobrado=Decimal("353.10"), tipoRegimen="01", tipoRegimenPorcentaje=Decimal("0.02"), operaciones=[ PercepcionRetencionOperacion( numeroOperacion=1, fechaOperacion=date(2025, 6, 10), importeOperacion=Decimal("53.10"), comprobante=ComprobanteAfectado( tipoComprobante="01", serieNumero="F001-45", fechaEmision=date(2025, 6, 10), importeTotal=Decimal("300.00"), moneda="PEN", ), ), ],)
xml = render_perception(perception)print(xml)import { createPerception } from "@openubl/sdk";import { zPerception } from "@openubl/sdk/zod.gen";
const perception = zPerception.parse({ serie: "P001", numero: 1, fechaEmision: "2025-06-10", proveedor: { ruc: "20100100100", razonSocial: "Mi Empresa S.A.C." }, cliente: { nombre: "Juan Pérez", numeroDocumentoIdentidad: "46779327", tipoDocumentoIdentidad: "1" }, importeTotalPercibido: "53.10", importeTotalCobrado: "353.10", tipoRegimen: "01", tipoRegimenPorcentaje: "0.02", operaciones: [{ numeroOperacion: 1, fechaOperacion: "2025-06-10", importeOperacion: "53.10", comprobante: { tipoComprobante: "01", serieNumero: "F001-45", fechaEmision: "2025-06-10", importeTotal: "300.00", moneda: "PEN" } }]});
const { data, error } = await createPerception({ body: perception });
if (error) { throw new Error(JSON.stringify(error));}
console.log(data.xml);curl -X POST "http://localhost:8000/api/v1/perception/create?validate=true" \ -H "Content-Type: application/json" \ -d '{ "serie": "P001", "numero": 1, "fechaEmision": "2025-06-10", "proveedor": { "ruc": "20100100100", "razonSocial": "Mi Empresa S.A.C." }, "cliente": { "nombre": "Juan Pérez", "numeroDocumentoIdentidad": "46779327", "tipoDocumentoIdentidad": "1" }, "importeTotalPercibido": "53.10", "importeTotalCobrado": "353.10", "tipoRegimen": "01", "tipoRegimenPorcentaje": "0.02", "operaciones": [{ "numeroOperacion": 1, "fechaOperacion": "2025-06-10", "importeOperacion": "53.10", "comprobante": { "tipoComprobante": "01", "serieNumero": "F001-45", "fechaEmision": "2025-06-10", "importeTotal": "300.00", "moneda": "PEN" } }] }'Casos especiales
Sección titulada «Casos especiales»Múltiples operaciones de percepción
Sección titulada «Múltiples operaciones de percepción»Un comprobante de percepción puede consolidar varias operaciones sobre comprobantes afectados distintos.
from openubl.models import ( Perception, PercepcionRetencionOperacion, ComprobanteAfectado, Proveedor, Cliente,)from openubl.renderer import render_perceptionfrom openubl.validator import SunatValidatorfrom decimal import Decimalfrom datetime import date
perception = Perception( serie="P001", numero=2, fechaEmision=date(2025, 6, 10), proveedor=Proveedor(ruc="20100100100", razonSocial="Mi Empresa S.A.C."), cliente=Cliente( nombre="Juan Pérez", numeroDocumentoIdentidad="46779327", tipoDocumentoIdentidad="1", ), importeTotalPercibido=Decimal("106.00"), importeTotalCobrado=Decimal("706.00"), tipoRegimen="01", tipoRegimenPorcentaje=Decimal("0.02"), operaciones=[ PercepcionRetencionOperacion( numeroOperacion=1, fechaOperacion=date(2025, 6, 10), importeOperacion=Decimal("53.10"), comprobante=ComprobanteAfectado( tipoComprobante="01", serieNumero="F001-45", fechaEmision=date(2025, 6, 10), importeTotal=Decimal("300.00"), moneda="PEN", ), ), PercepcionRetencionOperacion( numeroOperacion=2, fechaOperacion=date(2025, 6, 10), importeOperacion=Decimal("52.90"), comprobante=ComprobanteAfectado( tipoComprobante="01", serieNumero="F001-46", fechaEmision=date(2025, 6, 10), importeTotal=Decimal("298.50"), moneda="PEN", ), ), ],)
xml = render_perception(perception)errors = SunatValidator().validate_schema( xml, "sunat_schemas/xsd_2.1/2.0/maindoc/UBLPE-Perception-1.0.xsd",)assert errors == [], errorsprint(xml)import { createPerception } from "@openubl/sdk";import { zPerception } from "@openubl/sdk/zod.gen";
const perception = zPerception.parse({ serie: "P001", numero: 2, fechaEmision: "2025-06-10", proveedor: { ruc: "20100100100", razonSocial: "Mi Empresa S.A.C." }, cliente: { nombre: "Juan Pérez", numeroDocumentoIdentidad: "46779327", tipoDocumentoIdentidad: "1" }, importeTotalPercibido: "106.00", importeTotalCobrado: "706.00", tipoRegimen: "01", tipoRegimenPorcentaje: "0.02", operaciones: [ { numeroOperacion: 1, fechaOperacion: "2025-06-10", importeOperacion: "53.10", comprobante: { tipoComprobante: "01", serieNumero: "F001-45", fechaEmision: "2025-06-10", importeTotal: "300.00", moneda: "PEN", }, }, { numeroOperacion: 2, fechaOperacion: "2025-06-10", importeOperacion: "52.90", comprobante: { tipoComprobante: "01", serieNumero: "F001-46", fechaEmision: "2025-06-10", importeTotal: "298.50", moneda: "PEN", }, }, ],});
const { data, error } = await createPerception({ body: perception });
if (error) { throw new Error(JSON.stringify(error));}
console.log(data.xml);curl -X POST "http://localhost:8000/api/v1/perception/create" \ -H "Content-Type: application/json" \ -d '{ "serie": "P001", "numero": 2, "fechaEmision": "2025-06-10", "proveedor": { "ruc": "20100100100", "razonSocial": "Mi Empresa S.A.C." }, "cliente": { "nombre": "Juan Pérez", "numeroDocumentoIdentidad": "46779327", "tipoDocumentoIdentidad": "1" }, "importeTotalPercibido": "106.00", "importeTotalCobrado": "706.00", "tipoRegimen": "01", "tipoRegimenPorcentaje": "0.02", "operaciones": [ { "numeroOperacion": 1, "fechaOperacion": "2025-06-10", "importeOperacion": "53.10", "comprobante": { "tipoComprobante": "01", "serieNumero": "F001-45", "fechaEmision": "2025-06-10", "importeTotal": "300.00", "moneda": "PEN" } }, { "numeroOperacion": 2, "fechaOperacion": "2025-06-10", "importeOperacion": "52.90", "comprobante": { "tipoComprobante": "01", "serieNumero": "F001-46", "fechaEmision": "2025-06-10", "importeTotal": "298.50", "moneda": "PEN" } } ] }'Respuesta esperada
Sección titulada «Respuesta esperada»{ "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.