Bläddra i källkod

redesign and security system

latapp 10 månader sedan
förälder
incheckning
2b061af2d0
11 ändrade filer med 453 tillägg och 449 borttagningar
  1. 105 77
      data.json
  2. BIN
      impresora/__pycache__/order.cpython-313.pyc
  3. BIN
      impresora/__pycache__/printer.cpython-313.pyc
  4. 0 151
      impresora/chat.py
  5. 19 2
      main.py
  6. 30 30
      products.json
  7. 187 107
      public/index.html
  8. 78 63
      public/js/app.js
  9. 19 2
      public/js/service.js
  10. 9 17
      public/styles.css
  11. 6 0
      users.json

+ 105 - 77
data.json

@@ -1,78 +1,106 @@
 [
-    {
-      "pregunta": "¿Qué es Klein?",
-      "respuesta": "Klein, Cervecería Klein o 'el Klein' se refieren al Biergarten Klein."
-    },
-    {
-      "pregunta": "¿Dónde puedo ver el menú de Klein?",
-      "respuesta": "Puedes consultar la carta en https://menu.fu.do/klein/qr-menu."
-    },
-    {
-      "pregunta": "¿Qué cervezas tienen en Klein?",
-      "respuesta": "Ofrecemos cervezas artesanales de la casa como Hoppy Mosh (IPA), Black Mamba (Porter), Soviet Riot (Russian Imperial Stout), Queen Burlesque (American Strong Ale), 24K Gold (Golden Ale) y Bendición Gitana (Pale Ale)."
-    },
-    {
-      "pregunta": "¿Qué comida recomiendan con la Hoppy Mosh?",
-      "respuesta": "Prueba esta IPA con nuestro ceviche, te sorprenderá la combinación refrescante."
-    },
-    {
-      "pregunta": "¿Qué maridaje tiene la Black Mamba?",
-      "respuesta": "Combínala con la Capresse 3 Quesos o la Cuatro Quesos para una experiencia deliciosa."
-    },
-    {
-      "pregunta": "¿Con qué plato va bien la Soviet Riot?",
-      "respuesta": "Es excelente para acompañar la Mechaloca, nuestra hamburguesa de carne mechada con queso cheddar y tocino."
-    },
-    {
-      "pregunta": "¿Qué cerveza es ideal para la Rodeo Burger?",
-      "respuesta": "Queen Burlesque es perfecta para disfrutar con la Rodeo Burger, sus sabores a caramelo y pasas complementan los toques ahumados de la salsa BBQ."
-    },
-    {
-      "pregunta": "¿Qué bebida es ideal para el verano?",
-      "respuesta": "24K Gold, una Golden Ale suave y sedosa con notas cítricas, ideal para días calurosos."
-    },
-    {
-      "pregunta": "¿Qué cerveza recomiendan con pizza vegetariana?",
-      "respuesta": "Bendición Gitana, una Pale Ale ligera que se lleva bien con la Vegetariana o la Doble Pepperoni."
-    },
-    {
-      "pregunta": "¿Qué opciones de gin tienen?",
-      "respuesta": "Tenemos opciones como Summer Klein, Tropical Bliss, Spicy Mango Xawer y Kalfuko, además de nuestra Tónica pomelo de la casa."
-    },
-    {
-      "pregunta": "¿Qué lleva el cóctel Summer Klein?",
-      "respuesta": "Refrescante mezcla de gin propio, jugo de naranja, jugo de limón y nuestra ginger beer de la casa."
-    },
-    {
-      "pregunta": "¿Qué hamburguesas tienen en Klein?",
-      "respuesta": "Ofrecemos la Rodeo Burger, Cheese Burger, Burger Play, Play Harder, Mechaloca y Veggie Marley."
-    },
-    {
-      "pregunta": "¿Cuál es la hamburguesa más jugosa?",
-      "respuesta": "Mechaloca, con carne mechada, queso cheddar, tocino, cebolla caramelizada, pepinillos y salsa ahumada."
-    },
-    {
-      "pregunta": "¿Tienen hamburguesas vegetarianas?",
-      "respuesta": "Sí, tenemos la Veggie Marley con pan pita, seitán, tomate, lechuga, coleslaw, cebolla morada y salsa ali oli. También ofrecemos la Not Burger como alternativa en nuestras hamburguesas clásicas."
-    },
-    {
-      "pregunta": "¿Cuáles son sus pizzas?",
-      "respuesta": "Tenemos varias opciones como Campestre, Cuatro Quesos, Pepperoni, Bianca, Piacere, Camarón al ajillo, Napolitana, Vegetariana, Doble Pepperoni, Margarita y Pollo BBQ."
-    },
-    {
-      "pregunta": "¿Cuál es su pizza más popular?",
-      "respuesta": "La Cuatro Quesos, con queso mozzarella, camembert, azul y edam, es una de las favoritas."
-    },
-    {
-      "pregunta": "¿Tienen pizzas con carne mechada?",
-      "respuesta": "Sí, la Piacere y la Campestre llevan carne mechada."
-    },
-    {
-      "pregunta": "¿Qué pizza lleva camarón?",
-      "respuesta": "La Camarón al ajillo, con camarón salteado, champiñón, cebolla morada y queso mozzarella."
-    },
-    {
-      "pregunta": "¿Qué tragos con gin tienen?",
-      "respuesta": "Puedes probar Tropical Bliss, Spicy Mango Xawer y Kalfuko, además de nuestra Tónica pomelo de la casa."
-    }
-  ]
+  {
+    "q": "¿Qué es Klein?",
+    "ans": "Klein, Cervecería Klein o 'el Klein' se refieren al Biergarten Klein, un espacio cervecero inspirado en los jardines alemanes."
+  },
+  {
+    "q": "¿Dónde puedo ver el menú de Klein?",
+    "ans": "Puedes consultar la carta en https://menu.fu.do/klein/qr-menu."
+  },
+  {
+    "q": "¿Qué cervezas tienen en Klein?",
+    "ans": "Contamos con cervezas artesanales como Burlesque, Queen Burlesque, Hoppy Mosh, Black Mamba, Soviet Riot, 24K Gold, Bendición Gitana, Mexican Lager, Märzen, Cyber Candy, Klein Alkoholfrei, Tropical Stout, Euby, Rauchbock y Rye IPA."
+  },
+  {
+    "q": "¿Cuál es su cerveza más popular?",
+    "ans": "Burlesque es la más pedida, una Scottish Ale de 5.0º, IBU 12, SRM 16 con notas a caramelo y galleta."
+  },
+  {
+    "q": "¿Qué cerveza recomiendan si me gusta la cerveza negra?",
+    "ans": "Black Mamba, Porter de 6.0º, IBU 15, SRM 35, con notas a toffee, chocolate y café."
+  },
+  {
+    "q": "¿Cuál es una buena cerveza rubia para probar?",
+    "ans": "Hoppy Mosh, IPA de 6.0º, IBU 38, SRM 33, con intensas notas a frutas tropicales."
+  },
+  {
+    "q": "¿Qué bebida es ideal para el verano?",
+    "ans": "24K Gold, Golden Ale de 4.5º, IBU 20, SRM 4. Suave, sedosa y cítrica."
+  },
+  {
+    "q": "¿Qué opciones de gin tienen?",
+    "ans": "Summer Klein, Tropical Bliss, Spicy Mango Xawer y Kalfuko, con gin artesanal y tónica pomelo de la casa."
+  },
+  {
+    "q": "¿Qué lleva el cóctel Summer Klein?",
+    "ans": "Gin propio, jugo de naranja, jugo de limón y ginger beer artesanal."
+  },
+  {
+    "q": "¿Tienen ginger beer artesanal?",
+    "ans": "Sí, elaborada con jengibre fresco y bajo contenido alcohólico."
+  },
+  {
+    "q": "¿Qué hamburguesas tienen en Klein?",
+    "ans": "Rodeo Burger, Cheese Burger, Burger Play, Play Harder, Mechaloca y Veggie Marley."
+  },
+  {
+    "q": "¿Tienen hamburguesas vegetarianas?",
+    "ans": "Sí, la Veggie Marley con seitán, y la Not Burger como alternativa en todas las clásicas."
+  },
+  {
+    "q": "¿Cuál es la hamburguesa más jugosa?",
+    "ans": "Mechaloca: carne mechada, queso cheddar, tocino, cebolla caramelizada, pepinillos y salsa ahumada."
+  },
+  {
+    "q": "¿Qué pizzas tienen?",
+    "ans": "Campestre, Cuatro Quesos, Pepperoni, Bianca, Piacere, Camarón al ajillo, Napolitana, Vegetariana, Doble Pepperoni, Margarita y Pollo BBQ."
+  },
+  {
+    "q": "¿Tienen pizzas con carne mechada?",
+    "ans": "Sí, la Piacere y la Campestre."
+  },
+  {
+    "q": "¿Qué pizza lleva camarón?",
+    "ans": "Camarón al ajillo: camarón salteado, champiñón, cebolla morada y mozzarella."
+  },
+  {
+    "q": "¿Qué cerveza va con ceviche?",
+    "ans": "Hoppy Mosh, por su frescura y notas tropicales."
+  },
+  {
+    "q": "¿Qué maridaje tiene la Black Mamba?",
+    "ans": "Capresse 3 Quesos o Cuatro Quesos."
+  },
+  {
+    "q": "¿Qué cerveza va con la Mechaloca?",
+    "ans": "Soviet Riot, Russian Imperial Stout de 8.8º, IBU 60, SRM 40, con notas a caramelo y café."
+  },
+  {
+    "q": "¿Qué cerveza acompaña bien la Rodeo Burger?",
+    "ans": "Queen Burlesque: American Strong Ale de 8.0º, IBU 25, con caramelo y pasas negras."
+  },
+  {
+    "q": "¿Qué cerveza combina con pizza vegetariana?",
+    "ans": "Bendición Gitana, Pale Ale de 5.0º, IBU 15, SRM 3."
+  },
+  {
+    "q": "¿Qué cerveza va bien con carnes asadas?",
+    "ans": "Rye IPA de 6.5º, IBU 40, con centeno y lúpulos Simcoe, Amarillo y Centennial."
+  },
+  {
+    "q": "¿Qué tiene de especial la Rauchbock?",
+    "ans": "Cerveza experimental ahumada (6.8º) con maltas seleccionadas y merkén de la Araucanía."
+  },
+  {
+    "q": "¿Qué es la Tropical Stout?",
+    "ans": "Stout de 7.5º, IBU 40, con tonos vinosos, ron oscuro, café y cacao."
+  },
+  {
+    "q": "¿Qué es la Euby?",
+    "ans": "Festbier con levadura híbrida Eubayanus del sur de Chile, en colaboración con Sayka."
+  },
+  {
+    "q": "¿Tienen cerveza sin alcohol?",
+    "ans": "Sí, Klein Alkoholfrei: 0.4º, IBU 15, SRM 10, con notas a pan y galleta dulce."
+  }
+]

BIN
impresora/__pycache__/order.cpython-313.pyc


BIN
impresora/__pycache__/printer.cpython-313.pyc


+ 0 - 151
impresora/chat.py

@@ -1,151 +0,0 @@
-from ast import arg
-import openai
-from printer import Printer
-from order import Order, Item
-import threading
-from scrapper import scrap_menu
-
-# Reemplaza 'TU_API_KEY' con tu clave real de OpenAI
-openai.api_key = "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"
-lista = []
-
-def nueva_lista():
-    """Elimina la lista de compras."""
-    print("Lista de compras eliminada.")
-    lista.clear()
-
-def add_to_list(*items):
-    """Agrega uno o más items a la lista de compras."""
-    for item in items:
-        if isinstance(item, dict) and "name" in item and "quantity" in item:
-            if item not in lista:
-                lista.append(item)
-            else:
-                item["quantity"] += item["quantity"]
-    
-
-def buy():
-    """Envía la lista de compra."""
-    if not lista:
-        print("No hay items en la lista de compras.")
-        return
-    order = Order([Item(item["name"], 10, item["quantity"]) for item in lista])
-    printer = Printer()
-    # Lanzar impresión en segundo plano y esperar a que termine
-    thread = threading.Thread(target=printer.print_order, args=(order,))
-    thread.start()
-    thread.join()
-    print("Lista de compra enviada.")
-    nueva_lista()
-
-function_definitions = [
-    {
-        "name": "add_to_list",
-        "description": "Agrega items nuevos a la lista de compras",
-        "parameters": {
-            "type": "object",
-            "properties": {
-                "items": {
-                    "type": "array",
-                    "items": {
-                        "type": "object",
-                        "properties": {
-                            "name": {
-                                "type": "string",
-                                "description": "Nombre del producto"
-                            },
-                            "quantity": {
-                                "type": "integer",
-                                "description": "Cantidad del producto"
-                            }
-                        },
-                        "required": ["name", "quantity"]
-                    },
-                    "description": "Lista de items a agregar"
-                }
-            },
-            "required": ["items"]
-        }
-    },
-    {
-        "name": "buy",
-        "description": "Envía la lista de compra",
-        "parameters": {
-            "type": "object",
-            "properties": {}
-        }
-    }
-]
-
-def handle_function_call(name, arguments):
-    if name == "nueva_lista":
-        nueva_lista()
-        return "e vaciado la lista de compras. ¿Deseas agregar un producto?"
-    elif name == "add_to_list":
-        add_to_list(*arguments.get("items", []))
-        print(arguments)
-        return f'Claro eh agregado {", ".join([f"{item["quantity"]} {item["name"]}{"s" if item["quantity"] > 1 else ""}" for item in arguments["items"]])} a la lista de compras. ¿Deseas agregar otro producto?'
-    elif name == "buy":
-        buy()
-        return "Tu pedido llegará en un momento"
-    else:
-        return "Función no reconocida."
-
-def main():
-    print("Chat GPT-4o-mini. Escribe 'salir' para terminar.")
-    messages = [
-        {"role": "system", "content": "Eres un asistente de ventas, tu nombre sera 'Camilo Klein' amigable y profesional tu misión es ayudar a los clientes presentandoles tu cerveceria con sus compras cuando agregues un producto a la lista de compras confirma diciendo por ejemplo '1 [nombre del producto] agregado' y siempre pregunta si desean añadir algo más si dicen que no pregúntales si quieren enviar la lista de compras recuerda que al usar la función 'add to list' solo debes agregar el producto pedido, ignorando la lista de compras"},
-        {"role": "system", "content": """solo puedes responder preguntas de o relacionadas a la siguiente tabla
-         
-         Pregunta	Respuesta
-¿Qué es Klein?	Klein, Cervecería Klein o 'el Klein' se refieren al Biergarten Klein.
-¿Dónde puedo ver el menú de Klein?	Puedes consultar la carta en https://menu.fu.do/klein/qr-menu.
-¿Qué cervezas tienen en Klein?	Ofrecemos cervezas artesanales de la casa como Hoppy Mosh (IPA), Black Mamba (Porter), Soviet Riot (Russian Imperial Stout), Queen Burlesque (American Strong Ale), 24K Gold (Golden Ale) y Bendición Gitana (Pale Ale).
-¿Qué comida recomiendan con la Hoppy Mosh?	Prueba esta IPA con nuestro ceviche, te sorprenderá la combinación refrescante.
-¿Qué maridaje tiene la Black Mamba?	Combínala con la Capresse 3 Quesos o la Cuatro Quesos para una experiencia deliciosa.
-¿Con qué plato va bien la Soviet Riot?	Es excelente para acompañar la Mechaloca, nuestra hamburguesa de carne mechada con queso cheddar y tocino.
-¿Qué cerveza es ideal para la Rodeo Burger?	Queen Burlesque es perfecta para disfrutar con la Rodeo Burger, sus sabores a caramelo y pasas complementan los toques ahumados de la salsa BBQ.
-¿Qué bebida es ideal para el verano?	24K Gold, una Golden Ale suave y sedosa con notas cítricas, ideal para días calurosos.
-¿Qué cerveza recomiendan con pizza vegetariana?	Bendición Gitana, una Pale Ale ligera que se lleva bien con la Vegetariana o la Doble Pepperoni.
-¿Qué opciones de gin tienen?	Tenemos opciones como Summer Klein, Tropical Bliss, Spicy Mango Xawer y Kalfuko, además de nuestra Tónica pomelo de la casa.
-¿Qué lleva el cóctel Summer Klein?	Refrescante mezcla de gin propio, jugo de naranja, jugo de limón y nuestra ginger beer de la casa.
-¿Qué hamburguesas tienen en Klein?	Ofrecemos la Rodeo Burger, Cheese Burger, Burger Play, Play Harder, Mechaloca y Veggie Marley.
-¿Cuál es la hamburguesa más jugosa?	Mechaloca, con carne mechada, queso cheddar, tocino, cebolla caramelizada, pepinillos y salsa ahumada.
-¿Tienen hamburguesas vegetarianas?	Sí, tenemos la Veggie Marley con pan pita, seitán, tomate, lechuga, coleslaw, cebolla morada y salsa ali oli. También ofrecemos la Not Burger como alternativa en nuestras hamburguesas clásicas.
-¿Cuáles son sus pizzas?	Tenemos varias opciones como Campestre, Cuatro Quesos, Pepperoni, Bianca, Piacere, Camarón al ajillo, Napolitana, Vegetariana, Doble Pepperoni, Margarita y Pollo BBQ.
-¿Cuál es su pizza más popular?	La Cuatro Quesos, con queso mozzarella, camembert, azul y edam, es una de las favoritas.
-¿Tienen pizzas con carne mechada?	Sí, la Piacere y la Campestre llevan carne mechada.
-¿Qué pizza lleva camarón?	La Camarón al ajillo, con camarón salteado, champiñón, cebolla morada y queso mozzarella.
-¿Qué tragos con gin tienen?	Puedes probar Tropical Bliss, Spicy Mango Xawer y Kalfuko, además de nuestra Tónica pomelo de la casa."""},
-    {"role":"system", "content":"Lista actual de compras:\n\n{lista}\n"}
-    ]
-    while True:
-        user_input = input("Tú: ")
-        if user_input.lower() == "salir":
-            break
-        messages.append({"role": "user", "content": user_input})
-        response = openai.chat.completions.create(
-            model="gpt-4o-mini",
-            messages=messages,
-            functions=function_definitions,
-            function_call="auto",
-            temperature=0.3
-        )
-        reply_msg = response.choices[0].message
-        if reply_msg.function_call:
-            func_name = reply_msg.function_call.name
-            import json
-            arguments = json.loads(reply_msg.function_call.arguments)
-            result = handle_function_call(func_name, arguments)
-            print("Asistente:", result)
-            messages.append({
-                "role": "assistant",
-                "content": result
-            })
-        else:
-            reply = reply_msg.content.strip()
-            print("Asistente:", reply)
-            messages.append({"role": "assistant", "content": reply})
-
-if __name__ == "__main__":
-    main()

+ 19 - 2
main.py

@@ -109,7 +109,7 @@ async def generate_completion(messages_array: List[Message], session_id: str) ->
     print(f"[OpenAI Service Python] Session/Token {session_id} sent: {[msg.model_dump() for msg in messages_array]}")
 
     data_for_prompt = [
-        f'{{"pregunta": "{item.get("pregunta", "")}", "respuesta": "{item.get("respuesta", "")}"}}'
+        f'{{"pregunta": "{item.get("q", "")}", "respuesta": "{item.get("ans", "")}"}}'
         for item in bg_data_loaded
     ]
     data_string = "\n".join(data_for_prompt)
@@ -185,10 +185,27 @@ async def init_chat(request: Request):
         # print(f"Using existing antiAbuseToken for session: {current_token}")
         return JSONResponse({"chatToken": current_token})
 
-
+class UserCodeRequest(BaseModel):
+    user_code: str
+
+@app.post("/api/existsUser", summary="Check if user exists")
+async def exists_user(request: UserCodeRequest):
+    with open('users.json', 'r') as f:
+        users = json.load(f)
+        for user in users:
+            if user['userCode'] == request.user_code:
+                return JSONResponse({
+                    "success": True,
+                    "userName": user['userName']
+                })
+        return JSONResponse({
+            "success": False,
+            "userName": None
+        })
 @app.post("/api/printer/order", summary="Printer order", dependencies=[Depends(protect_chat_api)])
 async def printer_order(order: OrderWeb):
     print("Printer order received")
+    print(order)
     items = order.items
     table = order.table
     printer = PrinterUSB(0xfe6,0x811e)

+ 30 - 30
products.json

@@ -1,11 +1,26 @@
 [
+  {
+    "id": 3,
+    "name": "Burlesque",
+    "category": "Cervezas Artesanales",
+    "description": "5.0º - IBU 12 - SRM 16 - Cerveza Ale ámbar maltosa con notas a caramelo y galleta.",
+    "price": 5000,
+    "image": "https://placehold.co/300x200?text=Cerveza+Burlesque"
+  },  {
+    "id": 15,
+    "name": "Black Mamba",
+    "category": "Cervezas Artesanales",
+    "description": "Porter - 6.0º - IBU 15 - SRM 35 - Cerveza Ale negra con carácter. Notas a toffee y chocolate.",
+    "price": 5000,
+    "image": "https://placehold.co/300x200?text=Cerveza+Black+Mamba"
+  },
   {
     "id": 1,
     "name": "Witbier",
     "category": "Cervezas Artesanales",
     "description": "Cerveza estilo belga, con un 50% de trigo en su receta, además de cascara de naranja dulce y semillas de cilantro. Es una cerveza refrescante con solo 4,5° de alcohol. De aspecto turbio y de color claro.",
     "price": 5000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Witbier"
+    "image": "https://placehold.co/300x200?text=Cerveza+Witbier"
   },
   {
     "id": 2,
@@ -13,15 +28,7 @@
     "category": "Cervezas Artesanales",
     "description": "Golden Ale - 4,5º - IBU 20 - SRM 4 - Cerveza Ale dorada. En boca sedosa y suave con notas cítricas.",
     "price": 5000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+24K+Gold"
-  },
-  {
-    "id": 3,
-    "name": "Burlesque",
-    "category": "Cervezas Artesanales",
-    "description": "5.0º - IBU 12 - SRM 16 - Cerveza Ale ámbar maltosa con notas a caramelo y galleta.",
-    "price": 5000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Burlesque"
+    "image": "https://placehold.co/300x200?text=Cerveza+24K+Gold"
   },
   {
     "id": 4,
@@ -29,7 +36,7 @@
     "category": "Cervezas Artesanales",
     "description": "IPA - 6.0º - IBU 38 - SRM 33 - Cerveza Ale cobriza con intensas notas a frutas tropicales.",
     "price": 6500,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Hoppy+Mosh"
+    "image": "https://placehold.co/300x200?text=Cerveza+Hoppy+Mosh"
   },
   {
     "id": 5,
@@ -37,7 +44,7 @@
     "category": "Cervezas Artesanales",
     "description": "Blonde Ale - 5,0º - IBU 15 - SRM 3 - Lager ligera",
     "price": 5000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Bendicion+Gitana"
+    "image": "https://placehold.co/300x200?text=Cerveza+Bendicion+Gitana"
   },
   {
     "id": 6,
@@ -45,7 +52,7 @@
     "category": "Cervezas Artesanales",
     "description": "Abv: 5,8 Ibu: 22. Estilo Märzenbier, es una cerveza lager típica alemana donde prevalecen las notas a pan tostado y corteza,. Color ámbar brillante.",
     "price": 5000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Marzen"
+    "image": "https://placehold.co/300x200?text=Cerveza+Marzen"
   },
   {
     "id": 7,
@@ -53,7 +60,7 @@
     "category": "Cervezas Artesanales",
     "description": "ABV: 7,5 IBU:40 Carbonatación media-alta. Cerveza ligeramente dulce, con tonos vinosos y a ron oscuro, aromas a grano con un final que recuerdan a café y a cacao tostado. En boca deja una sensación suave y cremosa. Sus lúpulos de adición tardía recuerden a frutas como durazno, higos secos y piña. De aspecto oscuro, espuma color canela que se mantiene y sabores suavemente torrados, aunque mas lupulada de lo normal, serán el complemento perfecto para disfrutar los lluviosos días de invierno.",
     "price": 5500,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Tropical+Stout"
+    "image": "https://placehold.co/300x200?text=Cerveza+Tropical+Stout"
   },
   {
     "id": 8,
@@ -61,7 +68,7 @@
     "category": "Cervezas Artesanales",
     "description": "Cerveceza muy ligera, chispeante y refrescante. De color dorado claro. Fácil de beber y de baja graduación alcohólica. Abv: 4,5% Ibu 15",
     "price": 5000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Mexican+Lager"
+    "image": "https://placehold.co/300x200?text=Cerveza+Mexican+Lager"
   },
   {
     "id": 9,
@@ -69,7 +76,7 @@
     "category": "Cervezas Artesanales",
     "description": "Cerveza de solo 0.4º - IBU 15- SRM 10 Cerveza de cuerpo medio con sabores y aromas a pan y galleta dulce, amargor medio bajo, con un final balanceado.",
     "price": 5000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Klein+Alkoholfrei"
+    "image": "https://placehold.co/300x200?text=Cerveza+Klein+Alkoholfrei"
   },
   {
     "id": 10,
@@ -77,7 +84,7 @@
     "category": "Cervezas Artesanales",
     "description": "Rye IPA | 6.5% ABV – 40 IBU Cerveza lupulada con un 15% de centeno, lo que le aporta en boca una textura ligera pero intensa, similar a una clásica IPA americana. Elaborada con lúpulos Simcoe, Amarillo y Centennial. MARIDAJE: Pizza pollo BBQ, Rodeo Burger",
     "price": 6400,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Rye+IPA"
+    "image": "https://placehold.co/300x200?text=Cerveza+Rye+IPA"
   },
   {
     "id": 11,
@@ -85,7 +92,7 @@
     "category": "Cervezas Artesanales",
     "description": "Ibu : 25 Abv: 8 grados Estilo: American Strong Ale Cerveza ale fuerte, de color cobrizo oscuro, destaca su maltosidad y Lupulos americanos; sabores a caramelo, toffe y pasas negras.",
     "price": 6000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Queen+Burlesque"
+    "image": "https://placehold.co/300x200?text=Cerveza+Queen+Burlesque"
   },
   {
     "id": 12,
@@ -93,7 +100,7 @@
     "category": "Cervezas Artesanales",
     "description": "Russian Imperial Stout - 8.8º - IBU 60 - SRM 40 - Cerveza Ale negra, intensa y compleja que a pesar de su graduación alcohólica, es balanceada. Notas a caramelo y café.",
     "price": 6500,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Soviet+Riot"
+    "image": "https://placehold.co/300x200?text=Cerveza+Soviet+Riot"
   },
   {
     "id": 13,
@@ -101,7 +108,7 @@
     "category": "Cervezas Artesanales",
     "description": "Cerveza colaborativa con Ergo, de color dorado, con sabores a corteza de pan y notas florales. De amargor moderado. Con fácil tomabilidad a pesar de su graduación alcohólica, ligera en boca con un pequeño dulzor residual gracias a la miel. Abv 9 grados Ibu 23",
     "price": 6200,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Heller+Doppelbock+Barrica"
+    "image": "https://placehold.co/300x200?text=Cerveza+Heller+Doppelbock+Barrica"
   },
   {
     "id": 14,
@@ -109,22 +116,15 @@
     "category": "Cervezas Artesanales",
     "description": "Cerveza color cobrizo, de gran cuerpo, con un final dulce y carbonatación media. Su aspecto hazy es producto de la adición de avena y trigo. Destacan las notas a frutos tropicales, especialmente el mango producto de la adición de lúpulo Sabro. 6.9° ABV, IBU: 19.",
     "price": 6500,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Cyber+Candy"
-  },
-  {
-    "id": 15,
-    "name": "Black Mamba",
-    "category": "Cervezas Artesanales",
-    "description": "Porter - 6.0º - IBU 15 - SRM 35 - Cerveza Ale negra con carácter. Notas a toffee y chocolate.",
-    "price": 5000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Black+Mamba"
+    "image": "https://placehold.co/300x200?text=Cerveza+Cyber+Candy"
   },
+
   {
     "id": 16,
     "name": "Rauchbock",
     "category": "Cervezas Artesanales",
     "description": "Cerveza experimental parte del estilo Rauchbier con 6.8% ABV. De carácter ahumado, otorgado por maltas ahumadas y diversas variedades de merkén originarias de la Araucanía. Evoca a jamón serrano con un final delicadamente especiado/ahumado.",
     "price": 6000,
-    "image": "https://placehold.co/300x200/1a1a1a/e53e3e?text=Cerveza+Rauchbock"
+    "image": "https://placehold.co/300x200?text=Cerveza+Rauchbock"
   }
 ]

+ 187 - 107
public/index.html

@@ -1,117 +1,197 @@
 <!DOCTYPE html>
 <html lang="es">
 <head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Biergarten Klein - Pedidos</title> 
-    <script src="https://cdn.tailwindcss.com"></script>
-    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
-    <link rel="stylesheet" href="styles.css">
-    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
-    <script type="module" src="js/app.js"></script>
+  <meta charset="UTF-8" />
+  <title>Biergarten Klein</title>
+
+  <!-- Fuentes + Tailwind -->
+  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+  <link rel="stylesheet" as="style" onload="this.rel='stylesheet'"
+        href="https://fonts.googleapis.com/css2?display=swap&family=Noto+Sans:wght@400;500;700;900&family=Spline+Sans:wght@400;500;700">
+  <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
+
+  <!-- Markdown -->
+  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
+
+  <!-- Lógica principal -->
+  <script src="js/app.js" type="module"></script>
+  <link rel="stylesheet" href="styles.css">
+  <style>
+    @keyframes slideIn   {from {transform:translateY(-8px); opacity:0;} to {transform:translateY(0); opacity:1;}}
+    @keyframes slideOut  {from {transform:translateY(0);   opacity:1;} to {transform:translateY(-8px); opacity:0;}}
+  </style>
 </head>
-<body class="min-h-screen flex flex-col text-base leading-relaxed">
-
-    <div id="welcomeModal" class="modal" style="display: flex;">
-        <div class="modal-content text-center">
-            <h3 class="text-2xl font-semibold mb-4 accent-red">Bienvenido a Biergarten Klein</h3>
-            <p class="text-gray-400 mb-6">Para comenzar, por favor ingresa tu nombre y número de mesa.</p>
-            <div class="space-y-4">
-                <input type="text" id="userNameInput" class="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-accent-red focus:border-transparent outline-none text-white" placeholder="Tu Nombre">
-                <input type="number" id="userTableInput" class="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-accent-red focus:border-transparent outline-none text-white" placeholder="Número de Mesa">
+
+<body class="min-h-screen flex flex-col bg-gray-50 overflow-x-hidden"
+      style='font-family:"Spline Sans","Noto Sans",sans-serif;'>
+
+  <!-- ---------- HEADER ---------- -->
+  <header class="fixed top-0 inset-x-0 z-10 bg-gray-50 p-4 flex justify-center items-center border-b border-gray-200">
+    <h1 class="text-lg font-bold text-[#101419] tracking-tight">
+      Biergarten Klein
+    </h1>
+  </header>
+
+  <!-- ---------- MAIN  ---------- -->
+  <main class="flex-1 pt-16 pb-20">  
+    <!-- ===== MENÚ tab ===== -->
+    <section id="menuTab" data-tab>
+      <div class="px-4 pt-4 pb-3">
+        <h2 class="text-[22px] font-bold text-[#101419] tracking-tight">
+          Nuestra Selección de Cervezas
+        </h2>
+      </div>
+      <div class="px-4">
+        <ul id="productList" class="space-y-6"></ul>
+      </div>
+
+      <template id="product-card-template">
+        <li class="flex items-stretch justify-between gap-4 rounded-xl">
+          <div class="flex flex-[2_2_0px] flex-col gap-4">
+            <div class="flex flex-col gap-1">
+              <p class="product-type text-[#58728d] text-sm"></p>
+              <p class="product-name text-[#101419] text-base font-bold leading-tight"></p>
+              <p class="product-description text-[#58728d] text-sm"></p>
             </div>
-            <button id="startOrderButton" class="w-full mt-6 bg-accent-red hover:bg-red-700 text-white font-bold py-3 px-4 rounded-lg transition duration-300 ease-in-out transform hover:scale-105">
-                Comenzar Pedido
-            </button>
-        </div>
-    </div>
 
-    <header class="bg-black shadow-lg py-4">
-        <div class="container mx-auto px-4 text-center">
-            <h1 class="text-3xl font-bold accent-red tracking-tight">Biergarten <span class="text-white">Klein</span></h1>
-            <p class="text-gray-400 text-md mt-1">¡El sabor de la buena compañía!</p>
-        </div>
-    </header>
-
-    <main class="container mx-auto px-4 py-8 flex-grow grid grid-cols-1 lg:grid-cols-3 gap-8">
-        
-        <aside class="lg:col-span-1">
-            <div id="chat-container" class="sticky top-8 chat-panel-embedded">
-                <div class="chat-header">
-                    <h3 class="text-xl font-semibold accent-red">Chef IA <span class="text-sm text-gray-400">(Asistente Virtual)</span></h3>
-                </div>
-                <div class="chat-content-area">
-                    <div id="chatMessages" class="scrollable-chat">
-                        <div class="chat-bubble chat-bubble-ai">¡Hola! Soy tu asistente en Biergarten Klein. ¿Te gustaría una recomendación de nuestras cervezas artesanales?</div>
-                    </div>
-                    <div id="aiLoadingIndicator" class="hidden my-2">
-                        <div class="loading-spinner"></div>
-                        <p class="text-center text-sm text-gray-400 mt-1">Chef IA está pensando...</p>
-                    </div>
-                    <div class="chat-input-container">
-                        <input type="text" id="chatInput" class="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-accent-red focus:border-transparent outline-none text-white" placeholder="Escribe tu mensaje...">
-                        <button id="sendChatButton" class="bg-accent-red hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg transition duration-300">
-                            Enviar
-                        </button>
-                    </div>
-                </div>
+            <!-- Botón Añadir -->
+            <div class="flex items-center gap-3">
+              <button class="add-to-cart-btn flex items-center gap-1 w-fit h-8 px-3
+                             rounded-full bg-[#101419] hover:bg-[#37404a] text-white text-sm font-medium">
+                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
+                  <path d="M12 5v14m7-7H5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                </svg>
+                Añadir
+              </button>
+              <!-- Precio -->
+              <span class="product-price text-sm font-semibold text-[#101419]"></span>
             </div>
-        </aside>
-
-        <div class="lg:col-span-2 space-y-8">
-            <section id="productListSection">
-                <h2 class="category-title text-3xl font-semibold mb-4 pb-2">Nuestras Cervezas</h2>
-                <div id="productList" class="space-y-6">
-                    </div>
-            </section>
-
-            <aside id="cartAside">
-                <div class="sticky top-8">
-                    <h2 class="text-3xl font-semibold mb-4 border-b-2 border-accent-red pb-2">Tu Pedido</h2>
-                    <div id="cartItems" class="space-y-4 mb-6 product-card p-6 rounded-lg shadow-xl min-h-[150px]">
-                        <p id="emptyCartText" class="text-gray-400">Tu carrito está vacío.</p>
-                    </div>
-                    <div class="product-card p-6 rounded-lg shadow-xl">
-                        <div class="flex justify-between items-center text-xl font-semibold mb-4">
-                            <span>Total:</span>
-                            <span id="cartTotal" class="accent-red">$0</span>
-                        </div>
-                        <button id="checkoutButton" class="w-full bg-accent-red hover:bg-red-700 text-white font-bold py-3 px-4 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
-                            Finalizar Pedido
-                        </button>
-                    </div>
-                </div>
-            </aside>
-        </div>
-    </main>
-
-    <div id="orderConfirmationModal" class="modal">
-        <div class="modal-content text-center">
-            <span id="closeConfirmationModalButton" class="close-button absolute top-3 right-4">&times;</span>
-            <h3 class="text-2xl font-semibold mb-4 accent-red">¡Pedido Realizado!</h3>
-            <p class="text-lg mb-2">Gracias por tu compra en Biergarten Klein.</p>
-            <p class="text-gray-400 mb-6">Tu pedido está siendo preparado y llegará pronto.</p>
-            <svg class="w-16 h-16 text-green-500 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
-            <button id="newOrderButton" class="bg-accent-red hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg">
-                Hacer un Nuevo Pedido
-            </button>
-        </div>
-    </div>
-    <div id="orderErrorModal" class="modal">
-        <div class="modal-content text-center">
-            <span id="closeOrderErrorModalButton" class="close-button absolute top-3 right-4">&times;</span>
-            <h3 class="text-2xl font-semibold mb-4 accent-red">¡Error al enviar el pedido!</h3>
-            <p class="text-lg mb-2">Hubo un problema al enviar tu pedido. Por favor, inténtalo de nuevo.</p>
-            <svg class="w-16 h-16 text-red-500 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 9.172L14.828 14.828M14.828 9.172L9.172 14.828M12 2a10 10 0 110 20 10 10 0 010-20z"></path></svg>
-            <button id="retryButton" class="bg-accent-red hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg">Reintentar</button>
-        </div>
-    </div>
 
-    <footer class="bg-black py-6 mt-auto border-t border-gray-800">
-        <div class="container mx-auto px-4 text-center text-gray-500">
-            <p>&copy; <span id="currentYear"></span> Biergarten Klein. Todos los derechos reservados.</p>
-            <p>Diseñado con <span class="accent-red">&hearts;</span> para los amantes de la buena cerveza.</p>
+          </div>
+          <div class="product-image flex-1 aspect-video bg-cover bg-center rounded-xl"></div>
+        </li>
+      </template>
+    </section>
+
+    <!-- ===== CHAT ===== -->
+    <section id="chatTab" data-tab class="hidden flex flex-col h-full">
+      <div id="chatMessages" class="flex-1 overflow-y-auto p-4 space-y-3 text-sm leading-relaxed"></div>
+      <div id="aiLoadingIndicator" class="hidden px-4 py-2 text-center text-xs text-gray-500">Pensando…</div>
+      <form class="flex gap-2 p-3 border-t border-gray-200" onsubmit="event.preventDefault();">
+        <input id="chatInput" class="flex-1 text-sm px-3 py-2 rounded-md border border-gray-300 focus:outline-none text-neutral-800" autocomplete="off"
+               placeholder="Escribe tu mensaje...">
+        <button id="sendChatButton"
+                class="bg-[#101419] hover:bg-[#37404a] text-white px-3 py-2 rounded-md text-sm">
+          Enviar
+        </button>
+      </form>
+    </section>
+
+    <!-- ===== CARRITO ===== -->
+    <section id="cartTab" data-tab class="hidden flex flex-col h-full">
+      <header class="p-4 border-b border-gray-200">
+        <h3 class="text-lg font-bold text-[#101419]">Tu pedido</h3>
+      </header>
+
+      <div id="cartItems" class="flex-1 overflow-y-auto p-4 space-y-2"></div>
+      <p id="emptyCartText" class="hidden text-center text-gray-400 mt-4">Tu carrito está vacío.</p>
+
+      <footer class="p-4 border-t border-gray-200 space-y-3">
+        <div class="flex justify-between text-base">
+          <span>Total:</span>
+          <span id="cartTotal">$0</span>
         </div>
-    </footer>
+        <button id="checkoutButton"
+                class="w-full bg-[#101419] hover:bg-[#37404a] disabled:opacity-50 text-white py-2 rounded-md"
+                disabled>
+          Finalizar Pedido
+        </button>
+      </footer>
+    </section>
+  </main>
+
+  <!-- ---------- NAVBAR ---------- -->
+  <footer class="fixed bottom-0 inset-x-0 z-10 border-t border-gray-200 bg-gray-50 px-4 py-2">
+    <nav class="flex gap-2">
+      <button data-target="chatTab" class="tab-btn flex-1 flex flex-col items-center text-[#58728d]">
+        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
+             viewBox="0 0 256 256" class="h-8">
+          <path
+            d="M140,128a12,12,0,1,1-12-12A12,12,0,0,1,140,128ZM84,116a12,12,0,1,0,12,12A12,12,0,0,0,84,116Zm88,0a12,12,0,1,0,12,12A12,12,0,0,0,172,116Zm60,12A104,104,0,0,1,79.12,219.82L45.07,231.17a16,16,0,0,1-20.24-20.24l11.35-34.05A104,104,0,1,1,232,128Z" />
+        </svg>
+        <span class="text-xs font-medium">Chat</span>
+      </button>
+      <button data-target="menuTab" class="tab-btn flex-1 flex flex-col items-center text-[#101419]">
+         <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
+             viewBox="0 0 256 256" class="h-8">
+          <path
+            d="M56,128a16,16,0,1,1-16-16A16,16,0,0,1,56,128ZM40,48A16,16,0,1,0,56,64,16,16,0,0,0,40,48Zm0,128a16,16,0,1,0,16,16A16,16,0,0,0,40,176Zm176-64H88a8,8,0,0,0-8,8v16a8,8,0,0,0,8,8H216a8,8,0,0,0,8-8V120A8,8,0,0,0,216,112Zm0-64H88a8,8,0,0,0-8,8V72a8,8,0,0,0,8,8H216a8,8,0,0,0,8-8V56A8,8,0,0,0,216,48Zm0,128H88a8,8,0,0,0-8,8v16a8,8,0,0,0,8,8H216a8,8,0,0,0,8-8V184A8,8,0,0,0,216,176Z" />
+        </svg>
+        <span class="text-xs font-medium">Menú</span>
+      </button>
+      <button data-target="cartTab" class="tab-btn flex-1 flex flex-col items-center text-[#58728d]">
+        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
+             viewBox="0 0 256 256" class="h-8">
+          <path
+            d="M222.14,58.87A8,8,0,0,0,216,56H54.68L49.79,29.14A16,16,0,0,0,34.05,16H16a8,8,0,0,0,0,16h18L59.56,172.29a24,24,0,0,0,5.33,11.27,28,28,0,1,0,44.4,8.44h45.42A27.75,27.75,0,0,0,152,204a28,28,0,1,0,28-28H83.17a8,8,0,0,1-7.87-6.57L72.13,152h116a24,24,0,0,0,23.61-19.71l12.16-66.86A8,8,0,0,0,222.14,58.87ZM96,204a12,12,0,1,1-12-12A12,12,0,0,1,96,204Zm96,0a12,12,0,1,1-12-12A12,12,0,0,1,192,204Zm4-74.57A8,8,0,0,1,188.1,136H69.22L57.59,72H206.41Z" />
+        </svg>
+        <span class="text-xs font-medium">Carrito</span>
+      </button>
+    </nav>
+  </footer>
+
+  <!-- ---------- TOAST ---------- -->
+  <div id="toastCart"
+       class="fixed top-4 left-1/2 -translate-x-1/2 bg-[#101419] text-white text-sm
+              rounded-md px-4 py-2 shadow-lg opacity-0 pointer-events-none z-50"></div>
+
+  
+  <!-- === MODAL INICIO DE SESIÓN === -->
+<div id="sessionModal"
+     class="fixed inset-0 bg-black/70 flex items-center justify-center z-50">
+  <div class="bg-white w-full max-w-sm p-6 rounded-lg space-y-4 text-center">
+    <h2 class="text-xl font-bold">¡Bienvenido!</h2>
+    <p class="text-sm text-gray-600">
+      Ingresa tu número de mesa y tu código de cliente para comenzar.
+    </p>
+
+    <input id="tableInput"
+           type="number" min="1"
+           class="w-full border px-3 py-2 rounded-md"
+           placeholder="Mesa #" />
+
+    <input id="clientCodeInput"
+           class="w-full border px-3 py-2 rounded-md"
+           placeholder="Código de cliente" />
+
+    <button id="sessionAcceptBtn"
+            class="w-full bg-[#101419] hover:bg-[#37404a] text-white py-2 rounded-md">
+      Aceptar
+    </button>
+  </div>
+</div>
+
+              <!-- ---------- JS: conmutar tabs + toast ---------- -->
+  <script>
+    // conmutar pestañas
+    document.querySelectorAll('.tab-btn').forEach(btn => {
+      btn.addEventListener('click', () => {
+        const target = btn.dataset.target;
+        document.querySelectorAll('[data-tab]').forEach(tab => {
+          tab.classList.toggle('hidden', tab.id !== target);
+        });
+      });
+    });
+
+    // toast simple
+    window.showToast = (msg) => {
+      const toast = document.getElementById('toastCart');
+      toast.textContent = msg;
+      toast.style.animation = 'none';      // reset
+      void toast.offsetWidth;              // reflow
+      toast.style.opacity = '1';
+      toast.style.animation = 'slideIn 0.2s ease-out, slideOut 0.2s ease-in 0.8s forwards';
+    };
+  </script>
 </body>
-</html>
+</html>

+ 78 - 63
public/js/app.js

@@ -1,4 +1,4 @@
-import { initializeChat as serviceInitializeChat, sendMessage as serviceSendMessage, sendOrder, getProducts } from './service.js';
+import { initializeChat as serviceInitializeChat, sendMessage as serviceSendMessage, sendOrder, getProducts, existsUser } from './service.js';
 
 let userName = '';
 let userTable = null;
@@ -46,7 +46,7 @@ function createGlobalLoader() {
     globalLoaderElement.className = 'fixed inset-0 bg-black bg-opacity-80 flex flex-col items-center justify-center z-[2000] transition-opacity duration-300 ease-in-out pointer-events-none';
     globalLoaderElement.style.opacity = '0';
     globalLoaderElement.innerHTML = `
-        <div style="border: 6px solid rgba(255, 255, 255, 0.2); border-radius: 50%; border-top: 6px solid #dc2626; width: 60px; height: 60px; animation: spin 1s linear infinite;"></div>
+        <div style="border: 6px solid rgba(255, 255, 255, 0.2); border-radius: 50%; border-top: 6px solidrgb(172, 85, 85); width: 60px; height: 60px; animation: spin 1s linear infinite;"></div>
         <p class="text-white text-xl mt-4">Procesando su pedido...</p>
     `;
     document.body.appendChild(globalLoaderElement);
@@ -65,7 +65,6 @@ function hideGlobalLoader() {
     }
 }
 
-document.getElementById("currentYear").textContent = new Date().getFullYear().toString();
 
 function formatPrice(price) {
     return price.toLocaleString("es-CL", { style: "currency", currency: "CLP" });
@@ -110,38 +109,40 @@ async function processOrder() {
 
 async function renderProducts() {
     if (!productListElement) return;
+
+    const template = document.getElementById("product-card-template");
+    if (!template) return;
+
     productListElement.innerHTML = "";
     products = await getProducts();
+
     products.forEach(product => {
-        const productCardContainer = document.createElement('div');
-        productCardContainer.innerHTML = `
-            <div class="product-card rounded-lg shadow-lg overflow-hidden transition-all duration-300 hover:shadow-2xl hover:transform hover:-translate-y-1 flex flex-col sm:flex-row">
-                <img src="${product.image}" alt="[Imagen de ${product.name}]" class="w-full sm:w-1/3 h-48 sm:h-auto object-cover">
-                <div class="p-6 flex flex-col justify-between flex-grow">
-                    <div>
-                        <h4 class="text-xl font-semibold mb-1">${product.name}</h4>
-                        <p class="text-gray-400 text-base mb-3">${product.description}</p>
-                    </div>
-                    <div class="flex justify-between items-center mt-4">
-                        <span class="text-2xl font-bold accent-red">${formatPrice(product.price)}</span>
-                        <button data-product-id="${product.id}" class="add-to-cart-btn bg-accent-red hover:bg-red-700 text-white font-semibold py-2 px-4 rounded-lg transition duration-300 ease-in-out text-sm">
-                            Agregar
-                        </button>
-                    </div>
-                </div>
-            </div>
-        `;
-        productListElement.appendChild(productCardContainer.firstElementChild);
+        const clone = template.content.cloneNode(true);
+
+        clone.querySelector(".product-type").textContent = product.type || "Sin categoría";
+        clone.querySelector(".product-name").textContent = product.name;
+        clone.querySelector(".product-description").textContent = product.description;
+        clone.querySelector(".product-price").textContent = formatPrice(product.price);
+        clone.querySelector(".product-image").style.backgroundImage = `url('${product.image}')`;
+
+        const button = clone.querySelector(".product-price");
+        button.dataset.productId = product.id;
+        button.classList.add("add-to-cart-btn");
+        const addBtn = clone.querySelector(".add-to-cart-btn");
+        addBtn.dataset.productId = product.id;   // el listener usa esta info
+
+        productListElement.appendChild(clone);
     });
 
     document.querySelectorAll('.add-to-cart-btn').forEach(button => {
         button.addEventListener('click', (event) => {
-            const productId = parseInt(event.target.closest('button').dataset.productId);
-            addToCart(productId, event.target.closest('button'));
+            const productId = parseInt(event.target.dataset.productId);
+            addToCart(productId, event.target);
         });
     });
 }
 
+
 window.addToCart = async (productId, buttonElement = null) => {
     const product = products.find(p => p.id === productId);
     if (!product) return;
@@ -166,6 +167,9 @@ window.addToCart = async (productId, buttonElement = null) => {
         }, 1500);
     }
     updateCartDisplay();
+    // Dentro de window.addToCart (después de updateCartDisplay())
+    if (typeof showToast === "function") showToast(`${product.name} agregado al carrito`);
+
 };
 
 window.removeFromCart = (productId, removeAll = false) => {
@@ -242,9 +246,14 @@ async function sendMessageToAI() {
         if (!response) {
             displayChatMessage("ai", "Hubo un problema al conectar con el Chef IA.");
         } else if (response === "not_init") {
-            displayChatMessage("ai", "El chat no está inicializado. Intentando reconectar...");
             if (await serviceInitializeChat()) {
-                 displayChatMessage("ai", "Reconexión exitosa. Por favor, envía tu mensaje de nuevo.");
+                const response = await serviceSendMessage(userInput, chatHistory);
+                if (response) {
+                    chatHistory = response.messageList;
+                    displayChatMessage("ai", response.assistantResponse);
+                } else {
+                    displayChatMessage("ai", "Hubo un problema al enviar el mensaje.");
+                }
             } else {
                  displayChatMessage("ai", "Fallo la reconexión. Por favor, refresca la página.");
             }
@@ -263,24 +272,9 @@ async function sendMessageToAI() {
 
 // --- Event Listeners ---
 document.addEventListener("DOMContentLoaded", async () => {
+
     createGlobalLoader();
     updateCartDisplay();
-    
-    if (welcomeModal && startOrderButton) {
-        startOrderButton.onclick = () => {
-            const name = userNameInput.value.trim();
-            const table = userTableInput.value;
-            if (name && table) {
-                userName = name;
-                userTable = parseInt(table);
-                welcomeModal.style.display = 'none';
-                // Iniciar la app después de obtener los datos
-                initializeApp();
-            } else {
-                alert('Por favor, ingresa tu nombre y número de mesa para continuar.');
-            }
-        }
-    }
 
     async function initializeApp() {
         await renderProducts();
@@ -293,29 +287,50 @@ document.addEventListener("DOMContentLoaded", async () => {
             }
         } catch (error) {
             console.error("Error durante la inicialización del Chat AI:", error);
-            displayChatMessage("ai", "Error al iniciar el Chef IA.");
+            displayChatMessage("ai", "Error al iniciar la IA.");
         }
     }
-});
-
-if (checkoutButton) checkoutButton.addEventListener("click", processOrder);
-if (closeConfirmationModalButton) closeConfirmationModalButton.addEventListener("click", () => { orderConfirmationModal.style.display = "none"; });
-if (closeOrderErrorModalButton) closeOrderErrorModalButton.addEventListener("click", () => { orderErrorModal.style.display = "none"; });
-if (newOrderButton) {
-    newOrderButton.addEventListener("click", () => {
-        orderConfirmationModal.style.display = "none";
-        cart = [];
-        updateCartDisplay();
+    if (checkoutButton) checkoutButton.addEventListener("click", processOrder);
+
+    /* ---------- MANEJO DEL POPUP INICIAL ---------- */
+    const sessionModal      = document.getElementById('sessionModal');
+    const sessionAcceptBtn  = document.getElementById('sessionAcceptBtn');
+    const tableInput        = document.getElementById('tableInput');
+    const clientCodeInput   = document.getElementById('clientCodeInput');
+
+    sessionAcceptBtn.addEventListener('click', async () => {
+    const mesa   = parseInt(tableInput.value, 10);
+    const codigo = clientCodeInput.value.trim();
+
+    if (!mesa || !codigo) {
+        alert('Por favor completa ambos campos.');
+        return;
+    }
+    const existUser = await existsUser(codigo);
+    if (!existUser.success) {
+        alert('El código de cliente no existe.');
+        return;
+    }
+    userName = existUser.userName;
+    //destruye el modal
+    sessionModal.remove();
+    startSession(mesa);                
     });
-}
-if (retryButton) retryButton.addEventListener("click", () => { orderErrorModal.style.display = "none"; });
-
-if (sendChatButton) sendChatButton.addEventListener("click", sendMessageToAI);
-if (chatInputElement) {
-    chatInputElement.addEventListener("keypress", (event) => {
-        if (event.key === "Enter") {
-            event.preventDefault();
-            sendMessageToAI();
-        }
+
+    /* ---- FUNCIÓN que recibe los dos parámetros ---- */
+    function startSession(mesa) {
+    userTable = mesa;
+    initializeApp();   
+    }
+
     });
+
+    if (sendChatButton) sendChatButton.addEventListener("click", sendMessageToAI);
+    if (chatInputElement) {
+        chatInputElement.addEventListener("keypress", (event) => {
+            if (event.key === "Enter") {
+                event.preventDefault();
+                sendMessageToAI();
+            }
+        });
 }

+ 19 - 2
public/js/service.js

@@ -68,7 +68,6 @@ async function sendOrder(order) {
     throw error;
   }
 }
-
 async function getProducts(){
   const response = await fetch("/api/get_products");
   if (!response.ok) {
@@ -78,4 +77,22 @@ async function getProducts(){
   const data = await response.json();
   return data.products;
 }
-export { initializeChat, sendMessage, sendOrder, getProducts };
+async function existsUser(userCode){
+  const response = await fetch("/api/existsUser", {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json",
+      "X-App-Token": chatToken
+    },
+    body: JSON.stringify({
+      user_code: userCode
+    })
+  });
+  if (!response.ok) {
+    const errorData = await response.json().catch(() => ({ message: "Respuesta no válida del servidor." }));
+    throw new Error(errorData.message || `Error del servidor: ${response.status}`);
+  }
+  const data = await response.json();
+  return data;
+}
+export { initializeChat, sendMessage, sendOrder, getProducts, existsUser };

+ 9 - 17
public/styles.css

@@ -4,9 +4,9 @@
     --background-card: #1e1e1e;
     --background-header: #0a0a0a;
     --border-color: #2e2e2e;
-    --text-primary: #e0e0e0;
-    --text-secondary: #a0a0a0;
-    --accent-red: #dc2626; /* Rojo (Tailwind red-600) */
+    --text-primary: #373737;
+    --text-secondary: #0e0e0e;
+    --accent-red: #5490eb; /* Rojo (Tailwind red-600) */
     --accent-red-hover: #b91c1c; /* Rojo más oscuro (Tailwind red-700) */
 }
 
@@ -40,18 +40,6 @@ footer.bg-black { background-color: var(--background-header); }
     border-bottom: 2px solid var(--accent-red);
 }
 
-/* Chat Panel Integrado */
-.chat-panel-embedded {
-    border-radius: 12px;
-    overflow: hidden;
-    height: 75vh; /* Altura fija */
-    max-height: 800px;
-    background-color: var(--background-card);
-    border: 1px solid var(--border-color);
-    box-shadow: 0 5px 15px rgba(0,0,0,0.3);
-    display: flex;
-    flex-direction: column;
-}
 
 .chat-header {
     background-color: #1a1a1a;
@@ -93,12 +81,16 @@ footer.bg-black { background-color: var(--background-header); }
     border-bottom-right-radius: 5px;
 }
 .chat-bubble-ai {
-    background-color: #3b3b3b;
-    color: var(--text-primary);
+    background-color: #ffffff;
     margin-right: auto;
     border-bottom-left-radius: 5px;
+    & p {
+
+    color: rgb(17, 17, 17);
+    }
 }
 
+
 .chat-input-container {
     display: flex;
     gap: 0.5rem;

+ 6 - 0
users.json

@@ -0,0 +1,6 @@
+[
+    {
+        "userCode": "camilo1900",
+        "userName": "Camilo Klein"
+    }
+]