modularizado de prompts

This commit is contained in:
Lucas Tettamanti
2026-01-25 20:51:33 -03:00
parent b91ece867b
commit a489ec66a2
43 changed files with 5408 additions and 89 deletions

View File

@@ -19,6 +19,7 @@ import {
formatOptionsForDisplay,
} from "./orderModel.js";
import { handleRecommend } from "./recommendations.js";
import { getProductQtyRules } from "../0-ui/db/repo.js";
// ─────────────────────────────────────────────────────────────
// Utilidades
@@ -356,6 +357,62 @@ export async function handleCartState({ tenantId, text, nlu, order, audit, fromI
};
}
if (nextPending.status === PendingStatus.NEEDS_QUANTITY) {
// Detectar "para X personas" en el texto original ANTES de preguntar cantidad
const personasMatch = /(?:para\s+)?(\d+)\s*(personas?|comensales?|invitados?)/i.exec(text || "") ||
/\bpara\s+(\d+)\b/i.exec(text || "") ||
/\bcomo\s+para\s+(\d+)\b/i.exec(text || "");
if (personasMatch && nextPending.selected_woo_id) {
const peopleCount = parseInt(personasMatch[1], 10);
if (peopleCount > 0 && peopleCount <= 100) {
// Buscar reglas de cantidad por persona para este producto
let qtyRules = [];
try {
qtyRules = await getProductQtyRules({ tenantId, wooProductId: nextPending.selected_woo_id });
} catch (e) {
audit.qty_rules_error = e?.message;
}
// Calcular cantidad recomendada
let calculatedQty;
let calculatedUnit = nextPending.selected_unit || "kg";
const rule = qtyRules[0];
if (rule && rule.qty_per_person > 0) {
calculatedQty = rule.qty_per_person * peopleCount;
calculatedUnit = rule.unit || calculatedUnit;
audit.qty_from_rule = { rule_id: rule.id, qty_per_person: rule.qty_per_person, people: peopleCount };
} else {
// Fallback: 0.3 kg por persona para carnes
calculatedQty = 0.3 * peopleCount;
audit.qty_fallback = { default_per_person: 0.3, people: peopleCount };
}
// Actualizar el pending item y mover al cart
const updatedOrder = updatePendingItem(currentOrder, nextPending.id, {
qty: calculatedQty,
unit: calculatedUnit,
status: PendingStatus.READY,
});
const finalOrder = moveReadyToCart(updatedOrder);
const qtyStr = calculatedUnit === "unit" ? calculatedQty : `${calculatedQty}${calculatedUnit}`;
return {
plan: {
reply: `Para ${peopleCount} personas, te recomiendo ${qtyStr} de ${nextPending.selected_name}. Ya lo anoté. ¿Algo más?`,
next_state: ConversationState.CART,
intent: "add_to_cart",
missing_fields: [],
order_action: "add_to_cart",
},
decision: { actions: [{ type: "add_to_cart" }], order: finalOrder, audit },
};
}
}
// Si no hay "para X personas", preguntar cantidad normalmente
const unitQuestion = unitAskFor(nextPending.selected_unit || "kg");
return {
plan: {
@@ -840,6 +897,73 @@ async function processPendingClarification({ tenantId, text, nlu, order, pending
};
}
// Detectar "para X personas" y calcular cantidad automáticamente
const personasMatch = /(?:para\s+)?(\d+)\s*(personas?|comensales?|invitados?)/i.exec(text || "") ||
/\bpara\s+(\d+)\b/i.exec(text || "") ||
/\bcomo\s+para\s+(\d+)\b/i.exec(text || "");
if (personasMatch && pendingItem.selected_woo_id) {
const peopleCount = parseInt(personasMatch[1], 10);
if (peopleCount > 0 && peopleCount <= 100) {
// Buscar reglas de cantidad por persona para este producto
let qtyRules = [];
try {
qtyRules = await getProductQtyRules({ tenantId, wooProductId: pendingItem.selected_woo_id });
} catch (e) {
audit.qty_rules_error = e?.message;
}
// Buscar regla para evento "asado" o genérica (null)
const rule = qtyRules.find(r => r.event_type === "asado" && r.person_type === "adult") ||
qtyRules.find(r => r.event_type === null && r.person_type === "adult") ||
qtyRules.find(r => r.person_type === "adult") ||
qtyRules[0];
let calculatedQty;
let calculatedUnit = pendingItem.selected_unit || "kg";
if (rule && rule.qty_per_person > 0) {
// Usar regla de BD
calculatedQty = rule.qty_per_person * peopleCount;
calculatedUnit = rule.unit || calculatedUnit;
audit.qty_rule_used = { rule_id: rule.id, qty_per_person: rule.qty_per_person, unit: rule.unit };
} else {
// Fallback: 300g por persona para productos por peso
const fallbackPerPerson = calculatedUnit === "unit" ? 1 : 0.3;
calculatedQty = fallbackPerPerson * peopleCount;
audit.qty_fallback_used = { per_person: fallbackPerPerson, unit: calculatedUnit };
}
// Redondear a 1 decimal para kg, entero para unidades
if (calculatedUnit === "unit") {
calculatedQty = Math.ceil(calculatedQty);
} else {
calculatedQty = Math.round(calculatedQty * 10) / 10;
}
const updatedOrder = updatePendingItem(order, pendingItem.id, {
qty: calculatedQty,
unit: calculatedUnit,
status: PendingStatus.READY,
});
const finalOrder = moveReadyToCart(updatedOrder);
const qtyStr = calculatedUnit === "unit" ? calculatedQty : `${calculatedQty}${calculatedUnit}`;
return {
plan: {
reply: `Para ${peopleCount} personas, te recomiendo ${qtyStr} de ${pendingItem.selected_name}. Ya lo anoté. ¿Algo más?`,
next_state: ConversationState.CART,
intent: "add_to_cart",
missing_fields: [],
order_action: "add_to_cart",
},
decision: { actions: [{ type: "add_to_cart" }], order: finalOrder, audit },
};
}
}
// No entendió cantidad
const unitQuestion = unitAskFor(pendingItem.selected_unit || "kg");
return {