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