/** * ============================================ * 🐛 debugify.js — DiagnĂłstico Completo Total * ============================================ */ import { createWriteStream, mkdirSync, readFileSync, existsSync } from 'fs'; import { join } from 'path'; import archiver from 'archiver'; import * as db from './db.js'; const LOG_BUFFER_SIZE = 1000; const logBuffer = []; const errorBuffer = []; export function initDebugify() { const origLog = console.log.bind(console); const origErr = console.error.bind(console); const origWarn = console.warn.bind(console); console.log = (...args) => { const line = `[${new Date().toISOString()}] [LOG] ${args.join(' ')}`; logBuffer.push(line); if (logBuffer.length > LOG_BUFFER_SIZE) logBuffer.shift(); origLog(...args); }; console.error = (...args) => { const line = `[${new Date().toISOString()}] [ERR] ${args.join(' ')}`; logBuffer.push(line); errorBuffer.push(line); if (logBuffer.length > LOG_BUFFER_SIZE) logBuffer.shift(); if (errorBuffer.length > 500) errorBuffer.shift(); origErr(...args); }; console.warn = (...args) => { const line = `[${new Date().toISOString()}] [WARN] ${args.join(' ')}`; logBuffer.push(line); if (logBuffer.length > LOG_BUFFER_SIZE) logBuffer.shift(); origWarn(...args); }; process.on('uncaughtException', (err) => { errorBuffer.push(`[${new Date().toISOString()}] [UNCAUGHT] ${err.stack || err.message}`); }); process.on('unhandledRejection', (reason) => { errorBuffer.push(`[${new Date().toISOString()}] [UNHANDLED] ${reason?.stack || reason}`); }); console.log('[Debugify] ✅ Sistema de debug iniciado'); } async function q(sql, args = []) { try { const r = await db.db.execute({ sql, args }); return r.rows ?? []; } catch (e) { return [{ _error: e.message }]; } } export async function generateDebugZip(client, config) { const now = new Date(); const ts = now.toISOString().replace(/[:.]/g, '-').slice(0, 19); const zipName = `debugify-${ts}.zip`; const zipPath = join('./debugify', zipName); mkdirSync('./debugify', { recursive: true }); const output = createWriteStream(zipPath); const archive = archiver('zip', { zlib: { level: 9 } }); return new Promise(async (resolve, reject) => { output.on('close', () => resolve({ path: zipPath, name: zipName, size: archive.pointer() })); archive.on('error', reject); archive.pipe(output); // 01 — LOGS archive.append(logBuffer.join('\n') || '(sin logs)', { name: '01-logs/console-completo.txt' }); archive.append(errorBuffer.join('\n') || '(sin errores)', { name: '01-logs/solo-errores.txt' }); archive.append(logBuffer.slice(-100).join('\n') || '(vacĂ­o)', { name: '01-logs/ultimas-100-lineas.txt' }); // 02 — CONVERSACIONES USUARIO + ZELIN try { const convs = await q(`SELECT channel_id, role, content, username, created_at FROM conversation_context ORDER BY channel_id, created_at DESC LIMIT 2000`); const byChannel = new Map(); for (const row of convs) { const ch = row.channel_id ?? 'unknown'; if (!byChannel.has(ch)) byChannel.set(ch, []); const who = row.role === 'assistant' ? 'đŸ€– Zelin' : `đŸ‘€ ${row.username ?? 'Usuario'}`; byChannel.get(ch).push(`[${String(row.created_at ?? '').slice(0,19)}] ${who}: ${String(row.content ?? '').slice(0,500)}`); } for (const [ch, lines] of byChannel) archive.append(lines.reverse().join('\n\n'), { name: `02-conversaciones/canal-${ch}.txt` }); archive.append( [...byChannel.entries()].map(([ch, l]) => `${ch}: ${l.length} mensajes`).join('\n') || '(sin conversaciones)', { name: '02-conversaciones/RESUMEN.txt' } ); } catch (e) { archive.append(`Error: ${e.message}`, { name: '02-conversaciones/error.txt' }); } // 03 — MENSAJES DEL SERVIDOR try { const msgs = await q(`SELECT m.channel_id, u.username, m.content, m.created_at, m.is_edited FROM messages m LEFT JOIN users u ON m.user_id = u.user_id WHERE m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 500`); const byChannel = new Map(); for (const r of msgs) { const ch = r.channel_id ?? 'unknown'; if (!byChannel.has(ch)) byChannel.set(ch, []); byChannel.get(ch).push(`[${String(r.created_at ?? '').slice(11,19)}] ${r.username ?? '?'}${r.is_edited ? '[editado]' : ''}: ${String(r.content ?? '').slice(0,200)}`); } for (const [ch, lines] of byChannel) archive.append(lines.reverse().join('\n'), { name: `03-mensajes-servidor/canal-${ch}.txt` }); const deleted = await q(`SELECT username, content, channel_name, deleted_at FROM deleted_messages ORDER BY deleted_at DESC LIMIT 50`); archive.append( deleted.length ? deleted.map(r => `[${r.deleted_at}] ${r.username} en #${r.channel_name}: ${String(r.content ?? '').slice(0,150)}`).join('\n') : '(ninguno)', { name: '03-mensajes-servidor/mensajes-eliminados.txt' } ); } catch (e) { archive.append(`Error: ${e.message}`, { name: '03-mensajes-servidor/error.txt' }); } // 04 — DISCORD COMPLETO try { const guild = client?.guilds?.cache?.first(); archive.append([ `=== BOT ===`, `Tag: ${client?.user?.tag ?? '?'} | ID: ${client?.user?.id ?? '?'}`, `Guilds: ${client?.guilds?.cache?.size ?? 0} | Uptime: ${Math.floor((client?.uptime ?? 0)/60000)}min | Ping: ${client?.ws?.ping ?? '?'}ms`, ``, `=== GUILD ===`, `${guild?.name ?? 'N/A'} (ID: ${guild?.id ?? 'N/A'})`, `Miembros: ${guild?.memberCount ?? '?'} | Canales: ${guild?.channels?.cache?.size ?? '?'} | Roles: ${guild?.roles?.cache?.size ?? '?'}`, `Boost tier: ${guild?.premiumTier ?? '?'} | Boosts: ${guild?.premiumSubscriptionCount ?? 0}`, ``, `=== CANALES (${guild?.channels?.cache?.size ?? 0}) ===`, ...(guild?.channels?.cache?.map(ch => ` ${ch.type===2?'🔊':'#'}${ch.name} [${ch.id}]`) ?? []), ``, `=== ROLES (${guild?.roles?.cache?.size ?? 0}) ===`, ...(guild?.roles?.cache?.map(r => ` @${r.name}: ${r.members.size}m — ${r.id} — ${r.hexColor}`) ?? []), ].join('\n'), { name: '04-discord/estado-completo.txt' }); } catch (e) { archive.append(`Error: ${e.message}`, { name: '04-discord/error.txt' }); } // 05 — BASE DE DATOS try { const s = (await q(`SELECT (SELECT COUNT(*) FROM messages WHERE is_deleted=0) AS total_msgs, (SELECT COUNT(*) FROM messages WHERE created_at>datetime('now','-1 hour') AND is_deleted=0) AS msgs_1h, (SELECT COUNT(*) FROM messages WHERE created_at>datetime('now','-24 hours') AND is_deleted=0) AS msgs_24h, (SELECT COUNT(*) FROM messages WHERE created_at>datetime('now','-7 days') AND is_deleted=0) AS msgs_7d, (SELECT COUNT(DISTINCT user_id) FROM messages WHERE is_deleted=0) AS usuarios_con_msgs, (SELECT COUNT(*) FROM users) AS total_usuarios, (SELECT COUNT(*) FROM conversation_context) AS turns_conv, (SELECT COUNT(*) FROM zelin_memory) AS entradas_mem, (SELECT COUNT(*) FROM deleted_messages) AS msgs_eliminados`))[0] ?? {}; archive.append([ `=== ESTADÍSTICAS DB ===`, `Mensajes totales: ${s.total_msgs}`, `Mensajes Ășltima hora: ${s.msgs_1h}`, `Mensajes Ășltimas 24h: ${s.msgs_24h}`, `Mensajes Ășltimos 7d: ${s.msgs_7d}`, `Usuarios con mensajes: ${s.usuarios_con_msgs}`, `Usuarios registrados: ${s.total_usuarios}`, `Turns de conversaciĂłn: ${s.turns_conv}`, `Entradas de memoria: ${s.entradas_mem}`, `Mensajes eliminados: ${s.msgs_eliminados}`, ].join('\n'), { name: '05-database/stats.txt' }); const topUsers = await q(`SELECT u.username, COUNT(m.id) as msgs, MAX(m.created_at) as ultimo FROM messages m JOIN users u ON m.user_id=u.user_id WHERE m.is_deleted=0 AND m.created_at>datetime('now','-7 days') GROUP BY m.user_id ORDER BY msgs DESC LIMIT 20`); archive.append(topUsers.map(r => `${String(r.username??'?').padEnd(20)} ${r.msgs} msgs — ${String(r.ultimo??'').slice(0,16)}`).join('\n'), { name: '05-database/top-usuarios-activos.txt' }); const topCh = await q(`SELECT m.channel_id, COUNT(*) as msgs, MAX(m.created_at) as ultimo FROM messages m WHERE m.is_deleted=0 AND m.created_at>datetime('now','-7 days') GROUP BY m.channel_id ORDER BY msgs DESC LIMIT 20`); archive.append(topCh.map(r => `${String(r.channel_id).padEnd(20)} ${r.msgs} msgs — ${String(r.ultimo??'').slice(0,16)}`).join('\n'), { name: '05-database/canales-activos.txt' }); const allUsers = await q(`SELECT username, nickname, joined_at, left_at, message_count, notes FROM users ORDER BY joined_at DESC LIMIT 200`); archive.append(allUsers.map(r => `${r.username??'?'} (${r.nickname??'-'}) | entrĂł:${r.joined_at??'?'} | msgs:${r.message_count??0} | notas:${r.notes??'ninguna'}`).join('\n'), { name: '05-database/usuarios-registrados.txt' }); const memByCat = await q(`SELECT category, COUNT(*) as n, MAX(updated_at) as ultimo FROM zelin_memory GROUP BY category ORDER BY n DESC`); archive.append(memByCat.map(r => `${String(r.category).padEnd(25)} ${r.n} entradas — Ășltimo: ${String(r.ultimo??'').slice(0,16)}`).join('\n'), { name: '05-database/memoria-por-categoria.txt' }); const allMem = await q(`SELECT key, value, category, updated_at, expires_at FROM zelin_memory WHERE category!='skill' ORDER BY category, updated_at DESC LIMIT 200`); archive.append(allMem.map(r => `[${r.category}] ${r.key}\n ${String(r.value??'').slice(0,300)}\n actualizado:${r.updated_at} expira:${r.expires_at??'nunca'}`).join('\n---\n'), { name: '05-database/memoria-completa.txt' }); const events = await q(`SELECT type, user_id, target_id, old_val, new_val, created_at FROM server_events ORDER BY created_at DESC LIMIT 100`); archive.append(events.length ? events.map(r => `[${r.created_at}] ${r.type} user:${r.user_id} target:${r.target_id} ${r.old_val??''}→${r.new_val??''}`).join('\n') : '(sin eventos)', { name: '05-database/eventos-servidor.txt' }); const sums = await q(`SELECT channel_id, period_start, period_end, message_count, summary, top_topics FROM channel_summaries ORDER BY created_at DESC LIMIT 20`); archive.append(sums.length ? sums.map(r => `Canal:${r.channel_id} ${r.period_start}→${r.period_end} (${r.message_count} msgs)\nTemas:${r.top_topics??'?'}\n${String(r.summary??'').slice(0,400)}`).join('\n═══\n') : '(sin resĂșmenes)', { name: '05-database/resumenes.txt' }); const skills = await q(`SELECT key, value FROM zelin_memory WHERE category='skill'`); archive.append(skills.length ? skills.map(r => `KEY: ${r.key}\n${String(r.value??'').slice(0,500)}`).join('\n---\n') : '(sin skills)', { name: '05-database/skills.txt' }); } catch (e) { archive.append(`Error: ${e.message}\n${e.stack??''}`, { name: '05-database/error.txt' }); } // 06 — ESTADO IA try { const { getLocalAIStats, isLocalAIReady, getLocalAIError } = await import('./local-ai.js'); const { getDailyStats, getProviderStatus } = await import('./ai.js'); const { getHealerStats } = await import('./runtime-healer.js'); const aiStats = await getLocalAIStats().catch(() => ({})); const daily = getDailyStats(); const providers = getProviderStatus(); const healer = getHealerStats(); archive.append([ `=== MODELO LOCAL ===`, `Listo: ${isLocalAIReady()} | Error: ${getLocalAIError()??'ninguno'}`, `Modelo: ${aiStats.model??'?'}`, `Fast: ${aiStats.modelFast??'N/A'} (${aiStats.fastAvailable?'ok':'no disponible'})`, `Embed: ${aiStats.embedModel??'N/A'} (${aiStats.embedAvailable?'ok':'no disponible'})`, ``, `=== PROVIDERS (uso diario) ===`, ...Object.entries(daily).map(([n,s]) => ` ${n.padEnd(20)} req:${String(s.requests).padStart(4)}/${s.limit} (${s.pct}) state:${s.state} score:${s.score}`), ``, `=== CIRCUIT BREAKERS ===`, ...providers.map(p => ` ${p.name.padEnd(20)} estado:${p.state} score:${p.score}`), ``, `=== RUNTIME HEALER ===`, `Errores: ${healer.errors_stored} total / ${healer.errors_last_hour} Ășltima hora`, `Healings: ${healer.healing_this_hour}/${healer.healing_cap} | Degradado: ${healer.degraded}`, `Performance: ${JSON.stringify(healer.performance)}`, ].join('\n'), { name: '06-ia/estado-completo.txt' }); const runtimeErrs = await q(`SELECT key, value, updated_at FROM zelin_memory WHERE category='runtime_errors' ORDER BY updated_at DESC LIMIT 50`); archive.append(runtimeErrs.length ? runtimeErrs.map(r => `[${r.updated_at}] ${String(r.value??'').slice(0,400)}`).join('\n---\n') : '(sin errores runtime)', { name: '06-ia/errores-runtime.txt' }); const auditLogs = await q(`SELECT key, value, updated_at FROM zelin_memory WHERE category='audit_log' ORDER BY updated_at DESC LIMIT 30`); archive.append(auditLogs.length ? auditLogs.map(r => `[${r.updated_at}] ${String(r.value??'').slice(0,300)}`).join('\n---\n') : '(sin audit logs)', { name: '06-ia/audit-logs.txt' }); } catch (e) { archive.append(`Error: ${e.message}\n${e.stack??''}`, { name: '06-ia/error.txt' }); } // 07 — CONFIG try { const safe = JSON.parse(JSON.stringify(config ?? {})); const M = '***REDACTED***'; if (safe.discord?.token) safe.discord.token = M; if (safe.turso?.token) safe.turso.token = M; if (safe.sftp?.password) safe.sftp.password = M; if (safe.ai) for (const k of Object.keys(safe.ai)) { if (safe.ai[k]?.apiKey) safe.ai[k].apiKey = M; if (safe.ai[k]?.keys) safe.ai[k].keys = [`${(safe.ai[k].keys??[]).length} keys REDACTED`]; if (safe.ai[k]?.sk) safe.ai[k].sk = M; if (safe.ai[k]?.apiToken) safe.ai[k].apiToken = M; } archive.append(JSON.stringify(safe, null, 2), { name: '07-config/config-sanitizada.json' }); if (existsSync('./package.json')) archive.append(readFileSync('./package.json', 'utf8'), { name: '07-config/package.json' }); } catch (e) { archive.append(`Error: ${e.message}`, { name: '07-config/error.txt' }); } // 08 — SISTEMA const mem = process.memoryUsage(); archive.append([ `=== MEMORIA ===`, `RSS: ${(mem.rss/1024/1024).toFixed(1)}MB | Heap: ${(mem.heapUsed/1024/1024).toFixed(1)}/${(mem.heapTotal/1024/1024).toFixed(1)}MB | External: ${(mem.external/1024/1024).toFixed(1)}MB`, ``, `=== PROCESO ===`, `Uptime: ${Math.floor(process.uptime())}s (${(process.uptime()/3600).toFixed(2)}h)`, `PID: ${process.pid} | Node: ${process.version} | Platform: ${process.platform}/${process.arch}`, ``, `Generado: ${now.toISOString()}`, ].join('\n'), { name: '08-sistema/proceso.txt' }); // 09 — PSYCHE try { const { getStateSnapshot } = await import('./psyche.js'); archive.append(JSON.stringify(getStateSnapshot(), null, 2), { name: '09-psyche/estado-psicologico.json' }); const psycheData = await q(`SELECT key, value, updated_at FROM zelin_memory WHERE category='psyche' ORDER BY updated_at DESC LIMIT 50`); archive.append(psycheData.length ? psycheData.map(r => `[${r.updated_at}] ${r.key}: ${String(r.value??'').slice(0,200)}`).join('\n') : '(sin datos psyche)', { name: '09-psyche/afinidades-usuarios.txt' }); } catch (e) { archive.append(`Error: ${e.message}`, { name: '09-psyche/error.txt' }); } // 10 — LEARNING try { const ld = await q(`SELECT key, value, category, updated_at FROM zelin_memory WHERE category IN ('learning','user_profile','knowledge') ORDER BY updated_at DESC LIMIT 100`); archive.append(ld.length ? ld.map(r => `[${r.category}] ${r.key} — ${r.updated_at}\n ${String(r.value??'').slice(0,300)}`).join('\n---\n') : '(sin datos)', { name: '10-learning/datos.txt' }); } catch (e) { archive.append(`Error: ${e.message}`, { name: '10-learning/error.txt' }); } // 11 — STANDING PROMPTS try { const sp = await q(`SELECT key, value, updated_at FROM zelin_memory WHERE category='standing_prompt' OR key LIKE 'standing%' ORDER BY updated_at DESC`); archive.append(sp.length ? sp.map(r => `[${r.updated_at}] ${r.key}:\n${String(r.value??'').slice(0,500)}`).join('\n═══\n') : '(sin standing prompts)', { name: '11-standing-prompts/prompts.txt' }); } catch (e) { archive.append(`Error: ${e.message}`, { name: '11-standing-prompts/error.txt' }); } // README archive.append([ `DEBUGIFY COMPLETO — Zelin v5.8`, `Generado: ${now.toISOString()}`, ``, `01-logs/ → Consola completa, solo errores, Ășltimas 100 lĂ­neas`, `02-conversaciones/ → Todos los chats usuario↔Zelin por canal`, `03-mensajes-servidor/ → Mensajes del servidor + mensajes eliminados`, `04-discord/ → Estado completo Discord (guild, canales, roles)`, `05-database/ → Stats, top usuarios, memoria, eventos, resĂșmenes, skills`, `06-ia/ → Modelo local, providers, circuit breakers, errores runtime, audit logs`, `07-config/ → Config sanitizada + package.json`, `08-sistema/ → RAM, CPU, uptime, proceso`, `09-psyche/ → Estado psicolĂłgico de Zelin + afinidades con usuarios`, `10-learning/ → Datos de aprendizaje y perfiles de usuario`, `11-standing-prompts/ → Standing prompts activos en DB`, ].join('\n'), { name: 'README.txt' }); archive.finalize(); }); }