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" )