latapp 9 месяцев назад
Родитель
Сommit
e07f1ee480

+ 3 - 1
.env

@@ -3,4 +3,6 @@ SECRET_KEY = 866B3F5EE90BFED7EDAD0FCB0A9C0FC866F03166C05648478ECEF6148C9E13BEBF8
 OPENAI_API_KEY = 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
 OPENAI_API_KEY = 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
 NODE_ENV = development
 NODE_ENV = development
 FUDO_API_KEY=NzZAMTEzMzc4
 FUDO_API_KEY=NzZAMTEzMzc4
-FUDO_API_SECRET=FNKYiEbYGTVc3i0jLOTTXL8pVPkUIPLP
+FUDO_API_SECRET=FNKYiEbYGTVc3i0jLOTTXL8pVPkUIPLP
+PIN_KEY='GZUTI02vzKsRM2LLePkDy2mh8YpI5TjDEffRfkRNWLE='
+LOG_LEVEL=DEBUG

+ 2 - 1
.gitignore

@@ -7,4 +7,5 @@ users.json
 logs.csv
 logs.csv
 llm_logs.*
 llm_logs.*
 *.pyc
 *.pyc
-dksdabjhvjhSADhsbjksf.txt
+dksdabjhvjhSADhsbjksf.txt
+data/data.db

+ 305 - 51
README.md

@@ -1,94 +1,348 @@
-# Web Pedidos Klein - Backend
+# Biergarten Klein - Sistema de Pedidos Express
 
 
-## Estructura del Proyecto
+## Descripción del Proyecto
+
+**Biergarten Klein** es una aplicación web completa para la gestión de pedidos en un bar cervecero artesanal. El sistema permite a los clientes interactuar con un asistente de IA llamado "Camilo Klein" para consultar el menú, recibir recomendaciones de cervezas artesanales, realizar pedidos y gestionar su experiencia en el establecimiento.
+
+## Características Principales
+
+### 🤖 Asistente de IA Personalizado
+- **Camilo Klein**: Chatbot especializado en el menú del bar
+- Integración con OpenAI GPT-4o-mini
+- Respuestas carismáticas con emojis
+- Base de conocimientos específica sobre cervezas artesanales
+- Sistema anti-abuso con tokens de sesión
+
+### 🍺 Gestión de Productos
+- Catálogo completo de cervezas artesanales (Burlesque, Queen Burlesque, Hoppy Mosh, Black Mamba, etc.)
+- Sistema de carrito de compras interactivo
+- Información detallada de cada cerveza (IBU, SRM, notas de sabor)
+- Precios y disponibilidad en tiempo real
 
 
-El backend ha sido reorganizado para mejorar la mantenibilidad y seguir buenas prácticas de desarrollo. La nueva estructura es:
+### 🧾 Sistema de Pedidos
+- Procesamiento de pedidos con validación
+- Integración con impresora térmica USB
+- Sincronización con sistema Fudo POS
+- Notificaciones por email en caso de errores
+- Logging completo de transacciones
+
+### 🔐 Seguridad y Autenticación
+- Sistema de tokens anti-abuso
+- Validación de usuarios
+- Protección de endpoints sensibles
+- Middleware de sesiones seguras
+
+## Estructura del Proyecto
 
 
 ```
 ```
 pedidos_express/
 pedidos_express/
 ├── main.py                    # Punto de entrada principal
 ├── main.py                    # Punto de entrada principal
-├── app.py                     # Configuración de la aplicación FastAPI
-├── requirements.txt           # Dependencias
-├── .env                       # Variables de entorno
+├── app.py                     # Configuración FastAPI y rutas
+├── requirements.txt           # Dependencias del proyecto
+├── tailwind.config.js         # Configuración de Tailwind CSS
 ├── config/
 ├── config/
 │   ├── __init__.py
 │   ├── __init__.py
-│   └── settings.py           # Configuración y variables de entorno
+│   └── settings.py           # Variables de entorno y logging
 ├── models/
 ├── models/
 │   ├── __init__.py
 │   ├── __init__.py
-│   └── schemas.py            # Modelos Pydantic
+│   └── schemas.py            # Modelos Pydantic (Message, Order, User)
 ├── auth/
 ├── auth/
 │   ├── __init__.py
 │   ├── __init__.py
-│   └── security.py           # Autenticación y seguridad
+│   └── security.py           # Autenticación y tokens anti-abuso
 ├── services/
 ├── services/
 │   ├── __init__.py
 │   ├── __init__.py
-│   ├── data_service.py       # Manejo de datos (productos, usuarios)
-│   ├── openai_service.py     # Servicio de OpenAI/ChatGPT
-│   ├── email_service.py      # Servicio de envío de emails
-│   ├── fudo_service.py       # Integración con Fudo
-│   └── logging_service.py    # Logging de pedidos y respuestas
+│   ├── data_service.py       # Gestión de datos y productos
+│   ├── openai_service.py     # Integración con OpenAI
+│   ├── email_service.py      # Notificaciones por email
+│   ├── fudo_service.py       # Integración con Fudo POS
+│   └── logging_service.py    # Sistema de logs
 ├── routes/
 ├── routes/
 │   ├── __init__.py
 │   ├── __init__.py
-│   ├── chat.py              # Endpoints del chat
-│   ├── users.py             # Endpoints de usuarios
-│   ├── products.py          # Endpoints de productos
-│   ├── orders.py            # Endpoints de pedidos
+│   ├── chat.py              # Endpoints del chatbot
+│   ├── users.py             # Gestión de usuarios
+│   ├── products.py          # Catálogo de productos
+│   ├── orders.py            # Procesamiento de pedidos
 │   └── static.py            # Archivos estáticos
 │   └── static.py            # Archivos estáticos
-├── impresora/               # Módulo de impresión (existente)
-├── fudo/                    # Módulo Fudo (existente)
-└── public/                  # Archivos estáticos frontend
+├── impresora/               # Módulo de impresión térmica
+│   ├── __init__.py
+│   ├── order.py             # Modelos para órdenes de impresión
+│   └── printer.py           # Control de impresora USB
+├── fudo/                    # Integración con Fudo POS
+│   └── fudo.py              # API y gestión de tokens
+├── data/                    # Almacenamiento de datos
+│   ├── data.db              # Base de datos SQLite
+│   ├── llm_data.json        # Base de conocimientos para IA
+│   └── products.json        # Catálogo de productos
+├── public/                  # Frontend web
+│   ├── index.html           # Página principal
+│   ├── styles.css           # Estilos personalizados
+│   ├── assets/              # Recursos multimedia
+│   └── js/
+│       ├── app.js           # Lógica principal del frontend
+│       ├── interfaces.js    # Interfaces de usuario
+│       └── service/         # Servicios del frontend
+│           ├── auth.js      # Autenticación cliente
+│           ├── chat.js      # Comunicación con chatbot
+│           └── product.js   # Gestión de productos
+└── logs/                    # Archivos de log
+    └── app.log              # Logs de la aplicación
 ```
 ```
 
 
-## Módulos
+## Tecnologías Utilizadas
+
+### Backend
+- **FastAPI**: Framework web moderno y rápido
+- **Python 3.9+**: Lenguaje de programación principal
+- **OpenAI API**: Integración con GPT-4o-mini para el chatbot
+- **SQLite**: Base de datos local
+- **Redis**: Cache para tokens de autenticación
+- **Uvicorn**: Servidor ASGI para producción
+
+### Frontend
+- **HTML5/CSS3/JavaScript**: Tecnologías web estándar
+- **Tailwind CSS**: Framework de estilos utilitarios
+- **Vanilla JavaScript**: Sin frameworks adicionales para máximo rendimiento
+
+### Integraciones
+- **Fudo POS**: Sistema de punto de venta
+- **Impresora Térmica USB**: Para tickets de pedidos
+- **SMTP**: Envío de notificaciones por email
+
+## Módulos Principales
 
 
 ### config/settings.py
 ### config/settings.py
-- Configuración de la aplicación
-- Variables de entorno
-- Validación de configuración
+- Configuración centralizada de la aplicación
+- Gestión de variables de entorno
+- Sistema de logging configurado
+- Validación de configuración crítica
 
 
 ### models/schemas.py
 ### models/schemas.py
-- Modelos Pydantic para request/response
-- Validación de datos
+- **Message**: Estructura para mensajes del chat
+- **ChatCompletionRequest**: Requests para el chatbot
+- **OrderWeb**: Modelo completo de pedidos
+- **ItemWeb**: Elementos individuales del pedido
+- **UserCodeRequest**: Validación de códigos de usuario
 
 
 ### auth/security.py
 ### auth/security.py
-- Middleware de autenticación
-- Generación de tokens anti-abuse
-- Protección de endpoints
+- **Sistema de tokens anti-abuso**: Protección contra uso excesivo
+- **Validación de sesiones**: Control de acceso a endpoints
+- **Middleware de seguridad**: Protección de rutas sensibles
+- **Generación segura de tokens**: Utilizando secrets
 
 
 ### services/
 ### services/
-- **data_service.py**: Carga y manejo de datos (productos, usuarios, datos del menú)
-- **openai_service.py**: Integración con OpenAI para el chatbot
-- **email_service.py**: Envío de notificaciones por email
-- **fudo_service.py**: Integración con el sistema Fudo
-- **logging_service.py**: Logging de pedidos y respuestas del LLM
+- **data_service.py**: Carga y gestión de productos, usuarios y datos del menú
+- **openai_service.py**: Integración completa con OpenAI para el asistente Camilo Klein
+- **email_service.py**: Sistema de notificaciones por correo electrónico
+- **fudo_service.py**: Integración bidireccional con el sistema Fudo POS
+- **logging_service.py**: Registro detallado de pedidos y respuestas del LLM
 
 
 ### routes/
 ### routes/
-- **chat.py**: Endpoints relacionados con el chat (/api/chat/*)
-- **users.py**: Endpoints de usuarios (/api/existsUser)
-- **products.py**: Endpoints de productos (/api/get_products)
-- **orders.py**: Endpoints de pedidos (/api/printer/order)
-- **static.py**: Servir archivos estáticos
+- **chat.py**: 
+  - `/api/chat/init-chat` - Inicialización del chat y generación de tokens
+  - `/api/chat/completions` - Procesamiento de mensajes del chatbot
+- **users.py**: 
+  - `/api/existsUser` - Validación de códigos de usuario
+- **products.py**: 
+  - `/api/get_products` - Obtención del catálogo completo
+- **orders.py**: 
+  - `/api/printer/order` - Procesamiento e impresión de pedidos
+- **static.py**: 
+  - Servir archivos estáticos y página principal
+
+## API Endpoints
+
+### Chat y Asistente IA
+```
+GET  /api/chat/init-chat       # Inicializar sesión de chat
+POST /api/chat/completions     # Enviar mensaje al asistente
+```
+
+### Gestión de Productos
+```
+GET  /api/get_products         # Obtener catálogo de productos
+```
+
+### Gestión de Pedidos
+```
+POST /api/printer/order        # Procesar y imprimir pedido
+```
+
+### Usuarios
+```
+POST /api/existsUser          # Validar código de usuario
+```
+
+## Flujo de la Aplicación
+
+1. **Inicialización**: El usuario accede a la página principal
+2. **Autenticación**: Se genera un token anti-abuso para la sesión
+3. **Exploración**: El usuario puede consultar productos y chatear con Camilo Klein
+4. **Pedido**: Selección de productos y agregado al carrito
+5. **Procesamiento**: Validación, impresión y sincronización con Fudo POS
+6. **Confirmación**: Notificación al usuario y registro en logs
+
+## Base de Conocimientos del Asistente
+
+El asistente Camilo Klein utiliza una base de datos especializada (`llm_data.json`) que incluye:
+
+- **Información del establecimiento**: Historia y concepto del Biergarten Klein
+- **Catálogo completo de cervezas**: 15+ variedades artesanales con detalles técnicos
+- **Recomendaciones especializadas**: Sugerencias basadas en preferencias
+- **Información nutricional y técnica**: IBU, SRM, graduación alcohólica
+- **Horarios y ubicación**: Datos prácticos para los clientes
+
+## Características Técnicas Avanzadas
+
+### Sistema de Logging
+- Registro detallado de todas las interacciones
+- Logs estructurados con timestamps
+- Separación por niveles (INFO, WARNING, ERROR)
+- Rotación automática de archivos de log
+
+### Integración con Hardware
+- **Impresora térmica USB**: Detección automática y manejo de errores
+- **Códigos de barras**: Generación para tracking de pedidos
+- **Notificaciones automáticas**: Email en caso de fallos de hardware
+
+### Gestión de Estado
+- **Redis para cache**: Tokens de autenticación y sesiones
+- **SQLite para persistencia**: Datos permanentes y históricos
+- **JSON para configuración**: Datos del menú y configuraciones
 
 
-## Ventajas de la Nueva Estructura
+## Ventajas de la Arquitectura
 
 
-1. **Separación de responsabilidades**: Cada módulo tiene una función específica
-2. **Mantenibilidad**: Código más fácil de mantener y modificar
-3. **Testabilidad**: Cada módulo puede ser probado independientemente
-4. **Escalabilidad**: Fácil agregar nuevas funcionalidades
-5. **Legibilidad**: Código más organizado y fácil de entender
+1. **Modularidad**: Cada componente tiene responsabilidades específicas
+2. **Escalabilidad**: Fácil agregar nuevas funcionalidades
+3. **Mantenibilidad**: Código organizado y bien documentado
+4. **Testabilidad**: Componentes aislados para pruebas unitarias
+5. **Seguridad**: Múltiples capas de protección
+6. **Performance**: Optimizado para respuestas rápidas
 
 
-## Cómo Ejecutar
+## Instalación y Configuración
 
 
+### Prerequisitos
 ```bash
 ```bash
-python main.py
+Python 3.9+
+Redis Server
+Impresora térmica USB (opcional)
 ```
 ```
 
 
-## Variables de Entorno Requeridas
+### Instalación
+```bash
+# Clonar el repositorio
+git clone [repository-url]
+cd pedidos_express
+
+# Instalar dependencias
+pip install -r requirements.txt
+
+# Configurar variables de entorno
+cp .env.example .env
+```
 
 
+### Variables de Entorno Requeridas
 ```env
 ```env
-OPENAI_API_KEY=tu_api_key_aqui
-SECRET_KEY=tu_secret_key_para_sessions
+# OpenAI Configuration
+OPENAI_API_KEY=tu_api_key_de_openai
+
+# Security
+SECRET_KEY=tu_clave_secreta_muy_segura
+
+# Server Configuration  
 PORT=6001
 PORT=6001
+LOG_LEVEL=INFO
 
 
+# Fudo POS Integration
 FUDO_API_KEY=tu_api_key_fudo
 FUDO_API_KEY=tu_api_key_fudo
 FUDO_API_SECRET=tu_api_secret_fudo
 FUDO_API_SECRET=tu_api_secret_fudo
+
+# Redis Configuration
+REDIS_HOST=localhost
+REDIS_PORT=6379
+REDIS_DB=0
+
+# Email Configuration (opcional)
+SMTP_HOST=smtp.gmail.com
+SMTP_PORT=587
+SMTP_USER=tu_email@gmail.com
+SMTP_PASSWORD=tu_password_de_app
 ```
 ```
+
+## Ejecución
+
+### Desarrollo
+```bash
+python main.py
+```
+
+### Producción
+```bash
+gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:6001
+```
+
+## Monitoreo y Logs
+
+### Archivos de Log
+- `logs/app.log`: Log principal de la aplicación
+- `logs.csv`: Registro de interacciones del LLM
+
+### Métricas Monitoreadas
+- Tiempo de respuesta del chatbot
+- Errores de impresión
+- Fallos de conexión con Fudo POS
+- Uso del sistema anti-abuso
+
+## Seguridad
+
+### Medidas Implementadas
+- **Tokens anti-abuso**: Prevención de spam y uso excesivo
+- **Validación de entrada**: Sanitización de todos los inputs
+- **Protección CSRF**: Tokens de sesión seguros
+- **Rate limiting**: Control de frecuencia de requests
+- **Logging de seguridad**: Registro de intentos de acceso no autorizado
+
+## Desarrollo y Contribución
+
+### Estructura de Commits
+- `feat:` Nuevas características
+- `fix:` Corrección de bugs  
+- `docs:` Documentación
+- `style:` Formateo de código
+- `refactor:` Refactorización
+- `test:` Pruebas
+
+### Testing
+```bash
+# Ejecutar tests unitarios
+python -m pytest tests/
+
+# Tests de integración
+python -m pytest tests/integration/
+```
+
+## Roadmap Futuro
+
+### Próximas Características
+- [ ] Sistema de reservas de mesas
+- [ ] Programa de fidelización
+- [ ] Integración con redes sociales
+- [ ] App móvil nativa
+- [ ] Dashboard de analytics
+- [ ] Sistema de reviews y ratings
+
+### Mejoras Técnicas
+- [ ] Migración a PostgreSQL
+- [ ] Implementación de microservicios
+- [ ] Containerización con Docker
+- [ ] CI/CD con GitHub Actions
+- [ ] Monitoreo con Prometheus
+
+## Soporte y Contacto
+
+Para soporte técnico o consultas sobre el proyecto, contactar al equipo de desarrollo.
+
+---
+
+**Versión**: 1.0.0  
+**Última actualización**: Julio 2025  
+**Licencia**: Privada - Biergarten Klein

+ 17 - 15
app.py

@@ -1,3 +1,4 @@
+from time import struct_time
 from fastapi import FastAPI
 from fastapi import FastAPI
 from starlette.middleware.sessions import SessionMiddleware
 from starlette.middleware.sessions import SessionMiddleware
 from config.settings import SECRET_KEY, validate_config
 from config.settings import SECRET_KEY, validate_config
@@ -5,7 +6,8 @@ from config.settings import SECRET_KEY, validate_config
 
 
 def create_app() -> FastAPI:
 def create_app() -> FastAPI:
     """Create and configure FastAPI application"""
     """Create and configure FastAPI application"""
-    app = FastAPI(title="Web Pedidos Klein - FastAPI Backend")
+    app = FastAPI(title="Web Pedidos Klein - FastAPI Backend",
+                  description="Backend for the Web Pedidos Klein application using FastAPI",)
     
     
     # Add SessionMiddleware
     # Add SessionMiddleware
     app.add_middleware(
     app.add_middleware(
@@ -21,27 +23,27 @@ def setup_routes(app: FastAPI):
     """Setup all application routes"""
     """Setup all application routes"""
     from routes import chat, users, products, orders, static
     from routes import chat, users, products, orders, static
     from fastapi import Depends
     from fastapi import Depends
-    from auth.security import protect_chat_api
+    from auth.security import get_current_user
     
     
     # Chat routes
     # Chat routes
-    app.add_api_route("/api/chat/init-chat", chat.init_chat, methods=["GET"], summary="Initialize chat and get anti-abuse token")
-    app.add_api_route("/api/chat/completions", chat.chat_completions, methods=["POST"], 
-                     summary="Get chat completions from OpenAI", dependencies=[Depends(protect_chat_api)])
-    
+    app.include_router(chat.chat_router, prefix="/api/chat",tags=["Chat"], dependencies=[Depends(get_current_user)])
+
     # User routes
     # User routes
-    app.add_api_route("/api/existsUser", users.exists_user, methods=["POST"], summary="Check if user exists")
-    
+    app.include_router(users.user_router, prefix="/api/users", tags=["Users"])
+
     # Product routes
     # Product routes
-    app.add_api_route("/api/get_products", products.get_products, methods=["GET"], summary="Get products")
-    
+    app.include_router(products.product_router, prefix="/api/products", tags=["Products"],dependencies=[Depends(get_current_user)])
+
     # Order routes
     # Order routes
-    app.add_api_route("/api/printer/order", orders.printer_order, methods=["POST"], 
-                     summary="Printer order", dependencies=[Depends(protect_chat_api)])
+    app.include_router(orders.order_router, prefix="/api/orders", tags=["Orders"], dependencies=[Depends(get_current_user)])
     
     
     # Static routes
     # Static routes
     from fastapi.responses import HTMLResponse
     from fastapi.responses import HTMLResponse
-    app.add_api_route("/", static.serve_index_html, methods=["GET"], 
+    app.add_api_route("/express", static.serve_app_html, methods=["GET"], 
                      response_class=HTMLResponse, include_in_schema=False)
                      response_class=HTMLResponse, include_in_schema=False)
-    
+    app.add_api_route("/register", static.serve_register_html, methods=["GET"],
+                     response_class=HTMLResponse, include_in_schema=False)
+
     # Mount static files
     # Mount static files
-    static.mount_static_files(app)
+    static.mount_main_static_files(app)
+    static.mount_register_static_files(app)

+ 79 - 39
auth/security.py

@@ -1,46 +1,86 @@
+from datetime import datetime, timedelta
 from typing import Union
 from typing import Union
 from venv import logger
 from venv import logger
-from fastapi import Request, HTTPException, Header, Depends
+from fastapi import Depends, HTTPException
+from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 from typing import Annotated
 from typing import Annotated
-import secrets
 from logging import getLogger
 from logging import getLogger
 
 
+from pydantic import BaseModel
+from config.settings import SECRET_KEY
+from jose import jwt, JWTError
+from passlib.context import CryptContext
+
+from services.data_service import UserDataService
+
+
 logger = getLogger(__name__)
 logger = getLogger(__name__)
 
 
-async def get_session_token(request: Request) -> Union[str, None]:
-    """Get the anti-abuse token from the session"""
-    return request.session.get("antiAbuseToken")
-
-
-async def protect_chat_api(
-    request: Request,
-    x_app_token: Annotated[Union[str, None], Header(alias="X-App-Token")] = None,
-    session_token: Annotated[Union[str, None], Depends(get_session_token)] = None
-):
-    """Protect chat API endpoints with token validation"""
-    # Equivalent to protectChatAPI middleware
-    if not session_token:
-        if request.client:
-            logger.error(f"Session token is not initialized or invalid. IP: {request.client.host}")
-        else:
-            logger.error("Session token is not initialized or invalid.")
-        logger.error("Session token is not initialized or invalid.")
-        raise HTTPException(status_code=403, detail="Acceso denegado: Sesión inválida o token no inicializado.")
-
-    if not x_app_token:
-        if request.client:
-            logger.error(f"X-App-Token is missing. IP: {request.client.host}")
-        else:
-            logger.error("X-App-Token is missing.")
-        raise HTTPException(status_code=401, detail="Acceso denegado: Falta el token X-Chat-Token.")
-
-    if x_app_token != session_token:
-        # Log this attempt for security monitoring
-        logger.warning(f"Invalid token attempt. Expected: {session_token}, Received: {x_app_token}")
-        raise HTTPException(status_code=403, detail="Acceso denegado: Token inválido.")
-    return True  # Protection passed
-
-
-def generate_anti_abuse_token() -> str:
-    """Generate a new anti-abuse token"""
-    return secrets.token_hex(32)
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_DAYS = 60 * 24 * 7
+security = HTTPBearer()
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+user_data_service = UserDataService()
+
+class TokenData(BaseModel):
+    email: str
+
+
+def hash_password(password: str) -> str:
+    """Hash a password using bcrypt."""
+    return pwd_context.hash(password)
+
+def generate_token(email: str):
+    """Generate a JWT token for user authentication."""
+    data = {"sub": email}
+    expires_delta = timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS)
+    token = create_access_token(data=data, expires_delta=expires_delta)
+    logger.debug(f"Generated token for email {email}: {token}")
+    return token
+
+def create_access_token(data: dict, expires_delta: timedelta) -> str:
+    """Create a JWT access token."""
+    to_encode = data.copy()
+    if expires_delta:
+        expire = datetime.utcnow() + expires_delta
+    else:
+        expire = datetime.utcnow() + timedelta(days=1)
+        
+    to_encode.update({"exp": expire})
+    logger.debug(f"Creating access token with data: {to_encode}")
+    logger.debug(f"Token expiration time: {expire}")
+    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+
+def authenticate_user(email: str, password: str) -> bool:
+    """Authenticate a user by email and password."""
+    user = user_data_service.login(email, password)
+    if not user:
+        return False
+    return True
+
+async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
+    credentials_exception = HTTPException(
+        status_code=401,
+        detail="No se pudieron validar las credenciales",
+        headers={"WWW-Authenticate": "Bearer"},
+    )
+    
+    try:
+        token = credentials.credentials
+        logger.debug(f"Decoding token: {token}")
+        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+        email = payload.get("sub")
+        if email is None:
+            logger.error("Token does not contain email")
+            raise credentials_exception
+        token_data = TokenData(email=email)
+    except JWTError:
+        logger.error("JWTError: Invalid token")
+        raise credentials_exception
+    
+    user = user_data_service.get_by_email(token_data.email)
+    if user is None:
+        logger.error(f"User not found: {token_data.email}")
+        raise credentials_exception
+    return user
+

+ 262 - 0
config/mails.py

@@ -0,0 +1,262 @@
+
+
+REGISTER_MAIL = {
+    "subject": "Welcome to Pedidos Express",
+    "body": """
+<html>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>¡Bienvenido a KlowApp!</title>
+    <style>
+        body {{
+            margin: 0;
+            padding: 0;
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+            background-color: #f3f4f6;
+        }}
+        
+        .email-container {{
+            max-width: 600px;
+            margin: 0 auto;
+            background-color: #ffffff;
+            border-radius: 12px;
+            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
+            overflow: hidden;
+        }}
+        
+        .header {{
+            background-color: #101419;
+            color: white;
+            padding: 32px;
+            text-align: center;
+        }}
+        
+        .header h1 {{
+            margin: 0;
+            font-size: 28px;
+            font-weight: bold;
+        }}
+        
+        .header p {{
+            margin: 8px 0 0 0;
+            font-size: 16px;
+            opacity: 0.9;
+        }}
+        
+        .content {{
+            padding: 32px;
+        }}
+        
+        .welcome-message {{
+            text-align: center;
+            margin-bottom: 32px;
+        }}
+        
+        .welcome-message h2 {{
+            color: #101419;
+            font-size: 24px;
+            margin: 0 0 12px 0;
+            font-weight: bold;
+        }}
+        
+        .welcome-message p {{
+            color: #6b7280;
+            font-size: 16px;
+            line-height: 1.6;
+            margin: 0;
+        }}
+        
+        .benefits-section {{
+            background-color: #f9fafb;
+            border-radius: 8px;
+            padding: 24px;
+            margin: 24px 0;
+        }}
+        
+        .benefits-title {{
+            color: #101419;
+            font-size: 18px;
+            font-weight: 600;
+            margin: 0 0 16px 0;
+            text-align: center;
+        }}
+        
+        .benefits-list {{
+            list-style: none;
+            padding: 0;
+            margin: 0;
+        }}
+        
+        .benefits-list li {{
+            color: #374151;
+            font-size: 14px;
+            line-height: 1.5;
+            margin-bottom: 8px;
+            padding-left: 20px;
+            position: relative;
+        }}
+        
+        .benefits-list li:before {{
+            content: "✓";
+            color: #10b981;
+            font-weight: bold;
+            position: absolute;
+            left: 0;
+        }}
+        
+        .promo-section {{
+            background: linear-gradient(135deg, #101419 0%, #37404a 100%);
+            border-radius: 12px;
+            padding: 32px;
+            text-align: center;
+            margin: 32px 0;
+            color: white;
+        }}
+        
+        .promo-amount {{
+            font-size: 48px;
+            font-weight: bold;
+            margin: 16px 0;
+            border: 3px solid white;
+            border-radius: 8px;
+            padding: 16px;
+            display: inline-block;
+            min-width: 120px;
+        }}
+        
+        .promo-description {{
+            font-size: 18px;
+            margin: 16px 0;
+            opacity: 0.95;
+        }}
+        
+        .cta-button {{
+            background-color: white;
+            color: #101419;
+            padding: 16px 32px;
+            border-radius: 8px;
+            text-decoration: none;
+            font-weight: 600;
+            font-size: 16px;
+            display: inline-block;
+            margin-top: 16px;
+            transition: all 0.2s ease;
+        }}
+        
+        .cta-button:hover {{
+            background-color: #f3f4f6;
+            transform: translateY(-1px);
+        }}
+        
+        .website-section {{
+            text-align: center;
+            margin: 32px 0;
+            padding: 24px;
+            background-color: #f9fafb;
+            border-radius: 8px;
+        }}
+        
+        .website-url {{
+            color: #101419;
+            font-size: 20px;
+            font-weight: 600;
+            text-decoration: none;
+            border-bottom: 2px solid #101419;
+            padding-bottom: 4px;
+        }}
+        
+        .footer {{
+            background-color: #f9fafb;
+            padding: 24px 32px;
+            text-align: center;
+            border-top: 1px solid #e5e7eb;
+        }}
+        
+        .footer p {{
+            color: #6b7280;
+            font-size: 14px;
+            margin: 0;
+            line-height: 1.5;
+        }}
+        
+        @media (max-width: 600px) {{
+            .email-container {{
+                margin: 0;
+                border-radius: 0;
+            }}
+            
+            .header, .content {{
+                padding: 24px 16px;
+            }}
+            
+            .promo-amount {{
+                font-size: 36px;
+                padding: 12px;
+            }}
+        }}
+    </style>
+</head>
+<body>
+    <div style="padding: 20px;">
+        <div class="email-container">
+            <!-- Header -->
+            <div class="header">
+                <h1>¡Hola {name}</h1>
+                <p>Bienvenido a {app_name}</p>
+            </div>
+            
+            <!-- Content -->
+            <div class="content">
+                <!-- Welcome Message -->
+                <div class="welcome-message">
+                    <h2>Te damos la bienvenida a {app_name}</h2>
+                    <p>Tu registro ha sido exitoso. <br>Estamos emocionados de tenerte con nosotros. Esperamos que puedas disfrutar de nuestros beneficios.</p>
+                </div>
+                
+                <!-- Benefits Section -->
+                <div class="benefits-section">
+                    <h3 class="benefits-title">¿Qué puedes hacer con {app_name}?</h3>
+                    <ul class="benefits-list">
+                        <li>Realizar pedidos de forma rápida y sencilla</li>
+                        <li>Acceder a promociones exclusivas</li>
+                        <li>Disfrutar de una experiencia personalizada</li>
+                        <li>Usar nuestra inteligencia artificial</li>
+                    </ul>
+                </div>
+                
+                <!-- Promo Section -->
+                <div class="promo-section">
+                    <div class="promo-description">Este es tu pin de inicio de sesion</div>
+                    <div class="promo-amount">{pin}</div>
+                    <div class="promo-description">Guardalo muy bien</div>
+                    <a href="https://www.expressklein.com" class="cta-button">Y disfruta de {app_name}</a>
+                </div>
+                
+                <!-- Website Section -->
+                <div class="website-section">
+                    <p style="color: #6b7280; margin-bottom: 12px;">Visita nuestra plataforma:</p>
+                    <a href="https://www.expressklein.com" class="website-url">www.expressklein.com</a>
+                </div>
+            </div>
+            
+            <!-- Footer -->
+            <div class="footer">
+                <p>
+                    <strong>Express Klein</strong> - Tu aplicación de pedidos favorita<br>
+                    Si tienes alguna pregunta, no dudes en contactarnos.<br>
+                    © 2025 Express Klein. Todos los derechos reservados.
+                </p>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
+    """
+}
+
+PRINTER_DISCONNECTED_MAIL = {
+    "subject": "Printer Disconnected",
+    "body": """
+"""
+}

+ 6 - 1
config/settings.py

@@ -7,6 +7,8 @@ from httpx import get
 # Load environment variables from .env file
 # Load environment variables from .env file
 load_dotenv()
 load_dotenv()
 
 
+
+APPNAME = "Pedidos Express"
 LOGS_FOLDER = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'logs')
 LOGS_FOLDER = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'logs')
 if not os.path.exists(LOGS_FOLDER):
 if not os.path.exists(LOGS_FOLDER):
     os.makedirs(LOGS_FOLDER)
     os.makedirs(LOGS_FOLDER)
@@ -24,13 +26,16 @@ logging.basicConfig(
 # Configuration
 # Configuration
 OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
 OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
 PORT = int(os.getenv("PORT", 6001))
 PORT = int(os.getenv("PORT", 6001))
-
+PIN_KEY = os.getenv("PIN_KEY", "-1")
+if PIN_KEY == "-1":
+    logging.warning("Using default PIN_KEY. Please set a strong PIN_KEY in your .env file for production.")
 # SECRET_KEY is crucial for signing session cookies.
 # SECRET_KEY is crucial for signing session cookies.
 # Fallback to a default if not set, but warn that this is insecure for production.
 # Fallback to a default if not set, but warn that this is insecure for production.
 SECRET_KEY = os.getenv("SECRET_KEY", "your_very_very_secret_key_for_signing_cookies_python_v2")
 SECRET_KEY = os.getenv("SECRET_KEY", "your_very_very_secret_key_for_signing_cookies_python_v2")
 
 
 # Data paths
 # Data paths
 BG_DATA_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)),'data', 'llm_data.json')
 BG_DATA_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)),'data', 'llm_data.json')
+PRODUCT_DATA_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)),'data', 'products.json')
 DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)),'data', 'data.db')
 DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)),'data', 'data.db')
 
 
 def validate_config():
 def validate_config():


+ 17 - 3
data/products.json

@@ -1,51 +1,65 @@
 [
 [
+  
   {
   {
     "id": 6,
     "id": 6,
+    "status":1,
     "name": "Burlesque",
     "name": "Burlesque",
     "type": "Cerveza",
     "type": "Cerveza",
     "description": "Cerveza Ale ámbar, 5.0º - IBU 12",
     "description": "Cerveza Ale ámbar, 5.0º - IBU 12",
     "price": 5000,
     "price": 5000,
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/6"
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/6"
   },
   },
+  
   {
   {
     "id": 15,
     "id": 15,
+    "status":1,
     "name": "Bendicion Gitana",
     "name": "Bendicion Gitana",
     "type": "Cerveza",
     "type": "Cerveza",
     "description": "Pale Ale - 5,0º - IBU 15",
     "description": "Pale Ale - 5,0º - IBU 15",
     "price": 5000,
     "price": 5000,
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/15"
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/15"
-  },{
+  },
+  {
     "id":163,
     "id":163,
+    "status":1,
     "name":"Hoppy Mosh",
     "name":"Hoppy Mosh",
     "type":"Cerveza",
     "type":"Cerveza",
     "description":"IPA - 6.0º - IBU 38",
     "description":"IPA - 6.0º - IBU 38",
     "price": 6500,
     "price": 6500,
     "image":"https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/163"
     "image":"https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/163"
   },
   },
+  
   {
   {
     "id": 12,
     "id": 12,
+    "status":1,
     "name": "Black Mamba",
     "name": "Black Mamba",
     "type": "Cerveza",
     "type": "Cerveza",
     "description": "Porter - 6.0º - IBU 15",
     "description": "Porter - 6.0º - IBU 15",
     "price": 5000,
     "price": 5000,
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/12"
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/12"
-  },{
+  },
+  {
     "id": 665,
     "id": 665,
+    "status":1,
     "name": "Marzen",
     "name": "Marzen",
     "type": "Cerveza",
     "type": "Cerveza",
     "description": " Estilo Märzenbier, 5.0º - IBU 22",
     "description": " Estilo Märzenbier, 5.0º - IBU 22",
     "price": 5000,
     "price": 5000,
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/665"
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/665"
-  },{
+  },
+  {
     "id": 1,
     "id": 1,
+    "status":1,
     "name": "24k Gold",
     "name": "24k Gold",
     "type": "Cerveza",
     "type": "Cerveza",
     "description": "Golden Ale - 4,5º - IBU 20",
     "description": "Golden Ale - 4,5º - IBU 20",
     "price": 5000,
     "price": 5000,
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/1"
     "image": "https://fudo-apps-storage.s3.sa-east-1.amazonaws.com/production/113378/common/products/1"
   },
   },
+  
   {
   {
     "id": 655,
     "id": 655,
+    "status":1,
     "name": "🌟 Summer Klein",
     "name": "🌟 Summer Klein",
     "type": "Coctel",
     "type": "Coctel",
     "description": "Gin Juno, jugo de naranja, maracuya, limon y Ginger Beer",
     "description": "Gin Juno, jugo de naranja, maracuya, limon y Ginger Beer",

Разница между файлами не показана из-за своего большого размера
+ 655 - 0
logs/app.log


+ 5 - 1
main.py

@@ -7,6 +7,8 @@ from logging import getLogger
 from routes.orders import order_thread
 from routes.orders import order_thread
 from threading import Thread
 from threading import Thread
 
 
+from services.data_service import initialize_db
+
 logger = getLogger("main")
 logger = getLogger("main")
 
 
 def main():
 def main():
@@ -23,6 +25,7 @@ def main():
     # Create and configure app
     # Create and configure app
     app = create_app()
     app = create_app()
     setup_routes(app)
     setup_routes(app)
+    initialize_db()
     
     
     # Display startup information
     # Display startup information
     logger.info(f"Servidor corriendo en http://localhost:{PORT}")
     logger.info(f"Servidor corriendo en http://localhost:{PORT}")
@@ -36,7 +39,8 @@ def main():
     thread = Thread(target=order_thread, daemon=True)
     thread = Thread(target=order_thread, daemon=True)
     thread.start()
     thread.start()
     # Start the server
     # Start the server
-    uvicorn.run(app, host="0.0.0.0", port=PORT)
+    #rut without logs
+    uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="info", access_log=False)
 
 
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":

+ 21 - 0
models/__init__.py

@@ -1 +1,22 @@
 # Models module
 # Models module
+
+from .user import User, UserIDRequest, RegisterUserRequest
+from .items import Product, ProductWithQuantity
+from .sells import Sale, SellItem, ItemWeb, OrderWeb
+from .blacklist import Blacklist
+from .chat import Message, ChatCompletionRequest
+
+__all__ = [
+    "User",
+    "UserIDRequest",
+    "RegisterUserRequest",
+    "Product",
+    "ProductWithQuantity",
+    "Sale",
+    "SellItem",
+    "ItemWeb",
+    "OrderWeb", 
+    "Blacklist",
+    "Message",
+    "ChatCompletionRequest"
+]

+ 10 - 0
models/blacklist.py

@@ -0,0 +1,10 @@
+from typing import Optional
+from pydantic import BaseModel
+
+class Blacklist(BaseModel):
+    """Blacklist model matching the database schema"""
+    id: int
+    user_id: int
+    email: Optional[str] = None
+    nombre: Optional[str] = None
+    rut: Optional[str] = None

+ 13 - 0
models/chat.py

@@ -0,0 +1,13 @@
+
+from typing import List
+from pydantic import BaseModel
+
+
+class Message(BaseModel):
+    role: str
+    content: str
+
+
+class ChatCompletionRequest(BaseModel):
+    messages: List[Message]
+    user: str

+ 18 - 0
models/items.py

@@ -0,0 +1,18 @@
+from typing import List, Optional
+from pydantic import BaseModel
+
+class Product(BaseModel):
+    """Product model matching the database schema"""
+    id: int
+    name: str
+    type: Optional[str] = None
+    description: Optional[str] = None
+    price: float
+    image: Optional[str] = None
+    status: int = 1  # 0: Inactive, 1: Active
+
+class ProductWithQuantity(Product):
+    """Product model with quantity field for sales"""
+    cantidad: int = 1
+
+

+ 0 - 32
models/schemas.py

@@ -1,32 +0,0 @@
-from typing import List
-from pydantic import BaseModel
-
-
-class Message(BaseModel):
-    role: str
-    content: str
-
-
-class ChatCompletionRequest(BaseModel):
-    messages: List[Message]
-    user: str
-
-
-class ItemWeb(BaseModel):
-    id: int
-    name: str
-    quantity: int
-    price: float
-    itemTotal: float
-
-
-class OrderWeb(BaseModel):
-    customerName: str
-    items: List[ItemWeb]
-    totalAmount: float
-    orderDate: str
-    table: int
-
-
-class UserCodeRequest(BaseModel):
-    user_code: str

+ 45 - 0
models/sells.py

@@ -0,0 +1,45 @@
+from typing import List, Optional
+from pydantic import BaseModel
+
+class ItemWeb(BaseModel):
+    id: int
+    quantity: int
+
+class OrderWeb(BaseModel):
+    customerId: int
+    items: List[ItemWeb]
+    totalAmount: float
+    orderDate: str
+    table: int
+
+class Product(BaseModel):
+    """Legacy Product model - use models.items.Product instead"""
+    id: int
+    name: str
+    price: float
+    type: str
+    
+    description: str
+    image: str
+    status: int  # 0: inactive, 1: active
+    quantity: Optional[int] = 1  # Optional quantity for the product
+
+class Sale(BaseModel):
+    """Sale model matching the database schema"""
+    id: int
+    user_id: int
+    total: float
+    fudo_id: str
+    fecha: str
+    table: int
+    user_name: Optional[str] = None
+    user_email: Optional[str] = None
+
+class SellItem(BaseModel):
+    """Legacy model - use Sale instead"""
+    user_id: int
+    products: list[Product]
+    total: float
+    fudo_id: str
+    fecha: str
+    table: int  

+ 25 - 0
models/user.py

@@ -0,0 +1,25 @@
+from typing import Optional
+from pydantic import BaseModel, Field
+
+class UserIDRequest(BaseModel):
+    id: int
+
+class RegisterUserRequest(BaseModel):
+    name: str
+    email: str
+    rut: str
+
+class User(BaseModel):
+    """User model matching the database schema"""
+    id: int
+    email: str
+    nombre: str
+    rut: str
+    pin_hash: str
+    kleincoins: str
+    created_at: str
+
+
+class LoginRequest(BaseModel):
+    email: str
+    pin: str = Field(min_length=4, max_length=4, description="4-digit PIN for user authentication")

+ 0 - 0
public/js/interfaces.js → pin.key


+ 0 - 21
public/js/service/auth.js

@@ -1,21 +0,0 @@
-
-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 { existsUser};

+ 0 - 0
public/assets/summer.jpeg → public/main/assets/summer.jpeg


+ 2 - 2
public/index.html → public/main/index.html

@@ -26,8 +26,8 @@
     </script>
     </script>
   <!-- Markdown -->
   <!-- Markdown -->
   <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
   <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
-  <script src="js/app.js" type="module"></script>
-  <link rel="stylesheet" href="styles.css">
+  <script src="/express/js/app.js" type="module"></script>
+  <link rel="stylesheet" href="/express/styles.css">
   <!-- Animaciones -->
   <!-- Animaciones -->
   <style>
   <style>
     @keyframes slideRight {
     @keyframes slideRight {

+ 30 - 23
public/js/app.js → public/main/js/app.js

@@ -1,8 +1,10 @@
-import { initializeService , sendMessage as serviceSendMessage } from './service/chat.js';
+import { sendMessage as serviceSendMessage } from './service/chat.js';
 import { getProducts, sendOrder } from './service/product.js';
 import { getProducts, sendOrder } from './service/product.js';
+import { login } from './service/auth.js'
 
 
 // --- Variables de Usuario ---
 // --- Variables de Usuario ---
-let userName = '';
+let userId = -1;
+let userName = "Cliente";
 let userTable = null;
 let userTable = null;
 let userToken = null;
 let userToken = null;
 // --- Datos de Productos y Carrito ---
 // --- Datos de Productos y Carrito ---
@@ -37,19 +39,10 @@ let globalLoaderElement = null;
 
 
 //#region --- Inicialización y Configuracion ---
 //#region --- Inicialización y Configuracion ---
 async function initializeApp() {
 async function initializeApp() {
+    
+    showGlobalLoader();
     await renderProducts();
     await renderProducts();
-    try {
-        const [ok, token] = await initializeService();
-        userToken = token;
-        if (ok) {
-        } else {
-            console.warn("Chat AI no pudo inicializarse.");
-            displayChatMessage("ai", "No se pudo conectar con el Chef IA en este momento.");
-        }
-    } catch (error) {
-        console.error("Error durante la inicialización del Chat AI:", error);
-        displayChatMessage("ai", "Error al iniciar la IA.");
-    }
+    hideGlobalLoader();
 
 
     const chatSuggestions = Array.from(chatSuggestionsElement.children);
     const chatSuggestions = Array.from(chatSuggestionsElement.children);
 
 
@@ -76,9 +69,22 @@ function initializeLoginModal() {
             alert("Por favor, completa todos los campos.");
             alert("Por favor, completa todos los campos.");
             return;
             return;
         }
         }
+        try{
+            const {data} = await login(email, pin)
+            userToken = data.token;
+            userName = data.name;
+            console.log("Usuario autenticado:", data);
+            if (!userToken || data.id === undefined) {
+                alert("Error al iniciar sesión. Por favor, inténtalo de nuevo.");
+                return;
+            }
+
+            sessionModal.classList.add('hidden');
+
+            initializeApp();
+        }catch (error) {
+        }
 
 
-        sessionModal.classList.add('hidden');
-        initializeApp();
     });
     });
 }
 }
 
 
@@ -147,7 +153,8 @@ async function renderProducts() {
     if (!template) return;
     if (!template) return;
 
 
     productListElement.innerHTML = "";
     productListElement.innerHTML = "";
-    products = await getProducts();
+    console.log("Cargando productos...");
+    products = await getProducts(userToken);
 
 
     products.forEach(product => {
     products.forEach(product => {
         const clone = template.content.cloneNode(true);
         const clone = template.content.cloneNode(true);
@@ -313,9 +320,9 @@ async function processOrder() {
 
 
     try {
     try {
         const orderData = {
         const orderData = {
-            customerName: userName,
+            customerId: userId,
             table: userTable,
             table: userTable,
-            items: cart.map(item => ({ id: item.id, name: item.name, quantity: item.quantity, price: item.price, itemTotal: item.price * item.quantity })),
+            items: cart.map(item => ({ id: item.id, quantity: item.quantity})),
             totalAmount: cart.reduce((sum, item) => sum + item.price * item.quantity, 0),
             totalAmount: cart.reduce((sum, item) => sum + item.price * item.quantity, 0),
             orderDate: new Date().toLocaleString('sv-SE').replace(' ', 'T')
             orderDate: new Date().toLocaleString('sv-SE').replace(' ', 'T')
         };
         };
@@ -365,12 +372,12 @@ async function sendMessageToAI() {
     aiLoadingIndicator.classList.remove("hidden");
     aiLoadingIndicator.classList.remove("hidden");
 
 
     try {
     try {
-        const response = await serviceSendMessage(userInput, chatHistory, userName);
+        const response = await serviceSendMessage(userInput, chatHistory, userName, userToken);
         if (!response) {
         if (!response) {
             displayChatMessage("ai", "Hubo un problema al conectar con el Chef IA.");
             displayChatMessage("ai", "Hubo un problema al conectar con el Chef IA.");
         } else if (response === "not_init") {
         } else if (response === "not_init") {
-            if (await serviceInitializeChat()) {
-                const response = await serviceSendMessage(userInput, chatHistory, userName);
+            if (await initializeService()) {
+                const response = await serviceSendMessage(userInput, chatHistory, userName, userToken);
                 if (response) {
                 if (response) {
                     chatHistory = response.messageList;
                     chatHistory = response.messageList;
                     displayChatMessage("ai", response.assistantResponse);
                     displayChatMessage("ai", response.assistantResponse);
@@ -403,6 +410,6 @@ document.addEventListener("DOMContentLoaded", async () => {
     initializeChat()
     initializeChat()
     setupBasicListeners();
     setupBasicListeners();
     initializeLoginModal();
     initializeLoginModal();
-    initializeApp()
+    // initializeApp()
 
 
 });
 });

+ 0 - 0
public/main/js/interfaces.js


+ 33 - 0
public/main/js/service/auth.js

@@ -0,0 +1,33 @@
+
+async function login(email,pin){
+  const response = await fetch("/api/users/login", {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json"
+    },
+    body: JSON.stringify({ email, pin })
+}
+  );
+  if (response.status == 404) {
+    const errorData = await response.json().catch(() => ({ message: "El usuario no fue encontrado." }));
+    throw new Error(errorData.message);
+  }else if (response.status == 401) {
+    const errorData = await response.json().catch(() => ({ message: "Credenciales incorrectas.", attempts: 0 }));
+    alert(errorData.message);
+    throw new Error(errorData.message);
+  }else if (response.status != 200) {
+    console.error(response.status, response.statusText);
+    const errorData = await response.json().catch(() => ({ message: "Error al iniciar sesión." }));
+    alert(errorData.message, `Intentos restantes: ${errorData.attempts_remaining || 0}`);
+    throw new Error(errorData.message);
+  }
+  const data = await response.json();
+  if (!data || !data.data.token) {
+    alert("Error al iniciar sesión.");
+    throw new Error("Error al iniciar sesión.");
+  }
+  return data;
+}
+
+
+export { login };

+ 5 - 22
public/js/service/chat.js → public/main/js/service/chat.js

@@ -1,24 +1,7 @@
-async function initializeService() {
-  try {
-    const response = await fetch("/api/chat/init-chat");
-    if (!response.ok) {
-      const errorData = await response.json().catch(() => ({ message: "Error desconocido al inicializar." }));
-      throw new Error(errorData.message || `Error del servidor: ${response.status}`);
-    }
-    const data = await response.json();
-    const token = data.chatToken;
-    if (!token) {
-      throw new Error("No se pudo obtener el token de chat.");
-    }
-    return [true, token];
-  } catch (error) {
-    console.error("Error al inicializar el chat:", error);
-  }
-}
-async function sendMessage(message, messageList, userName) {
-  if (!chatToken) { 
+
+async function sendMessage(message, messageList, userName, token) {
+  if (!token) { 
     return "not_init";
     return "not_init";
-    return;
   }
   }
   messageList.push({ role: "user", content: message });
   messageList.push({ role: "user", content: message });
   const cuerpo = { messages: messageList, user: userName }
   const cuerpo = { messages: messageList, user: userName }
@@ -26,7 +9,7 @@ async function sendMessage(message, messageList, userName) {
     method: "POST",
     method: "POST",
     headers: {
     headers: {
       "Content-Type": "application/json",
       "Content-Type": "application/json",
-      "X-App-Token": chatToken
+      "Authorization": `Bearer ${token}`
     },
     },
     body: JSON.stringify(cuerpo)
     body: JSON.stringify(cuerpo)
   });
   });
@@ -44,4 +27,4 @@ async function sendMessage(message, messageList, userName) {
   }
   }
 }
 }
 
 
-export { initializeService , sendMessage };
+export { sendMessage };

+ 8 - 3
public/js/service/product.js → public/main/js/service/product.js

@@ -2,7 +2,7 @@
 async function sendOrder(order, token) {
 async function sendOrder(order, token) {
   console.log("Enviando orden:", order);
   console.log("Enviando orden:", order);
   try {
   try {
-    const response = await fetch("/api/printer/order", {
+    const response = await fetch("/api/orders/send", {
       method: "POST",
       method: "POST",
       headers: {
       headers: {
         "Content-Type": "application/json",
         "Content-Type": "application/json",
@@ -21,8 +21,13 @@ async function sendOrder(order, token) {
     throw error;
     throw error;
   }
   }
 }
 }
-async function getProducts(){
-  const response = await fetch("/api/get_products");
+async function getProducts(token){
+  const response = await fetch("/api/products/", {
+    headers: {
+      "Content-Type": "application/json",
+      "Authorization": `Bearer ${token}`
+    }
+  });
   if (!response.ok) {
   if (!response.ok) {
     const errorData = await response.json().catch(() => ({ message: "Respuesta no válida del servidor." }));
     const errorData = await response.json().catch(() => ({ message: "Respuesta no válida del servidor." }));
     throw new Error(errorData.message || `Error del servidor: ${response.status}`);
     throw new Error(errorData.message || `Error del servidor: ${response.status}`);

+ 0 - 0
public/styles.css → public/main/styles.css


+ 62 - 0
public/register/app.js

@@ -0,0 +1,62 @@
+function showRegisterModal() {
+            document.getElementById('registerModal').classList.remove('hidden');
+        }
+
+        function hideRegisterModal() {
+            document.getElementById('registerModal').classList.add('hidden');
+        }
+
+        // Formateo automático del RUT mientras se escribe
+        document.getElementById('rutInput').addEventListener('input', function(e) {
+            let rut = e.target.value.replace(/[^0-9kK]/g, '');
+            
+            if (rut.length > 1) {
+                let body = rut.slice(0, -1);
+                let dv = rut.slice(-1);
+                
+                if (body.length > 0) {
+                    // Agregar puntos cada 3 dígitos desde la derecha
+                    body = body.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
+                    rut = body + '-' + dv;
+                }
+            }
+            
+            e.target.value = rut;
+        });
+
+        // Manejo del envío del formulario
+        document.getElementById('registerForm').addEventListener('submit', function(e) {
+            e.preventDefault();
+            
+            const formData = new FormData(e.target);
+            const data = {
+                name: formData.get('name'),
+                email: formData.get('email'),
+                rut: formData.get('rut')
+            };
+            console.log('Datos del formulario:', data);
+            fetch('/api/users/register', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify(data)
+            })
+            .then(response => {
+                if (!response.ok) {
+                    return response.json().then(errorData => {
+                        throw new Error(errorData.message || 'Error al registrar el usuario.');
+                    });
+                }
+                return response.json();
+            })
+            .then(data => {
+                console.log('Registro exitoso:', data);
+                alert('Registro exitoso! Revisa tu correo electrónico para el PIN.');
+                hideRegisterModal();
+            })
+        });
+
+document.addEventListener('DOMContentLoaded', function() {
+    showRegisterModal();
+});

+ 74 - 0
public/register/index.html

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="es">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Modal de Registro</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <script src="/register/app.js" defer></script>
+</head>
+<body class="bg-gray-100 p-8">
+
+    <!-- === MODAL DE REGISTRO === -->
+    <div id="registerModal"
+         class="fixed hidden inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
+      <form id="registerForm" class="bg-white w-full max-w-md p-8 rounded-xl shadow-xl space-y-6">
+        <div class="text-center">
+          <h2 class="text-2xl font-bold text-gray-900">¡Regístrate!</h2>
+          <p id="registerMessage" class="text-sm text-gray-600 mt-2">
+            Crea tu cuenta para realizar pedidos
+          </p>
+        </div>
+
+        <div class="space-y-4">
+          <div>
+            <label for="nameInput" class="block text-sm font-medium text-gray-700 mb-2">
+              Nombre completo
+            </label>
+            <input id="nameInput"
+                   name="name"
+                   type="text"
+                    pattern="[A-Za-zÀ-ÿ\s]{2,}"
+                   class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] focus:border-transparent outline-none transition-all"
+                   placeholder="Juan Pérez" 
+                   required />
+          </div>
+
+          <div>
+            <label for="emailRegisterInput" class="block text-sm font-medium text-gray-700 mb-2">
+              Correo electrónico
+            </label>
+            <input id="emailRegisterInput"
+                   name="email"
+                   type="email"
+                   class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] focus:border-transparent outline-none transition-all"
+                   placeholder="tu@email.com" 
+                   required />
+          </div>
+
+          <div>
+            <label for="rutInput" class="block text-sm font-medium text-gray-700 mb-2">
+              RUT
+            </label>
+            <input id="rutInput"
+                   name="rut"
+                   type="text"
+                   maxlength="12"
+                   pattern="^\d{1,2}\.\d{3}\.\d{3}-[\dkK]$"
+                   class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] focus:border-transparent outline-none transition-all"
+                   placeholder="12345678-9" 
+                   required />
+          </div>
+        </div>
+
+        <div class="flex gap-3">
+          <button id="registerAcceptBtn"
+                  type="submit"
+                  class="flex-1 bg-[#101419] hover:bg-[#37404a] text-white py-3 rounded-lg font-medium transition-colors duration-200 focus:ring-2 focus:ring-offset-2 focus:ring-[#101419]">
+            Registrarse
+          </button>
+        </div>
+      </form>
+    </div>
+</body>
+</html>

+ 0 - 0
public/register/styles.css


+ 6 - 17
routes/chat.py

@@ -1,29 +1,18 @@
-from math import log
 from fastapi import Request, HTTPException, Depends
 from fastapi import Request, HTTPException, Depends
 from fastapi.responses import JSONResponse
 from fastapi.responses import JSONResponse
-from models.schemas import ChatCompletionRequest
+from httpx import get
+from models.chat import ChatCompletionRequest
 from services.openai_service import generate_completion
 from services.openai_service import generate_completion
 from services.logging_service import log_llm_response
 from services.logging_service import log_llm_response
-from auth.security import protect_chat_api, generate_anti_abuse_token
+from auth.security import get_current_user
 import logging
 import logging
-
+from fastapi import APIRouter
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
-async def init_chat(request: Request):
-    """Initialize chat and get anti-abuse token"""
-    if request.client:
-        logger.info(f"App initialized from client: {request.client.host}")
-
-    current_token = request.session.get("antiAbuseToken")
-    if not current_token:
-        new_token = generate_anti_abuse_token()
-        request.session["antiAbuseToken"] = new_token
-        logger.info(f"Generated new antiAbuseToken for session: {new_token}")
-        return JSONResponse({"chatToken": new_token})
-    else:
-        return JSONResponse({"chatToken": current_token})
+chat_router = APIRouter()
 
 
 
 
+@chat_router.post("/completions")
 async def chat_completions(request_data: ChatCompletionRequest, request: Request):
 async def chat_completions(request_data: ChatCompletionRequest, request: Request):
     """Get chat completions from OpenAI"""
     """Get chat completions from OpenAI"""
     # Uses session_token (which is the antiAbuseToken) as an identifier for logging
     # Uses session_token (which is the antiAbuseToken) as an identifier for logging

+ 20 - 8
routes/orders.py

@@ -1,9 +1,10 @@
+from itertools import product
 from math import log
 from math import log
 import time
 import time
-from fastapi import HTTPException
+from fastapi import HTTPException, APIRouter
 from fastapi.responses import JSONResponse
 from fastapi.responses import JSONResponse
 from fudo import fudo
 from fudo import fudo
-from models.schemas import OrderWeb
+from models.sells import OrderWeb
 from services.fudo_service import add_product_to_fudo
 from services.fudo_service import add_product_to_fudo
 from services.email_service import send_email
 from services.email_service import send_email
 from services.logging_service import log_order
 from services.logging_service import log_order
@@ -11,12 +12,16 @@ from impresora.printer import PrinterUSB
 from impresora.order import Order, Item
 from impresora.order import Order, Item
 from logging import getLogger
 from logging import getLogger
 from threading import Thread
 from threading import Thread
+from services.data_service import ProductDataService, UserDataService
+from config.mails import PRINTER_DISCONNECTED_MAIL
 
 
 logger = getLogger(__name__)
 logger = getLogger(__name__)
-
+user_data_service = UserDataService()
+product_data_service = ProductDataService()
 printer_orders = []
 printer_orders = []
+order_router = APIRouter()
 
 
-
+@order_router.post("/send")
 async def printer_order(order: OrderWeb):
 async def printer_order(order: OrderWeb):
     """Process printer order"""
     """Process printer order"""
     logger.info("Printer order received")
     logger.info("Printer order received")
@@ -25,7 +30,10 @@ async def printer_order(order: OrderWeb):
     if not PrinterUSB.check_usb_port(0xfe6, 0x811e):
     if not PrinterUSB.check_usb_port(0xfe6, 0x811e):
         logger.error("Printer is not connected.")
         logger.error("Printer is not connected.")
         email_thread = Thread(
         email_thread = Thread(
-            target=send_email, daemon=True)
+            target=send_email,
+            args=(PRINTER_DISCONNECTED_MAIL["subject"], PRINTER_DISCONNECTED_MAIL["body"], ["erwinjacimino2003@gmail.com", "mompyn@gmail.com"]),
+            daemon=True
+        )
         email_thread.start()
         email_thread.start()
         logger.error("Email sent to admin about printer issue.")
         logger.error("Email sent to admin about printer issue.")
         return JSONResponse(status_code=424, content={"message": "Printer is not connected."})
         return JSONResponse(status_code=424, content={"message": "Printer is not connected."})
@@ -54,15 +62,19 @@ async def printer_order(order: OrderWeb):
             content={"message": "Error adding products to table.", "errors": product_errors}
             content={"message": "Error adding products to table.", "errors": product_errors}
         )
         )
     
     
+    user = user_data_service.get_by_id(order.customerId)
+    if not user:
+        return JSONResponse(status_code=404, content={"message": "User not found."})
+    products = product_data_service.get_products([item.id for item in items])
     # Print order
     # Print order
     printer_orders.append(Order(
     printer_orders.append(Order(
-        order.customerName, 
-        [Item(item.name, item.price, item.quantity) for item in items]
+        user=user.nombre if user else "Unknown User",
+        items=[Item(product.name, product.price, item.quantity) for product, item in zip(products, items)]
     ))
     ))
     
     
     
     
     # Log order
     # Log order
-    log_order(order, items)
+    log_order(user.nombre, order.table, order_date=order.orderDate, items=[product.name for product in products])
     
     
     return JSONResponse({"message": "Order processed successfully"})
     return JSONResponse({"message": "Order processed successfully"})
 
 

+ 13 - 4
routes/products.py

@@ -1,10 +1,19 @@
+from math import prod
 from fastapi.responses import JSONResponse
 from fastapi.responses import JSONResponse
-from services.data_service import all_products
+from auth.security import get_current_user
+from services.data_service import ProductDataService
 from logging import getLogger
 from logging import getLogger
-
+from fastapi import APIRouter, Depends
 logger = getLogger(__name__)
 logger = getLogger(__name__)
 
 
-async def get_products():
+product_data_service = ProductDataService()
+
+product_router = APIRouter()
+
+@product_router.get("/")
+async def get_products(current_user = Depends(get_current_user)):
     """Get products"""
     """Get products"""
+    logger.debug(f"Current user: {current_user.email if current_user else 'Anonymous'}")
     logger.info("Fetching all products")
     logger.info("Fetching all products")
-    return JSONResponse({"products": all_products})
+    all_products = list(map(lambda p: p.model_dump(), product_data_service.get_all()))
+    return JSONResponse({"products": all_products})

+ 2 - 0
routes/sells.py

@@ -0,0 +1,2 @@
+from pydantic import BaseModel
+

+ 14 - 4
routes/static.py

@@ -4,14 +4,24 @@ from fastapi.responses import HTMLResponse, FileResponse
 from fastapi.staticfiles import StaticFiles
 from fastapi.staticfiles import StaticFiles
 
 
 
 
-async def serve_index_html():
+async def serve_app_html():
     """Serve the main HTML file"""
     """Serve the main HTML file"""
-    index_path = os.path.join("public", "index.html")
+    index_path = os.path.join("public","main", "index.html")
     if not os.path.exists(index_path):
     if not os.path.exists(index_path):
         raise HTTPException(status_code=404, detail="public/index.html not found.")
         raise HTTPException(status_code=404, detail="public/index.html not found.")
     return FileResponse(index_path)
     return FileResponse(index_path)
 
 
+async def serve_register_html():
+    """Serve the register HTML file"""
+    register_path = os.path.join("public", "register", "index.html")
+    if not os.path.exists(register_path):
+        raise HTTPException(status_code=404, detail="public/register/index.html not found.")
+    return FileResponse(register_path)
 
 
-def mount_static_files(app):
+def mount_register_static_files(app):
+    """Mount static files for the register page"""
+    app.mount("/register/", StaticFiles(directory="public/register", html=False), name="register_static")
+
+def mount_main_static_files(app):
     """Mount static files"""
     """Mount static files"""
-    app.mount("/", StaticFiles(directory="public", html=False), name="public_root_assets")
+    app.mount("/express/", StaticFiles(directory="public/main", html=False), name="public_root_assets")

+ 105 - 17
routes/users.py

@@ -1,18 +1,106 @@
+from logging import getLogger
+from fastapi import APIRouter
 from fastapi.responses import JSONResponse
 from fastapi.responses import JSONResponse
-from models.schemas import UserCodeRequest
-from services.data_service import load_users
-
-
-async def exists_user(request: UserCodeRequest):
-    """Check if user exists"""
-    users = load_users()
-    for user in users:
-        if user['userCode'] == request.user_code:
-            return JSONResponse({
-                "success": True,
-                "userName": user['userName']
-            })
-    return JSONResponse({
-        "success": True,
-        "userName": request.user_code
-    })
+from httpx import RequestError
+import redis
+import config
+from models import user
+from models.user import  LoginRequest, UserIDRequest, RegisterUserRequest
+from services.data_service import UserDataService
+from cryptography.fernet import Fernet
+from config.settings import PIN_KEY
+from auth.security import generate_token
+from services.email_service import send_email
+from config.mails import REGISTER_MAIL
+from config.settings import APPNAME
+fernet = Fernet(PIN_KEY.encode())
+logger = getLogger(__name__)
+user_data_service = UserDataService()
+
+user_router = APIRouter()
+
+
+def unique_pin_generate():
+    """Generate a unique 4-digit PIN"""
+    import random
+    pin = str(random.randint(1000, 9999))
+    return pin
+
+@user_router.post("/exists")
+async def exists_user(request: UserIDRequest):
+        """Check if user exists"""
+        user = user_data_service.get_by_id(request.id)
+        if user:
+            return JSONResponse(status_code=200, content={"exists": True})
+        else:
+            return JSONResponse(status_code=404, content={"exists": False, "data": {"message": "User does not exist."}})
+
+@user_router.post("/register")
+async def register_user(request: RegisterUserRequest):
+    """Register a new user"""
+    pin = unique_pin_generate()
+    userID = user_data_service.create(request.name, request.email, request.rut, pin)
+    if userID == -1:
+        return JSONResponse(status_code=400, content={"message": "User already exists."})
+    user = user_data_service.get_by_id(userID)
+    if not user:
+        return JSONResponse(status_code=500, content={"message": "Error creating user."})
+    send_email(
+        REGISTER_MAIL["subject"],
+        REGISTER_MAIL["body"],
+        [user.email], name=user.nombre, app_name=APPNAME, pin=pin
+    )
+    return JSONResponse(status_code=201, content={"message": "User created successfully.", "data": {
+        **user.model_dump(exclude={"pin_hash"}),
+        "token": generate_token(user.email)
+    }})
+
+@user_router.post("/login")
+async def login_user(request: LoginRequest):
+    """Login user with email and PIN"""
+    logger.debug(f"Login attempt for email: {request.email}")
+    logger.debug(f"Login request: {request.pin}")
+    user = user_data_service.login(request.email, request.pin)
+    if user:
+        # Successful login, return user data and token
+        return JSONResponse(status_code=200, content={"message": "Login successful.", "data": {
+            "id": user.id,
+            "name": user.nombre,
+            "email": user.email,
+            "kleincoins": user.kleincoins,
+            "created_at": user.created_at,
+            "token": generate_token(user.email)
+        }})
+    else:
+        # Failed login: increment attempts in Redis
+        redis_client = redis.Redis(host='localhost', port=6379, db=0)
+        redis_client.incr(f"login_attempts:{request.email}")
+        redis_client.expire(f"login_attempts:{request.email}", 3600)
+        redis_attempts = redis_client.get(f"login_attempts:{request.email}")
+        attempts = int(redis_attempts) if redis_attempts else 0 # type: ignore
+        if attempts and int(attempts) > 5:
+            # Too many attempts, block login
+            return JSONResponse(status_code=429, content={"message": "Too many login attempts. Please try again later."})
+        else:
+            # Set flag for last failed login
+            redis_client.set(f"last_failed_login:{request.email}", "true")
+            redis_client.expire(f"last_failed_login:{request.email}", 3600)
+            logger.warning(f"Failed login attempt for {request.email}. Attempts: {attempts}")
+            logger.error("Invalid email or PIN.")
+        # Return unauthorized with attempts remaining
+        return JSONResponse(status_code=401, content={"message": "Invalid email or PIN.", "attempts_remaining": 5 - attempts if attempts else 5})
+
+@user_router.delete("/delete")
+async def delete_user(request: UserIDRequest):
+    """Delete a user by ID"""
+    user = user_data_service.delete(request.id)
+    if user:
+        return JSONResponse(status_code=200, content={"message": "User deleted successfully.", "data": user})
+    else:
+        return JSONResponse(status_code=404, content={"message": "User not found."})
+
+@user_router.get("/all")
+async def get_all_users():
+    """Get all users"""
+    users = list(map(lambda u: u.model_dump(), user_data_service.get_all()))
+    return JSONResponse(status_code=200, content={"data": users})

Разница между файлами не показана из-за своего большого размера
+ 472 - 188
services/data_service.py


+ 13 - 52
services/email_service.py

@@ -1,67 +1,28 @@
 import smtplib
 import smtplib
 from email.message import EmailMessage
 from email.message import EmailMessage
+from logging import getLogger
 
 
+logger = getLogger(__name__)
 
 
-def send_email():
-    """Send email notification when printer is disconnected"""
+def send_email(
+    subject: str,
+    body: str,
+    to: list[str],
+    **kwargs
+):
+    logger.debug(str(kwargs))
+    """Send email """
     # Datos del remitente
     # Datos del remitente
     EMAIL_ORIGEN = 'expresspedidos211@gmail.com'
     EMAIL_ORIGEN = 'expresspedidos211@gmail.com'
-    EMAIL_DESTINO = ['erwinjacimino2003@gmail.com', "mompyn@gmail.com"]
     CONTRASENA = 'drkassszdtgapufg'
     CONTRASENA = 'drkassszdtgapufg'
 
 
     # Crear el correo
     # Crear el correo
     msg = EmailMessage()
     msg = EmailMessage()
-    msg['Subject'] = 'Impresora Desconectada weon :('
+    msg['Subject'] = subject
     msg['From'] = EMAIL_ORIGEN
     msg['From'] = EMAIL_ORIGEN
-    msg['To'] = ", ".join(EMAIL_DESTINO)
+    msg['To'] = ", ".join(to)
     msg.set_content('Este correo tiene contenido HTML.')
     msg.set_content('Este correo tiene contenido HTML.')
-    msg.add_alternative("""
-    <html>
-    <body style="margin:0; padding:0; background-color:#5a67d8; font-family: Arial, sans-serif;">
-        <table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="padding: 40px 0;">
-        <tr>
-            <td align="center">
-            <table border="0" cellpadding="0" cellspacing="0" width="500" style="background-color: #e3e3e3; border-radius: 25px; padding: 40px; text-align: center;">
-                <tr>
-                <td>
-                    <div style="font-size: 60px; background-color: #ff6b6b; width: 80px; height: 80px; line-height: 80px; border-radius: 15px; margin: 0 auto 20px; color: white;">
-                    🖨️
-                    </div>
-                </td>
-                </tr>
-                <tr>
-                <td>
-                    <img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExZGlraDhpb2tkeHEweDZ2eWdnZDZlNXFvODhmNzZieWN6OXp0b3ZqNCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3hetVnNSl0IBa/giphy.gif" alt="Gatito peleando con la impresora" width="250" style="border-radius: 12px; margin-bottom: 20px;" />
-                </td>
-                </tr>
-                <tr>
-                <td>
-                    <h1 style="font-size: 24px; color: #ff6b6b; margin-bottom: 10px;">¡Impresora Desconectada!</h1>
-                </td>
-                </tr>
-                <tr>
-                <td>
-                    <p style="font-size: 16px; color: #333333; line-height: 1.5; margin-bottom: 20px;">
-                    No se puede establecer conexión con la impresora.<br>
-                    Por favor, verifica la conexión y vuelve a intentarlo.
-                    </p>
-                </td>
-                </tr>
-                <tr>
-                <td>
-                    <span style="display: inline-block; background: #ff6b6b; color: white; padding: 12px 24px; border-radius: 25px; font-weight: bold; font-size: 16px;">
-                    🔴 Estado: Desconectada
-                    </span>
-                </td>
-                </tr>
-            </table>
-            </td>
-        </tr>
-        </table>
-    </body>
-    </html>
-
-    """, subtype='html')
+    msg.add_alternative(body.format(**kwargs), subtype='html')
 
 
     # Enviar el correo usando SMTP de Gmail
     # Enviar el correo usando SMTP de Gmail
     with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
     with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:

+ 6 - 6
services/logging_service.py

@@ -1,10 +1,10 @@
 import csv
 import csv
 import os
 import os
 from typing import List
 from typing import List
-from models.schemas import OrderWeb, ItemWeb
+from models.sells import OrderWeb, ItemWeb
 
 
 
 
-def log_order(order: OrderWeb, items: List[ItemWeb]):
+def log_order(username, table, order_date, items: List[str]):
     """Log order information to CSV file"""
     """Log order information to CSV file"""
     if not os.path.exists('logs.csv'):
     if not os.path.exists('logs.csv'):
         with open('logs.csv', 'w', newline='') as f:
         with open('logs.csv', 'w', newline='') as f:
@@ -14,10 +14,10 @@ def log_order(order: OrderWeb, items: List[ItemWeb]):
     with open('logs.csv', 'a', newline='') as f:
     with open('logs.csv', 'a', newline='') as f:
         writer = csv.writer(f)
         writer = csv.writer(f)
         writer.writerow([
         writer.writerow([
-            order.customerName, 
-            order.table, 
-            order.orderDate, 
-            list(map(lambda item: item.name, items))
+            username,
+            table,
+            order_date,
+            items
         ])
         ])
 
 
 
 

+ 3 - 3
services/openai_service.py

@@ -2,8 +2,8 @@ from typing import List
 from fastapi import HTTPException
 from fastapi import HTTPException
 from openai import OpenAI
 from openai import OpenAI
 from config.settings import OPENAI_API_KEY
 from config.settings import OPENAI_API_KEY
-from models.schemas import Message
-from services.data_service import bg_data_loaded
+from models.chat import Message
+from services.data_service import data_bg_loaded
 from logging import getLogger
 from logging import getLogger
 # Initialize OpenAI client
 # Initialize OpenAI client
 openai_client = OpenAI(api_key=OPENAI_API_KEY)
 openai_client = OpenAI(api_key=OPENAI_API_KEY)
@@ -20,7 +20,7 @@ async def generate_completion(messages_array: List[Message], session_id: str) ->
 
 
     data_for_prompt = [
     data_for_prompt = [
         f'{{"pregunta": "{item.get("q", "")}", "respuesta": "{item.get("ans", "")}"}}'
         f'{{"pregunta": "{item.get("q", "")}", "respuesta": "{item.get("ans", "")}"}}'
-        for item in bg_data_loaded
+        for item in data_bg_loaded
     ]
     ]
     data_string = "\n".join(data_for_prompt)
     data_string = "\n".join(data_for_prompt)
 
 

+ 113 - 0
test_models.py

@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+"""
+Test script to verify the models are correctly defined
+"""
+
+# Test imports
+try:
+    from models.user import User, UserModel, UserIDRequest, RegisterUserRequest
+    print("✅ User models imported successfully")
+except ImportError as e:
+    print(f"❌ Error importing user models: {e}")
+
+try:
+    from models.items import Product, ProductWithQuantity
+    print("✅ Product models imported successfully")
+except ImportError as e:
+    print(f"❌ Error importing product models: {e}")
+
+try:
+    from models.sells import Sale, SellItem, ItemWeb, OrderWeb
+    print("✅ Sales models imported successfully")
+except ImportError as e:
+    print(f"❌ Error importing sales models: {e}")
+
+try:
+    from models.blacklist import Blacklist
+    print("✅ Blacklist model imported successfully")
+except ImportError as e:
+    print(f"❌ Error importing blacklist model: {e}")
+
+try:
+    from models.chat import Message, ChatCompletionRequest
+    print("✅ Chat models imported successfully")
+except ImportError as e:
+    print(f"❌ Error importing chat models: {e}")
+
+# Test model creation
+try:
+    user = User(
+        id=1,
+        email="test@example.com",
+        nombre="Test User",
+        rut="12345678-9",
+        pin_hash="hashed_pin",
+        kleincoins="100",
+        created_at="2023-01-01T00:00:00"
+    )
+    print("✅ User model creation successful")
+    print(f"   User: {user.nombre} ({user.email})")
+except Exception as e:
+    print(f"❌ Error creating user model: {e}")
+
+try:
+    product = Product(
+        id=1,
+        name="Test Product",
+        type="food",
+        description="A test product",
+        price=10.99,
+        image="test.jpg",
+        status=1
+    )
+    print("✅ Product model creation successful")
+    print(f"   Product: {product.name} - ${product.price}")
+except Exception as e:
+    print(f"❌ Error creating product model: {e}")
+
+try:
+    product_with_quantity = ProductWithQuantity(
+        id=1,
+        name="Test Product",
+        type="food",
+        description="A test product",
+        price=10.99,
+        image="test.jpg",
+        status=1,
+        cantidad=3
+    )
+    print("✅ ProductWithQuantity model creation successful")
+    print(f"   Product: {product_with_quantity.name} - Qty: {product_with_quantity.cantidad}")
+except Exception as e:
+    print(f"❌ Error creating ProductWithQuantity model: {e}")
+
+try:
+    sale = Sale(
+        id=1,
+        user_id=1,
+        total=50.99,
+        fudo_id="FUDO123",
+        fecha="2023-01-01T12:00:00",
+        table=5,
+        user_name="Test User",
+        user_email="test@example.com"
+    )
+    print("✅ Sale model creation successful")
+    print(f"   Sale: {sale.fudo_id} - Total: ${sale.total}")
+except Exception as e:
+    print(f"❌ Error creating sale model: {e}")
+
+try:
+    blacklist = Blacklist(
+        id=1,
+        user_id=1,
+        email="blocked@example.com",
+        nombre="Blocked User",
+        rut="98765432-1"
+    )
+    print("✅ Blacklist model creation successful")
+    print(f"   Blacklisted: {blacklist.nombre} ({blacklist.email})")
+except Exception as e:
+    print(f"❌ Error creating blacklist model: {e}")
+
+print("\n🎉 All model tests completed!")

Некоторые файлы не были показаны из-за большого количества измененных файлов