Multiples usuarios
This commit is contained in:
parent
5ecc8e7073
commit
cea3be083f
34
app.py
34
app.py
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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.")
|
||||
|
|
|
|||
Loading…
Reference in New Issue