El bot conversacional no maneja pagos. Su trabajo: pedidos, datos de entrega, dejar la orden anotada en Woo (status=pending). El cobro lo gestiona el comercio offline. Todo lo de payment_type / is_paid / PAYMENT / WAITING_WEBHOOKS era legacy de un flow viejo que se baja. Nuevo flow: IDLE → CART → SHIPPING → IDLE (con orden creada). Cuando el usuario completa shipping (pickup elegido OR delivery+address), shipping.js emite create_order y el bot cierra con order.confirmed. - fsm.js: 4 estados (IDLE/CART/SHIPPING/AWAITING_HUMAN). hasPaymentInfo e isPaid eliminados. deriveNextState gira SHIPPING→IDLE en vez de →PAYMENT→WAITING. ALLOWED transitions actualizadas. - orderModel.js: createEmptyOrder() sin payment_type/is_paid. migrateOldContext deja de leer payment_method / mp.payment_status. - stateHandlers: payment.js y waiting.js eliminados. shipping.js gana finalizeOrder() que emite create_order action y vuelve a IDLE. - replyTemplates: payment.* y waiting.* fuera. order.confirmed nuevo, con 3 variantes y rewriter habilitado. - NLU openai.js + nlu/schemas.js: select_payment fuera del enum, payment_method fuera de entities. Prompt sin la regla de SELECCIONAR PAGO. - nlu/router.js + nlu/index.js: dominio "payment" eliminado. shouldSkipRouter ya no chequea PAYMENT. - nlu/specialists/payment.js: eliminado. - promptsRepo.js + promptLoader.js: PROMPT_KEYS sin "payment". - turnEngineV3.js: switch ya no dispatcha a PAYMENT/WAITING. normalizeState mapea estados legacy (PAYMENT/WAITING_WEBHOOKS/COMPLETED) a IDLE. context_patch ya no emite payment_method. - wooOrders.createOrder: paymentMethod param eliminado. Order queda en status=pending sin payment_method (cobro offline). - pipeline.js: paymentMethod fuera del create_order glue. Invariant "no_checkout_without_payment_link" eliminado. signal payment_selected reemplazado por shipping_completed. - XState machine: top-level PAYMENT y WAITING eliminados. SELECT_PAYMENT event fuera. SHIPPING ahora cierra con enqueueWooCreateOrder + replyOrderConfirmed → IDLE. Guards hasPayment/isPaid borrados. - Tests fsm.test.js / orderModel.test.js / machine/index.test.js actualizados al nuevo contrato. 188 tests pasando. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
183 lines
5.7 KiB
JavaScript
183 lines
5.7 KiB
JavaScript
import { pool } from "../../shared/db/pool.js";
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Prompt Templates - CRUD con versionado
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
// Prompt keys válidos
|
|
export const PROMPT_KEYS = ["router", "greeting", "orders", "shipping", "browse"];
|
|
|
|
// Modelos por defecto para cada prompt
|
|
export const DEFAULT_MODELS = {
|
|
router: "gpt-4o-mini",
|
|
greeting: "gpt-4-turbo",
|
|
orders: "gpt-4-turbo",
|
|
shipping: "gpt-4o-mini",
|
|
browse: "gpt-4-turbo",
|
|
};
|
|
|
|
/**
|
|
* Obtiene el prompt activo para un tenant y key
|
|
* @returns {Object|null} { id, prompt_key, content, model, version, is_active, created_at, created_by }
|
|
*/
|
|
export async function getActivePrompt({ tenantId, promptKey }) {
|
|
const sql = `
|
|
SELECT id, prompt_key, content, model, version, is_active, created_at, created_by
|
|
FROM prompt_templates
|
|
WHERE tenant_id = $1 AND prompt_key = $2 AND is_active = true
|
|
LIMIT 1
|
|
`;
|
|
const { rows } = await pool.query(sql, [tenantId, promptKey]);
|
|
return rows[0] || null;
|
|
}
|
|
|
|
/**
|
|
* Lista todos los prompts activos de un tenant
|
|
*/
|
|
export async function listActivePrompts({ tenantId }) {
|
|
const sql = `
|
|
SELECT id, prompt_key, content, model, version, is_active, created_at, created_by
|
|
FROM prompt_templates
|
|
WHERE tenant_id = $1 AND is_active = true
|
|
ORDER BY prompt_key
|
|
`;
|
|
const { rows } = await pool.query(sql, [tenantId]);
|
|
return rows;
|
|
}
|
|
|
|
/**
|
|
* Obtiene todas las versiones de un prompt
|
|
*/
|
|
export async function getPromptVersions({ tenantId, promptKey, limit = 20 }) {
|
|
const sql = `
|
|
SELECT id, prompt_key, content, model, version, is_active, created_at, created_by
|
|
FROM prompt_templates
|
|
WHERE tenant_id = $1 AND prompt_key = $2
|
|
ORDER BY version DESC
|
|
LIMIT $3
|
|
`;
|
|
const { rows } = await pool.query(sql, [tenantId, promptKey, limit]);
|
|
return rows;
|
|
}
|
|
|
|
/**
|
|
* Obtiene una versión específica de un prompt
|
|
*/
|
|
export async function getPromptVersion({ tenantId, promptKey, version }) {
|
|
const sql = `
|
|
SELECT id, prompt_key, content, model, version, is_active, created_at, created_by
|
|
FROM prompt_templates
|
|
WHERE tenant_id = $1 AND prompt_key = $2 AND version = $3
|
|
LIMIT 1
|
|
`;
|
|
const { rows } = await pool.query(sql, [tenantId, promptKey, version]);
|
|
return rows[0] || null;
|
|
}
|
|
|
|
/**
|
|
* Desactiva el prompt activo actual (para crear nueva versión)
|
|
*/
|
|
export async function deactivatePrompt({ tenantId, promptKey }) {
|
|
const sql = `
|
|
UPDATE prompt_templates
|
|
SET is_active = false
|
|
WHERE tenant_id = $1 AND prompt_key = $2 AND is_active = true
|
|
`;
|
|
await pool.query(sql, [tenantId, promptKey]);
|
|
}
|
|
|
|
/**
|
|
* Crea una nueva versión del prompt (automáticamente desactiva la anterior)
|
|
* @returns {Object} El prompt creado con su versión
|
|
*/
|
|
export async function createPrompt({ tenantId, promptKey, content, model, createdBy = null }) {
|
|
// Validar prompt_key
|
|
if (!PROMPT_KEYS.includes(promptKey)) {
|
|
throw new Error(`Invalid prompt_key: ${promptKey}. Valid keys: ${PROMPT_KEYS.join(", ")}`);
|
|
}
|
|
|
|
// Desactivar versión anterior
|
|
await deactivatePrompt({ tenantId, promptKey });
|
|
|
|
// Insertar nueva versión (el trigger calcula la versión automáticamente)
|
|
const sql = `
|
|
INSERT INTO prompt_templates (tenant_id, prompt_key, content, model, is_active, created_by)
|
|
VALUES ($1, $2, $3, $4, true, $5)
|
|
RETURNING id, prompt_key, content, model, version, is_active, created_at, created_by
|
|
`;
|
|
const { rows } = await pool.query(sql, [
|
|
tenantId,
|
|
promptKey,
|
|
content,
|
|
model || DEFAULT_MODELS[promptKey] || "gpt-4-turbo",
|
|
createdBy,
|
|
]);
|
|
return rows[0];
|
|
}
|
|
|
|
/**
|
|
* Restaura una versión anterior del prompt (crea nueva versión con el contenido antiguo)
|
|
*/
|
|
export async function rollbackPrompt({ tenantId, promptKey, toVersion, createdBy = null }) {
|
|
// Obtener la versión a restaurar
|
|
const oldVersion = await getPromptVersion({ tenantId, promptKey, version: toVersion });
|
|
if (!oldVersion) {
|
|
throw new Error(`Version ${toVersion} not found for prompt ${promptKey}`);
|
|
}
|
|
|
|
// Crear nueva versión con el contenido antiguo
|
|
return createPrompt({
|
|
tenantId,
|
|
promptKey,
|
|
content: oldVersion.content,
|
|
model: oldVersion.model,
|
|
createdBy,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Resetea un prompt a su default (desactiva todas las versiones custom)
|
|
*/
|
|
export async function resetPromptToDefault({ tenantId, promptKey }) {
|
|
const sql = `
|
|
UPDATE prompt_templates
|
|
SET is_active = false
|
|
WHERE tenant_id = $1 AND prompt_key = $2
|
|
`;
|
|
await pool.query(sql, [tenantId, promptKey]);
|
|
return { success: true, message: `Prompt ${promptKey} reset to default` };
|
|
}
|
|
|
|
/**
|
|
* Elimina todas las versiones de un prompt (usar con cuidado)
|
|
*/
|
|
export async function deleteAllPromptVersions({ tenantId, promptKey }) {
|
|
const sql = `
|
|
DELETE FROM prompt_templates
|
|
WHERE tenant_id = $1 AND prompt_key = $2
|
|
RETURNING id
|
|
`;
|
|
const { rows } = await pool.query(sql, [tenantId, promptKey]);
|
|
return { deleted: rows.length };
|
|
}
|
|
|
|
/**
|
|
* Obtiene estadísticas de prompts de un tenant
|
|
*/
|
|
export async function getPromptStats({ tenantId }) {
|
|
const sql = `
|
|
SELECT
|
|
prompt_key,
|
|
COUNT(*) as total_versions,
|
|
MAX(version) as latest_version,
|
|
MAX(CASE WHEN is_active THEN version END) as active_version,
|
|
MAX(created_at) as last_updated
|
|
FROM prompt_templates
|
|
WHERE tenant_id = $1
|
|
GROUP BY prompt_key
|
|
ORDER BY prompt_key
|
|
`;
|
|
const { rows } = await pool.query(sql, [tenantId]);
|
|
return rows;
|
|
}
|