feat: improve error handling and type safety in points modal processing

This commit is contained in:
2025-10-03 23:41:11 -05:00
parent 2a399cbf35
commit 1a9eabb7af
3 changed files with 80 additions and 61 deletions

View File

@@ -84,7 +84,7 @@ export default {
} catch (e) { } catch (e) {
// @ts-ignore // @ts-ignore
logger.error('Error en ldManagePoints:', e); logger.error('Error en ldManagePoints:', e as Error);
await interaction.reply({ await interaction.reply({
content: '❌ Error al abrir el modal de gestión.', content: '❌ Error al abrir el modal de gestión.',
flags: MessageFlags.Ephemeral flags: MessageFlags.Ephemeral

View File

@@ -3,10 +3,27 @@ import {
ModalSubmitInteraction, ModalSubmitInteraction,
MessageFlags, MessageFlags,
PermissionFlagsBits, PermissionFlagsBits,
EmbedBuilder EmbedBuilder,
User,
Collection,
Snowflake
} from 'discord.js'; } from 'discord.js';
import { prisma } from '../../core/database/prisma'; import { prisma } from '../../core/database/prisma';
interface UserSelectComponent {
custom_id: string;
type: number;
values: string[];
}
interface ComponentData {
components?: ComponentData[];
component?: ComponentData;
custom_id?: string;
type?: number;
values?: string[];
}
export default { export default {
customId: 'ld_points_modal', customId: 'ld_points_modal',
run: async (interaction: ModalSubmitInteraction) => { run: async (interaction: ModalSubmitInteraction) => {
@@ -30,15 +47,16 @@ export default {
try { try {
// Obtener valores del modal con manejo seguro de errores // Obtener valores del modal con manejo seguro de errores
let totalInput: string; let totalInput: string = '';
let selectedUsers: any; let selectedUsers: ReturnType<typeof interaction.components.getSelectedUsers> = null;
let userId: string; let userId: string | undefined = undefined;
let userName: string; let userName: string | undefined = undefined;
try { try {
totalInput = interaction.components.getTextInputValue('points_input').trim(); totalInput = interaction.components.getTextInputValue('points_input').trim();
} catch (error) { } catch (error) {
logger.error('Error obteniendo points_input:', error); // @ts-ignore
logger.error('Error obteniendo points_input:', String(error));
return interaction.reply({ return interaction.reply({
content: '❌ Error al obtener el valor de puntos del modal.', content: '❌ Error al obtener el valor de puntos del modal.',
flags: MessageFlags.Ephemeral flags: MessageFlags.Ephemeral
@@ -51,10 +69,10 @@ export default {
if (!selectedUsers || selectedUsers.size === 0) { if (!selectedUsers || selectedUsers.size === 0) {
// Fallback: intentar obtener los IDs directamente de los datos raw // Fallback: intentar obtener los IDs directamente de los datos raw
const rawData = (interaction as any).data?.components; const rawData = (interaction as any).data?.components as ComponentData[] | undefined;
if (rawData) { if (rawData) {
const userSelectComponent = this.findUserSelectComponent(rawData, 'user_select'); const userSelectComponent = findUserSelectComponent(rawData, 'user_select');
if (userSelectComponent?.values?.length > 0) { if (userSelectComponent?.values?.length && userSelectComponent.values.length > 0) {
userId = userSelectComponent.values[0]; userId = userSelectComponent.values[0];
logger.info(`🔄 Fallback: UserId extraído de datos raw: ${userId}`); logger.info(`🔄 Fallback: UserId extraído de datos raw: ${userId}`);
} }
@@ -67,26 +85,30 @@ export default {
}); });
} }
} else { } else {
const selectedUser = Array.from(selectedUsers.values())[0]; const selectedUser = Array.from(selectedUsers.values())[0] as User;
userId = selectedUser?.id; if (selectedUser) {
userName = selectedUser?.tag ?? selectedUser?.username ?? userId; userId = selectedUser.id;
userName = selectedUser.tag ?? selectedUser.username ?? userId;
}
} }
} catch (error) { } catch (error) {
logger.error('Error procesando UserSelect, intentando fallback:', error); // @ts-ignore
logger.error('Error procesando UserSelect, intentando fallback:', String(error));
// Fallback más agresivo: obtener directamente de los datos raw // Fallback más agresivo: obtener directamente de los datos raw
try { try {
const rawData = (interaction as any).data?.components; const rawData = (interaction as any).data?.components as ComponentData[] | undefined;
const userSelectComponent = this.findUserSelectComponent(rawData, 'user_select'); const userSelectComponent = findUserSelectComponent(rawData, 'user_select');
if (userSelectComponent?.values?.length > 0) { if (userSelectComponent?.values?.length && userSelectComponent.values.length > 0) {
userId = userSelectComponent.values[0]; userId = userSelectComponent.values[0];
logger.info(`🔄 Fallback agresivo: UserId extraído: ${userId}`); logger.info(`🔄 Fallback agresivo: UserId extraído: ${userId}`);
} else { } else {
throw new Error('No se pudo extraer userId de los datos raw'); throw new Error('No se pudo extraer userId de los datos raw');
} }
} catch (fallbackError) { } catch (fallbackError) {
logger.error('Falló el fallback:', fallbackError); // @ts-ignore
logger.error('Falló el fallback:', String(fallbackError));
return interaction.reply({ return interaction.reply({
content: '❌ Error procesando la selección de usuario. Inténtalo de nuevo.', content: '❌ Error procesando la selección de usuario. Inténtalo de nuevo.',
flags: MessageFlags.Ephemeral flags: MessageFlags.Ephemeral
@@ -117,7 +139,8 @@ export default {
const targetMember = await interaction.guild.members.fetch(userId); const targetMember = await interaction.guild.members.fetch(userId);
userName = targetMember.displayName || targetMember.user.username; userName = targetMember.displayName || targetMember.user.username;
} catch (error) { } catch (error) {
logger.warn(`No se pudo obtener info del usuario ${userId}:`, error); // @ts-ignore
logger.warn(`No se pudo obtener info del usuario ${userId}:`, String(error));
userName = `Usuario ${userId}`; userName = `Usuario ${userId}`;
} }
} }
@@ -274,7 +297,8 @@ export default {
} catch (error) { } catch (error) {
// @ts-ignore // @ts-ignore
logger.error('❌ Error en ldPointsModal:', error); // @ts-ignore
logger.error('❌ Error en ldPointsModal:', String(error));
if (!interaction.replied && !interaction.deferred) { if (!interaction.replied && !interaction.deferred) {
await interaction.reply({ await interaction.reply({
@@ -283,31 +307,31 @@ export default {
}); });
} }
} }
}, }
};
// Función auxiliar para buscar componentes UserSelect en datos raw // Función auxiliar para buscar componentes UserSelect en datos raw
findUserSelectComponent(components: any[], customId: string): any { function findUserSelectComponent(components: ComponentData[] | undefined, customId: string): UserSelectComponent | null {
if (!components) return null; if (!components) return null;
for (const comp of components) { for (const comp of components) {
if (comp.components) { if (comp.components) {
const found = this.findUserSelectComponent(comp.components, customId); const found = findUserSelectComponent(comp.components, customId);
if (found) return found; if (found) return found;
} }
if (comp.component) { if (comp.component) {
if (comp.component.custom_id === customId) { if (comp.component.custom_id === customId) {
return comp.component; return comp.component as UserSelectComponent;
} }
const found = this.findUserSelectComponent([comp.component], customId); const found = findUserSelectComponent([comp.component], customId);
if (found) return found; if (found) return found;
} }
if (comp.custom_id === customId) { if (comp.custom_id === customId) {
return comp; return comp as UserSelectComponent;
} }
} }
return null; return null;
} }
};

View File

@@ -8,17 +8,13 @@ bot.on(Events.ClientReady, () => {
// ============================================ // ============================================
// 🛡️ HANDLER GLOBAL PARA ERRORES DE DISCORD.JS // 🛡️ HANDLER GLOBAL PARA ERRORES DE DISCORD.JS
// ============================================ // ============================================
// Interceptar errores específicos de ModalSubmitInteraction con UserSelect
process.on('uncaughtException', (error) => { process.on('uncaughtException', (error: Error) => {
// Interceptar errores específicos de Discord.js GuildMemberManager // Interceptar errores específicos de Discord.js GuildMemberManager
if (error.message?.includes("Cannot read properties of undefined (reading 'id')") && if (error.message?.includes("Cannot read properties of undefined (reading 'id')") &&
error.stack?.includes('GuildMemberManager._add')) { error.stack?.includes('GuildMemberManager._add')) {
logger.warn('🔧 Discord.js bug interceptado: GuildMemberManager error con UserSelect en modal'); logger.warn('🔧 Discord.js bug interceptado: GuildMemberManager error con UserSelect en modal');
// @ts-ignore
logger.warn('Stack trace:', error.stack);
// NO terminar el proceso, solo logear el error // NO terminar el proceso, solo logear el error
return; return;
} }
@@ -29,24 +25,23 @@ bot.on(Events.ClientReady, () => {
process.exit(1); process.exit(1);
}); });
process.on('unhandledRejection', (reason, promise) => { process.on('unhandledRejection', (reason: unknown) => {
// Interceptar rechazos relacionados con el mismo bug // Interceptar rechazos relacionados con el mismo bug
if (reason && typeof reason === 'object' && if (reason &&
typeof reason === 'object' &&
reason !== null &&
'message' in reason && 'message' in reason &&
// @ts-ignore typeof (reason as any).message === 'string' &&
reason.message?.includes("Cannot read properties of undefined (reading 'id')")) { (reason as any).message.includes("Cannot read properties of undefined (reading 'id')")) {
logger.warn('🔧 Discord.js promise rejection interceptada: GuildMemberManager error'); logger.warn('🔧 Discord.js promise rejection interceptada: GuildMemberManager error');
// @ts-ignore
logger.warn('Reason:', reason);
// NO terminar el proceso // NO terminar el proceso
return; return;
} }
// Para otras promesas rechazadas, logear pero continuar // Para otras promesas rechazadas, logear pero continuar
// @ts-ignore // @ts-ignore
logger.error('🚨 UnhandledRejection:', reason); logger.error('🚨 UnhandledRejection:', reason as Error);
}); });
// ============================================ // ============================================