/** * 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(); });