diff --git a/scripts/reset-tenant-data.mjs b/scripts/reset-tenant-data.mjs new file mode 100644 index 0000000..d6f94b8 --- /dev/null +++ b/scripts/reset-tenant-data.mjs @@ -0,0 +1,244 @@ +/** + * Script para resetear datos de conversación de un tenant + * + * Uso: + * node scripts/reset-tenant-data.mjs --tenant-key piaf + * node scripts/reset-tenant-data.mjs --tenant-key piaf --keep-audit + * node scripts/reset-tenant-data.mjs --tenant-key piaf --yes # Sin confirmación + * + * Tablas que se limpian (en orden): + * 1. human_takeovers + * 2. wa_messages + * 3. conversation_runs + * 4. wa_conversation_state + * 5. wa_identity_map + * 6. audit_log (opcional, se borra por defecto) + */ + +import "dotenv/config"; +import readline from "readline"; +import { pool } from "../src/modules/shared/db/pool.js"; + +// ───────────────────────────────────────────────────────────── +// Args parsing +// ───────────────────────────────────────────────────────────── + +function parseArgs() { + const args = process.argv.slice(2); + const out = { + tenantKey: null, + keepAudit: false, + yes: false, + }; + + for (let i = 0; i < args.length; i++) { + const a = args[i]; + if (a === "--tenant-key") out.tenantKey = args[++i]; + else if (a === "--keep-audit") out.keepAudit = true; + else if (a === "--yes" || a === "-y") out.yes = true; + else if (a === "--help" || a === "-h") { + printHelp(); + process.exit(0); + } + } + + if (!out.tenantKey) { + console.error("Error: --tenant-key es requerido\n"); + printHelp(); + process.exit(1); + } + + return out; +} + +function printHelp() { + console.log(` +Uso: node scripts/reset-tenant-data.mjs [opciones] + +Opciones: + --tenant-key Clave del tenant (requerido) + --keep-audit No borrar audit_log + --yes, -y Confirmar automáticamente (sin prompt) + --help, -h Mostrar esta ayuda + +Ejemplos: + node scripts/reset-tenant-data.mjs --tenant-key piaf + node scripts/reset-tenant-data.mjs --tenant-key piaf --keep-audit + node scripts/reset-tenant-data.mjs --tenant-key piaf --yes +`); +} + +// ───────────────────────────────────────────────────────────── +// Confirmación interactiva +// ───────────────────────────────────────────────────────────── + +async function askConfirmation(message) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => { + rl.question(message, (answer) => { + rl.close(); + resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes" || answer.toLowerCase() === "si"); + }); + }); +} + +// ───────────────────────────────────────────────────────────── +// Reset de datos +// ───────────────────────────────────────────────────────────── + +async function getTenantByKey(tenantKey) { + const { rows } = await pool.query( + `SELECT id, key, name FROM tenants WHERE key = $1`, + [tenantKey] + ); + return rows[0] || null; +} + +async function getStats(tenantId) { + const queries = [ + { name: "human_takeovers", sql: `SELECT COUNT(*) as count FROM human_takeovers WHERE tenant_id = $1` }, + { name: "wa_messages", sql: `SELECT COUNT(*) as count FROM wa_messages WHERE tenant_id = $1` }, + { name: "conversation_runs", sql: `SELECT COUNT(*) as count FROM conversation_runs WHERE tenant_id = $1` }, + { name: "wa_conversation_state", sql: `SELECT COUNT(*) as count FROM wa_conversation_state WHERE tenant_id = $1` }, + { name: "wa_identity_map", sql: `SELECT COUNT(*) as count FROM wa_identity_map WHERE tenant_id = $1` }, + { name: "audit_log", sql: `SELECT COUNT(*) as count FROM audit_log WHERE tenant_id = $1` }, + ]; + + const stats = {}; + for (const q of queries) { + const { rows } = await pool.query(q.sql, [tenantId]); + stats[q.name] = parseInt(rows[0].count, 10); + } + return stats; +} + +async function resetTenantData({ tenantId, keepAudit = false }) { + const client = await pool.connect(); + + try { + await client.query("BEGIN"); + + // 1. human_takeovers + const r1 = await client.query( + `DELETE FROM human_takeovers WHERE tenant_id = $1`, + [tenantId] + ); + console.log(` - human_takeovers: ${r1.rowCount} registros eliminados`); + + // 2. wa_messages + const r2 = await client.query( + `DELETE FROM wa_messages WHERE tenant_id = $1`, + [tenantId] + ); + console.log(` - wa_messages: ${r2.rowCount} registros eliminados`); + + // 3. conversation_runs + const r3 = await client.query( + `DELETE FROM conversation_runs WHERE tenant_id = $1`, + [tenantId] + ); + console.log(` - conversation_runs: ${r3.rowCount} registros eliminados`); + + // 4. wa_conversation_state + const r4 = await client.query( + `DELETE FROM wa_conversation_state WHERE tenant_id = $1`, + [tenantId] + ); + console.log(` - wa_conversation_state: ${r4.rowCount} registros eliminados`); + + // 5. wa_identity_map + const r5 = await client.query( + `DELETE FROM wa_identity_map WHERE tenant_id = $1`, + [tenantId] + ); + console.log(` - wa_identity_map: ${r5.rowCount} registros eliminados`); + + // 6. audit_log (opcional) + if (!keepAudit) { + const r6 = await client.query( + `DELETE FROM audit_log WHERE tenant_id = $1`, + [tenantId] + ); + console.log(` - audit_log: ${r6.rowCount} registros eliminados`); + } else { + console.log(` - audit_log: conservado (--keep-audit)`); + } + + await client.query("COMMIT"); + + return { ok: true }; + } catch (err) { + await client.query("ROLLBACK"); + throw err; + } finally { + client.release(); + } +} + +// ───────────────────────────────────────────────────────────── +// Main +// ───────────────────────────────────────────────────────────── + +async function main() { + const args = parseArgs(); + + console.log("\n🔄 Reset de datos de tenant\n"); + + // Buscar tenant + const tenant = await getTenantByKey(args.tenantKey); + if (!tenant) { + console.error(`Error: No se encontró tenant con key "${args.tenantKey}"`); + process.exit(1); + } + + console.log(`Tenant: ${tenant.name} (${tenant.key})`); + console.log(`ID: ${tenant.id}\n`); + + // Mostrar estadísticas + console.log("Registros actuales:"); + const stats = await getStats(tenant.id); + for (const [table, count] of Object.entries(stats)) { + console.log(` - ${table}: ${count}`); + } + console.log(""); + + const total = Object.values(stats).reduce((a, b) => a + b, 0); + if (total === 0) { + console.log("No hay datos para eliminar."); + process.exit(0); + } + + // Confirmación + if (!args.yes) { + const confirm = await askConfirmation( + `⚠️ Se eliminarán ${total} registros. ¿Continuar? (y/N): ` + ); + if (!confirm) { + console.log("Operación cancelada."); + process.exit(0); + } + } + + console.log("\nEliminando datos...\n"); + + // Ejecutar reset + await resetTenantData({ + tenantId: tenant.id, + keepAudit: args.keepAudit + }); + + console.log("\n✅ Reset completado exitosamente.\n"); +} + +main() + .catch((err) => { + console.error("\n❌ Error:", err.message); + process.exit(1); + }) + .finally(() => { + pool.end(); + }); diff --git a/src/modules/0-ui/db/settingsRepo.js b/src/modules/0-ui/db/settingsRepo.js index 869f914..d6f6b6d 100644 --- a/src/modules/0-ui/db/settingsRepo.js +++ b/src/modules/0-ui/db/settingsRepo.js @@ -100,11 +100,7 @@ export async function upsertSettings({ tenantId, settings }) { pickup_hours_end || null, ]; - console.log("[settingsRepo] upsertSettings params:", params); - const { rows } = await pool.query(sql, params); - - console.log("[settingsRepo] upsertSettings result:", rows[0]); return rows[0]; } diff --git a/src/modules/0-ui/handlers/settings.js b/src/modules/0-ui/handlers/settings.js index 96d9b8f..c937392 100644 --- a/src/modules/0-ui/handlers/settings.js +++ b/src/modules/0-ui/handlers/settings.js @@ -44,8 +44,6 @@ export async function handleGetSettings({ tenantId }) { * Guarda la configuración del tenant */ export async function handleSaveSettings({ tenantId, settings }) { - console.log("[settings] handleSaveSettings", { tenantId, settings }); - // Validaciones básicas if (!settings.store_name?.trim()) { throw new Error("store_name is required");