productos, equivalencias, cross-sell y cantidades
This commit is contained in:
@@ -594,7 +594,7 @@ export async function getRecoRulesByProductIds({ tenant_id, product_ids = [] })
|
||||
export async function getRecoRuleByKey({ tenant_id, rule_key }) {
|
||||
const sql = `
|
||||
select id, tenant_id, rule_key, trigger, queries, boosts, ask_slots, active, priority,
|
||||
trigger_product_ids, recommended_product_ids, created_at, updated_at
|
||||
trigger_product_ids, recommended_product_ids, rule_type, trigger_event, created_at, updated_at
|
||||
from product_reco_rules
|
||||
where tenant_id=$1 and rule_key=$2
|
||||
limit 1
|
||||
@@ -603,6 +603,95 @@ export async function getRecoRuleByKey({ tenant_id, rule_key }) {
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener reglas de qty_per_person por tipo de evento (asado, horno, etc.)
|
||||
* DEPRECATED: Usar getProductQtyRulesByEvent en su lugar
|
||||
*/
|
||||
export async function getQtyPerPersonRules({ tenant_id, event_type }) {
|
||||
const sql = `
|
||||
select r.id, r.rule_key, r.rule_type, r.trigger_event, r.priority,
|
||||
json_agg(json_build_object(
|
||||
'woo_product_id', i.woo_product_id,
|
||||
'audience_type', i.audience_type,
|
||||
'qty_per_person', i.qty_per_person,
|
||||
'unit', i.unit,
|
||||
'reason', i.reason,
|
||||
'display_order', i.display_order
|
||||
) order by i.display_order) as items
|
||||
from product_reco_rules r
|
||||
inner join reco_rule_items i on i.rule_id = r.id
|
||||
where r.tenant_id = $1
|
||||
and r.active = true
|
||||
and r.rule_type = 'qty_per_person'
|
||||
and (r.trigger_event = $2 or r.trigger_event is null)
|
||||
group by r.id, r.rule_key, r.rule_type, r.trigger_event, r.priority
|
||||
order by
|
||||
case when r.trigger_event = $2 then 0 else 1 end,
|
||||
r.priority asc
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [tenant_id, event_type]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener reglas de cantidad por evento desde la nueva tabla product_qty_rules
|
||||
*/
|
||||
export async function getProductQtyRulesByEvent({ tenant_id, event_type }) {
|
||||
const sql = `
|
||||
select woo_product_id, event_type, person_type, qty_per_person, unit
|
||||
from product_qty_rules
|
||||
where tenant_id = $1 and event_type = $2
|
||||
order by woo_product_id, person_type
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [tenant_id, event_type]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener items de una regla específica con detalles
|
||||
*/
|
||||
export async function getRecoRuleItems({ rule_id }) {
|
||||
const sql = `
|
||||
select id, rule_id, woo_product_id, audience_type, qty_per_person, unit, reason, display_order
|
||||
from reco_rule_items
|
||||
where rule_id = $1
|
||||
order by display_order asc
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [rule_id]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener productos mapeados a un alias con scores
|
||||
*/
|
||||
export async function getAliasProductMappings({ tenant_id, alias }) {
|
||||
const normalizedAlias = String(alias || "").toLowerCase().trim();
|
||||
if (!normalizedAlias) return [];
|
||||
|
||||
const sql = `
|
||||
select woo_product_id, score
|
||||
from alias_product_mappings
|
||||
where tenant_id = $1 and alias = $2
|
||||
order by score desc
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [tenant_id, normalizedAlias]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener todos los mappings de alias para un tenant (para búsqueda)
|
||||
*/
|
||||
export async function getAllAliasProductMappings({ tenant_id }) {
|
||||
const sql = `
|
||||
select alias, woo_product_id, score
|
||||
from alias_product_mappings
|
||||
where tenant_id = $1
|
||||
order by alias, score desc
|
||||
`;
|
||||
const { rows } = await pool.query(sql, [tenant_id]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function getProductEmbedding({ tenant_id, content_hash }) {
|
||||
const sql = `
|
||||
select tenant_id, content_hash, content_text, embedding, model, updated_at
|
||||
|
||||
@@ -338,7 +338,13 @@ const runStatus = llmMeta?.error ? "warn" : "ok";
|
||||
railguard: { simulated: isSimulated, source: meta?.source || null },
|
||||
woo_customer_error: wooCustomerError,
|
||||
};
|
||||
const nextState = safeNextState(prev_state, context, { requested_checkout: plan.intent === "checkout" }).next_state;
|
||||
// El nuevo FSM usa context.order, extraerlo para safeNextState
|
||||
const orderForFsm = context?.order || context?.order_basket || {};
|
||||
const signals = {
|
||||
confirm_order: plan.intent === "confirm_order",
|
||||
payment_selected: plan.intent === "select_payment",
|
||||
};
|
||||
const nextState = safeNextState(prev_state, orderForFsm, signals).next_state;
|
||||
plan.next_state = nextState;
|
||||
|
||||
const stateRow = await upsertConversationState({
|
||||
@@ -366,8 +372,17 @@ const nextState = safeNextState(prev_state, context, { requested_checkout: plan.
|
||||
await updateRunLatency({ tenant_id: tenantId, run_id, latency_ms: end_to_end_ms });
|
||||
}
|
||||
|
||||
// Incluir carrito completo para la UI
|
||||
const fullBasket = context?.order_basket?.items || [];
|
||||
// Incluir carrito completo para la UI (nuevo formato order.cart o legacy order_basket)
|
||||
const orderData = context?.order || {};
|
||||
const fullBasket = (orderData.cart || []).map(c => ({
|
||||
product_id: c.woo_id,
|
||||
woo_product_id: c.woo_id,
|
||||
quantity: c.qty,
|
||||
unit: c.unit,
|
||||
label: c.name,
|
||||
name: c.name,
|
||||
price: c.price,
|
||||
}));
|
||||
|
||||
sseSend("run.created", {
|
||||
run_id,
|
||||
@@ -377,6 +392,8 @@ const nextState = safeNextState(prev_state, context, { requested_checkout: plan.
|
||||
status: runStatus,
|
||||
prev_state,
|
||||
input: { text },
|
||||
// Incluir order completo para la UI
|
||||
order: orderData,
|
||||
llm_output: { ...plan, _llm: llmMeta, full_basket: { items: fullBasket } },
|
||||
tools,
|
||||
invariants,
|
||||
|
||||
Reference in New Issue
Block a user