File size: 11,568 Bytes
f801a4f 91ce2f1 560b580 18e6776 7d51675 18e6776 560b580 1188b5a 560b580 7d51675 560b580 1188b5a 1bb848f 1188b5a 18e6776 1188b5a f7f814a 1188b5a f7f814a 1188b5a 7d51675 1188b5a 7d51675 1188b5a 7d51675 1188b5a 7d51675 1188b5a d827db1 7d51675 d827db1 1188b5a 7d51675 d827db1 7d51675 d827db1 1188b5a 7d51675 d827db1 1188b5a 18e6776 1188b5a 18e6776 d827db1 eb1f27f 18e6776 7d51675 18e6776 7d51675 18e6776 1188b5a 560b580 1188b5a 560b580 1188b5a 560b580 18e6776 560b580 91ce2f1 560b580 1a2307b 1188b5a 1a2307b 560b580 18e6776 7d51675 18e6776 3aec588 19b6561 7d51675 1188b5a 7d51675 db79278 18e6776 560b580 7d51675 18e6776 560b580 772a6b4 1bb848f 772a6b4 1188b5a 772a6b4 1188b5a 772a6b4 1188b5a 1bb848f 772a6b4 1188b5a 772a6b4 1188b5a 772a6b4 1188b5a 772a6b4 1188b5a 772a6b4 1188b5a 772a6b4 1188b5a 772a6b4 1188b5a 772a6b4 1188b5a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | 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("<para leftIndent=-40>N° de factura:</para>", styles['CustomNormal']),
Paragraph(f"<para leftIndent=-40><font color='darkblue'>{data['invoice_number']}</font></para>", styles['CustomNormal']),
Paragraph("Fecha:", styles['CustomNormal']),
Paragraph(f"<para leftIndent=-40><font color='darkblue'>{data['date']}</font></para>", styles['CustomNormal'])],
[Paragraph("<para leftIndent=-40>Forma de pago:</para>", styles['CustomNormal']),
Paragraph(f"<para leftIndent=-40><font color='darkblue'>{data['payment_method']}</font></para>", 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']}<br/>
{client_data['Dirección']}<br/>
{client_data['Código Postal']}, {client_data['Ciudad']}, {client_data['Comunidad Autónoma']}, {client_data['País']}<br/>
NIF/DNI: {client_data['CIF/NIF']}
"""
company_info = """
LAVADO A MANO DEL BIERZO CB<br/>
Avda Galicia 132, 3d<br/>
24404 Ponferrada, Castilla y León, España<br/>
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"
) |