feat(pos): chatbot IA con OpenRouter — busqueda de partes por lenguaje natural

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 07:18:55 +00:00
parent 32581739ad
commit 0a44fb5304
5 changed files with 776 additions and 0 deletions

148
pos/blueprints/chat_bp.py Normal file
View File

@@ -0,0 +1,148 @@
# /home/Autopartes/pos/blueprints/chat_bp.py
"""Chat blueprint: AI-powered parts lookup via natural language.
Endpoints (all under /pos/api/chat):
POST / — send a message, get AI response + catalog search results
"""
from flask import Blueprint, request, jsonify, g
from middleware import require_auth
from tenant_db import get_master_conn, get_tenant_conn
from services import catalog_service, ai_chat
chat_bp = Blueprint("chat", __name__, url_prefix="/pos/api/chat")
@chat_bp.route("", methods=["POST"])
@require_auth("catalog.view")
def chat():
body = request.get_json(force=True)
user_message = (body.get("message") or "").strip()
if not user_message:
return jsonify({"error": "message required"}), 400
history = body.get("history") or []
# Call AI
ai_response = ai_chat.chat(user_message, history)
search_results = []
vehicle_match = None
master = None
tenant = None
try:
# If AI suggests a search query, run it against the catalog
search_query = ai_response.get("search_query")
vehicle = ai_response.get("vehicle")
if search_query or vehicle:
master = get_master_conn()
tenant = get_tenant_conn(g.tenant_id)
branch_id = g.branch_id
# Try to resolve vehicle to MYE
if vehicle and master:
vehicle_match = _resolve_vehicle(master, vehicle)
# Run catalog search if we have a search query
if search_query and master and tenant:
try:
results = catalog_service.smart_search(
master, search_query, tenant, branch_id, limit=10
)
search_results = results if results else []
except Exception:
pass # search failure is non-fatal
except Exception:
pass # DB failure is non-fatal for chat
finally:
if master:
try:
master.close()
except Exception:
pass
if tenant:
try:
tenant.close()
except Exception:
pass
return jsonify(
{
"response": ai_response.get("message", ""),
"search_results": search_results,
"vehicle": vehicle_match or ai_response.get("vehicle"),
}
)
def _resolve_vehicle(master_conn, vehicle):
"""Try to resolve AI-extracted vehicle info to brand_id/model_id in DB."""
brand_name = (vehicle.get("brand") or "").upper().strip()
model_name = (vehicle.get("model") or "").strip()
year = vehicle.get("year")
if not brand_name:
return vehicle
cur = master_conn.cursor()
result = dict(vehicle)
try:
# Find brand
cur.execute(
"SELECT id_brand, name_brand FROM brands WHERE UPPER(name_brand) = %s",
(brand_name,),
)
brand_row = cur.fetchone()
if brand_row:
result["brand_id"] = brand_row[0]
result["brand"] = brand_row[1]
# Find model
if model_name:
cur.execute(
"""SELECT m.id_model, m.name_model
FROM models m
WHERE m.brand_id = %s
AND UPPER(m.name_model) LIKE %s
ORDER BY m.name_model
LIMIT 5""",
(brand_row[0], f"%{model_name.upper()}%"),
)
model_row = cur.fetchone()
if model_row:
result["model_id"] = model_row[0]
result["model"] = model_row[1]
# Find year -> MYE
if year:
cur.execute(
"""SELECT mye.id_mye, y.year_car, e.name_engine, mye.trim_level
FROM model_year_engine mye
JOIN years y ON y.id_year = mye.year_id
JOIN engines e ON e.id_engine = mye.engine_id
WHERE mye.model_id = %s AND y.year_car = %s
ORDER BY e.name_engine
LIMIT 10""",
(model_row[0], int(year)),
)
mye_rows = cur.fetchall()
if mye_rows:
result["mye_options"] = [
{
"mye_id": r[0],
"year": r[1],
"engine": r[2],
"trim": r[3],
}
for r in mye_rows
]
except Exception:
pass
finally:
cur.close()
return result