import tkinter as tk
from tkinter import scrolledtext, filedialog, messagebox
import sqlite3
try:
from PIL import Image, ImageTk
PIL_DISPONIBLE = True
except Exception:
PIL_DISPONIBLE = False
# ----------------- Base de datos -----------------
def inicializar_db():
conn = [Link]("[Link]")
cursor = [Link]()
[Link]("""
CREATE TABLE IF NOT EXISTS recetas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
categoria TEXT NOT NULL,
nombre TEXT NOT NULL,
ingredientes TEXT,
preparacion TEXT,
imagen TEXT
""")
[Link]()
[Link]()
def guardar_receta(categoria, nombre, ingredientes, preparacion, imagen):
conn = [Link]("[Link]")
cursor = [Link]()
[Link](
"INSERT INTO recetas (categoria, nombre, ingredientes, preparacion, imagen) VALUES (?, ?, ?, ?, ?)",
(categoria, nombre, ingredientes, preparacion, imagen)
[Link]()
[Link]()
def obtener_recetas(categoria):
conn = [Link]("[Link]")
cursor = [Link]()
[Link]("SELECT id, nombre FROM recetas WHERE categoria = ? ORDER BY nombre COLLATE
NOCASE", (categoria,))
recetas = [Link]()
[Link]()
return recetas
def obtener_receta_por_id(receta_id):
conn = [Link]("[Link]")
cursor = [Link]()
[Link]("SELECT categoria, nombre, ingredientes, preparacion, imagen FROM recetas WHERE id = ?",
(receta_id,))
receta = [Link]()
[Link]()
return receta
def borrar_receta(receta_id):
conn = [Link]("[Link]")
cursor = [Link]()
[Link]("DELETE FROM recetas WHERE id = ?", (receta_id,))
[Link]()
[Link]()
# ----------------- Utilidades -----------------
def limpiar_ventana(root):
for w in root.winfo_children():
[Link]()
def parse_cantidad(token):
token = [Link]()
if not token:
return None
if "/" in token:
try:
a, b = [Link]("/", 1)
return float(a) / float(b)
except Exception:
return None
try:
return float(token)
except Exception:
return None
def escalar_ingredientes(texto_ingredientes, factor):
resultado = []
for linea in texto_ingredientes.splitlines():
linea = [Link]()
if not linea:
continue
partes = [Link]()
cantidad_val = parse_cantidad(partes[0]) if partes else None
if cantidad_val is not None:
nueva = cantidad_val * factor
if abs(nueva - round(nueva)) < 1e-9:
nueva_str = str(int(round(nueva)))
else:
nueva_str = f"{nueva:.3f}".rstrip("0").rstrip(".")
[Link](f"{nueva_str} {' '.join(partes[1:])}")
else:
[Link](linea)
return "\n".join(resultado)
# ----------------- Aplicación -----------------
class RecetarioApp:
def __init__(self, root):
[Link] = root
[Link]("Recetario")
[Link]("900x800")
[Link](bg="#f7f7f7")
[Link] = {
"Carnes": "#b71c1c",
"Lácteos": "#64b5f6",
"Frutas": "#4caf50",
"Cereales": "#ff9800"
self.categoria_actual = None
inicializar_db()
self.pantalla_bienvenida()
def pantalla_bienvenida(self):
limpiar_ventana([Link])
[Link]([Link], text="¡Bienvenidos al Recetario!", font=("Arial", 28, "bold"),
bg="#f7f7f7", fg="#222").pack(pady=40)
[Link]([Link], text="🚀 Iniciar", width=25, height=3,
font=("Arial", 16, "bold"), bg="#b31408", fg="white",
command=self.menu_principal).pack(pady=20)
#-------------------------
def menu_principal(self):
limpiar_ventana([Link])
[Link]([Link], text="Recetario", font=("Arial", 26, "bold"),
bg="#f7f7f7", fg="#222").pack(pady=18)
botones = [Link]([Link], bg="#f7f7f7")
[Link](pady=5)
[Link](botones, text="📖 Ver recetario", width=22, height=2,
font=("Arial", 14, "bold"), bg="#4CAF50", fg="white",
command=self.mostrar_recetario).grid(row=0, column=0, padx=8, pady=8)
[Link](botones, text="➕ Agregar receta", width=22, height=2,
font=("Arial", 14, "bold"), bg="#2196F3", fg="white",
command=self.agregar_receta).grid(row=1, column=0, padx=8, pady=8)
[Link](botones, text="❌ Salir", width=22, height=2,
font=("Arial", 14, "bold"), bg="#f44336", fg="white",
command=[Link]).grid(row=2, column=0, padx=8, pady=8)
def mostrar_recetario(self):
limpiar_ventana([Link])
[Link]([Link], text="Menú de recetas", font=("Arial", 22, "bold"), bg="#ffffff").pack(pady=15)
cont = [Link]([Link], bg="#ffffff")
[Link](pady=10, padx=20)
for i, (categoria, color) in enumerate([Link]()):
[Link](cont, text=categoria, font=("Arial", 14, "bold"),
bg=color, fg="white", width=20, height=2,
command=lambda c=categoria: self.abrir_categoria(c)).grid(row=i//3, column=i%3, padx=8,
pady=8)
[Link]([Link], text="⬅️Volver", font=("Arial", 12),
bg="#cccccc", command=self.menu_principal).pack(pady=10)
[Link]([Link], text="❌ Salir", font=("Arial", 12),
bg="#f44336", fg="white", command=[Link]).pack(pady=10)
def abrir_categoria(self, nombre):
limpiar_ventana([Link])
self.categoria_actual = nombre
[Link]([Link], text=f"Categoría: {nombre}", font=("Arial", 22, "bold"), bg="#ffffff").pack(pady=15)
recetas = obtener_recetas(nombre)
lista_frame = [Link]([Link], bg="#ffffff")
lista_frame.pack(pady=6)
if recetas:
for receta_id, nombre_receta in recetas:
[Link](lista_frame, text=nombre_receta, font=("Arial", 14),
bg="#eeeeee", fg="#333", width=32, height=2,
command=lambda rid=receta_id: self.mostrar_receta(rid)).pack(pady=6)
else:
[Link]([Link], text="No hay recetas en esta categoría.", font=("Arial", 12),
bg="#ffffff", fg="red").pack(pady=10)
[Link]([Link], text="⬅️Volver", font=("Arial", 12),
bg="#cccccc", command=self.mostrar_recetario).pack(pady=10)
[Link]([Link], text="❌ Salir", font=("Arial", 12),
bg="#f44336", fg="white", command=[Link]).pack(pady=10)
def mostrar_receta(self, receta_id):
limpiar_ventana([Link])
data = obtener_receta_por_id(receta_id)
if not data:
[Link]([Link], text="Receta no encontrada.", fg="red").pack(pady=10)
[Link]([Link], text="⬅️Volver", command=lambda:
self.abrir_categoria(self.categoria_actual)).pack()
return
categoria, nombre, ingredientes, preparacion, imagen = data
[Link]([Link], text=nombre, font=("Arial", 22, "bold"), bg="#ffffff").pack(pady=10)
acciones = [Link]([Link], bg="#ffffff")
[Link](pady=8)
[Link](acciones, text="📜 Ver procedimiento", font=("Arial", 12),
bg="#2196F3", fg="white",
command=lambda: self.ver_procedimiento(preparacion)).grid(row=0, column=0, padx=6, pady=6)
[Link](acciones, text="📊 Calcular cantidades", font=("Arial", 12),
bg="#4CAF50", fg="white",
command=lambda: self.calcular_view(ingredientes or "")).grid(row=0, column=1, padx=6, pady=6)
[Link](acciones, text=" Borrar receta", font=("Arial", 12),
bg="#f44336", fg="white",
command=lambda: self.confirmar_borrado(receta_id)).grid(row=0, column=2, padx=6, pady=6)
[Link](acciones, text="❌ Salir", font=("Arial", 12),
bg="#f44336", fg="white",
command=[Link]).grid(row=0, column=3, padx=6, pady=6)
card = [Link]([Link], bg="#fffbea", bd=2, relief="ridge")
[Link](pady=15, padx=20, fill="x")
[Link](card, text="Ingredientes:", font=("Arial", 14, "bold"), bg="#fffbea").pack(pady=6)
[Link](card, text=ingredientes or "—", font=("Arial", 12), bg="#fffbea",
justify="left", anchor="w").pack(pady=4, padx=8)
if imagen and PIL_DISPONIBLE:
try:
img = [Link](imagen)
max_w, max_h = 500, 350
[Link]((max_w, max_h))
photo = [Link](img)
lbl_img = [Link]([Link], image=photo, bg="#ffffff")
lbl_img.photo = photo
lbl_img.pack(pady=12)
except Exception:
[Link]([Link], text="No se pudo cargar la imagen.", fg="red", bg="#ffffff").pack(pady=5)
[Link]([Link], text="⬅️Volver", font=("Arial", 12),
bg="#cccccc", command=lambda: self.abrir_categoria(self.categoria_actual)).pack(pady=10)
def ver_procedimiento(self, preparacion):
limpiar_ventana([Link])
[Link]([Link], text="Procedimiento", font=("Arial", 22, "bold"), bg="#ffffff").pack(pady=10)
texto = [Link]([Link], width=100, height=28, font=("Arial", 12))
[Link]([Link], (preparacion or "—"))
[Link](state="disabled")
[Link](pady=12)
[Link]([Link], text="⬅️Volver", font=("Arial", 12),
bg="#cccccc", command=lambda: self.abrir_categoria(self.categoria_actual)).pack(pady=20)
def confirmar_borrado(self, receta_id):
resp = [Link]("Confirmar borrado", "¿Seguro que deseas eliminar esta receta?")
if resp:
borrar_receta(receta_id)
self.abrir_categoria(self.categoria_actual)
def calcular_view(self, ingredientes):
limpiar_ventana([Link])
[Link]([Link], text="Calcular cantidades", font=("Arial", 22, "bold"), bg="#ffffff").pack(pady=10)
contenedor = [Link]([Link], bg="#ffffff")
[Link](fill="both", expand=True)
canvas = [Link](contenedor, bg="#ffffff", highlightthickness=0)
scroll_y = [Link](contenedor, orient="vertical", command=[Link])
marco = [Link](canvas, bg="#ffffff")
[Link]("<Configure>", lambda e: [Link](scrollregion=[Link]("all")))
canvas.create_window((0, 0), window=marco, anchor="nw")
[Link](yscrollcommand=scroll_y.set)
[Link](side="left", fill="both", expand=True)
scroll_y.pack(side="right", fill="y")
box = [Link](marco, bg="#ffffff")
[Link](pady=8)
[Link](box, text="Factor directo (ej. 2 para duplicar):", font=("Arial", 12), bg="#ffffff")\
.grid(row=0, column=0, sticky="w", padx=4, pady=4)
factor_entry = [Link](box, width=10, font=("Arial", 12))
factor_entry.grid(row=0, column=1, padx=4, pady=4)
[Link](box, text="Base (ej. 1 litro):", font=("Arial", 12), bg="#ffffff")\
.grid(row=1, column=0, sticky="w", padx=4, pady=4)
base_entry = [Link](box, width=10, font=("Arial", 12))
base_entry.grid(row=1, column=1, padx=4, pady=4)
[Link](box, text="Deseado (ej. 2 litros):", font=("Arial", 12), bg="#ffffff")\
.grid(row=2, column=0, sticky="w", padx=4, pady=4)
deseado_entry = [Link](box, width=10, font=("Arial", 12))
deseado_entry.grid(row=2, column=1, padx=4, pady=4)
fila_botones = [Link](marco, bg="#ffffff")
fila_botones.pack(pady=8)
def mostrar_explicacion():
mensaje = (
"📘 Cómo funciona el cálculo:\n\n"
"1) Factor directo: 2 duplica, 0.5 reduce a la mitad.\n"
"2) Base/Deseado: si base=1 litro y deseado=2 litros → factor=2.\n\n"
"⚠️No convierte unidades; solo escala números."
[Link]("¿Cómo funciona?", mensaje)
result_area = [Link](marco, width=100, height=24, font=("Arial", 12))
result_area.pack(pady=12)
def aplicar_calculo():
factor = None
fd = factor_entry.get().strip()
if fd:
try:
factor = float(fd)
except Exception:
factor = None
if factor is None:
try:
base = float(base_entry.get().strip())
deseado = float(deseado_entry.get().strip())
if base > 0:
factor = deseado / base
except Exception:
pass
result_area.config(state="normal")
result_area.delete("1.0", [Link])
if factor is None:
result_area.insert([Link], "⚠️Ingresa un factor válido o base y deseado.\n")
else:
escalado = escalar_ingredientes(ingredientes, factor)
result_area.insert([Link], f"✅ Factor aplicado: {factor}\n\n{escalado}")
result_area.config(state="disabled")
[Link](fila_botones, text="📊 Calcular cantidades", font=("Arial", 12),
bg="#4CAF50", fg="white", command=aplicar_calculo).grid(row=0, column=0, padx=6, pady=6)
[Link](fila_botones, text="¿Cómo funciona?", font=("Arial", 12),
bg="#FF9800", fg="white", command=mostrar_explicacion).grid(row=0, column=1, padx=6, pady=6)
[Link](fila_botones, text="❌ Salir", font=("Arial", 12),
bg="#f44336", fg="white", command=[Link]).grid(row=0, column=2, padx=6, pady=6)
[Link](marco, text="⬅️Volver", font=("Arial", 12),
bg="#cccccc", command=lambda: self.abrir_categoria(self.categoria_actual)).pack(pady=10)
def agregar_receta(self):
limpiar_ventana([Link])
[Link]([Link], text="Agregar receta", font=("Arial", 22, "bold"), bg="#ffffff").pack(pady=15)
form = [Link]([Link], bg="#ffffff")
[Link](pady=8)
# --- Menú de categoría + botones arriba ---
fila_superior = [Link](form, bg="#ffffff")
fila_superior.grid(row=0, column=0, columnspan=2, pady=4)
[Link](fila_superior, text="Categoría:", font=("Arial", 12), bg="#ffffff").pack(side="left", padx=(0, 6))
categorias_opciones = ["Carnes", "Cereales", "Frutas", "Lácteos"]
categoria_var = [Link](value=categorias_opciones[0])
categoria_menu = [Link](fila_superior, categoria_var, *categorias_opciones)
categoria_menu.config(font=("Arial", 12), bg="#eeeeee")
categoria_menu.pack(side="left", padx=6)
[Link](fila_superior, text="Generar campos", font=("Arial", 12),
bg="#FF9800", fg="white", command=lambda: generar_campos()).pack(side="left", padx=6)
[Link](fila_superior, text="⬅️Volver", font=("Arial", 12),
bg="#cccccc", command=self.menu_principal).pack(side="left", padx=6)
[Link](fila_superior, text="❌ Salir", font=("Arial", 12),
bg="#d42114", fg="white", command=[Link]).pack(side="left", padx=6)
[Link](form, text="Nombre de la receta:", font=("Arial", 12), bg="#ffffff").grid(row=1, column=0,
sticky="w", padx=4, pady=4)
nombre_entry = [Link](form, width=30, font=("Arial", 12))
nombre_entry.grid(row=1, column=1, padx=4, pady=4)
[Link](form, text="¿Cuántos ingredientes?", font=("Arial", 12), bg="#ffffff").grid(row=2, column=0,
sticky="w", padx=4, pady=4)
num_entry = [Link](form, width=6, font=("Arial", 12))
num_entry.grid(row=2, column=1, padx=4, pady=4)
imagen_path = [Link](value="")
campos = []
# --- Área con scroll para campos dinámicos ---
campos_frame = [Link]([Link], bg="#ffffff")
campos_frame.pack(pady=10, padx=20, fill="both", expand=True)
canvas = [Link](campos_frame, bg="#ffffff", highlightthickness=0)
scroll_y = [Link](campos_frame, orient="vertical", command=[Link])
inner = [Link](canvas, bg="#ffffff")
[Link]("<Configure>", lambda e: [Link](scrollregion=[Link]("all")))
canvas.create_window((0, 0), window=inner, anchor="nw")
[Link](yscrollcommand=scroll_y.set)
[Link](side="left", fill="both", expand=True)
scroll_y.pack(side="right", fill="y")
def seleccionar_imagen():
ruta = [Link](
title="Seleccionar imagen",
filetypes=[("Imágenes", ".jpg *.jpeg *.png *.gif"), ("Todos", ".*")]
if ruta:
imagen_path.set(ruta)
[Link](inner, text=f"Imagen seleccionada: {ruta}", bg="#ffffff", fg="green").pack(pady=4)
def guardar_final():
categoria = categoria_var.get()
nombre = nombre_entry.get().strip()
if not categoria or not nombre:
[Link]("Error", "Categoría y nombre son obligatorios.")
return
lines = []
for ing_e, cant_e in campos:
ing = ing_e.get().strip()
cant = cant_e.get().strip()
if ing:
linea = (cant + " " + ing).strip()
[Link](linea)
ingredientes_txt = "\n".join(lines)
preparacion_txt = prep_text.get("1.0", [Link]).strip()
ruta_imagen = imagen_path.get()
try:
guardar_receta(categoria, nombre, ingredientes_txt, preparacion_txt, ruta_imagen)
[Link]("OK", "Receta guardada correctamente.")
self.menu_principal()
except Exception as e:
[Link]("Error", f"No se pudo guardar la receta.\n{e}")
def generar_campos():
for w in inner.winfo_children():
[Link]()
[Link]()
try:
n = int(num_entry.get())
if n <= 0 or n > 30:
raise ValueError
except Exception:
[Link](inner, text="Ingresa un número válido entre 1 y 30.", fg="red", bg="#ffffff").pack(pady=6)
return
[Link](inner, text="Ingredientes", font=("Arial", 14, "bold"), bg="#ffffff").pack(pady=6)
for i in range(n):
fila = [Link](inner, bg="#ffffff")
[Link](fill="x", padx=4, pady=4)
[Link](fila, text=f"{i+1}. Ingrediente:", font=("Arial", 11), bg="#ffffff").grid(row=0, column=0,
sticky="w")
ing_entry = [Link](fila, width=30, font=("Arial", 11))
ing_entry.grid(row=0, column=1, padx=4)
[Link](fila, text="Cantidad:", font=("Arial", 11), bg="#ffffff").grid(row=0, column=2, sticky="w",
padx=(12,0))
cant_entry = [Link](fila, width=12, font=("Arial", 11))
cant_entry.grid(row=0, column=3, padx=4)
[Link](fila, text="Ejemplos: 2, 1.5, 1/2", font=("Arial", 9), fg="#666", bg="#ffffff").grid(row=0,
column=4, padx=6)
[Link]((ing_entry, cant_entry))
# --- Botones útiles debajo de ingredientes ---
[Link](inner, text="Seleccionar imagen", font=("Arial", 12),
bg="#9C27B0", fg="white", command=seleccionar_imagen).pack(pady=6)
[Link](inner, text="Guardar receta", font=("Arial", 12),
bg="#4CAF50", fg="white", command=guardar_final).pack(pady=6)
[Link](inner, text="Preparación", font=("Arial", 14, "bold"), bg="#ffffff").pack(pady=8)
global prep_text
prep_text = [Link](inner, width=100, height=15, font=("Arial", 12))
prep_text.pack(pady=6)
# ----------------- Ejecutar -----------------
if __name__ == "__main__":
root = [Link]()
app = RecetarioApp(root)
[Link]()