PresupuestoService gestiona el ciclo de vida completo de los presupuestos en LEFA, desde la creación del borrador hasta la conversión en factura. Todos sus métodos son estáticos y operan sobre SQLite mediante SQLAlchemy. La trazabilidad bidireccional entre presupuesto y factura se mantiene automáticamente: al convertir un presupuesto, el campo factura_id del presupuesto y el campo presupuesto_origen_id de la factura quedan enlazados en la base de datos. La serie de numeración de presupuestos es fija y se obtiene de la constante SERIE_PRESUPUESTO definida en lefa.config.
DTO
LineaPresupuestoDTO
Objeto de transferencia para líneas de presupuesto desde la interfaz de usuario.
str
required
Descripción del concepto o servicio presupuestado.
float
required
Número de unidades del concepto.
float
required
Precio por unidad en euros (sin impuestos).
Métodos
listar_todos
id descendente. Las relaciones cliente y lineas se cargan con joinedload.
Retorna: Lista de objetos Presupuesto desvinculados de la sesión.
obtener_por_id
cliente y lineas precargadas.
int
required
Identificador del presupuesto en la base de datos.
Presupuesto desvinculado de la sesión, o None si no existe.
guardar_borrador
BORRADOR o actualiza uno existente. El número oficial (numero_presupuesto) permanece NULL hasta la emisión. La serie se asigna automáticamente a partir de SERIE_PRESUPUESTO.
int
required
Cliente destinatario del presupuesto.
list[LineaPresupuestoDTO]
required
Líneas del presupuesto. Debe contener al menos un elemento.
float
required
Porcentaje de IVA (p. ej.
21.0).float
required
Porcentaje de retención de IRPF (p. ej.
15.0).int | None
Si se indica, actualiza el borrador existente con ese ID. Las líneas anteriores se eliminan y se reemplazan por las nuevas.
date | None
Fecha de vencimiento de la oferta. Si es
None, se calculará automáticamente como hoy + 30 días durante la emisión.Presupuesto en estado BORRADOR desvinculado de la sesión.
Lanza ValueError si:
lineasestá vacía.- Se proporciona
presupuesto_idpero el presupuesto no existe. - El presupuesto encontrado no está en estado
BORRADOR.
emitir_presupuesto
validez_hasta no fue establecida en el borrador, se calcula automáticamente como hoy + 30 días. La numeración se obtiene mediante NumeracionService.siguiente_secuencia_presupuesto, que mantiene una secuencia independiente de las facturas.
int
required
ID del presupuesto borrador a emitir.
Presupuesto en estado EMITIDO con número oficial asignado.
Lanza ValueError si:
- El presupuesto no existe.
- No está en estado
BORRADOR. - No tiene líneas.
aceptar
EMITIDO.
int
required
ID del presupuesto a aceptar.
Presupuesto en estado ACEPTADO.
Lanza ValueError si:
- El presupuesto no existe.
- No está en estado
EMITIDO.
rechazar
EMITIDO como ACEPTADO (un cliente puede retractarse de su aceptación).
int
required
ID del presupuesto a rechazar.
Presupuesto en estado RECHAZADO.
Lanza ValueError si:
- El presupuesto no existe.
- El estado no es
EMITIDOniACEPTADO.
convertir_a_factura
EMITIDO, se acepta automáticamente antes de la conversión. Tras crear el borrador de factura, el presupuesto pasa al estado CONVERTIDO y su campo factura_id queda enlazado con el ID del nuevo borrador, estableciendo la trazabilidad bidireccional.
int
required
ID del presupuesto a convertir. Debe estar en estado
EMITIDO o ACEPTADO.int con el ID del borrador de factura recién creado.
Lanza ValueError si:
- El presupuesto no existe.
- Ya fue convertido (
CONVERTIDO). - Está en estado
RECHAZADO. - Está en estado
BORRADOR(debe emitirse primero).
Ejemplo de uso: flujo completo de presupuesto a factura
convertir_a_factura abre una sesión para leer el presupuesto y sus líneas, luego la cierra antes de llamar a FacturaService.guardar_borrador (que abre su propia sesión). Esto evita tener dos sesiones anidadas activas sobre la misma conexión SQLite.