feat: add agents/app
This commit is contained in:
		
							
								
								
									
										476
									
								
								agents/app/langgraph_tools/tools/orders/db_manager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										476
									
								
								agents/app/langgraph_tools/tools/orders/db_manager.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,476 @@ | ||||
| import sqlite3 | ||||
| import uuid | ||||
| import os | ||||
| import re | ||||
| from datetime import datetime | ||||
| from typing import List, Dict, Optional, Tuple | ||||
| from contextlib import contextmanager | ||||
|  | ||||
| # Construir la ruta de la base de datos de manera más robusta | ||||
| current_dir = os.path.dirname(os.path.abspath(__file__)) | ||||
| app_dir = os.path.abspath(os.path.join(current_dir, "..", "..", "..")) | ||||
| DATABASE_PATH = os.path.join(app_dir, "data", "orders.db") | ||||
|  | ||||
| # Estados válidos para órdenes | ||||
| ORDER_STATES = ['in_cart', 'confirmed', 'processing', 'ready', 'delivering', 'delivered', 'cancelled'] | ||||
| DELIVERY_STATES = ['pending', 'assigned', 'in_transit', 'delivered', 'failed'] | ||||
|  | ||||
| def validate_phone(phone: str) -> bool: | ||||
|     """Valida el formato del número de teléfono""" | ||||
|     phone_pattern = re.compile(r'^\+?1?\d{9,15}$') | ||||
|     return bool(phone_pattern.match(phone)) | ||||
|  | ||||
| def init_database(): | ||||
|     """Inicializa la base de datos de pedidos""" | ||||
|     os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True) | ||||
|  | ||||
|     conn = sqlite3.connect(DATABASE_PATH) | ||||
|     cursor = conn.cursor() | ||||
|  | ||||
|     # Crear tabla de carritos/pedidos con nuevos campos | ||||
|     cursor.execute(""" | ||||
|     CREATE TABLE IF NOT EXISTS orders ( | ||||
|         order_id TEXT PRIMARY KEY, | ||||
|         phone TEXT NOT NULL, | ||||
|         status TEXT NOT NULL, | ||||
|         total REAL DEFAULT 0, | ||||
|         delivery_address TEXT, | ||||
|         delivery_status TEXT, | ||||
|         payment_method TEXT, | ||||
|         discount_applied REAL DEFAULT 0, | ||||
|         notes TEXT, | ||||
|         created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||||
|         updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | ||||
|     ) | ||||
|     """) | ||||
|  | ||||
|     # Crear tabla de items del pedido con unidad de medida | ||||
|     cursor.execute(""" | ||||
|     CREATE TABLE IF NOT EXISTS order_items ( | ||||
|         id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||
|         order_id TEXT NOT NULL, | ||||
|         product_id TEXT NOT NULL, | ||||
|         quantity INTEGER NOT NULL, | ||||
|         price REAL NOT NULL, | ||||
|         unit TEXT NOT NULL, | ||||
|         FOREIGN KEY (order_id) REFERENCES orders (order_id), | ||||
|         UNIQUE(order_id, product_id) | ||||
|     ) | ||||
|     """) | ||||
|  | ||||
|     conn.commit() | ||||
|     conn.close() | ||||
|  | ||||
| @contextmanager | ||||
| def get_db_connection(): | ||||
|     """Contexto para manejar la conexión a la base de datos""" | ||||
|     conn = sqlite3.connect(DATABASE_PATH) | ||||
|     conn.row_factory = sqlite3.Row | ||||
|     try: | ||||
|         yield conn | ||||
|     finally: | ||||
|         conn.close() | ||||
|  | ||||
| class OrderManager: | ||||
|     @staticmethod | ||||
|     def get_active_cart(phone: str) -> Optional[str]: | ||||
|         """Obtiene el carrito activo de un cliente o crea uno nuevo""" | ||||
|         if not validate_phone(phone): | ||||
|             raise ValueError("Número de teléfono inválido") | ||||
|  | ||||
|         with get_db_connection() as conn: | ||||
|             cursor = conn.cursor() | ||||
|             cursor.execute( | ||||
|                 """ | ||||
|                 SELECT order_id FROM orders  | ||||
|                 WHERE phone = ? AND status = 'in_cart' | ||||
|                 ORDER BY created_at DESC LIMIT 1 | ||||
|             """, | ||||
|                 (phone,), | ||||
|             ) | ||||
|             result = cursor.fetchone() | ||||
|  | ||||
|             if result: | ||||
|                 return result["order_id"] | ||||
|  | ||||
|             order_id = str(uuid.uuid4()) | ||||
|             cursor.execute( | ||||
|                 """ | ||||
|                 INSERT INTO orders (order_id, phone, status) | ||||
|                 VALUES (?, ?, 'in_cart') | ||||
|             """, | ||||
|                 (order_id, phone), | ||||
|             ) | ||||
|             conn.commit() | ||||
|             return order_id | ||||
|  | ||||
|     @staticmethod | ||||
|     def add_to_cart(phone: str, product_id: str, quantity: int, price: float, unit: str) -> bool: | ||||
|         """Añade un producto al carrito del cliente""" | ||||
|         try: | ||||
|             order_id = OrderManager.get_active_cart(phone) | ||||
|             with get_db_connection() as conn: | ||||
|                 cursor = conn.cursor() | ||||
|                  | ||||
|                 # Verificar si el producto ya está en el carrito | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     SELECT quantity FROM order_items  | ||||
|                     WHERE order_id = ? AND product_id = ? | ||||
|                 """, | ||||
|                     (order_id, product_id), | ||||
|                 ) | ||||
|                 existing_item = cursor.fetchone() | ||||
|  | ||||
|                 if existing_item: | ||||
|                     # Actualizar cantidad si ya existe | ||||
|                     cursor.execute( | ||||
|                         """ | ||||
|                         UPDATE order_items  | ||||
|                         SET quantity = quantity + ?,  | ||||
|                             price = ? | ||||
|                         WHERE order_id = ? AND product_id = ? | ||||
|                     """, | ||||
|                         (quantity, price, order_id, product_id), | ||||
|                     ) | ||||
|                 else: | ||||
|                     # Insertar nuevo item | ||||
|                     cursor.execute( | ||||
|                         """ | ||||
|                         INSERT INTO order_items (order_id, product_id, quantity, price, unit) | ||||
|                         VALUES (?, ?, ?, ?, ?) | ||||
|                     """, | ||||
|                         (order_id, product_id, quantity, price, unit), | ||||
|                     ) | ||||
|  | ||||
|                 # Actualizar total del carrito | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     UPDATE orders  | ||||
|                     SET total = ( | ||||
|                         SELECT SUM(quantity * price)  | ||||
|                         FROM order_items  | ||||
|                         WHERE order_id = ? | ||||
|                     ) | ||||
|                     WHERE order_id = ? | ||||
|                 """, | ||||
|                     (order_id, order_id), | ||||
|                 ) | ||||
|  | ||||
|                 conn.commit() | ||||
|                 return True | ||||
|         except Exception as e: | ||||
|             print(f"Error adding to cart: {e}") | ||||
|             return False | ||||
|  | ||||
|     @staticmethod | ||||
|     def remove_from_cart(phone: str, product_id: str) -> bool: | ||||
|         """Elimina un producto del carrito""" | ||||
|         try: | ||||
|             order_id = OrderManager.get_active_cart(phone) | ||||
|             with get_db_connection() as conn: | ||||
|                 cursor = conn.cursor() | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     DELETE FROM order_items  | ||||
|                     WHERE order_id = ? AND product_id = ? | ||||
|                 """, | ||||
|                     (order_id, product_id), | ||||
|                 ) | ||||
|  | ||||
|                 # Actualizar total | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     UPDATE orders  | ||||
|                     SET total = ( | ||||
|                         SELECT SUM(quantity * price)  | ||||
|                         FROM order_items  | ||||
|                         WHERE order_id = ? | ||||
|                     ) | ||||
|                     WHERE order_id = ? | ||||
|                 """, | ||||
|                     (order_id, order_id), | ||||
|                 ) | ||||
|  | ||||
|                 conn.commit() | ||||
|                 return True | ||||
|         except Exception as e: | ||||
|             print(f"Error removing from cart: {e}") | ||||
|             return False | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_cart_items(phone: str) -> List[Dict]: | ||||
|         """Obtiene los items en el carrito del cliente""" | ||||
|         order_id = OrderManager.get_active_cart(phone) | ||||
|         with get_db_connection() as conn: | ||||
|             cursor = conn.cursor() | ||||
|             cursor.execute( | ||||
|                 """ | ||||
|                 SELECT * FROM order_items  | ||||
|                 WHERE order_id = ? | ||||
|             """, | ||||
|                 (order_id,), | ||||
|             ) | ||||
|             return [dict(row) for row in cursor.fetchall()] | ||||
|  | ||||
|     @staticmethod | ||||
|     def confirm_order(phone: str, delivery_address: Optional[str] = None,  | ||||
|                      payment_method: Optional[str] = None, notes: Optional[str] = None) -> Tuple[bool, str]: | ||||
|         """Confirma un pedido y lo marca como confirmado""" | ||||
|         try: | ||||
|             order_id = OrderManager.get_active_cart(phone) | ||||
|             with get_db_connection() as conn: | ||||
|                 cursor = conn.cursor() | ||||
|                  | ||||
|                 # Verificar que hay items en el carrito | ||||
|                 cursor.execute("SELECT COUNT(*) FROM order_items WHERE order_id = ?", (order_id,)) | ||||
|                 if cursor.fetchone()[0] == 0: | ||||
|                     return False, "El carrito está vacío" | ||||
|  | ||||
|                 # Actualizar estado de la orden | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     UPDATE orders  | ||||
|                     SET status = 'confirmed', | ||||
|                         delivery_address = ?, | ||||
|                         payment_method = ?, | ||||
|                         notes = ?, | ||||
|                         delivery_status = 'pending', | ||||
|                         updated_at = CURRENT_TIMESTAMP | ||||
|                     WHERE order_id = ? | ||||
|                 """, | ||||
|                     (delivery_address, payment_method, notes, order_id), | ||||
|                 ) | ||||
|  | ||||
|                 conn.commit() | ||||
|                 return True, order_id | ||||
|         except Exception as e: | ||||
|             print(f"Error confirming order: {e}") | ||||
|             return False, str(e) | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_order_history(phone: str) -> List[Dict]: | ||||
|         """Obtiene el historial de pedidos del cliente""" | ||||
|         with get_db_connection() as conn: | ||||
|             cursor = conn.cursor() | ||||
|             cursor.execute( | ||||
|                 """ | ||||
|                 SELECT o.*,  | ||||
|                        COUNT(oi.id) as item_count, | ||||
|                        GROUP_CONCAT(oi.quantity || 'x ' || oi.product_id) as items | ||||
|                 FROM orders o | ||||
|                 LEFT JOIN order_items oi ON o.order_id = oi.order_id | ||||
|                 WHERE o.phone = ? AND o.status != 'in_cart' | ||||
|                 GROUP BY o.order_id | ||||
|                 ORDER BY o.created_at DESC | ||||
|             """, | ||||
|                 (phone,), | ||||
|             ) | ||||
|             return [dict(row) for row in cursor.fetchall()] | ||||
|  | ||||
|     @staticmethod | ||||
|     def update_delivery_status(order_id: str, status: str) -> bool: | ||||
|         """Actualiza el estado de entrega de una orden""" | ||||
|         if status not in DELIVERY_STATES: | ||||
|             raise ValueError(f"Estado de entrega inválido. Debe ser uno de: {', '.join(DELIVERY_STATES)}") | ||||
|          | ||||
|         try: | ||||
|             with get_db_connection() as conn: | ||||
|                 cursor = conn.cursor() | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     UPDATE orders  | ||||
|                     SET delivery_status = ?, | ||||
|                         updated_at = CURRENT_TIMESTAMP | ||||
|                     WHERE order_id = ? | ||||
|                 """, | ||||
|                     (status, order_id), | ||||
|                 ) | ||||
|                 conn.commit() | ||||
|                 return True | ||||
|         except Exception as e: | ||||
|             print(f"Error updating delivery status: {e}") | ||||
|             return False | ||||
|  | ||||
|     @staticmethod | ||||
|     def apply_discount(order_id: str, discount_amount: float) -> bool: | ||||
|         """Aplica un descuento al total de la orden""" | ||||
|         try: | ||||
|             with get_db_connection() as conn: | ||||
|                 cursor = conn.cursor() | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     UPDATE orders  | ||||
|                     SET discount_applied = ?, | ||||
|                         total = (SELECT SUM(quantity * price) FROM order_items WHERE order_id = ?) - ?, | ||||
|                         updated_at = CURRENT_TIMESTAMP | ||||
|                     WHERE order_id = ? | ||||
|                 """, | ||||
|                     (discount_amount, order_id, discount_amount, order_id), | ||||
|                 ) | ||||
|                 conn.commit() | ||||
|                 return True | ||||
|         except Exception as e: | ||||
|             print(f"Error applying discount: {e}") | ||||
|             return False | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_order_details(order_id: str) -> Optional[Dict]: | ||||
|         """Obtiene los detalles completos de una orden""" | ||||
|         with get_db_connection() as conn: | ||||
|             cursor = conn.cursor() | ||||
|             cursor.execute("SELECT * FROM orders WHERE order_id = ?", (order_id,)) | ||||
|             order = cursor.fetchone() | ||||
|              | ||||
|             if not order: | ||||
|                 return None | ||||
|                  | ||||
|             cursor.execute("SELECT * FROM order_items WHERE order_id = ?", (order_id,)) | ||||
|             items = cursor.fetchall() | ||||
|              | ||||
|             order_dict = dict(order) | ||||
|             order_dict['items'] = [dict(item) for item in items] | ||||
|             return order_dict | ||||
|  | ||||
|     @staticmethod | ||||
|     def merge_orders(phone: str, order_ids: List[str], new_address: Optional[str] = None) -> Tuple[bool, str]: | ||||
|         """Crea un nuevo pedido combinando los productos de varios pedidos""" | ||||
|         try: | ||||
|             with get_db_connection() as conn: | ||||
|                 cursor = conn.cursor() | ||||
|                  | ||||
|                 # Verificar que todos los pedidos existen y son del mismo cliente | ||||
|                 for order_id in order_ids: | ||||
|                     cursor.execute( | ||||
|                         "SELECT phone, status FROM orders WHERE order_id = ?", | ||||
|                         (order_id,) | ||||
|                     ) | ||||
|                     order = cursor.fetchone() | ||||
|                     if not order or order[0] != phone: | ||||
|                         return False, f"El pedido {order_id} no existe o no pertenece a este cliente" | ||||
|                     if order[1] not in ['in_cart', 'confirmed']: | ||||
|                         return False, f"El pedido {order_id} no se puede modificar en su estado actual" | ||||
|  | ||||
|                 # Crear nuevo pedido | ||||
|                 new_order_id = str(uuid.uuid4()) | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     INSERT INTO orders (order_id, phone, status, delivery_address) | ||||
|                     VALUES (?, ?, 'confirmed', ?) | ||||
|                     """, | ||||
|                     (new_order_id, phone, new_address) | ||||
|                 ) | ||||
|  | ||||
|                 # Copiar items de los pedidos originales | ||||
|                 for order_id in order_ids: | ||||
|                     cursor.execute( | ||||
|                         """ | ||||
|                         INSERT INTO order_items (order_id, product_id, quantity, price, unit) | ||||
|                         SELECT ?, product_id, quantity, price, unit | ||||
|                         FROM order_items WHERE order_id = ? | ||||
|                         """, | ||||
|                         (new_order_id, order_id) | ||||
|                     ) | ||||
|  | ||||
|                 # Actualizar total | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     UPDATE orders  | ||||
|                     SET total = ( | ||||
|                         SELECT SUM(quantity * price)  | ||||
|                         FROM order_items  | ||||
|                         WHERE order_id = ? | ||||
|                     ) | ||||
|                     WHERE order_id = ? | ||||
|                     """, | ||||
|                     (new_order_id, new_order_id) | ||||
|                 ) | ||||
|  | ||||
|                 conn.commit() | ||||
|                 return True, new_order_id | ||||
|         except Exception as e: | ||||
|             print(f"Error merging orders: {e}") | ||||
|             return False, "Error al combinar los pedidos" | ||||
|  | ||||
|     @staticmethod | ||||
|     def delete_order(phone: str, order_id: str) -> bool: | ||||
|         """Elimina un pedido si está en estado permitido""" | ||||
|         try: | ||||
|             with get_db_connection() as conn: | ||||
|                 cursor = conn.cursor() | ||||
|                  | ||||
|                 # Verificar propiedad y estado del pedido | ||||
|                 cursor.execute( | ||||
|                     "SELECT status FROM orders WHERE order_id = ? AND phone = ?", | ||||
|                     (order_id, phone) | ||||
|                 ) | ||||
|                 order = cursor.fetchone() | ||||
|                  | ||||
|                 if not order: | ||||
|                     return False | ||||
|                  | ||||
|                 if order[0] not in ['in_cart', 'confirmed']: | ||||
|                     return False | ||||
|                  | ||||
|                 # Eliminar items y pedido | ||||
|                 cursor.execute("DELETE FROM order_items WHERE order_id = ?", (order_id,)) | ||||
|                 cursor.execute("DELETE FROM orders WHERE order_id = ?", (order_id,)) | ||||
|                  | ||||
|                 conn.commit() | ||||
|                 return True | ||||
|         except Exception as e: | ||||
|             print(f"Error deleting order: {e}") | ||||
|             return False | ||||
|  | ||||
|     @staticmethod | ||||
|     def modify_order_items(phone: str, order_id: str, items: List[Dict]) -> bool: | ||||
|         """Modifica los items de un pedido existente""" | ||||
|         try: | ||||
|             with get_db_connection() as conn: | ||||
|                 cursor = conn.cursor() | ||||
|                  | ||||
|                 # Verificar propiedad y estado del pedido | ||||
|                 cursor.execute( | ||||
|                     "SELECT status FROM orders WHERE order_id = ? AND phone = ?", | ||||
|                     (order_id, phone) | ||||
|                 ) | ||||
|                 order = cursor.fetchone() | ||||
|                  | ||||
|                 if not order or order[0] not in ['in_cart', 'confirmed']: | ||||
|                     return False | ||||
|                  | ||||
|                 # Eliminar items actuales | ||||
|                 cursor.execute("DELETE FROM order_items WHERE order_id = ?", (order_id,)) | ||||
|                  | ||||
|                 # Insertar nuevos items | ||||
|                 for item in items: | ||||
|                     cursor.execute( | ||||
|                         """ | ||||
|                         INSERT INTO order_items (order_id, product_id, quantity, price, unit) | ||||
|                         VALUES (?, ?, ?, ?, ?) | ||||
|                         """, | ||||
|                         (order_id, item['product_id'], item['quantity'], item['price'], item['unit']) | ||||
|                     ) | ||||
|                  | ||||
|                 # Actualizar total | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     UPDATE orders  | ||||
|                     SET total = ( | ||||
|                         SELECT SUM(quantity * price)  | ||||
|                         FROM order_items  | ||||
|                         WHERE order_id = ? | ||||
|                     ) | ||||
|                     WHERE order_id = ? | ||||
|                     """, | ||||
|                     (order_id, order_id) | ||||
|                 ) | ||||
|                  | ||||
|                 conn.commit() | ||||
|                 return True | ||||
|         except Exception as e: | ||||
|             print(f"Error modifying order: {e}") | ||||
|             return False | ||||
|  | ||||
| # Inicializar la base de datos al importar el módulo | ||||
| init_database() | ||||
							
								
								
									
										370
									
								
								agents/app/langgraph_tools/tools/orders/order_tools.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								agents/app/langgraph_tools/tools/orders/order_tools.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,370 @@ | ||||
| from langchain_core.tools import tool | ||||
| from typing import Optional, List | ||||
| from .db_manager import OrderManager | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def add_to_cart(phone: str, product_id: str, quantity: int) -> str: | ||||
|     """ | ||||
|     Añade un producto al carrito del cliente. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         product_id: ID o nombre del producto | ||||
|         quantity: Cantidad a añadir | ||||
|     """ | ||||
|     # Primero intentar obtener el producto por ID o nombre | ||||
|     product = CatalogManager.get_product(product_id) | ||||
|     if not product: | ||||
|         # Si no se encuentra, intentar buscar por nombre exacto | ||||
|         product = CatalogManager.get_product_by_name(product_id) | ||||
|         if not product: | ||||
|             return f"❌ No se encontró el producto '{product_id}' en el catálogo. Por favor, verifica el nombre o ID del producto." | ||||
|  | ||||
|     if product["stock"] < quantity: | ||||
|         return f"❌ Stock insuficiente. Solo hay {product['stock']} {product['unidad']} disponibles de {product['producto']}." | ||||
|  | ||||
|     # Añadir al carrito | ||||
|     success = OrderManager.add_to_cart( | ||||
|         phone,  | ||||
|         str(product["id"]),  | ||||
|         quantity,  | ||||
|         product["precio"], | ||||
|         product["unidad"] | ||||
|     ) | ||||
|      | ||||
|     if success: | ||||
|         remaining_stock = product["stock"] - quantity | ||||
|         response = f"✅ Se añadieron {quantity} {product['unidad']} de {product['producto']} al carrito." | ||||
|          | ||||
|         # Advertencia de stock bajo | ||||
|         if remaining_stock < 10: | ||||
|             response += f"\n⚠️ Aviso: Solo quedan {remaining_stock} {product['unidad']} en stock." | ||||
|              | ||||
|         # Sugerir productos relacionados | ||||
|         related_products = CatalogManager.search_products(product["categoria"]) | ||||
|         if len(related_products) > 1: | ||||
|             response += "\n\n📦 También podría interesarte:" | ||||
|             for related in related_products[:3]: | ||||
|                 if related["id"] != product["id"] and related["stock"] > 0: | ||||
|                     response += f"\n- {related['producto']} ({related['unidad']} a ${related['precio']:,})" | ||||
|          | ||||
|         return response | ||||
|     else: | ||||
|         return "❌ Hubo un error al añadir el producto al carrito. Por favor, intenta de nuevo." | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def remove_from_cart(phone: str, product_id: str) -> str: | ||||
|     """ | ||||
|     Elimina un producto del carrito del cliente. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         product_id: ID o nombre del producto a eliminar | ||||
|     """ | ||||
|     product = CatalogManager.get_product(product_id) | ||||
|     if not product: | ||||
|         product = CatalogManager.get_product_by_name(product_id) | ||||
|         if not product: | ||||
|             return f"❌ No se encontró el producto '{product_id}' en el catálogo." | ||||
|  | ||||
|     success = OrderManager.remove_from_cart(phone, str(product["id"])) | ||||
|     if success: | ||||
|         return f"✅ Se eliminó {product['producto']} del carrito." | ||||
|     else: | ||||
|         return "❌ No se pudo eliminar el producto del carrito. ¿Está seguro que el producto está en su carrito?" | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def view_cart(phone: str) -> str: | ||||
|     """ | ||||
|     Muestra los productos en el carrito del cliente. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|     """ | ||||
|     items = OrderManager.get_cart_items(phone) | ||||
|     if not items: | ||||
|         return "El carrito está vacío." | ||||
|  | ||||
|     result = "🛒 Productos en el carrito:\n\n" | ||||
|     total = 0 | ||||
|     for item in items: | ||||
|         product = CatalogManager.get_product(item["product_id"]) | ||||
|         if product: | ||||
|             subtotal = item["quantity"] * item["price"] | ||||
|             total += subtotal | ||||
|             result += f"- {product['producto']}\n" | ||||
|             result += f"  Cantidad: {item['quantity']} {item['unit']}\n" | ||||
|             result += f"  Precio unitario: ${item['price']:,}\n" | ||||
|             result += f"  Subtotal: ${subtotal:,}\n\n" | ||||
|  | ||||
|     result += f"Total del carrito: ${total:,}" | ||||
|     return result | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def confirm_order(phone: str, delivery_address: Optional[str] = None,  | ||||
|                  payment_method: Optional[str] = None, notes: Optional[str] = None) -> str: | ||||
|     """ | ||||
|     Confirma el pedido actual del cliente. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         delivery_address: Dirección de entrega (opcional) | ||||
|         payment_method: Método de pago (opcional) | ||||
|         notes: Notas adicionales para el pedido (opcional) | ||||
|     """ | ||||
|     success, result = OrderManager.confirm_order( | ||||
|         phone, delivery_address, payment_method, notes | ||||
|     ) | ||||
|      | ||||
|     if success: | ||||
|         order_details = OrderManager.get_order_details(result) | ||||
|         response = "✅ ¡Pedido confirmado exitosamente!\n\n" | ||||
|         response += "📋 Resumen del pedido:\n" | ||||
|          | ||||
|         # Detalles de entrega | ||||
|         if delivery_address: | ||||
|             response += f"📍 Dirección de entrega: {delivery_address}\n" | ||||
|         if payment_method: | ||||
|             response += f"💳 Método de pago: {payment_method}\n" | ||||
|         if notes: | ||||
|             response += f"📝 Notas: {notes}\n" | ||||
|          | ||||
|         response += "\nProductos:\n" | ||||
|         for item in order_details['items']: | ||||
|             product = CatalogManager.get_product(item['product_id']) | ||||
|             response += f"- {item['quantity']} {item['unit']} de {product['producto']} a ${item['price']:,} c/u\n" | ||||
|          | ||||
|         response += f"\n💰 Total: ${order_details['total']:,}" | ||||
|          | ||||
|         if delivery_address: | ||||
|             response += "\n\n🚚 El pedido será preparado y enviado a la dirección proporcionada." | ||||
|             response += "\nRecibirás actualizaciones sobre el estado de tu pedido." | ||||
|         else: | ||||
|             response += "\n\n🏪 El pedido estará listo para retirar en tienda." | ||||
|          | ||||
|         return response | ||||
|     else: | ||||
|         return f"❌ Error al confirmar el pedido: {result}" | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def view_order_history(phone: str) -> str: | ||||
|     """ | ||||
|     Muestra el historial de órdenes del cliente. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|     """ | ||||
|     orders = OrderManager.get_order_history(phone) | ||||
|     if not orders: | ||||
|         return "No hay pedidos registrados para este número." | ||||
|  | ||||
|     result = "📚 Historial de pedidos:\n\n" | ||||
|     for order in orders: | ||||
|         result += f"🔸 Pedido #{order['order_id'][:8]}\n" | ||||
|         result += f"  Fecha: {order['created_at']}\n" | ||||
|         result += f"  Estado: {order['status']}\n" | ||||
|         if order['delivery_status']: | ||||
|             result += f"  Estado de entrega: {order['delivery_status']}\n" | ||||
|         result += f"  Total: ${order['total']:,}\n" | ||||
|         if order['delivery_address']: | ||||
|             result += f"  Dirección: {order['delivery_address']}\n" | ||||
|         if order['notes']: | ||||
|             result += f"  Notas: {order['notes']}\n" | ||||
|         result += "\n" | ||||
|  | ||||
|     return result | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def update_delivery_status(phone: str, order_id: str, status: str) -> str: | ||||
|     """ | ||||
|     Actualiza el estado de entrega de una orden. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         order_id: ID de la orden | ||||
|         status: Nuevo estado de entrega | ||||
|     """ | ||||
|     success = OrderManager.update_delivery_status(order_id, status) | ||||
|     if success: | ||||
|         return f"✅ Estado de entrega actualizado a: {status}" | ||||
|     return "❌ Error al actualizar el estado de entrega" | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def apply_discount(phone: str, discount_amount: float) -> str: | ||||
|     """ | ||||
|     Aplica un descuento al carrito actual. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         discount_amount: Cantidad del descuento | ||||
|     """ | ||||
|     order_id = OrderManager.get_active_cart(phone) | ||||
|     success = OrderManager.apply_discount(order_id, discount_amount) | ||||
|     if success: | ||||
|         return f"✅ Descuento de ${discount_amount:,} aplicado al carrito" | ||||
|     return "❌ Error al aplicar el descuento" | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def get_order_status(phone: str, order_id: str) -> str: | ||||
|     """ | ||||
|     Obtiene el estado actual de una orden específica. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         order_id: ID de la orden | ||||
|     """ | ||||
|     order = OrderManager.get_order_details(order_id) | ||||
|     if not order: | ||||
|         return f"No se encontró la orden con ID {order_id}" | ||||
|      | ||||
|     result = f"📦 Estado del pedido #{order_id[:8]}:\n" | ||||
|     result += f"Estado: {order['status']}\n" | ||||
|     if order['delivery_status']: | ||||
|         result += f"Estado de entrega: {order['delivery_status']}\n" | ||||
|     result += f"Total: ${order['total']:,}\n" | ||||
|     if order['delivery_address']: | ||||
|         result += f"Dirección de entrega: {order['delivery_address']}\n" | ||||
|      | ||||
|     return result | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def merge_orders(phone: str, order_ids: List[str], delivery_address: Optional[str] = None) -> str: | ||||
|     """ | ||||
|     Combina varios pedidos en uno nuevo. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         order_ids: Lista de IDs de pedidos a combinar | ||||
|         delivery_address: Nueva dirección de entrega (opcional) | ||||
|     """ | ||||
|     success, result = OrderManager.merge_orders(phone, order_ids, delivery_address) | ||||
|      | ||||
|     if success: | ||||
|         order_details = OrderManager.get_order_details(result) | ||||
|         response = "✅ ¡Pedidos combinados exitosamente!\n\n" | ||||
|         response += "📋 Resumen del nuevo pedido:\n" | ||||
|          | ||||
|         if delivery_address: | ||||
|             response += f"📍 Dirección de entrega: {delivery_address}\n" | ||||
|          | ||||
|         response += "\nProductos:\n" | ||||
|         for item in order_details['items']: | ||||
|             product = CatalogManager.get_product(item['product_id']) | ||||
|             response += f"- {item['quantity']} {item['unit']} de {product['producto']} a ${item['price']:,} c/u\n" | ||||
|          | ||||
|         response += f"\n💰 Total: ${order_details['total']:,}" | ||||
|         return response | ||||
|     else: | ||||
|         return f"❌ Error: {result}" | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def delete_order(phone: str, order_id: str) -> str: | ||||
|     """ | ||||
|     Elimina un pedido si está en estado permitido. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         order_id: ID del pedido a eliminar | ||||
|     """ | ||||
|     success = OrderManager.delete_order(phone, order_id) | ||||
|      | ||||
|     if success: | ||||
|         return f"✅ El pedido #{order_id[:8]} ha sido eliminado exitosamente." | ||||
|     else: | ||||
|         return "❌ No se pudo eliminar el pedido. Solo se pueden eliminar pedidos en estado 'en carrito' o 'confirmado'." | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def modify_order(phone: str, order_id: str, modifications: str) -> str: | ||||
|     """ | ||||
|     Modifica los productos de un pedido existente. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         order_id: ID del pedido a modificar | ||||
|         modifications: Descripción de las modificaciones (ej: "agregar 2 kg de arroz, quitar café") | ||||
|     """ | ||||
|     # Obtener detalles actuales del pedido | ||||
|     current_order = OrderManager.get_order_details(order_id) | ||||
|     if not current_order: | ||||
|         return f"❌ No se encontró el pedido #{order_id[:8]}" | ||||
|      | ||||
|     # Procesar las modificaciones (esto es un ejemplo simplificado) | ||||
|     try: | ||||
|         # Aquí iría la lógica para interpretar las modificaciones | ||||
|         # Por ahora solo mostraremos un mensaje informativo | ||||
|         return ( | ||||
|             "Para modificar el pedido, por favor especifica exactamente qué cambios deseas hacer:\n" | ||||
|             "- Para agregar productos: 'agregar X unidades de [producto]'\n" | ||||
|             "- Para quitar productos: 'quitar [producto]'\n" | ||||
|             "- Para cambiar cantidades: 'cambiar [producto] a X unidades'\n" | ||||
|             "\nPedido actual:\n" + | ||||
|             "\n".join(f"- {item['quantity']} {item['unit']} de {CatalogManager.get_product(item['product_id'])['producto']}" | ||||
|                      for item in current_order['items']) | ||||
|         ) | ||||
|     except Exception as e: | ||||
|         return f"❌ Error al modificar el pedido: {str(e)}" | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def get_order_products(phone: str, order_id: str) -> str: | ||||
|     """ | ||||
|     Obtiene la lista detallada de productos en un pedido. | ||||
|     Args: | ||||
|         phone: Número de teléfono del cliente | ||||
|         order_id: ID del pedido | ||||
|     """ | ||||
|     # Limpiar el ID del pedido (remover # si existe) | ||||
|     clean_order_id = order_id.strip().replace('#', '') | ||||
|      | ||||
|     order = OrderManager.get_order_details(clean_order_id) | ||||
|     if not order: | ||||
|         return f"❌ No se encontró el pedido #{clean_order_id[:8]}" | ||||
|      | ||||
|     if order['phone'] != phone: | ||||
|         return "❌ Este pedido no pertenece a este cliente" | ||||
|      | ||||
|     if not order.get('items'): | ||||
|         return f"ℹ️ El pedido #{clean_order_id[:8]} no tiene productos registrados" | ||||
|      | ||||
|     result = f"📦 Productos en el pedido #{clean_order_id[:8]}:\n\n" | ||||
|      | ||||
|     total = 0 | ||||
|     for item in order['items']: | ||||
|         product = CatalogManager.get_product(item['product_id']) | ||||
|         if product: | ||||
|             subtotal = item['quantity'] * item['price'] | ||||
|             total += subtotal | ||||
|             result += f"- {item['quantity']} {item['unit']} de {product['producto']}\n" | ||||
|             result += f"  💵 Precio unitario: ${item['price']:,}\n" | ||||
|             result += f"  💰 Subtotal: ${subtotal:,}\n\n" | ||||
|         else: | ||||
|             result += f"- {item['quantity']} {item['unit']} de producto no encontrado (ID: {item['product_id']})\n" | ||||
|      | ||||
|     result += f"💰 Total del pedido: ${order['total']:,}\n" | ||||
|      | ||||
|     if order.get('discount_applied', 0) > 0: | ||||
|         result += f"🏷️ Descuento aplicado: ${order['discount_applied']:,}\n" | ||||
|      | ||||
|     if order.get('delivery_address'): | ||||
|         result += f"📍 Dirección de entrega: {order['delivery_address']}\n" | ||||
|     if order.get('delivery_status'): | ||||
|         result += f"🚚 Estado de entrega: {order['delivery_status']}\n" | ||||
|      | ||||
|     return result | ||||
|  | ||||
|  | ||||
| # Lista de todas las herramientas disponibles | ||||
| tools = [ | ||||
|     add_to_cart, | ||||
|     remove_from_cart, | ||||
|     view_cart, | ||||
|     confirm_order, | ||||
|     view_order_history, | ||||
|     update_delivery_status, | ||||
|     apply_discount, | ||||
|     get_order_status, | ||||
|     merge_orders, | ||||
|     delete_order, | ||||
|     modify_order, | ||||
|     get_order_products, | ||||
| ] | ||||
							
								
								
									
										243
									
								
								agents/app/langgraph_tools/tools/orders/order_tools_2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								agents/app/langgraph_tools/tools/orders/order_tools_2.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,243 @@ | ||||
| from langchain_core.tools import tool | ||||
| from typing import Optional, List | ||||
| import requests | ||||
| import json | ||||
|  | ||||
| url = "http://192.168.0.25:8000" | ||||
| key = "9a9ffc430146447d81e6698240199a4be2b0e774cb18474999d0f60e33b5b1eb1cfff9d9141346a98844879b5a9e787489c891ddc8fb45cc903b7244cab64fb1" | ||||
| db = "tryton" | ||||
| application_name = "sale_don_confiao" | ||||
| url_don_confiao = "{}/{}/{}".format(url, db, application_name) | ||||
| url_order = "{}/{}/{}".format(url, db, "sale_order") | ||||
|  | ||||
|  | ||||
| #@tool | ||||
| def create_party( | ||||
|     party_full_name: str, | ||||
|     contact_method_type: str, | ||||
|     contact_method_value: str, | ||||
| ): | ||||
|     """ | ||||
|     Crea un nuevo cliente (party) en el sistema. | ||||
|      | ||||
|     Args: | ||||
|         party_full_name (str): Nombre completo del cliente. | ||||
|         contact_method_type (str): Tipo de método de contacto (ej. 'email', 'phone'). | ||||
|         contact_method_value (str): Valor del método de contacto (ej. dirección de email o número de teléfono). | ||||
|          | ||||
|     Returns: | ||||
|         requests.Response: La respuesta del servidor que contiene la información del cliente creado. | ||||
|  | ||||
|     """ | ||||
|      | ||||
|     url = "http://192.168.0.25:8000" | ||||
|     key = "9a9ffc430146447d81e6698240199a4be2b0e774cb18474999d0f60e33b5b1eb1cfff9d9141346a98844879b5a9e787489c891ddc8fb45cc903b7244cab64fb1" | ||||
|     db = "tryton" | ||||
|     application_name = "sale_don_confiao" | ||||
|     url_don_confiao = "{}/{}/{}".format(url, db, application_name) | ||||
|     url_order = "{}/{}/{}".format(url, db, "sale_order") | ||||
|  | ||||
|     url_order = url_order.replace("sale_order", "sale_don_confiao") | ||||
|  | ||||
|     data = { | ||||
|         "name": party_full_name, | ||||
|         "contact_mechanisms": [ | ||||
|             ["create", [{"type": contact_method_type, "value": contact_method_value}]] | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = requests.post( | ||||
|         url_order + "/parties", | ||||
|         headers={"Authorization": f"bearer {key}"}, | ||||
|         data=json.dumps(data), | ||||
|     ) | ||||
|  | ||||
|     return json.loads(response.text) | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def create_sale_order( | ||||
|     party: int, pickup_location: str = "on_site", lines: Optional[List] = None | ||||
| ) -> int: | ||||
|     """ | ||||
|     Crea una nueva orden de venta en el sistema. | ||||
|  | ||||
|     Args: | ||||
|         party (int): El ID del cliente. | ||||
|         pickup_location (str, optional): Ubicación de recogida. Valores posibles: | ||||
|             - "on_site": Recoger en el local | ||||
|             - "at_home": Entrega a domicilio | ||||
|             Por defecto es "on_site". | ||||
|         lines (List, optional): Lista de líneas de la orden. Por defecto es None. | ||||
|             Cada línea debe ser una lista con el formato: ["create", [{"product": str, "unit": str, "quantity": str, "unitprice": str}]] | ||||
|             Donde: | ||||
|                 - product (str): ID del producto | ||||
|                 - unit (str): ID de la unidad de medida | ||||
|                 - quantity (str): Cantidad del producto | ||||
|                 - unitprice (str): Precio unitario del producto | ||||
|             Ejemplo: | ||||
|                 lines=[["create", [{ | ||||
|                     "product": "1", | ||||
|                     "unit": "1", | ||||
|                     "quantity": "5", | ||||
|                     "unitprice": "10" | ||||
|                 }]]] | ||||
|  | ||||
|     Returns: | ||||
|         int: El ID de la orden creada | ||||
|     """ | ||||
|     data = { | ||||
|         "party": party, | ||||
|         "pickup_location": pickup_location, | ||||
|     } | ||||
|  | ||||
|     if lines: | ||||
|         data["lines"] = lines | ||||
|  | ||||
|     response = requests.post( | ||||
|         url_order + "/order", | ||||
|         headers={ | ||||
|             "Authorization": f"bearer {key}", | ||||
|         }, | ||||
|         data=json.dumps(data), | ||||
|     ) | ||||
|  | ||||
|     # Extraer y retornar directamente el ID de la orden | ||||
|     return json.loads(json.loads(response.text)[0]).get("id") | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def search_sale_order(order_id: int): | ||||
|     """ | ||||
|     Busca una orden de venta específica en el sistema. | ||||
|  | ||||
|     Args: | ||||
|         order_id (int): El ID de la orden de venta a buscar. | ||||
|  | ||||
|     Returns: | ||||
|         requests.Response: La respuesta del servidor que contiene: | ||||
|             - party (int): ID del cliente | ||||
|             - id (int): ID de la orden | ||||
|             - lines (List): Lista de líneas de la orden | ||||
|             Ejemplo de respuesta: | ||||
|             [{"party": 2573, "id": 22, "lines": []}] | ||||
|     """ | ||||
|     response_sale = requests.get( | ||||
|         url_order + f"/order/{order_id}", | ||||
|         headers={ | ||||
|             "Authorization": f"bearer {key}", | ||||
|         }, | ||||
|     ) | ||||
|  | ||||
|     return response_sale | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def add_lines_to_order( | ||||
|     order_id: int, product: str, unit: str, quantity: str, unitprice: str | ||||
| ): | ||||
|     """ | ||||
|     Agrega una línea de producto a una orden existente. | ||||
|  | ||||
|     Args: | ||||
|         order_id (int): ID de la orden a la que se agregará la línea. | ||||
|         product (str): ID del producto a agregar. | ||||
|         unit (str): ID de la unidad de medida del producto. | ||||
|         quantity (str): Cantidad del producto a agregar. | ||||
|         unitprice (str): Precio unitario del producto. | ||||
|  | ||||
|     Returns: | ||||
|         requests.Response: La respuesta del servidor que contiene: | ||||
|             - order_lines (List[int]): Lista de IDs de las líneas creadas | ||||
|             - status (str): Estado de la operación ('success' si fue exitosa) | ||||
|             - message (str): Mensaje descriptivo del resultado | ||||
|             Ejemplo de respuesta: | ||||
|             {"order_lines": [1], "status": "success", "message": "Order lines created successfully"} | ||||
|     """ | ||||
|     data = { | ||||
|         "order": order_id, | ||||
|         "product": product, | ||||
|         "unit": unit, | ||||
|         "quantity": quantity, | ||||
|         "unitprice": unitprice, | ||||
|     } | ||||
|  | ||||
|     response = requests.post( | ||||
|         url_order + f"/{order_id}/order_line", | ||||
|         headers={ | ||||
|             "Authorization": f"bearer {key}", | ||||
|         }, | ||||
|         data=json.dumps(data), | ||||
|     ) | ||||
|  | ||||
|     return response | ||||
|  | ||||
|  | ||||
| @tool | ||||
| def search_associate_party_to_contact_mechanism(contact_mechanism: str): | ||||
|     """ | ||||
|     Busca un cliente en el sistema por un metodo de contacto que pueda ser un numero de celular o un correo electronico. | ||||
|  | ||||
|     Args: | ||||
|         contact_mechanism (str): El número de contacto del cliente. | ||||
|  | ||||
|     Returns: | ||||
|         requests.Response: La respuesta del servidor que contiene: | ||||
|             - id (int): ID de la asociación en la base de datos | ||||
|             - associate_party (int): ID del cliente asociado (Party) | ||||
|             - status (str): Estado de la operación ('success' si fue exitosa) | ||||
|             - type (str): Tipo de contacto asociado | ||||
|             - message (str): Mensaje descriptivo del resultado | ||||
|             Ejemplo de respuesta: | ||||
|             {"id": 1, "associate_party": 1, "status": "success", "type": "phone", "message": "Associate Party found"} | ||||
|     """ | ||||
|     response = requests.get( | ||||
|         url_order + f"/associate_party/{contact_mechanism}", | ||||
|         headers={ | ||||
|             "Authorization": f"bearer {key}", | ||||
|         }, | ||||
|     ) | ||||
|  | ||||
|     return response | ||||
|  | ||||
|  | ||||
| tools = [ | ||||
|     create_party, | ||||
|     create_sale_order, | ||||
|     search_sale_order, | ||||
|     add_lines_to_order, | ||||
|     search_associate_party_to_contact_mechanism, | ||||
| ] | ||||
|  | ||||
| # if __name__ == "__main__": | ||||
| #     # Crear una orden de venta | ||||
| #     party = 2573 | ||||
| #     pickup_location = "at_home" | ||||
| #     order_id = create_sale_order(party=party, pickup_location=pickup_location) | ||||
| #     print(f"\nOrden creada con ID: {order_id}") | ||||
|  | ||||
| #     # Agregar líneas a la orden | ||||
| #     product = "1" | ||||
| #     unit = "1" | ||||
| #     quantity = "3" | ||||
| #     unitprice = "15" | ||||
|  | ||||
| #     add_line_response = add_lines_to_order(order_id, product, unit, quantity, unitprice) | ||||
| #     print(f"\nRespuesta al agregar línea: {add_line_response.text}") | ||||
|  | ||||
| #     # Verificar la orden actualizada | ||||
| #     updated_order = search_sale_order(order_id) | ||||
| #     print(f"\nOrden actualizada: {updated_order.text}") | ||||
|  | ||||
| #     # Agregar otra línea a la orden | ||||
| #     product = "2" | ||||
| #     unit = "1" | ||||
| #     quantity = "3" | ||||
| #     unitprice = "15" | ||||
|  | ||||
| #     add_line_response = add_lines_to_order(order_id, product, unit, quantity, unitprice) | ||||
| #     print(f"\nRespuesta al agregar línea: {add_line_response.text}") | ||||
|  | ||||
| #     # Verificar la orden actualizada | ||||
| #     updated_order = search_sale_order(order_id) | ||||
| #     print(f"\nOrden actualizada: {updated_order.text}") | ||||
		Reference in New Issue
	
	Block a user