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:

  1. ID (único para poder modificar).
  2. Fecha (DD/MM/AAAA).
  3. Finca (Texto).
  4. Kilogramos (Real).
  5. Rendimiento (Porcentaje, ej: 21.5).
  6. Jornales (Entero).

B. Funcionalidades Principales

  1. Formulario de Entrada: Validar que los números sean positivos.
  2. Visor de Datos: Una tabla (Treeview) para ver todas las entregas.
  3. Lógica de Cálculo: 
  4. 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

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