Skip to content

The invoice model

Guillermo Ubeda edited this page Sep 1, 2026 · 1 revision

🇬🇧 English first · 🇪🇸 Español más abajo

The invoice model

Facturae is a large schema. This library does not try to mirror all of it — it models the part you actually fill in for a normal invoice, and gives you the element tree if you need the rest.


The objects

Class What it is
Factura The invoice. Holds issuer, recipient and lines
Emisor / Receptor The two parties, each with a Direccion
Direccion Postal address
Linea One invoice line: description, quantity, unit price
Impuesto One tax applied to a line: a code and a rate

Supporting enumerations: TipoPersona (natural or legal), TipoResidencia, ClaseFactura, TipoDocumento.


Tax codes

These are Facturae's own codes, not names we invented. Getting one wrong produces a document that parses and is rejected downstream, which is why they are exported as constants rather than left as string literals in your code.

Constant Code Tax
IVA 01 Value added tax
IPSI 02 Ceuta and Melilla production, services and imports tax
IGIC 03 Canary Islands general indirect tax
IRPF 04 Personal income tax withholding

IRPF is the one that behaves differently: it is withheld, not charged, so it subtracts from the total instead of adding to it.

Other constants worth knowing:

Constant Value Meaning
ClaseFactura.ORIGINAL OO An original invoice, not a correcting one
TipoDocumento.COMPLETA FC A complete invoice, not simplified
VERSION_ESQUEMA 3.2.2 The schema version generated

Totals are computed, never supplied

There is no total field to fill in. Factura exposes four properties:

Property What it is
total_bruto Sum of the lines, before tax
total_repercutido Taxes charged (VAT, IGIC, IPSI)
total_retenido Taxes withheld (IRPF)
total total_bruto + total_repercutido − total_retenido

Why this matters. In a model where totals are fields, nothing stops the header from saying 1 000 while the lines add up to 1 100 — and that is one of the most common reasons an invoice is rejected. Here the totals are derived from the lines, so they cannot disagree with the detail.

Properties versus methods

A distinction worth internalising, because it bites once:

  • The four scalars above are propertiesfactura.total, no parentheses.
  • The breakdowns are methodsfactura.impuestos_repercutidos() and factura.impuestos_retenidos(), with parentheses.

The scalars are cheap reads; the breakdowns build and return a list, and are methods to make that visible at the call site. Calling a property with () or iterating a method without them fails with a TypeError that does not obviously point at the cause.

A worked example

The invoice used by autocomprobar, which the library recomputes at runtime:

total_bruto 1250.00
total_repercutido 262.50
total_retenido 150.00
total 1362.50

Money is Decimal, always

Amounts go through a_decimal(), and a float is converted via its repr() first.

That last detail is the whole reason the function exists:

Decimal(0.1)       →  0.1000000000000000055511151231257827
Decimal(str(0.1))  →  0.1

An invoice built from floats is arithmetically wrong in a way that shows up as a cent of difference between the lines and the total — after which the document is rejected and the cause is genuinely hard to find. redondear() and formatear() handle rounding and the string form the schema expects.


Validation

FacturaInvalida (a ValueError subclass) is raised for a document that cannot be built. Notably, an unknown key in the input dictionary is an error, not something quietly ignored — see Design decisions.


Getting data in and out

Function Direction
desde_dict / desde_json Data → Factura
a_dict Factura → data
generar Factura → XML string
generar_arbol Factura → element tree, for post-processing

The JSON round trip is what lets a system written in something other than Python use this: build the JSON there, pipe it into the CLI, get the XML back.



🇪🇸 Español

El modelo de factura

Facturae es un esquema grande. Esta biblioteca no intenta reflejarlo entero: modela la parte que de verdad rellenas en una factura normal, y te da el árbol de elementos si necesitas el resto.


Los objetos

Clase Qué es
Factura La factura. Contiene emisor, receptor y líneas
Emisor / Receptor Las dos partes, cada una con su Direccion
Direccion Dirección postal
Linea Una línea de factura: descripción, cantidad, precio unitario
Impuesto Un impuesto aplicado a una línea: un código y un tipo

Enumeraciones de apoyo: TipoPersona (física o jurídica), TipoResidencia, ClaseFactura, TipoDocumento.


Códigos de impuesto

Son los códigos de Facturae, no nombres que nos hayamos inventado. Equivocarse en uno produce un documento que se parsea y luego se rechaza, y por eso se exportan como constantes en vez de quedarse como literales sueltos en tu código.

Constante Código Impuesto
IVA 01 Impuesto sobre el valor añadido
IPSI 02 Impuesto sobre la producción, los servicios y la importación (Ceuta y Melilla)
IGIC 03 Impuesto general indirecto canario
IRPF 04 Retención del impuesto sobre la renta

El IRPF es el que se comporta distinto: se retiene, no se repercute, así que resta del total en vez de sumar.

Otras constantes que conviene conocer:

Constante Valor Significado
ClaseFactura.ORIGINAL OO Factura original, no rectificativa
TipoDocumento.COMPLETA FC Factura completa, no simplificada
VERSION_ESQUEMA 3.2.2 Versión del esquema que se genera

Los totales se calculan, nunca se aportan

No hay un campo total que rellenar. Factura expone cuatro propiedades:

Propiedad Qué es
total_bruto Suma de las líneas, antes de impuestos
total_repercutido Impuestos repercutidos (IVA, IGIC, IPSI)
total_retenido Impuestos retenidos (IRPF)
total total_bruto + total_repercutido − total_retenido

Por qué importa. En un modelo donde los totales son campos, nada impide que la cabecera diga 1.000 mientras las líneas suman 1.100 — y ese es uno de los motivos más frecuentes de rechazo de una factura. Aquí los totales se derivan de las líneas, así que no pueden discrepar del detalle.

Propiedades frente a métodos

Una distinción que conviene interiorizar, porque muerde una vez:

  • Los cuatro escalares de arriba son propiedades: factura.total, sin paréntesis.
  • Los desgloses son métodos: factura.impuestos_repercutidos() e factura.impuestos_retenidos(), con paréntesis.

Los escalares son lecturas baratas; los desgloses construyen y devuelven una lista, y son métodos para que eso se vea en el punto de llamada. Llamar a una propiedad con () o iterar un método sin ellos falla con un TypeError que no apunta de forma evidente a la causa.

Un ejemplo real

La factura que usa autocomprobar, que la biblioteca recalcula en tiempo de ejecución:

total_bruto 1250.00
total_repercutido 262.50
total_retenido 150.00
total 1362.50

El dinero es Decimal, siempre

Los importes pasan por a_decimal(), y un float se convierte antes a través de su repr().

Ese último detalle es toda la razón de ser de la función:

Decimal(0.1)       →  0.1000000000000000055511151231257827
Decimal(str(0.1))  →  0.1

Una factura construida con float está aritméticamente mal de una forma que aparece como un céntimo de diferencia entre las líneas y el total, tras lo cual el documento se rechaza y la causa es genuinamente difícil de encontrar. redondear() y formatear() se ocupan del redondeo y de la forma textual que espera el esquema.


Validación

FacturaInvalida (subclase de ValueError) se lanza para un documento que no se puede construir. En particular, una clave desconocida en el diccionario de entrada es un error, no algo que se ignore en silencio — ver Design decisions.


Entrada y salida de datos

Función Sentido
desde_dict / desde_json Datos → Factura
a_dict Factura → datos
generar Factura → cadena XML
generar_arbol Factura → árbol de elementos, para post-procesar

El viaje de ida y vuelta por JSON es lo que permite usar esto desde un sistema escrito en algo que no sea Python: construyes el JSON allí, lo canalizas a la CLI y recuperas el XML.