Diseñar y programar una aplicación en Python con interfaz gráfica (GUI) que gestione la trazabilidad de la aceituna, permitiendo el almacenamiento, la edición y el análisis de datos críticos para el agricultor.
Para que el proyecto sea manejable pero profesional, utilizaremos Tkinter (interfaz gráfica), archivos csv para guardar los datos y FPDF2 (para el informe).
A. Estructura de Datos
Los campos que debe manejar la base de datos o el sistema de almacenamiento son:
- ID (único para poder modificar).
- Fecha (DD/MM/AAAA).
- Finca (Texto).
- Kilogramos (Real).
- Rendimiento (Porcentaje, ej: 21.5).
- Jornales (Entero).
B. Funcionalidades Principales
- Formulario de Entrada: Validar que los números sean positivos.
- Visor de Datos: Una tabla (
Treeview) para ver todas las entregas. - Lógica de Cálculo:
- Exportación PDF: Uso de una librería para maquetar el informe final.
import tkinter as tk #Esta libreria crea la interfaz grafica
from tkinter import ttk, messagebox #Esto hace que salgan mensajes de error
import csv #Esta libreria guarda y modifica los datos en un archivo CSV
from fpdf import FPDF #Esta libreria genera los informes PDF
import os #Esta libreria abre los datos CSV
datos = []
archivo_csv = "datos.csv"
#CREAR ARCHIVO CSV
if not os.path.exists(archivo_csv):
archivo_abierto = open(archivo_csv, "w", newline="", encoding="utf-8")
escritor = csv.writer(archivo_abierto)
escritor.writerow(["ID","Fecha","Finca","Kilos","Rendimiento","Jornales"])
archivo_abierto.close()
#Esto verifica si el archivo .csv esta creado, si no lo esta lo crea el
#LEER ARCHIVO CSV
archivo_abierto = open(archivo_csv, "r", newline="", encoding="utf-8")
lector = csv.reader(archivo_abierto)
for fila in lector:
if fila[0] == "ID":
continue
registro = [int(fila[0]), fila[1], fila[2], float(fila[3]), float(fila[4]), int(fila[5])]
datos.append(registro)
archivo_abierto.close()
#Esto lee el archivo .csv que ya esta creado
#INTERFAZ GRAFICA:
ventana = tk.Tk()
ventana.title("Almazara")
ventana.geometry("1000x500")
frame = tk.Frame(ventana)
frame.pack(pady=10)
#Esto es la ventana que se va a abrir
tabla = ttk.Treeview(
ventana,
columns=("ID", "Fecha", "Finca", "Kilos", "Rendimiento", "Jornales"),
show="headings"
)
for c in tabla["columns"]:
tabla.heading(c, text=c)
tabla.pack(expand=True, fill="both", pady=10)
#Esto es la tabla tipo TreeView para ver los datos
# ---------- CARGAR DATOS EN LA TABLA ----------
for r in datos:
tabla.insert("", tk.END, values=r)
#INTRODUCIR DATOS:
tk.Label(frame, text="Fecha (DD/MM/AAAA)").grid(row=0, column=0, sticky="w")
fecha = tk.Entry(frame, justify="left", width=20)
fecha.grid(row=0, column=1, sticky="w")
tk.Label(frame, text="Finca").grid(row=1, column=0, sticky="w")
finca = tk.Entry(frame, justify="left", width=20)
finca.grid(row=1, column=1, sticky="w")
tk.Label(frame, text="Kilos").grid(row=2, column=0, sticky="w")
kilos = tk.Entry(frame, justify="left", width=20)
kilos.grid(row=2, column=1, sticky="w")
tk.Label(frame, text="Rendimiento (%)").grid(row=3, column=0, sticky="w")
rendimiento = tk.Entry(frame, justify="left", width=20)
rendimiento.grid(row=3, column=1, sticky="w")
tk.Label(frame, text="Jornales").grid(row=4, column=0, sticky="w")
jornales = tk.Entry(frame, justify="left", width=20)
jornales.grid(row=4, column=1, sticky="w")
#BOTONES:
frame_botones = tk.Frame(frame)
frame_botones.grid(row=5, column=0, columnspan=2, pady=10)
#BOTON AÑADIR REGISTRO:
def boton_añadir():
try:
if float(kilos.get()) <= 0 or float(rendimiento.get()) <= 0 or int(jornales.get()) <= 0:
messagebox.showerror("Error", "Los valores numéricos deben ser positivos")
return
except ValueError:
messagebox.showerror("Error", "Introduce números válidos en Kilos, Rendimiento y Jornales")
return
if fecha.get() == "" or finca.get() == "":
messagebox.showerror("Error", "Rellena todos los campos")
return
nuevo_id = len(datos) + 1
registro = [
nuevo_id,
fecha.get(),
finca.get(),
float(kilos.get()),
float(rendimiento.get()),
int(jornales.get())
]
datos.append(registro)
tabla.insert("", tk.END, values=registro)
archivo_abierto = open(archivo_csv, "a", newline="", encoding="utf-8")
escritor = csv.writer(archivo_abierto)
if nuevo_id == 1:
escritor.writerow(["ID","Fecha","Finca","Kilos","Rendimiento","Jornales"])
escritor.writerow(registro)
archivo_abierto.close()
fecha.delete(0, tk.END)
kilos.delete(0, tk.END)
rendimiento.delete(0, tk.END)
jornales.delete(0, tk.END)
tk.Button(frame_botones, text="Añadir registro", width=15, command=boton_añadir).pack(side="left", padx=5)
#BOTON MODIFICAR REGISTRO:
def boton_modificar():
fila = tabla.selection()
if not fila:
messagebox.showerror("Error", "Selecciona una fila")
return
indice = tabla.index(fila)
try:
registro = [
datos[indice][0],
fecha.get(),
finca.get(),
float(kilos.get()),
float(rendimiento.get()),
int(jornales.get())
]
except ValueError:
messagebox.showerror("Error", "Introduce números válidos en Kilos, Rendimiento y Jornales")
return
if fecha.get() == "" or finca.get() == "":
messagebox.showerror("Error", "Rellena todos los campos")
return
datos[indice] = registro
tabla.item(fila, values=registro)
archivo_abierto = open(archivo_csv, "w", newline="", encoding="utf-8")
escritor = csv.writer(archivo_abierto)
escritor.writerow(["ID","Fecha","Finca","Kilos","Rendimiento","Jornales"])
for r in datos:
escritor.writerow(r)
archivo_abierto.close()
tk.Button(frame_botones, text="Modificar registro", width=15, command=boton_modificar).pack(side="left", padx=5)
#BOTON BORRAR REGISTRO:
def boton_borrar():
fila = tabla.selection()
if not fila:
messagebox.showerror("Error", "Selecciona una fila")
return
indice = tabla.index(fila)
datos.pop(indice)
tabla.delete(fila)
archivo_abierto = open(archivo_csv, "w", newline="", encoding="utf-8")
escritor = csv.writer(archivo_abierto)
escritor.writerow(["ID","Fecha","Finca","Kilos","Rendimiento","Jornales"])
for i, r in enumerate(datos):
r[0] = i + 1
escritor.writerow(r)
archivo_abierto.close()
tk.Button(frame_botones, text="Borrar registro", width=15, command=boton_borrar).pack(side="left", padx=5)
#GENERAR PDF:
def boton_pdf():
if len(datos) == 0:
messagebox.showerror("Error", "No hay datos")
return
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", "B", 16)
pdf.cell(0, 10, "Almazara", ln=True, align="C")
pdf.ln(2)
pdf.line(10, pdf.get_y(), 200, pdf.get_y())
pdf.ln(8)
pdf.set_font("Helvetica", "B", 10)
pdf.cell(15, 8, "ID", border=0)
pdf.cell(30, 8, "Fecha", border=0)
pdf.cell(45, 8, "Finca", border=0)
pdf.cell(40, 8, "KG recogidos", border=0)
pdf.cell(30, 8, "Rend. %", border=0)
pdf.cell(30, 8, "Aceite (L)", border=0, ln=True)
# Línea debajo de cabecera
pdf.line(10, pdf.get_y(), 200, pdf.get_y())
pdf.ln(5)
# ---------- DATOS ----------
pdf.set_font("Helvetica", size=10)
total_kilos = 0
total_aceite = 0
for r in datos:
id_finca = r[0]
fecha = r[1]
finca = r[2]
kilos = r[3]
rendimiento = r[4]
aceite = kilos * rendimiento / 100
total_kilos += kilos
total_aceite += aceite
pdf.cell(15, 8, str(id_finca), border=0)
pdf.cell(30, 8, fecha, border=0)
pdf.cell(45, 8, finca, border=0)
pdf.cell(40, 8, f"{kilos:.2f}", border=0)
pdf.cell(30, 8, f"{rendimiento:.2f}", border=0)
pdf.cell(30, 8, f"{aceite:.2f}", border=0, ln=True)
# Línea final tabla
pdf.ln(3)
pdf.line(10, pdf.get_y(), 200, pdf.get_y())
pdf.ln(10)
# ---------- TOTALES ----------
pdf.set_font("Helvetica", "B", 11)
pdf.cell(100, 8, f"Kilos totales: {total_kilos:.2f} KG", border=0)
pdf.cell(90, 8, f"Aceite total: {total_aceite:.2f} L", border=0, ln=True)
pdf.output("informe_almazara.pdf")
messagebox.showinfo("PDF generado", "El PDF se ha generado correctamente")
tk.Button(frame_botones, text="Generar PDF", width=15, command=boton_pdf).pack(side="left", padx=5)
ventana.mainloop()
#Francisco José Plaza García
https://drive.google.com/file/d/1w14FQZeE5lt-PcSiQLIteiTExQ1uETYF/view?usp=sharing
Este programa es una aplicación completa con ventana gráfica para gestionar una almazara.
Sirve para:
- Guardar registros de recogida (fecha, finca, kilos, rendimiento y jornales).
- Guardarlos en un archivo
datos.csv. - Verlos en una tabla.
- Modificarlos.
- Borrarlos.
- Generar un PDF con un informe.
Al iniciar el programa
Primero comprueba si existe el archivo datos.csv.
- Si no existe → lo crea con las columnas: ID, Fecha, Finca, Kilos, Rendimiento, Jornales
- Si ya existe → lo lee y carga los datos en una lista llamada
datos.
Crea la ventana
Usa Tkinter para crear:
- Una ventana llamada “Almazara”.
- Una tabla donde se ven todos los registros.
- Campos para escribir nuevos datos.
- Botones para trabajar con los registros.
Botón “Añadir registro”
Cuando lo pulsas:
- Comprueba que los números sean válidos y positivos.
- Comprueba que no falten campos.
- Crea un nuevo ID automático.
- Guarda el registro:
- En la lista
datos - En la tabla
- En el archivo CSV
- En la lista
Después limpia los campos.
Botón “Modificar registro”
- Seleccionas una fila de la tabla.
- Cambias los datos.
- Se actualiza:
- La lista
- La tabla
- El archivo CSV completo
Botón “Borrar registro”
- Seleccionas una fila.
- La elimina.
- Reordena los IDs.
- Vuelve a guardar todo el CSV.
Botón “Generar PDF”
Crea un archivo llamado:
informe_almazara.pdf
El PDF incluye:
- Título “Almazara”
- Tabla con:
- ID
- Fecha
- Finca
- Kilos
- Rendimiento
- Aceite calculado
- Totales finales de:
- Kilos
- Aceite
El aceite se calcula así:
aceite = kilos * rendimiento / 100