Multiples usuarios

This commit is contained in:
Tatiana Villa Ema 2026-04-25 13:32:50 +02:00
parent 5ecc8e7073
commit cea3be083f
4 changed files with 61 additions and 28 deletions

34
app.py
View File

@ -29,9 +29,17 @@ from werkzeug.security import generate_password_hash, check_password_hash
# Configuracion
# -----------------------------------------------------------------------
BASE_DIR = Path(__file__).parent
DATOS_JSON = BASE_DIR / "datos.json"
USERS_FILE = BASE_DIR / "users.json"
TICKETS_DIR = BASE_DIR / "tickets"
def get_tickets_dir(usuario: str) -> Path:
"""Directorio de tickets del usuario. Se crea si no existe."""
d = BASE_DIR / "tickets" / usuario
d.mkdir(parents=True, exist_ok=True)
return d
def get_datos_json(usuario: str) -> Path:
"""Ruta al datos.json del usuario."""
return BASE_DIR / "datos" / usuario / "datos.json"
ALLOWED_IMAGE_TYPES = {'image/jpeg', 'image/jpg', 'image/png', 'image/webp'}
MAX_IMAGE_BYTES = 15 * 1024 * 1024 # 15 MB
@ -195,6 +203,9 @@ def registro():
"nombre": nombre
}
guardar_usuarios(users)
# Crear carpeta de tickets para el nuevo usuario
(BASE_DIR / "tickets" / usuario).mkdir(parents=True, exist_ok=True)
(BASE_DIR / "datos" / usuario).mkdir(parents=True, exist_ok=True)
ok = "Cuenta creada correctamente. Ya puedes iniciar sesion."
return render_template("registro.html", error=error, ok=ok)
@ -212,9 +223,10 @@ def index():
@app.route("/api/datos")
@login_required
def api_datos():
if not DATOS_JSON.exists():
datos_json = get_datos_json(session["usuario"])
if not datos_json.exists():
return jsonify({"error": "datos.json no generado", "predicciones": []}), 404
with open(DATOS_JSON, encoding="utf-8") as f:
with open(datos_json, encoding="utf-8") as f:
datos = json.load(f)
return jsonify(datos)
@ -224,13 +236,14 @@ def api_datos():
@app.route("/api/regenerar", methods=["POST"])
@login_required
def api_regenerar():
usuario = session["usuario"]
try:
subprocess.run(
[sys.executable, str(BASE_DIR / "autocompra7.py")],
[sys.executable, str(BASE_DIR / "autocompra7.py"), "--usuario", usuario],
cwd=str(BASE_DIR), check=True, capture_output=True, timeout=120
)
subprocess.run(
[sys.executable, str(BASE_DIR / "generar_lista.py")],
[sys.executable, str(BASE_DIR / "generar_lista.py"), "--usuario", usuario],
cwd=str(BASE_DIR), check=True, capture_output=True, timeout=30
)
return jsonify({"ok": True, "mensaje": "Pipeline ejecutado correctamente"})
@ -245,7 +258,8 @@ def api_regenerar():
@app.route("/tickets/<path:filename>")
@login_required
def ticket_file(filename):
return send_from_directory(str(TICKETS_DIR), filename)
# Cada usuario solo accede a su propia carpeta de tickets
return send_from_directory(str(get_tickets_dir(session["usuario"])), filename)
# -----------------------------------------------------------------------
# Gestion de usuarios (solo admin)
@ -341,9 +355,9 @@ def api_ocr_ticket():
fecha_ticket = date_match.group(1) if date_match else datetime.now().strftime('%d/%m/%Y')
# Guardar como JSON en la carpeta tickets/
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
ticket_path = TICKETS_DIR / f"ocr_{ts}.json"
TICKETS_DIR.mkdir(exist_ok=True)
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
tdir = get_tickets_dir(session['usuario'])
ticket_path = tdir / f"ocr_{ts}.json"
with open(ticket_path, 'w', encoding='utf-8') as f:
json.dump({
"fecha": fecha_ticket,

View File

@ -3,11 +3,20 @@ import pandas as pd
import numpy as np
import os
import json
import argparse
from datetime import datetime, timedelta
from PyPDF2 import PdfReader
# Carpeta con los tickets PDF
ticket_folder = "tickets"
# --- Argumentos --------------------------------------------------------
parser = argparse.ArgumentParser()
parser.add_argument('--usuario', default='default', help='Nombre del usuario')
args = parser.parse_args()
# Carpeta con los tickets del usuario y directorio de salida
ticket_folder = os.path.join("tickets", args.usuario)
output_dir = os.path.join("datos", args.usuario)
os.makedirs(ticket_folder, exist_ok=True)
os.makedirs(output_dir, exist_ok=True)
# Palabras clave que indican líneas que no hay que procesar
exclude_keywords = [
@ -87,7 +96,7 @@ df = pd.DataFrame(datos, columns=columnas)
df.dropna(subset=["fecha"], inplace=True)
# Guardar detalle completo
df.to_csv("detalle_productos.csv", index=False)
df.to_csv(os.path.join(output_dir, "detalle_productos.csv"), index=False)
# Agrupar por producto
resumen = df.groupby("producto").agg(
@ -97,12 +106,12 @@ resumen = df.groupby("producto").agg(
primera_vez=("fecha", "min"),
ultima_vez=("fecha", "max")
).sort_values("gasto_total", ascending=False)
resumen.to_csv("resumen_productos.csv")
resumen.to_csv(os.path.join(output_dir, "resumen_productos.csv"))
# Gasto mensual
df["mes"] = df["fecha"].dt.to_period("M")
gasto_mensual = df.groupby("mes")["precio_total"].sum()
gasto_mensual.to_csv("gasto_mensual.csv")
gasto_mensual.to_csv(os.path.join(output_dir, "gasto_mensual.csv"))
# ---------------------------------------------------------------------------
# Cálculo de frecuencia con estacionalidad real por meses del año
@ -188,11 +197,11 @@ df = df.merge(resumen_estacional, on="producto", how="left")
compra_estimacion = df[df["proxima_compra"] <= proxima_semana]
# Guardar lista estimada
compra_estimacion.to_csv("compra_estimacion.csv", index=False)
compra_estimacion.to_csv(os.path.join(output_dir, "compra_estimacion.csv"), index=False)
# Generar HTML visual
html = compra_estimacion[["producto", "cantidad", "precio_unitario", "precio_total", "proxima_compra"]].sort_values("proxima_compra").to_html(index=False)
with open("lista_compra_estimada.html", "w") as file:
with open(os.path.join(output_dir, "lista_compra_estimada.html"), "w") as file:
file.write(html)
print("\n✅ Todo listo. Archivos generados:")
@ -207,5 +216,5 @@ lista_estimado = resumen_estacional.dropna(subset=["diferencia_dias"]).copy()
lista_estimado["producto"] = lista_estimado["producto"].str.title()
lista_estimado["fecha_estimada_proxima_compra"] = lista_estimado["proxima_compra"].dt.strftime("%d/%m/%Y")
lista_estimado = lista_estimado.sort_values("diferencia_dias", ascending=True)
lista_estimado.to_csv("lista_compra_estimado.csv", index=False)
lista_estimado.to_csv(os.path.join(output_dir, "lista_compra_estimado.csv"), index=False)
print("- lista_compra_estimado.csv")

View File

@ -8,11 +8,10 @@
SECRET_KEY: "${SECRET_KEY}"
ADMIN_PASSWORD: "${ADMIN_PASSWORD}"
volumes:
# Persistir tickets, datos generados y configuracion fuera del contenedor
# Persistir tickets y datos generados por usuario fuera del contenedor
- ./tickets:/app/tickets
- ./datos.json:/app/datos.json
- ./datos:/app/datos
- ./users.json:/app/users.json
- ./config.ini:/app/config.ini
- ./lista_compra_estimado.csv:/app/lista_compra_estimado.csv
ports:
- "8088:5000"

View File

@ -6,8 +6,19 @@ Uso: python generar_lista.py
import pandas as pd
import json
import re
import os
import argparse
from datetime import datetime
# --- Argumentos --------------------------------------------------------
parser = argparse.ArgumentParser()
parser.add_argument('--usuario', default='default', help='Nombre del usuario')
args = parser.parse_args()
input_csv = os.path.join('datos', args.usuario, 'lista_compra_estimado.csv')
output_dir = os.path.join('datos', args.usuario)
os.makedirs(output_dir, exist_ok=True)
# ---------------------------------------------------------------------------
# Filtros de basura (líneas del ticket que no son productos)
# ---------------------------------------------------------------------------
@ -51,12 +62,12 @@ def limpiar_nombre(nombre):
try:
# Intentar UTF-8 primero, si falla usar cp1252 (Windows)
try:
df = pd.read_csv('lista_compra_estimado.csv', encoding='utf-8')
df = pd.read_csv(input_csv, encoding='utf-8')
except UnicodeDecodeError:
df = pd.read_csv('lista_compra_estimado.csv', encoding='cp1252')
df = pd.read_csv(input_csv, encoding='cp1252')
except FileNotFoundError:
print("❌ No se encontró lista_compra_estimado.csv")
print(" Ejecuta primero: python autocompra5.py (o autocompra7.py)")
print("❌ No se encontró", input_csv)
print(" Ejecuta primero: python autocompra7.py --usuario", args.usuario)
exit(1)
print(f" Productos en CSV: {len(df)}")
@ -101,7 +112,7 @@ for _, row in df.iterrows():
# Escribir datos.js (cargable como <script src> sin servidor HTTP)
ts = datetime.now().strftime('%d/%m/%Y %H:%M')
with open('datos.js', 'w', encoding='utf-8') as f:
with open(os.path.join(output_dir, 'datos.js'), 'w', encoding='utf-8') as f:
f.write('// Generado el ' + ts + ' - ' + str(len(resultado)) + ' productos\n')
f.write('const GENERADO = "' + ts + '";\n')
f.write('const predicciones = ')
@ -114,8 +125,8 @@ datos_json = {
'total': len(resultado),
'predicciones': resultado,
}
with open('datos.json', 'w', encoding='utf-8') as f:
with open(os.path.join(output_dir, 'datos.json'), 'w', encoding='utf-8') as f:
json.dump(datos_json, f, ensure_ascii=False, indent=2)
print(f"{len(resultado)} predicciones escritas en datos.js y datos.json")
print(f"{len(resultado)} predicciones escritas en datos.json ({args.usuario})")
print(f" Abre index.html en el navegador o arranca app.py para ver la lista.")