""" WhatsApp Learning Service — ruta de aprendizaje para piezas no resueltas. Registra sesiones donde el bot no pudo identificar una pieza, y las resuelve asíncronamente cuando el cliente realiza una compra futura. """ import json def register_unresolved_search(phone, customer_id, description, offered_parts, tenant_conn): """Registrar una sesión no resuelta para aprendizaje futuro.""" if not tenant_conn or not phone or not description: return None cur = tenant_conn.cursor() cur.execute(""" INSERT INTO wa_learning_sessions (phone, customer_id, description, offered_parts, status, created_at) VALUES (%s, %s, %s, %s, 'pending', NOW()) RETURNING id """, (phone, customer_id, description, json.dumps(offered_parts or []))) sid = cur.fetchone()[0] tenant_conn.commit() cur.close() return sid def find_pending_sessions(phone, tenant_conn): """Buscar sesiones pendientes de aprendizaje para un número WA.""" if not tenant_conn or not phone: return [] cur = tenant_conn.cursor() cur.execute(""" SELECT id, description, offered_parts, created_at FROM wa_learning_sessions WHERE phone = %s AND status = 'pending' ORDER BY created_at DESC """, (phone,)) rows = cur.fetchall() cur.close() return [{'id': r[0], 'description': r[1], 'offered_parts': r[2], 'created_at': str(r[3])} for r in rows] def find_pending_sessions_by_customer(customer_id, tenant_conn): """Buscar sesiones pendientes por customer_id.""" if not tenant_conn or not customer_id: return [] cur = tenant_conn.cursor() cur.execute(""" SELECT id, phone, description, offered_parts, created_at FROM wa_learning_sessions WHERE customer_id = %s AND status = 'pending' ORDER BY created_at DESC """, (customer_id,)) rows = cur.fetchall() cur.close() return [{'id': r[0], 'phone': r[1], 'description': r[2], 'offered_parts': r[3], 'created_at': str(r[4])} for r in rows] def resolve_session(session_id, resolved_part_id, sale_id, tenant_conn): """Marcar sesión como resuelta con la pieza comprada.""" if not tenant_conn or not session_id: return cur = tenant_conn.cursor() cur.execute(""" UPDATE wa_learning_sessions SET status = 'learned', resolved_part_id = %s, resolution_sale_id = %s, resolved_at = NOW() WHERE id = %s """, (resolved_part_id, sale_id, session_id)) tenant_conn.commit() cur.close() def get_learning_pairs_for_training(tenant_conn, limit=100): """Obtener pares (descripción del cliente → pieza real) para entrenamiento.""" if not tenant_conn: return [] cur = tenant_conn.cursor() cur.execute(""" SELECT l.description, i.name, i.part_number, i.brand FROM wa_learning_sessions l JOIN inventory i ON i.id = l.resolved_part_id WHERE l.status = 'learned' AND l.resolved_at > NOW() - INTERVAL '90 days' ORDER BY l.resolved_at DESC LIMIT %s """, (limit,)) rows = cur.fetchall() cur.close() return [{'description': r[0], 'part_name': r[1], 'part_number': r[2], 'brand': r[3]} for r in rows] def check_learning_resolution(sale_id, customer_id, tenant_conn): """ Hook para llamar después de completar una venta. Verifica si esta venta resuelve una sesión de aprendizaje pendiente. """ if not tenant_conn or not customer_id: return sessions = find_pending_sessions_by_customer(customer_id, tenant_conn) if not sessions: return # Obtener items de esta venta cur = tenant_conn.cursor() cur.execute(""" SELECT si.inventory_id, i.name, i.part_number FROM sale_items si JOIN inventory i ON i.id = si.inventory_id WHERE si.sale_id = %s """, (sale_id,)) sale_items = cur.fetchall() cur.close() if not sale_items: return # Matching heurístico for sess in sessions: desc_words = set(sess['description'].lower().split()) for inv_id, item_name, part_number in sale_items: item_words = set(item_name.lower().split()) # Intersección de palabras significativas common = desc_words & item_words - {'de', 'la', 'el', 'para', 'un', 'una', 'con', 'y', 'o', 'en', 'al', 'del', 'los', 'las'} if len(common) >= 2: resolve_session(sess['id'], inv_id, sale_id, tenant_conn) print(f"[WA-LEARN] Resolved session {sess['id']} with sale {sale_id}, item {inv_id}") break