2025-10-03 20:48:33 -05:00
|
|
|
|
import logger from "../../core/lib/logger";
|
|
|
|
|
|
import {
|
|
|
|
|
|
ModalSubmitInteraction,
|
|
|
|
|
|
MessageFlags,
|
|
|
|
|
|
PermissionFlagsBits,
|
2025-10-03 23:41:11 -05:00
|
|
|
|
EmbedBuilder,
|
|
|
|
|
|
User,
|
|
|
|
|
|
Collection,
|
|
|
|
|
|
Snowflake
|
2025-10-03 20:48:33 -05:00
|
|
|
|
} from 'discord.js';
|
|
|
|
|
|
import { prisma } from '../../core/database/prisma';
|
|
|
|
|
|
|
2025-10-03 23:41:11 -05:00
|
|
|
|
interface UserSelectComponent {
|
|
|
|
|
|
custom_id: string;
|
|
|
|
|
|
type: number;
|
|
|
|
|
|
values: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ComponentData {
|
|
|
|
|
|
components?: ComponentData[];
|
|
|
|
|
|
component?: ComponentData;
|
|
|
|
|
|
custom_id?: string;
|
|
|
|
|
|
type?: number;
|
|
|
|
|
|
values?: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 20:48:33 -05:00
|
|
|
|
export default {
|
|
|
|
|
|
customId: 'ld_points_modal',
|
|
|
|
|
|
run: async (interaction: ModalSubmitInteraction) => {
|
2025-10-03 21:03:30 -05:00
|
|
|
|
logger.info(`🔍 Modal ldPointsModal ejecutado. CustomId: ${interaction.customId}`);
|
|
|
|
|
|
|
2025-10-03 20:48:33 -05:00
|
|
|
|
if (!interaction.guild) {
|
|
|
|
|
|
return interaction.reply({
|
|
|
|
|
|
content: '❌ Solo disponible en servidores.',
|
|
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Verificar permisos
|
|
|
|
|
|
const member = await interaction.guild.members.fetch(interaction.user.id);
|
|
|
|
|
|
if (!member.permissions.has(PermissionFlagsBits.ManageGuild)) {
|
|
|
|
|
|
return interaction.reply({
|
|
|
|
|
|
content: '❌ Solo los administradores pueden gestionar puntos.',
|
|
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-10-03 23:29:53 -05:00
|
|
|
|
// Obtener valores del modal con manejo seguro de errores
|
2025-10-03 23:41:11 -05:00
|
|
|
|
let totalInput: string = '';
|
|
|
|
|
|
let selectedUsers: ReturnType<typeof interaction.components.getSelectedUsers> = null;
|
|
|
|
|
|
let userId: string | undefined = undefined;
|
|
|
|
|
|
let userName: string | undefined = undefined;
|
2025-10-03 21:03:30 -05:00
|
|
|
|
|
2025-10-03 23:29:53 -05:00
|
|
|
|
try {
|
|
|
|
|
|
totalInput = interaction.components.getTextInputValue('points_input').trim();
|
|
|
|
|
|
} catch (error) {
|
2025-10-03 23:41:11 -05:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
logger.error('Error obteniendo points_input:', String(error));
|
2025-10-03 20:48:33 -05:00
|
|
|
|
return interaction.reply({
|
2025-10-03 23:29:53 -05:00
|
|
|
|
content: '❌ Error al obtener el valor de puntos del modal.',
|
2025-10-03 20:48:33 -05:00
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 23:29:53 -05:00
|
|
|
|
// Manejo seguro del UserSelect con fallback
|
|
|
|
|
|
try {
|
|
|
|
|
|
selectedUsers = interaction.components.getSelectedUsers('user_select');
|
|
|
|
|
|
|
|
|
|
|
|
if (!selectedUsers || selectedUsers.size === 0) {
|
|
|
|
|
|
// Fallback: intentar obtener los IDs directamente de los datos raw
|
2025-10-03 23:41:11 -05:00
|
|
|
|
const rawData = (interaction as any).data?.components as ComponentData[] | undefined;
|
2025-10-03 23:29:53 -05:00
|
|
|
|
if (rawData) {
|
2025-10-03 23:41:11 -05:00
|
|
|
|
const userSelectComponent = findUserSelectComponent(rawData, 'user_select');
|
|
|
|
|
|
if (userSelectComponent?.values?.length && userSelectComponent.values.length > 0) {
|
2025-10-03 23:29:53 -05:00
|
|
|
|
userId = userSelectComponent.values[0];
|
|
|
|
|
|
logger.info(`🔄 Fallback: UserId extraído de datos raw: ${userId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!userId) {
|
|
|
|
|
|
return interaction.reply({
|
|
|
|
|
|
content: '❌ Debes seleccionar un usuario del leaderboard.',
|
|
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-10-03 23:41:11 -05:00
|
|
|
|
const selectedUser = Array.from(selectedUsers.values())[0] as User;
|
|
|
|
|
|
if (selectedUser) {
|
|
|
|
|
|
userId = selectedUser.id;
|
|
|
|
|
|
userName = selectedUser.tag ?? selectedUser.username ?? userId;
|
|
|
|
|
|
}
|
2025-10-03 23:29:53 -05:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2025-10-03 23:41:11 -05:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
logger.error('Error procesando UserSelect, intentando fallback:', String(error));
|
2025-10-03 23:29:53 -05:00
|
|
|
|
|
|
|
|
|
|
// Fallback más agresivo: obtener directamente de los datos raw
|
|
|
|
|
|
try {
|
2025-10-03 23:41:11 -05:00
|
|
|
|
const rawData = (interaction as any).data?.components as ComponentData[] | undefined;
|
|
|
|
|
|
const userSelectComponent = findUserSelectComponent(rawData, 'user_select');
|
2025-10-03 23:29:53 -05:00
|
|
|
|
|
2025-10-03 23:41:11 -05:00
|
|
|
|
if (userSelectComponent?.values?.length && userSelectComponent.values.length > 0) {
|
2025-10-03 23:29:53 -05:00
|
|
|
|
userId = userSelectComponent.values[0];
|
|
|
|
|
|
logger.info(`🔄 Fallback agresivo: UserId extraído: ${userId}`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('No se pudo extraer userId de los datos raw');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (fallbackError) {
|
2025-10-03 23:41:11 -05:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
logger.error('Falló el fallback:', String(fallbackError));
|
2025-10-03 23:29:53 -05:00
|
|
|
|
return interaction.reply({
|
|
|
|
|
|
content: '❌ Error procesando la selección de usuario. Inténtalo de nuevo.',
|
|
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(`🔍 Input recibido: ${totalInput}`);
|
|
|
|
|
|
logger.info(`🔍 UserId extraído: ${userId}`);
|
|
|
|
|
|
|
|
|
|
|
|
if (!totalInput) {
|
2025-10-03 22:38:49 -05:00
|
|
|
|
return interaction.reply({
|
2025-10-03 23:29:53 -05:00
|
|
|
|
content: '❌ Debes ingresar un valor para modificar.',
|
2025-10-03 22:38:49 -05:00
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-10-03 20:48:33 -05:00
|
|
|
|
|
2025-10-03 22:38:49 -05:00
|
|
|
|
if (!userId) {
|
2025-10-03 20:48:33 -05:00
|
|
|
|
return interaction.reply({
|
2025-10-03 22:38:49 -05:00
|
|
|
|
content: '❌ Error al identificar el usuario seleccionado.',
|
2025-10-03 20:48:33 -05:00
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 23:29:53 -05:00
|
|
|
|
// Si no tenemos userName, intentar obtenerlo del servidor
|
|
|
|
|
|
if (!userName) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const targetMember = await interaction.guild.members.fetch(userId);
|
|
|
|
|
|
userName = targetMember.displayName || targetMember.user.username;
|
|
|
|
|
|
} catch (error) {
|
2025-10-03 23:41:11 -05:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
logger.warn(`No se pudo obtener info del usuario ${userId}:`, String(error));
|
2025-10-03 23:29:53 -05:00
|
|
|
|
userName = `Usuario ${userId}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-03 22:38:49 -05:00
|
|
|
|
|
2025-10-03 20:48:33 -05:00
|
|
|
|
// Obtener o crear el registro de stats del usuario
|
|
|
|
|
|
let stats = await prisma.partnershipStats.findUnique({
|
|
|
|
|
|
where: {
|
|
|
|
|
|
userId_guildId: {
|
|
|
|
|
|
userId,
|
|
|
|
|
|
guildId: interaction.guild.id
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!stats) {
|
2025-10-03 21:03:30 -05:00
|
|
|
|
logger.info(`🔍 Creando nuevo registro de stats para userId: ${userId}`);
|
2025-10-03 20:48:33 -05:00
|
|
|
|
// Crear nuevo registro si no existe
|
|
|
|
|
|
stats = await prisma.partnershipStats.create({
|
|
|
|
|
|
data: {
|
|
|
|
|
|
userId,
|
|
|
|
|
|
guildId: interaction.guild.id,
|
|
|
|
|
|
totalPoints: 0,
|
|
|
|
|
|
weeklyPoints: 0,
|
|
|
|
|
|
monthlyPoints: 0
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 21:08:58 -05:00
|
|
|
|
logger.info(`🔍 Stats actuales - Total: ${stats.totalPoints}, Semanal: ${stats.weeklyPoints}, Mensual: ${stats.monthlyPoints}`);
|
2025-10-03 21:03:30 -05:00
|
|
|
|
|
2025-10-03 20:48:33 -05:00
|
|
|
|
// Función para parsear el input y calcular el nuevo valor
|
|
|
|
|
|
const calculateNewValue = (input: string, currentValue: number): number => {
|
|
|
|
|
|
const firstChar = input[0];
|
|
|
|
|
|
|
|
|
|
|
|
if (firstChar === '+') {
|
2025-10-03 20:59:22 -05:00
|
|
|
|
// Añadir puntos
|
|
|
|
|
|
const numValue = parseInt(input.substring(1)) || 0;
|
2025-10-03 20:48:33 -05:00
|
|
|
|
return Math.max(0, currentValue + numValue);
|
|
|
|
|
|
} else if (firstChar === '-') {
|
2025-10-03 20:59:22 -05:00
|
|
|
|
// Quitar puntos (los últimos N puntos añadidos)
|
|
|
|
|
|
const numValue = parseInt(input.substring(1)) || 0;
|
2025-10-03 20:48:33 -05:00
|
|
|
|
return Math.max(0, currentValue - numValue);
|
|
|
|
|
|
} else if (firstChar === '=') {
|
2025-10-03 20:59:22 -05:00
|
|
|
|
// Establecer valor absoluto
|
|
|
|
|
|
const numValue = parseInt(input.substring(1)) || 0;
|
2025-10-03 20:48:33 -05:00
|
|
|
|
return Math.max(0, numValue);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Si no tiene símbolo, tratar como valor absoluto
|
|
|
|
|
|
const parsedValue = parseInt(input);
|
|
|
|
|
|
return isNaN(parsedValue) ? currentValue : Math.max(0, parsedValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-03 20:59:22 -05:00
|
|
|
|
// Calcular nuevo valor de puntos totales
|
2025-10-03 20:48:33 -05:00
|
|
|
|
const newTotalPoints = calculateNewValue(totalInput, stats.totalPoints);
|
2025-10-03 21:08:58 -05:00
|
|
|
|
const totalDifference = newTotalPoints - stats.totalPoints;
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(`🔍 Nuevo total calculado: ${newTotalPoints} (diferencia: ${totalDifference})`);
|
|
|
|
|
|
|
|
|
|
|
|
// Calcular nuevos puntos semanales y mensuales
|
|
|
|
|
|
let newWeeklyPoints = stats.weeklyPoints;
|
|
|
|
|
|
let newMonthlyPoints = stats.monthlyPoints;
|
|
|
|
|
|
|
|
|
|
|
|
if (totalInput[0] === '+') {
|
|
|
|
|
|
// Si añadimos puntos, sumar a semanal y mensual también
|
|
|
|
|
|
const pointsToAdd = parseInt(totalInput.substring(1)) || 0;
|
|
|
|
|
|
newWeeklyPoints = stats.weeklyPoints + pointsToAdd;
|
|
|
|
|
|
newMonthlyPoints = stats.monthlyPoints + pointsToAdd;
|
|
|
|
|
|
logger.info(`➕ Añadiendo ${pointsToAdd} puntos a todas las categorías`);
|
|
|
|
|
|
} else if (totalInput[0] === '-') {
|
|
|
|
|
|
// Si quitamos puntos, restar proporcionalmente de semanal y mensual
|
|
|
|
|
|
const pointsToRemove = parseInt(totalInput.substring(1)) || 0;
|
|
|
|
|
|
newWeeklyPoints = Math.max(0, stats.weeklyPoints - pointsToRemove);
|
|
|
|
|
|
newMonthlyPoints = Math.max(0, stats.monthlyPoints - pointsToRemove);
|
|
|
|
|
|
logger.info(`➖ Quitando ${pointsToRemove} puntos de todas las categorías`);
|
|
|
|
|
|
} else if (totalInput[0] === '=') {
|
|
|
|
|
|
// Si establecemos un valor absoluto, ajustar semanal y mensual proporcionalmente
|
|
|
|
|
|
const targetTotal = parseInt(totalInput.substring(1)) || 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (stats.totalPoints > 0) {
|
|
|
|
|
|
// Calcular el ratio y aplicarlo
|
|
|
|
|
|
const ratio = targetTotal / stats.totalPoints;
|
|
|
|
|
|
newWeeklyPoints = Math.round(stats.weeklyPoints * ratio);
|
|
|
|
|
|
newMonthlyPoints = Math.round(stats.monthlyPoints * ratio);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Si no había puntos antes, establecer todo a 0
|
|
|
|
|
|
newWeeklyPoints = 0;
|
|
|
|
|
|
newMonthlyPoints = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.info(`🎯 Estableciendo total a ${targetTotal} y ajustando proporcionalmente`);
|
|
|
|
|
|
}
|
2025-10-03 20:48:33 -05:00
|
|
|
|
|
2025-10-03 21:08:58 -05:00
|
|
|
|
// Asegurar que semanal no exceda mensual, y mensual no exceda total
|
|
|
|
|
|
newWeeklyPoints = Math.min(newWeeklyPoints, newMonthlyPoints, newTotalPoints);
|
|
|
|
|
|
newMonthlyPoints = Math.min(newMonthlyPoints, newTotalPoints);
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(`🔍 Nuevos valores calculados - Total: ${newTotalPoints}, Semanal: ${newWeeklyPoints}, Mensual: ${newMonthlyPoints}`);
|
|
|
|
|
|
|
|
|
|
|
|
// Actualizar en base de datos (todos los puntos)
|
2025-10-03 20:59:22 -05:00
|
|
|
|
await prisma.partnershipStats.update({
|
2025-10-03 20:48:33 -05:00
|
|
|
|
where: {
|
|
|
|
|
|
userId_guildId: {
|
|
|
|
|
|
userId,
|
|
|
|
|
|
guildId: interaction.guild.id
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
data: {
|
2025-10-03 21:08:58 -05:00
|
|
|
|
totalPoints: newTotalPoints,
|
|
|
|
|
|
weeklyPoints: newWeeklyPoints,
|
|
|
|
|
|
monthlyPoints: newMonthlyPoints
|
2025-10-03 20:48:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-03 21:03:30 -05:00
|
|
|
|
logger.info(`✅ Puntos actualizados exitosamente en la base de datos`);
|
|
|
|
|
|
|
2025-10-03 21:08:58 -05:00
|
|
|
|
// Calcular las diferencias
|
|
|
|
|
|
const totalDiff = newTotalPoints - stats.totalPoints;
|
|
|
|
|
|
const weeklyDiff = newWeeklyPoints - stats.weeklyPoints;
|
|
|
|
|
|
const monthlyDiff = newMonthlyPoints - stats.monthlyPoints;
|
|
|
|
|
|
|
|
|
|
|
|
const totalDiffText = totalDiff > 0 ? `+${totalDiff}` : `${totalDiff}`;
|
|
|
|
|
|
const weeklyDiffText = weeklyDiff > 0 ? `+${weeklyDiff}` : `${weeklyDiff}`;
|
|
|
|
|
|
const monthlyDiffText = monthlyDiff > 0 ? `+${monthlyDiff}` : `${monthlyDiff}`;
|
2025-10-03 20:59:22 -05:00
|
|
|
|
|
2025-10-03 20:48:33 -05:00
|
|
|
|
// Crear embed de confirmación
|
|
|
|
|
|
const embed = new EmbedBuilder()
|
2025-10-03 21:08:58 -05:00
|
|
|
|
.setColor(totalDiff >= 0 ? 0x00ff00 : 0xff9900)
|
2025-10-03 20:48:33 -05:00
|
|
|
|
.setTitle('✅ Puntos Actualizados')
|
|
|
|
|
|
.setDescription(`Se han actualizado los puntos de **${userName}**`)
|
|
|
|
|
|
.addFields(
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '📊 Puntos Totales',
|
2025-10-03 21:08:58 -05:00
|
|
|
|
value: `${stats.totalPoints} → **${newTotalPoints}** (${totalDiffText})`,
|
|
|
|
|
|
inline: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '🗓️ Puntos Mensuales',
|
|
|
|
|
|
value: `${stats.monthlyPoints} → **${newMonthlyPoints}** (${monthlyDiffText})`,
|
|
|
|
|
|
inline: true
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '📅 Puntos Semanales',
|
|
|
|
|
|
value: `${stats.weeklyPoints} → **${newWeeklyPoints}** (${weeklyDiffText})`,
|
|
|
|
|
|
inline: true
|
2025-10-03 20:48:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
)
|
2025-10-03 23:25:17 -05:00
|
|
|
|
.setFooter({ text: `Actualizado por ${interaction.user.username}` })
|
|
|
|
|
|
.setTimestamp();
|
2025-10-03 20:48:33 -05:00
|
|
|
|
|
|
|
|
|
|
await interaction.reply({
|
|
|
|
|
|
embeds: [embed],
|
|
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-03 23:25:17 -05:00
|
|
|
|
} catch (error) {
|
2025-10-03 23:41:11 -05:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
logger.error('❌ Error en ldPointsModal:', String(error));
|
2025-10-03 21:03:30 -05:00
|
|
|
|
|
2025-10-03 23:25:17 -05:00
|
|
|
|
if (!interaction.replied && !interaction.deferred) {
|
|
|
|
|
|
await interaction.reply({
|
|
|
|
|
|
content: '❌ Error interno al procesar la solicitud. Revisa los logs para más detalles.',
|
|
|
|
|
|
flags: MessageFlags.Ephemeral
|
|
|
|
|
|
});
|
2025-10-03 21:03:30 -05:00
|
|
|
|
}
|
2025-10-03 20:48:33 -05:00
|
|
|
|
}
|
2025-10-03 23:41:11 -05:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-10-03 23:29:53 -05:00
|
|
|
|
|
2025-10-03 23:41:11 -05:00
|
|
|
|
// Función auxiliar para buscar componentes UserSelect en datos raw
|
|
|
|
|
|
function findUserSelectComponent(components: ComponentData[] | undefined, customId: string): UserSelectComponent | null {
|
|
|
|
|
|
if (!components) return null;
|
2025-10-03 23:29:53 -05:00
|
|
|
|
|
2025-10-03 23:41:11 -05:00
|
|
|
|
for (const comp of components) {
|
|
|
|
|
|
if (comp.components) {
|
|
|
|
|
|
const found = findUserSelectComponent(comp.components, customId);
|
|
|
|
|
|
if (found) return found;
|
|
|
|
|
|
}
|
2025-10-03 23:29:53 -05:00
|
|
|
|
|
2025-10-03 23:41:11 -05:00
|
|
|
|
if (comp.component) {
|
|
|
|
|
|
if (comp.component.custom_id === customId) {
|
|
|
|
|
|
return comp.component as UserSelectComponent;
|
2025-10-03 23:29:53 -05:00
|
|
|
|
}
|
2025-10-03 23:41:11 -05:00
|
|
|
|
const found = findUserSelectComponent([comp.component], customId);
|
|
|
|
|
|
if (found) return found;
|
2025-10-03 23:29:53 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 23:41:11 -05:00
|
|
|
|
if (comp.custom_id === customId) {
|
|
|
|
|
|
return comp as UserSelectComponent;
|
|
|
|
|
|
}
|
2025-10-03 20:48:33 -05:00
|
|
|
|
}
|
2025-10-03 23:41:11 -05:00
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|