import streamlit as st
import pandas as pd
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from io import BytesIO
from datetime import date
# Configuración de la página
st.set_page_config(page_title="Generador de Facturas", layout="wide")
st.title("🧾 Generador de Facturas")
# Datos predefinidos de clientes
clientes = {
"Nuevo cliente": {
"Nombre": "Cliente",
"Dirección": "Calle",
"Código Postal": "24400",
"Ciudad": "Ponferrada",
"Comunidad Autónoma": "Castilla y León",
"País": "España",
"CIF/NIF": "71111111K"
},
"ECO BIERZO": {
"Nombre": "ECO BIERZO COMPOSITE SL",
"Dirección": "P.I. LA ROSALEDA ISAAC PRADO BODELON SN",
"Código Postal": "24516",
"Ciudad": "Parandones",
"Comunidad Autónoma": "Castilla y León",
"País": "España",
"CIF/NIF": "A28047884"
},
"AUTOTRANSPORTE TURÍSTICO": {
"Nombre": "AUTOTRANSPORTE TURÍSTICO ESPAÑOL S.A",
"Dirección": "Avenida Del Ensanche De Vallecas 27",
"Código Postal": "28051",
"Ciudad": "Madrid",
"Comunidad Autónoma": "Comunidad de Madrid",
"País": "España",
"CIF/NIF": "A28047884"
}
}
def generate_pdf(data, client_data, services):
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=1.5*cm, rightMargin=1.5*cm, topMargin=1.5*cm, bottomMargin=1.5*cm)
elements = []
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='CustomTitle', fontName='Helvetica-Bold', fontSize=20, textColor=colors.darkblue, spaceAfter=20))
styles.add(ParagraphStyle(name='CustomSubTitle', fontName='Helvetica-Bold', fontSize=12, textColor=colors.darkblue, spaceAfter=12))
styles.add(ParagraphStyle(name='CustomNormal', fontName='Helvetica', fontSize=10, spaceAfter=6))
# Título de factura
elements.append(Paragraph("FACTURA", styles['CustomTitle']))
elements.append(Spacer(1, 20))
# Información de factura en una tabla con ajuste de alineación, espaciado y color
invoice_info_data = [
[Paragraph("N° de factura:", styles['CustomNormal']),
Paragraph(f"{data['invoice_number']}", styles['CustomNormal']),
Paragraph("Fecha:", styles['CustomNormal']),
Paragraph(f"{data['date']}", styles['CustomNormal'])],
[Paragraph("Forma de pago:", styles['CustomNormal']),
Paragraph(f"{data['payment_method']}", styles['CustomNormal']), "", ""]
]
invoice_info_table = Table(invoice_info_data, colWidths=[3.5*cm, 4*cm, 3.5*cm, 4*cm]) # Ajuste del ancho de columnas
invoice_info_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('TEXTCOLOR', (0, 0), (-1, -1), colors.darkblue),
('ALIGN', (0, 0), (-1, -1), 'LEFT'), # Alineación a la izquierda en todas las columnas
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('LEFTPADDING', (0, 0), (-1, -1), 2), # Ajuste de padding para mejorar la alineación
('RIGHTPADDING', (0, 0), (-1, -1), 2), # Padding derecho uniforme para cada columna
]))
elements.append(invoice_info_table)
elements.append(Spacer(1, 20))
# Información de cliente y empresa en una tabla con alineación ajustada
client_info = f"""
{client_data['Nombre']}
{client_data['Dirección']}
{client_data['Código Postal']}, {client_data['Ciudad']}, {client_data['Comunidad Autónoma']}, {client_data['País']}
NIF/DNI: {client_data['CIF/NIF']}
"""
company_info = """
LAVADO A MANO DEL BIERZO CB
Avda Galicia 132, 3d
24404 Ponferrada, Castilla y León, España
CIF/NIF: E24667024
"""
# Ajuste para mostrar EMPRESA a la izquierda y CLIENTE a la derecha
client_company_data = [
["EMPRESA", "CLIENTE"],
[Paragraph(company_info, styles['CustomNormal']),
Paragraph(client_info, styles['CustomNormal'])]
]
client_company_table = Table(client_company_data, colWidths=[doc.width/2, doc.width/2])
client_company_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('TEXTCOLOR', (0, 0), (-1, 0), colors.darkblue),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('TOPPADDING', (0, 0), (-1, -1), 6),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(client_company_table)
elements.append(Spacer(1, 20))
# Título de servicios prestados
elements.append(Paragraph("SERVICIOS PRESTADOS", styles['CustomSubTitle']))
table_data = [["Descripción", "Cantidad (h)", "Precio (€)", "Importe (€)"]]
# Generar filas para cada servicio, asegurando que el símbolo de € esté al final
for service in services:
table_data.append([
service['description'],
str(service['quantity']),
f"{service['price']:.2f} €", # Añadimos el símbolo de euro al final del precio
f"{service['amount']:.2f} €" # Añadimos el símbolo de euro al final del importe
])
# Cálculos
subtotal = sum(service['amount'] for service in services)
iva = subtotal * 0.21
total = subtotal + iva
table_data.extend([
["", "", Paragraph("Subtotal:", styles['CustomNormal']), Paragraph(f"{subtotal:.2f} €", styles['CustomNormal'])],
["", "", Paragraph("IVA 21%:", styles['CustomNormal']), Paragraph(f"{iva:.2f} €", styles['CustomNormal'])],
["", "", Paragraph("Total (EUR):", styles['CustomNormal']), Paragraph(f"{total:.2f} €", styles['CustomNormal'])]
])
services_table = Table(table_data, colWidths=[doc.width*0.4, doc.width*0.2, doc.width*0.2, doc.width*0.2])
services_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 10),
('BOTTOMPADDING', (0, 0), (-1, 0), 8),
('TOPPADDING', (0, 1), (-1, -1), 8),
('BACKGROUND', (0, 1), (-1, -1), colors.white),
('TEXTCOLOR', (0, 1), (-1, -1), colors.black),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 10),
('GRID', (0, 0), (-1, -1), 0.5, colors.lightgrey),
]))
elements.append(services_table)
# Pie de página
elements.append(Spacer(1, 20))
footer = Paragraph("CIF/NIF: E24667024 | Móvil: 643 34 00 52 | E-mail: mihai_lavado@yahoo.es", styles['CustomNormal'])
elements.append(footer)
doc.build(elements)
buffer.seek(0)
return buffer
# Inicialización de la sesión state para los servicios
if 'services' not in st.session_state:
st.session_state.services = [{'description': 'Lavado de coche', 'quantity': 1, 'price': 9.00, 'amount': 9.00}]
# Función para añadir un nuevo servicio con valores predeterminados
def add_service():
service_count = len(st.session_state.services) + 1
st.session_state.services.append({'description': f'Servicio adicional {service_count}', 'quantity': 1, 'price': 10.00, 'amount': 10.00})
# Función para actualizar el importe en tiempo real
def update_totals():
for service in st.session_state.services:
service['amount'] = service['quantity'] * service['price']
# Botón para añadir más servicios
if st.button("Añadir otro servicio"):
add_service()
# Información del cliente y de la factura
col1, col2 = st.columns(2)
with col1:
invoice_number = st.text_input("N° de factura", value="1LUGO")
invoice_date = st.date_input("Fecha", value=date.today())
payment_method = st.selectbox("Forma de pago", ["Transferencia", "Efectivo", "Tarjeta"])
# Desplegable para seleccionar el cliente
selected_client = st.selectbox("Seleccione Cliente", list(clientes.keys()))
# Rellenar automáticamente los campos de cliente en función de la selección
client_data = clientes[selected_client]
# Mostrar los campos de cliente en la segunda columna
with col2:
client_name = st.text_input("Nombre del cliente", value=client_data["Nombre"])
client_address = st.text_input("Dirección del cliente", value=client_data["Dirección"])
client_postal_code = st.text_input("Código Postal", value=client_data["Código Postal"])
client_city = st.text_input("Ciudad del cliente", value=client_data["Ciudad"])
client_community = st.text_input("Comunidad Autónoma", value=client_data["Comunidad Autónoma"]) # Nuevo campo
client_country = st.text_input("País del cliente", value=client_data["País"])
client_nif = st.text_input("CIF/NIF del cliente", value=client_data["CIF/NIF"])
# Tabla de servicios dinámica sin mostrar el importe
st.subheader("Servicios prestados")
for i, service in enumerate(st.session_state.services):
col1, col2, col3 = st.columns(3) # Eliminamos la columna de importe
with col1:
service['description'] = st.text_input(f"Descripción {i+1}", value=service['description'], key=f"desc_{i}", on_change=update_totals)
with col2:
service['quantity'] = st.number_input(f"Cantidad {i+1}", value=service['quantity'], min_value=0, key=f"qty_{i}", on_change=update_totals)
with col3:
service['price'] = st.number_input(f"Precio (€) {i+1}", value=service['price'], min_value=0.00, step=0.01, key=f"price_{i}", on_change=update_totals)
# Botón para generar el PDF
if st.button("Generar Factura"):
update_totals()
services = [service for service in st.session_state.services if service['description'] and service['quantity'] and service['price']]
subtotal = sum(service['amount'] for service in services)
iva = subtotal * 0.21
total = subtotal + iva
st.subheader("Resumen de la factura")
st.write(f"Subtotal: {subtotal:.2f} €")
st.write(f"IVA (21%): {iva:.2f} €")
st.write(f"Total: {total:.2f} €")
# Generar el PDF
pdf = generate_pdf(
{
"invoice_number": invoice_number,
"date": invoice_date.strftime("%d/%m/%Y"),
"payment_method": payment_method
},
{
"Nombre": client_name,
"Dirección": client_address,
"Código Postal": client_postal_code,
"Ciudad": client_city,
"Comunidad Autónoma": client_community, # Nuevo campo
"País": client_country,
"CIF/NIF": client_nif
},
services
)
# Crear el nombre del archivo incluyendo el número de factura y la fecha en formato español
file_name = f"factura_{invoice_number}_{invoice_date.strftime('%d_%m_%Y')}.pdf"
# Botón para descargar el PDF
st.download_button(
label="Descargar Factura PDF",
data=pdf,
file_name=file_name,
mime="application/pdf"
)