From 630d58e8be566de862389d0891a64cce2abf4650 Mon Sep 17 00:00:00 2001 From: shni Date: Thu, 2 Oct 2025 20:11:33 -0500 Subject: [PATCH] feat: integrate pino logger for improved error handling and logging consistency --- .env | 11 ++ package-lock.json | 129 ++++++++++++++++++ package.json | 1 + src/commands/messages/AI/chat.ts | 3 +- .../alliaces/createDisplayComponent.ts | 11 +- .../messages/alliaces/removeChannel.ts | 3 +- src/commands/messages/others/recordar.ts | 3 +- .../messages/settings-server/settings.ts | 3 +- src/components/buttons/cmdClearGlobal.ts | 3 +- src/components/buttons/cmdClearGuild.ts | 3 +- src/components/buttons/cmdMemRefresh.ts | 3 +- src/components/buttons/cmdRegisterGlobal.ts | 3 +- src/components/buttons/cmdRegisterGuild.ts | 3 +- src/components/buttons/ldRefresh.ts | 3 +- src/components/modals/prefixSettingsModal.ts | 3 +- src/core/api/discordAPI.ts | 37 ++--- src/core/api/reminders.ts | 15 +- src/core/api/remindersSchema.ts | 7 +- src/core/client.ts | 9 +- src/core/database/redis.ts | 7 +- src/core/lib/components.ts | 17 +-- src/core/lib/logger.ts | 22 +++ src/core/loaders/loader.ts | 9 +- src/core/loaders/loaderEvents.ts | 5 +- src/core/memory/memoryMonitor.ts | 3 +- src/core/memory/memoryOptimizer.ts | 7 +- src/events/extras/alliace.ts | 18 +-- src/events/interactionCreate.ts | 3 +- src/events/messageCreate.ts | 5 +- src/events/ready.ts | 3 +- src/main.ts | 43 +++--- 31 files changed, 293 insertions(+), 102 deletions(-) create mode 100644 src/core/lib/logger.ts diff --git a/.env b/.env index 6c1d136..7994d15 100644 --- a/.env +++ b/.env @@ -1,6 +1,10 @@ # Configuración de ejemplo para optimización de memoria # Copia este archivo como .env.test y ajusta los valores según tus necesidades +# =========================================== +# CONFIGURACIÓN DE APPWRITE +# =========================================== + APPWRITE_PROJECT_ID="68d8c4b2001abc54d3cd" APPWRITE_PROJECT_NAME="amayo" APPWRITE_ENDPOINT="https://nyc.cloud.appwrite.io/v1" @@ -80,3 +84,10 @@ SWEEP_MESSAGES_LIFETIME_SECONDS=900 # 15 minutos # SWEEP_MESSAGES_INTERVAL_SECONDS=600 # SWEEP_MESSAGES_LIFETIME_SECONDS=1800 # MEMORY_LOG_INTERVAL_SECONDS=300 + +# =========================================== +# NOTAS: +# Mode es un cambio en cada estacion de mes, como algo festivo. +# =========================================== + +MODE='halloween' # normal, halloween, christmas, newyear \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e6a30fb..7ed6756 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "discord-api-types": "0.38.24", "discord.js": "14.22.1", "node-appwrite": "19.1.0", + "pino": "9.13.0", "prisma": "6.16.2", "redis": "5.8.2" }, @@ -506,6 +507,15 @@ "dev": true, "license": "MIT" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1044,6 +1054,15 @@ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", "license": "MIT" }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1056,6 +1075,43 @@ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", "license": "MIT" }, + "node_modules/pino": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.13.0.tgz", + "integrity": "sha512-SpTXQhkQXekIKEe7c887S3lk3v90Q+/HVRZVyNAhe98PQc++6I5ec/R0pciH8/CciXjCoVZIZfRNicbC6KZgnw==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, "node_modules/pkg-types": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", @@ -1092,6 +1148,22 @@ } } }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -1108,6 +1180,12 @@ ], "license": "MIT" }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/rc9": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", @@ -1131,6 +1209,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/redis": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/redis/-/redis-5.8.2.tgz", @@ -1167,6 +1254,48 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/slow-redact": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.0.tgz", + "integrity": "sha512-cf723wn9JeRIYP9tdtd86GuqoR5937u64Io+CYjlm2i7jvu7g0H+Cp0l0ShAf/4ZL+ISUTVT+8Qzz7RZmp9FjA==", + "license": "MIT" + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tinyexec": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", diff --git a/package.json b/package.json index 2caa310..a9d5cf7 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "discord-api-types": "0.38.24", "discord.js": "14.22.1", "node-appwrite": "19.1.0", + "pino": "9.13.0", "prisma": "6.16.2", "redis": "5.8.2" }, diff --git a/src/commands/messages/AI/chat.ts b/src/commands/messages/AI/chat.ts index 8651f75..0e59506 100644 --- a/src/commands/messages/AI/chat.ts +++ b/src/commands/messages/AI/chat.ts @@ -1,3 +1,4 @@ +import logger from "../../../core/lib/logger"; import { GoogleGenAI } from "@google/genai"; import {CommandMessage} from "../../../core/types/commands"; import { TextChannel, DMChannel, NewsChannel, ThreadChannel } from "discord.js"; @@ -256,7 +257,7 @@ ${userHistory.messages.slice(-3).join('\n')}`; } } catch (error: any) { - console.error('Error en comando AI:', error); + logger.error('Error en comando AI:', error); // Manejar errores específicos incluyendo límites de tokens let errorMessage = "❌ **Error:** Ocurrió un problema al comunicarse con la IA."; diff --git a/src/commands/messages/alliaces/createDisplayComponent.ts b/src/commands/messages/alliaces/createDisplayComponent.ts index a72916a..ddfe8a6 100644 --- a/src/commands/messages/alliaces/createDisplayComponent.ts +++ b/src/commands/messages/alliaces/createDisplayComponent.ts @@ -1,3 +1,4 @@ +import logger from "../../../core/lib/logger"; import { CommandMessage } from "../../../core/types/commands"; // @ts-ignore import { @@ -1259,17 +1260,17 @@ export const command: CommandMessage = { }); } catch (error: any) { if (error.code === 10008) { - console.log('Mensaje del editor eliminado'); + logger.info('Mensaje del editor eliminado'); } else if (error.code === 10062) { - console.log('Interacción expirada'); + logger.info('Interacción expirada'); } else { - console.error('Error actualizando preview:', error.message || error); + logger.error('Error actualizando preview:', error.message || error); } } }, 500); } catch (error: any) { - console.error('Error en modal:', error); + logger.error('Error en modal:', error); try { if (error.code !== 10062 && !interaction.replied && !interaction.deferred) { await interaction.reply({ content: '❌ Error procesando el modal.', flags: 64 }); @@ -1301,7 +1302,7 @@ export const command: CommandMessage = { }); } } catch (error) { - console.log('No se pudo actualizar el mensaje final'); + logger.info('No se pudo actualizar el mensaje final'); } } }); diff --git a/src/commands/messages/alliaces/removeChannel.ts b/src/commands/messages/alliaces/removeChannel.ts index b8a5f5d..df60f28 100644 --- a/src/commands/messages/alliaces/removeChannel.ts +++ b/src/commands/messages/alliaces/removeChannel.ts @@ -1,3 +1,4 @@ +import logger from "../../../core/lib/logger"; import { CommandMessage } from "../../../core/types/commands"; // @ts-ignore import { EmbedBuilder, ButtonStyle, MessageFlags, ChannelType } from "discord.js"; @@ -209,7 +210,7 @@ export const command: CommandMessage = { }); } catch (error) { - console.log(error) + logger.info(error) const errorEmbed = new EmbedBuilder() .setTitle("❌ Error de Eliminación") .setDescription(`💥 **Error al eliminar el canal:**\n\n📺 Canal: ${channelName}\n🧩 Configuración: \`${channelConfig?.blockConfigName}\`\n\n🔍 **Posibles causas:**\n• El canal ya fue eliminado\n• Error de base de datos\n• Permisos insuficientes\n\n🔄 Intenta nuevamente.`) diff --git a/src/commands/messages/others/recordar.ts b/src/commands/messages/others/recordar.ts index 5277577..807bf85 100644 --- a/src/commands/messages/others/recordar.ts +++ b/src/commands/messages/others/recordar.ts @@ -1,3 +1,4 @@ +import logger from "../../../core/lib/logger"; // Comando para crear recordatorios con Appwrite: !recordar {texto} {fecha} // Ejemplos: // !recordar hacer esto el miércoles a las 5pm @@ -236,7 +237,7 @@ export const command: CommandMessage = { executeAt: iso }); } catch (e) { - console.error('Error programando recordatorio:', e); + logger.error('Error programando recordatorio:', e); await message.reply('❌ No pude guardar el recordatorio. Revisa la configuración de Appwrite.'); return; } diff --git a/src/commands/messages/settings-server/settings.ts b/src/commands/messages/settings-server/settings.ts index 5a74acb..72165e2 100644 --- a/src/commands/messages/settings-server/settings.ts +++ b/src/commands/messages/settings-server/settings.ts @@ -1,3 +1,4 @@ +import logger from "../../../core/lib/logger"; import { CommandMessage } from "../../../core/types/commands"; export const command: CommandMessage = { @@ -254,7 +255,7 @@ export const command: CommandMessage = { } }).catch(async (error: any) => { // Modal timeout o cancelado - console.log("Modal timeout o error:", error.message); + logger.info("Modal timeout o error:", error.message); }); } diff --git a/src/components/buttons/cmdClearGlobal.ts b/src/components/buttons/cmdClearGlobal.ts index e4b87fb..5915ebb 100644 --- a/src/components/buttons/cmdClearGlobal.ts +++ b/src/components/buttons/cmdClearGlobal.ts @@ -1,3 +1,4 @@ +import logger from "../../core/lib/logger"; import {ButtonInteraction, MessageFlags} from 'discord.js'; import {clearGlobalCommands} from '../../core/api/discordAPI'; import type { Button } from '../../core/types/components'; @@ -21,7 +22,7 @@ export default { await clearGlobalCommands(); await interaction.editReply('🧹 Comandos GLOBAL eliminados.'); } catch (e: any) { - console.error('Error limpiando comandos globales:', e); + logger.error('Error limpiando comandos globales:', e); if (interaction.deferred || interaction.replied) { await interaction.editReply('❌ Error limpiando comandos globales.'); } else { diff --git a/src/components/buttons/cmdClearGuild.ts b/src/components/buttons/cmdClearGuild.ts index 943111c..0676f78 100644 --- a/src/components/buttons/cmdClearGuild.ts +++ b/src/components/buttons/cmdClearGuild.ts @@ -1,3 +1,4 @@ +import logger from "../../core/lib/logger"; import {ButtonInteraction, MessageFlags} from 'discord.js'; import { clearAllCommands } from '../../core/api/discordAPI'; import type { Button } from '../../core/types/components'; @@ -21,7 +22,7 @@ export default { await clearAllCommands(); await interaction.editReply('🧹 Comandos de GUILD eliminados.'); } catch (e: any) { - console.error('Error limpiando comandos guild:', e); + logger.error('Error limpiando comandos guild:', e); if (interaction.deferred || interaction.replied) { await interaction.editReply('❌ Error limpiando comandos de guild.'); } else { diff --git a/src/components/buttons/cmdMemRefresh.ts b/src/components/buttons/cmdMemRefresh.ts index 7ed8527..104e58e 100644 --- a/src/components/buttons/cmdMemRefresh.ts +++ b/src/components/buttons/cmdMemRefresh.ts @@ -1,3 +1,4 @@ +import logger from "../../core/lib/logger"; import {ButtonInteraction, MessageFlags} from 'discord.js'; import { buildAdminPanel } from '../../commands/messages/net/commandsAdmin'; @@ -15,7 +16,7 @@ export default { // Edita el mensaje original reemplazando componentes (solo el contenedor con filas internas) await interaction.message.edit({ components: [panel] }); } catch (e) { - console.error('Error refrescando panel de memoria:', e); + logger.error('Error refrescando panel de memoria:', e); if (!interaction.deferred && !interaction.replied) await interaction.reply({ content: '❌ Error refrescando panel.', flags: MessageFlags.Ephemeral }); } diff --git a/src/components/buttons/cmdRegisterGlobal.ts b/src/components/buttons/cmdRegisterGlobal.ts index f9a624b..bde86bc 100644 --- a/src/components/buttons/cmdRegisterGlobal.ts +++ b/src/components/buttons/cmdRegisterGlobal.ts @@ -1,3 +1,4 @@ +import logger from "../../core/lib/logger"; import {ButtonInteraction, MessageFlags} from 'discord.js'; import { registeringGlobalCommands } from '../../core/api/discordAPI'; @@ -19,7 +20,7 @@ export default { await registeringGlobalCommands(); await interaction.editReply('✅ Comandos GLOBAL registrados (propagación puede tardar).'); } catch (e: any) { - console.error('Error registrando comandos globales:', e); + logger.error('Error registrando comandos globales:', e); if (interaction.deferred || interaction.replied) { await interaction.editReply('❌ Error registrando comandos globales.'); } else { diff --git a/src/components/buttons/cmdRegisterGuild.ts b/src/components/buttons/cmdRegisterGuild.ts index 6f75682..7394051 100644 --- a/src/components/buttons/cmdRegisterGuild.ts +++ b/src/components/buttons/cmdRegisterGuild.ts @@ -1,3 +1,4 @@ +import logger from "../../core/lib/logger"; import {ButtonInteraction, MessageFlags} from 'discord.js'; import { registeringCommands } from '../../core/api/discordAPI'; import type { Button } from '../../core/types/components'; @@ -21,7 +22,7 @@ export default { await registeringCommands(); await interaction.editReply('✅ Comandos de GUILD registrados correctamente.'); } catch (e: any) { - console.error('Error registrando comandos guild:', e); + logger.error('Error registrando comandos guild:', e); if (interaction.deferred || interaction.replied) { await interaction.editReply('❌ Error registrando comandos de guild. Revisa logs.'); } else { diff --git a/src/components/buttons/ldRefresh.ts b/src/components/buttons/ldRefresh.ts index 65a827b..4d78806 100644 --- a/src/components/buttons/ldRefresh.ts +++ b/src/components/buttons/ldRefresh.ts @@ -1,3 +1,4 @@ +import logger from "../../core/lib/logger"; import { ButtonInteraction, MessageFlags } from 'discord.js'; import { buildLeaderboardPanel } from '../../commands/messages/alliaces/leaderboard'; @@ -14,7 +15,7 @@ export default { const panel = await buildLeaderboardPanel(fakeMessage); await interaction.message.edit({ components: [panel] }); } catch (e) { - console.error('Error refrescando leaderboard:', e); + logger.error('Error refrescando leaderboard:', e); if (!interaction.deferred && !interaction.replied) await interaction.reply({ content: '❌ Error refrescando leaderboard.', flags: MessageFlags.Ephemeral }); } diff --git a/src/components/modals/prefixSettingsModal.ts b/src/components/modals/prefixSettingsModal.ts index fcd9370..765d2da 100644 --- a/src/components/modals/prefixSettingsModal.ts +++ b/src/components/modals/prefixSettingsModal.ts @@ -1,3 +1,4 @@ +import logger from "../../core/lib/logger"; import {ModalSubmitInteraction, MessageFlags} from "discord.js"; import type { Modal } from '../../core/types/components'; import type Amayo from '../../core/client'; @@ -22,7 +23,7 @@ export default { flags: MessageFlags.Ephemeral }); } catch (error) { - console.error('Error cambiando prefix:', error); + logger.error('Error cambiando prefix:', error); await interaction.reply({ content: '❌ Error al cambiar el prefix.', flags: MessageFlags.Ephemeral diff --git a/src/core/api/discordAPI.ts b/src/core/api/discordAPI.ts index 17bc3ec..9abb469 100644 --- a/src/core/api/discordAPI.ts +++ b/src/core/api/discordAPI.ts @@ -1,3 +1,4 @@ +import logger from "../lib/logger"; import { REST } from "discord.js"; // @ts-ignore import { Routes } from "discord-api-types/v10"; @@ -19,12 +20,12 @@ export async function registeringCommands(): Promise { options: cmd.options ?? [] }); - console.log(`✅ Preparado para registrar (guild): ${cmd.name}`); + logger.info(`✅ Preparado para registrar (guild): ${cmd.name}`); } } try { - console.log(`🧹 Limpiando comandos antiguos/residuales (guild)...`); + logger.info(`🧹 Limpiando comandos antiguos/residuales (guild)...`); // Primero eliminamos TODOS los comandos existentes await rest.put( @@ -35,11 +36,11 @@ export async function registeringCommands(): Promise { { body: [] } // Array vacío elimina todos los comandos ); - console.log(`✅ Comandos antiguos de guild eliminados.`); + logger.info(`✅ Comandos antiguos de guild eliminados.`); // Pequeña pausa para asegurar que Discord procese la eliminación await new Promise(r => setTimeout(r, 1000)); - console.log(`🚀 Registrando ${commandsToRegister.length} comandos slash nuevos (guild)...`); + logger.info(`🚀 Registrando ${commandsToRegister.length} comandos slash nuevos (guild)...`); // Ahora registramos los comandos actuales const data: any = await rest.put( @@ -50,9 +51,9 @@ export async function registeringCommands(): Promise { { body: commandsToRegister } ); - console.log(`✅ ${data.length} comandos de guild registrados.`); + logger.info(`✅ ${data.length} comandos de guild registrados.`); } catch (error) { - console.error("❌ Error en el proceso de comandos de guild:", error); + logger.error("❌ Error en el proceso de comandos de guild:", error); } } @@ -66,31 +67,31 @@ export async function registeringGlobalCommands(): Promise { type: 1, options: cmd.options ?? [] }); - console.log(`🌍 Preparado para registrar global: ${cmd.name}`); + logger.info(`🌍 Preparado para registrar global: ${cmd.name}`); } } try { - console.log(`🧹 Limpiando comandos globales existentes...`); + logger.info(`🧹 Limpiando comandos globales existentes...`); await rest.put( Routes.applicationCommands(process.env.CLIENT!), { body: [] } ); - console.log(`✅ Comandos globales previos eliminados.`); + logger.info(`✅ Comandos globales previos eliminados.`); await new Promise(r => setTimeout(r, 1500)); - console.log(`🚀 Registrando ${commandsToRegister.length} comandos globales... (propagación puede tardar hasta 1h)`); + logger.info(`🚀 Registrando ${commandsToRegister.length} comandos globales... (propagación puede tardar hasta 1h)`); const data: any = await rest.put( Routes.applicationCommands(process.env.CLIENT!), { body: commandsToRegister } ); - console.log(`✅ ${data.length} comandos globales enviados a la API.`); + logger.info(`✅ ${data.length} comandos globales enviados a la API.`); } catch (error) { - console.error("❌ Error registrando comandos globales:", error); + logger.error("❌ Error registrando comandos globales:", error); } } export async function clearAllCommands(): Promise { try { - console.log(`🧹 Eliminando TODOS los comandos slash (guild)...`); + logger.info(`🧹 Eliminando TODOS los comandos slash (guild)...`); await rest.put( Routes.applicationGuildCommands( process.env.CLIENT!, @@ -98,21 +99,21 @@ export async function clearAllCommands(): Promise { ), { body: [] } ); - console.log(`✅ Todos los comandos de guild eliminados.`); + logger.info(`✅ Todos los comandos de guild eliminados.`); } catch (error) { - console.error("❌ Error eliminando comandos de guild:", error); + logger.error("❌ Error eliminando comandos de guild:", error); } } export async function clearGlobalCommands(): Promise { try { - console.log(`🌍 Eliminando comandos globales...`); + logger.info(`🌍 Eliminando comandos globales...`); await rest.put( Routes.applicationCommands(process.env.CLIENT!), { body: [] } ); - console.log(`✅ Comandos globales eliminados.`); + logger.info(`✅ Comandos globales eliminados.`); } catch (error) { - console.error("❌ Error eliminando comandos globales:", error); + logger.error("❌ Error eliminando comandos globales:", error); } } diff --git a/src/core/api/reminders.ts b/src/core/api/reminders.ts index a0ecc10..074fa1a 100644 --- a/src/core/api/reminders.ts +++ b/src/core/api/reminders.ts @@ -1,3 +1,4 @@ +import logger from "../lib/logger"; // eslint-disable-next-line @typescript-eslint/no-var-requires const sdk: any = require('node-appwrite'); import type Amayo from '../client'; @@ -62,7 +63,7 @@ async function fetchDueReminders(limit = 25): Promise { ]) as unknown as { documents?: ReminderRow[] }; return (list.documents || []) as ReminderRow[]; } catch (e) { - console.error('Error listando recordatorios vencidos:', e); + logger.error('Error listando recordatorios vencidos:', e); return []; } } @@ -82,7 +83,7 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) { delivered = true; } } catch (e) { - console.warn('No se pudo enviar al canal original:', e); + logger.warn('No se pudo enviar al canal original:', e); } } // 2) Fallback: DM al usuario @@ -92,7 +93,7 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) { await user.send({ content: `⏰ Recordatorio: ${message}` }); delivered = true; } catch (e) { - console.warn('No se pudo enviar DM al usuario:', e); + logger.warn('No se pudo enviar DM al usuario:', e); } } @@ -101,12 +102,12 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) { export function startReminderPoller(bot: Amayo) { if (!isAppwriteConfigured()) { - console.warn('Appwrite no configurado: el poller de recordatorios no se iniciará.'); + logger.warn('Appwrite no configurado: el poller de recordatorios no se iniciará.'); return null; } const intervalSec = parseInt(process.env.REMINDERS_POLL_INTERVAL_SECONDS || '30', 10); - console.log(`⏱️ Iniciando poller de recordatorios cada ${intervalSec}s`); + logger.info(`⏱️ Iniciando poller de recordatorios cada ${intervalSec}s`); const timer = setInterval(async () => { try { @@ -119,11 +120,11 @@ export function startReminderPoller(bot: Amayo) { const db = getDatabases(); if (db) await db.deleteDocument(APPWRITE_DATABASE_ID, APPWRITE_COLLECTION_REMINDERS_ID, d.$id); } catch (e) { - console.warn('No se pudo eliminar recordatorio entregado:', e); + logger.warn('No se pudo eliminar recordatorio entregado:', e); } } } catch (e) { - console.error('Error en ciclo de recordatorios:', e); + logger.error('Error en ciclo de recordatorios:', e); } }, Math.max(10, intervalSec) * 1000); diff --git a/src/core/api/remindersSchema.ts b/src/core/api/remindersSchema.ts index 8092c11..e5a2f51 100644 --- a/src/core/api/remindersSchema.ts +++ b/src/core/api/remindersSchema.ts @@ -1,3 +1,4 @@ +import logger from "../lib/logger"; // eslint-disable-next-line @typescript-eslint/no-var-requires const sdk: any = require('node-appwrite'); import { getDatabases, isAppwriteConfigured, APPWRITE_COLLECTION_REMINDERS_ID, APPWRITE_DATABASE_ID } from './appwrite'; @@ -20,7 +21,7 @@ export async function ensureRemindersSchema() { ]); // Nota: No añadimos permisos de lectura pública para evitar fuga de datos } catch (e) { - console.warn('No se pudo crear la colección de recordatorios (puede existir ya):', e); + logger.warn('No se pudo crear la colección de recordatorios (puede existir ya):', e); } } @@ -30,7 +31,7 @@ export async function ensureRemindersSchema() { const msg = String(e?.message || e); if (!/already exists|attribute_already_exists/i.test(msg)) { // Otros errores se muestran - console.warn('No se pudo crear atributo:', msg); + logger.warn('No se pudo crear atributo:', msg); } } }; @@ -49,7 +50,7 @@ export async function ensureRemindersSchema() { } catch (e: any) { const msg = String(e?.message || e); if (!/already exists|index_already_exists/i.test(msg)) { - console.warn('No se pudo crear índice executeAt:', msg); + logger.warn('No se pudo crear índice executeAt:', msg); } } } diff --git a/src/core/client.ts b/src/core/client.ts index d47b361..4024442 100644 --- a/src/core/client.ts +++ b/src/core/client.ts @@ -1,5 +1,6 @@ import { Client, GatewayIntentBits, Options, Partials } from 'discord.js'; import { prisma, ensurePrismaConnection } from './database/prisma'; +import logger from './lib/logger'; // Verificar si process.loadEnvFile existe (Node.js 20.6+) if (typeof process.loadEnvFile === 'function') { @@ -9,6 +10,7 @@ if (typeof process.loadEnvFile === 'function') { class Amayo extends Client { public key: string; public prisma = prisma; + public mode: string; constructor() { super({ @@ -44,19 +46,20 @@ class Amayo extends Client { }); this.key = process.env.TOKEN ?? ''; + this.mode = process.env.MODE ?? 'Normal'; } async play() { if (!this.key) { - console.error('No key provided'); + logger.error('No key provided'); throw new Error('Missing DISCORD TOKEN'); } else { try { await ensurePrismaConnection(); - console.log('Successfully connected to the database (singleton).'); + logger.info('Successfully connected to the database (singleton).'); await this.login(this.key); } catch (error) { - console.error('Failed to connect to DB or login to Discord:', error); + logger.error({ err: error }, 'Failed to connect to DB or login to Discord'); throw error; } } diff --git a/src/core/database/redis.ts b/src/core/database/redis.ts index 4f3b2db..bca745b 100644 --- a/src/core/database/redis.ts +++ b/src/core/database/redis.ts @@ -1,4 +1,5 @@ import { createClient } from "redis"; +import logger from "../lib/logger"; export const redis = createClient({ username: 'default', @@ -9,9 +10,9 @@ export const redis = createClient({ } }); -redis.on("error", (err: any) => console.error("Redis error:", err)); -redis.on("connect", () => console.log("✅ Conectado a Redis")); -redis.on("reconnecting", () => console.warn("♻️ Reintentando conexión Redis")); +redis.on("error", (err: any) => logger.error({ err }, "Redis error")); +redis.on("connect", () => logger.info("✅ Conectado a Redis")); +redis.on("reconnecting", () => logger.warn("♻️ Reintentando conexión Redis")); export async function redisConnect () { if (!redis.isOpen) await redis.connect(); diff --git a/src/core/lib/components.ts b/src/core/lib/components.ts index de885aa..8c9472e 100644 --- a/src/core/lib/components.ts +++ b/src/core/lib/components.ts @@ -1,3 +1,4 @@ +import logger from "./logger"; import * as fs from "node:fs"; import * as path from "node:path"; import { Collection } from "discord.js"; @@ -11,7 +12,7 @@ export const contextmenus: Collection = new Collection { + return { level: label }; + }, + }, +}); + +export default logger; + diff --git a/src/core/loaders/loader.ts b/src/core/loaders/loader.ts index e434621..0498d73 100644 --- a/src/core/loaders/loader.ts +++ b/src/core/loaders/loader.ts @@ -1,3 +1,4 @@ +import logger from "../lib/logger"; import * as fs from "node:fs"; import path from "node:path"; import { Collection } from "discord.js"; @@ -7,7 +8,7 @@ export const commands = new Collection(); export function loadCommands(dir: string = path.resolve(__dirname, "../../commands")) { // Evitar fallo si el directorio no existe en el entorno if (!fs.existsSync(dir)) { - console.warn(`⚠️ Directorio de comandos no encontrado: ${dir}`); + logger.warn(`⚠️ Directorio de comandos no encontrado: ${dir}`); return; } @@ -29,12 +30,12 @@ export function loadCommands(dir: string = path.resolve(__dirname, "../../comman const command = imported.command ?? imported.default ?? imported; if (!command?.data?.name && !command?.name) { - console.warn(`⚠️ Archivo ignorado: ${file} (no es un comando válido)`); + logger.warn(`⚠️ Archivo ignorado: ${file} (no es un comando válido)`); continue; } const name = command.data?.name ?? command.name; - console.log(`📦 Loading command: ${name}`); + logger.info(`📦 Loading command: ${name}`); // @ts-ignore commands.set(name, command); @@ -45,6 +46,6 @@ export function loadCommands(dir: string = path.resolve(__dirname, "../../comman } } - console.log(`✅ Cargado comando: ${name}`); + logger.info(`✅ Cargado comando: ${name}`); } } diff --git a/src/core/loaders/loaderEvents.ts b/src/core/loaders/loaderEvents.ts index da7c9e8..f542c91 100644 --- a/src/core/loaders/loaderEvents.ts +++ b/src/core/loaders/loaderEvents.ts @@ -1,11 +1,12 @@ import { bot } from "../../main"; import path from "node:path"; import * as fs from "node:fs"; +import logger from "../lib/logger"; export function loadEvents(dir: string = path.resolve(__dirname, "../../events")) { // Evitar fallo si el directorio no existe if (!fs.existsSync(dir)) { - console.warn(`⚠️ Directorio de eventos no encontrado: ${dir}`); + logger.warn(`⚠️ Directorio de eventos no encontrado: ${dir}`); return; } @@ -33,6 +34,6 @@ export function loadEvents(dir: string = path.resolve(__dirname, "../../events") bot.on(event.name, (...args: any[]) => event.execute(...args)); } - console.log(`Evento cargado: ${event.name}`); + logger.info(`Evento cargado: ${event.name}`); } } diff --git a/src/core/memory/memoryMonitor.ts b/src/core/memory/memoryMonitor.ts index 9ddeb76..041eb18 100644 --- a/src/core/memory/memoryMonitor.ts +++ b/src/core/memory/memoryMonitor.ts @@ -1,3 +1,4 @@ +import logger from "../lib/logger"; // Monitor ligero de memoria y event loop. // Se activa si defines MEMORY_LOG_INTERVAL_SECONDS. import { monitorEventLoopDelay } from 'node:perf_hooks'; @@ -41,7 +42,7 @@ export function startMemoryMonitor(opts: MemoryMonitorOptions) { } } - console.log(`[MEM] rss=${rss} heapUsed=${heapUsed} heapTotal=${heapTotal} ext=${external} evLoopDelay=${elDelay.toFixed(2)}ms${warn}`); + logger.info(`[MEM] rss=${rss} heapUsed=${heapUsed} heapTotal=${heapTotal} ext=${external} evLoopDelay=${elDelay.toFixed(2)}ms${warn}`); // Resetear métricas de event loop delay para la siguiente ventana h.reset(); diff --git a/src/core/memory/memoryOptimizer.ts b/src/core/memory/memoryOptimizer.ts index 9d9cc32..ecb429a 100644 --- a/src/core/memory/memoryOptimizer.ts +++ b/src/core/memory/memoryOptimizer.ts @@ -1,3 +1,4 @@ +import logger from "../lib/logger"; // Sistema adicional de optimización de memoria para complementar el monitor existente export interface MemoryOptimizerOptions { @@ -21,7 +22,7 @@ export class MemoryOptimizer { start() { // Solo habilitar si está disponible el GC manual if (typeof global.gc !== 'function') { - console.warn('⚠️ Manual GC no disponible. Inicia con --expose-gc para habilitar optimizaciones adicionales.'); + logger.warn('⚠️ Manual GC no disponible. Inicia con --expose-gc para habilitar optimizaciones adicionales.'); return; } @@ -34,7 +35,7 @@ export class MemoryOptimizer { this.gcTimer.unref(); // No bloquear el cierre del proceso } - console.log(`✅ Memory Optimizer iniciado - GC cada ${this.options.forceGCInterval}min, umbral: ${this.options.maxHeapUsageBeforeGC}MB`); + logger.info(`✅ Memory Optimizer iniciado - GC cada ${this.options.forceGCInterval}min, umbral: ${this.options.maxHeapUsageBeforeGC}MB`); } stop() { @@ -69,7 +70,7 @@ export class MemoryOptimizer { const duration = Date.now() - startTime; const heapFreed = (before.heapUsed - after.heapUsed) / 1024 / 1024; - console.log(`🗑️ GC ${reason}: liberó ${heapFreed.toFixed(1)}MB en ${duration}ms`); + logger.info(`🗑️ GC ${reason}: liberó ${heapFreed.toFixed(1)}MB en ${duration}ms`); } } } diff --git a/src/events/extras/alliace.ts b/src/events/extras/alliace.ts index 4deaedc..1605fc2 100644 --- a/src/events/extras/alliace.ts +++ b/src/events/extras/alliace.ts @@ -4,6 +4,7 @@ import { // Reemplaza instancia local -> usa singleton import { prisma } from "../../core/database/prisma"; import { replaceVars } from "../../core/lib/vars"; +import logger from "../../core/lib/logger"; // Regex para detectar URLs válidas (corregido) @@ -69,7 +70,7 @@ export async function alliance(message: Message) { } } catch (error) { - console.error('Error en función alliance:', error); + logger.error({ err: error }, 'Error en función alliance'); } } @@ -130,10 +131,10 @@ async function processValidLink(message: Message, allianceChannel: any, link: st // Enviar el bloque configurado usando Display Components await sendBlockConfigV2(message, allianceChannel.blockConfigName, message.guild!.id, link, userStats, inviteData); - console.log(`✅ Punto otorgado a ${message.author.tag} por enlace válido: ${link}`); + logger.info(`✅ Punto otorgado a ${message.author.tag} por enlace válido: ${link}`); } catch (error) { - console.error('Error procesando enlace válido:', error); + logger.error({ err: error }, 'Error procesando enlace válido'); } } @@ -161,7 +162,7 @@ async function validateDiscordInvite(link: string): Promise { return null; } catch (error) { - console.error('Error validando invitación de Discord:', error); + logger.error({ err: error }, 'Error validando invitación de Discord'); return null; // En caso de error, considerar como inválido } } @@ -247,7 +248,7 @@ async function sendBlockConfigV2(message: Message, blockConfigName: string, guil }); if (!blockConfig) { - console.error(`❌ Bloque "${blockConfigName}" no encontrado para guild ${guildId}`); + logger.error(`❌ Bloque "${blockConfigName}" no encontrado para guild ${guildId}`); return; } @@ -266,8 +267,7 @@ async function sendBlockConfigV2(message: Message, blockConfigName: string, guil }); } catch (error) { - console.error('❌ Error enviando bloque de configuración V2:', error); - console.log('Detalles del error:', error); + logger.error({ err: error }, '❌ Error enviando bloque de configuración V2'); // Fallback: usar mensaje simple try { @@ -275,7 +275,7 @@ async function sendBlockConfigV2(message: Message, blockConfigName: string, guil content: '✅ ¡Enlace de alianza procesado correctamente!' }); } catch (fallbackError) { - console.error('❌ Error en fallback:', fallbackError); + logger.error({ err: fallbackError }, '❌ Error en fallback'); } } } @@ -381,7 +381,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an return { type: 17, accent_color: config.color ?? null, components: previewComponents }; } catch (error) { - console.error('Error convirtiendo configuración a Display Component:', error); + logger.error({ err: error }, 'Error convirtiendo configuración a Display Component'); return { type: 17, accent_color: null, components: [ { type: 10, content: 'Error al procesar la configuración del bloque.' } ] }; } } diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index b1617d8..63f53ac 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -4,6 +4,7 @@ import { Events } from "discord.js"; import { redis } from "../core/database/redis"; import { commands } from "../core/loaders/loader"; import { buttons, modals, selectmenus } from "../core/lib/components"; +import logger from "../core/lib/logger"; bot.on(Events.InteractionCreate, async (interaction: BaseInteraction) => { try { @@ -45,7 +46,7 @@ bot.on(Events.InteractionCreate, async (interaction: BaseInteraction) => { if (modal) await modal.run(interaction, bot); } } catch (error) { - console.error(error); + logger.error({ err: error }, "Error ejecutando interacción"); if (interaction.isRepliable()) { await interaction.reply({ content: "❌ Hubo un error ejecutando la interacción.", ephemeral: true }); } diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 1447496..39bce18 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -3,6 +3,7 @@ import {Events} from "discord.js"; import {redis} from "../core/database/redis"; import {commands} from "../core/loaders/loader"; import {alliance} from "./extras/alliace"; +import logger from "../core/lib/logger"; bot.on(Events.MessageCreate, async (message) => { @@ -30,7 +31,7 @@ bot.on(Events.MessageCreate, async (message) => { if (cooldown > 0) { const key = `cooldown:${command.name}:${message.author.id}`; const ttl = await redis.ttl(key); - console.log(`Key: ${key}, TTL: ${ttl}`); + logger.debug(`Key: ${key}, TTL: ${ttl}`); if (ttl > 0) { return message.reply(`⏳ Espera ${ttl}s antes de volver a usar **${command.name}**.`); @@ -44,7 +45,7 @@ bot.on(Events.MessageCreate, async (message) => { try { await command.run(message, args, message.client); } catch (error) { - console.error(error); + logger.error({ err: error }, "Error ejecutando comando"); await message.reply("❌ Hubo un error ejecutando el comando."); } }) \ No newline at end of file diff --git a/src/events/ready.ts b/src/events/ready.ts index 27763b2..09e054b 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,6 +1,7 @@ import {bot} from "../main"; import {Events} from "discord.js"; +import logger from "../core/lib/logger"; bot.on(Events.ClientReady, () => { - console.log("Ready!"); + logger.info("Ready!"); }) \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 03b75e1..5cb401d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,7 @@ import { startMemoryMonitor } from "./core/memory/memoryMonitor"; import {memoryOptimizer} from "./core/memory/memoryOptimizer"; import { startReminderPoller } from "./core/api/reminders"; import { ensureRemindersSchema } from "./core/api/remindersSchema"; +import logger from "./core/lib/logger"; // Activar monitor de memoria si se define la variable const __memInt = parseInt(process.env.MEMORY_LOG_INTERVAL_SECONDS || '0', 10); @@ -23,8 +24,8 @@ if (process.env.ENABLE_MEMORY_OPTIMIZER === 'true') { export const bot = new Amayo(); // Listeners de robustez del cliente Discord -bot.on('error', (e) => console.error('🐞 Discord client error:', e)); -bot.on('warn', (m) => console.warn('⚠️ Discord warn:', m)); +bot.on('error', (e) => logger.error({ err: e }, '🐞 Discord client error')); +bot.on('warn', (m) => logger.warn('⚠️ Discord warn: %s', m)); // Evitar reintentos de re-login simultáneos let relogging = false; @@ -32,10 +33,10 @@ let relogging = false; bot.on('invalidated', () => { if (relogging) return; relogging = true; - console.error('🔄 Sesión de Discord invalidada. Reintentando login...'); + logger.error('🔄 Sesión de Discord invalidada. Reintentando login...'); withRetry('Re-login tras invalidated', () => bot.play(), { minDelayMs: 2000, maxDelayMs: 60_000 }) .catch(() => { - console.error('No se pudo reloguear tras invalidated, se seguirá intentando en el bucle general.'); + logger.error('No se pudo reloguear tras invalidated, se seguirá intentando en el bucle general.'); }) .finally(() => { relogging = false; }); }); @@ -68,10 +69,10 @@ async function withRetry(name: string, fn: () => Promise, opts?: { } catch (err) { attempt++; const errMsg = err instanceof Error ? `${err.name}: ${err.message}` : String(err); - console.error(`❌ ${name} falló (intento ${attempt}) =>`, errMsg); + logger.error(`❌ ${name} falló (intento ${attempt}) => %s`, errMsg); if (!isRetryable(err, attempt)) { - console.error(`⛔ ${name}: error no recuperable, deteniendo reintentos.`); + logger.error(`⛔ ${name}: error no recuperable, deteniendo reintentos.`); throw err; } @@ -85,7 +86,7 @@ async function withRetry(name: string, fn: () => Promise, opts?: { } else { wait = Math.min(maxDelayMs, delay); } - console.warn(`⏳ Reintentando ${name} en ${wait}ms...`); + logger.warn(`⏳ Reintentando ${name} en ${wait}ms...`); await new Promise((r) => setTimeout(r, wait)); delay = Math.min(maxDelayMs, Math.floor(delay * factor)); } @@ -94,11 +95,11 @@ async function withRetry(name: string, fn: () => Promise, opts?: { // Handlers globales para robustez process.on('unhandledRejection', (reason: any, p) => { - console.error('🚨 UnhandledRejection en Promise:', p, 'razón:', reason); + logger.error({ promise: p, reason }, '🚨 UnhandledRejection en Promise'); }); process.on('uncaughtException', (err) => { - console.error('🚨 UncaughtException:', err); + logger.error({ err }, '🚨 UncaughtException'); // No salimos; dejamos que el bot continúe vivo }); @@ -115,14 +116,14 @@ process.on('multipleResolves', (type, promise, reason: any) => { // Ruido benigno de reconexiones del WS de Discord: ignorar return; } - console.warn('⚠️ multipleResolves:', type, msg); + logger.warn('⚠️ multipleResolves: %s %s', type, msg); }); let shuttingDown = false; async function gracefulShutdown() { if (shuttingDown) return; shuttingDown = true; - console.log('🛑 Apagado controlado iniciado...'); + logger.info('🛑 Apagado controlado iniciado...'); try { // Detener optimizador de memoria memoryOptimizer.stop(); @@ -131,10 +132,10 @@ async function gracefulShutdown() { try { if (redis?.isOpen) { await redis.quit(); - console.log('🔌 Redis cerrado'); + logger.info('🔌 Redis cerrado'); } } catch (e) { - console.warn('No se pudo cerrar Redis limpiamente:', e); + logger.warn({ err: e }, 'No se pudo cerrar Redis limpiamente'); } // Cerrar Prisma y Discord try { @@ -144,7 +145,7 @@ async function gracefulShutdown() { await bot.destroy(); } catch {} } finally { - console.log('✅ Apagado controlado completo'); + logger.info('✅ Apagado controlado completo'); } } @@ -152,17 +153,17 @@ process.on('SIGINT', gracefulShutdown); process.on('SIGTERM', gracefulShutdown); async function bootstrap() { - console.log("🚀 Iniciando bot..."); + logger.info("🚀 Iniciando bot..."); // Cargar recursos locales (no deberían tirar el proceso si fallan) - try { loadCommands(); } catch (e) { console.error('Error cargando comandos:', e); } - try { loadComponents(); } catch (e) { console.error('Error cargando componentes:', e); } - try { loadEvents(); } catch (e) { console.error('Error cargando eventos:', e); } + try { loadCommands(); } catch (e) { logger.error({ err: e }, 'Error cargando comandos'); } + try { loadComponents(); } catch (e) { logger.error({ err: e }, 'Error cargando componentes'); } + try { loadEvents(); } catch (e) { logger.error({ err: e }, 'Error cargando eventos'); } // Registrar comandos en segundo plano con reintentos; no bloquea el arranque del bot withRetry('Registrar slash commands', async () => { await registeringCommands(); - }).catch((e) => console.error('Registro de comandos agotó reintentos:', e)); + }).catch((e) => logger.error({ err: e }, 'Registro de comandos agotó reintentos')); // Conectar Redis con reintentos await withRetry('Conectar a Redis', async () => { @@ -181,12 +182,12 @@ async function bootstrap() { }); // Asegurar esquema de Appwrite para recordatorios (colección + atributos + índice) - try { await ensureRemindersSchema(); } catch (e) { console.warn('No se pudo asegurar el esquema de recordatorios:', e); } + try { await ensureRemindersSchema(); } catch (e) { logger.warn({ err: e }, 'No se pudo asegurar el esquema de recordatorios'); } // Iniciar poller de recordatorios si Appwrite está configurado startReminderPoller(bot); - console.log("✅ Bot conectado a Discord"); + logger.info("✅ Bot conectado a Discord"); } // Bucle de arranque resiliente: si bootstrap completo falla, reintenta sin matar el proceso