Saltearse al contenido

API y Base de Datos

📊 API y Base de Datos

Documentación técnica completa del sistema de base de datos y API interna del bot.

🗄️ Sistema de Base de Datos

Tecnología

  • Motor: Better-SQLite3
  • Tipo: Base de datos embebida
  • Ubicación: ./data/bot.db
  • Backup: Automático cada 24 horas

Estructura de Tablas

guild_configs - Configuraciones por Servidor

CREATE TABLE guild_configs (
guild_id TEXT PRIMARY KEY,
config TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Estructura JSON del campo config:

{
"language": "es",
"prefix": "!",
"welcome": {
"enabled": false,
"channel_id": null,
"message": "¡Bienvenido {user} a {server}!",
"image_url": null
},
"tickets": {
"enabled": false,
"category_id": null,
"panel_channel_id": null,
"log_channel_id": null,
"staff_role_id": null,
"categories": []
},
"levels": {
"enabled": true,
"xp_per_message": { "min": 15, "max": 25 },
"cooldown": 60000,
"excluded_channels": []
},
"moderation": {
"log_channel_id": null,
"auto_mod": false,
"warn_threshold": 3
}
}

user_levels - Sistema de Niveles

CREATE TABLE user_levels (
user_id TEXT NOT NULL,
guild_id TEXT NOT NULL,
xp INTEGER DEFAULT 0,
level INTEGER DEFAULT 0,
total_messages INTEGER DEFAULT 0,
last_xp_gain DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, guild_id)
);

tickets - Sistema de Tickets

CREATE TABLE tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
user_id TEXT NOT NULL,
category TEXT NOT NULL,
status TEXT DEFAULT 'open',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
closed_at DATETIME,
closed_by TEXT,
transcript_path TEXT
);

role_panels - Sistema de Roles

CREATE TABLE role_panels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
message_id TEXT NOT NULL,
config TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

🔧 API Interna

Database Manager

class DatabaseManager {
constructor(dbPath = './data/bot.db') {
this.db = new Database(dbPath);
this.initTables();
}
// Configuraciones de Guild
getGuildConfig(guildId) {
return this.db.prepare('SELECT config FROM guild_configs WHERE guild_id = ?').get(guildId);
}
setGuildConfig(guildId, config) {
return this.db.prepare(`
INSERT OR REPLACE INTO guild_configs (guild_id, config, updated_at)
VALUES (?, ?, datetime('now'))
`).run(guildId, JSON.stringify(config));
}
// Sistema de Niveles
getUserLevel(userId, guildId) {
return this.db.prepare(`
SELECT * FROM user_levels
WHERE user_id = ? AND guild_id = ?
`).get(userId, guildId);
}
addXP(userId, guildId, xpAmount) {
const stmt = this.db.prepare(`
INSERT OR REPLACE INTO user_levels
(user_id, guild_id, xp, level, total_messages, last_xp_gain)
VALUES (?, ?,
COALESCE((SELECT xp FROM user_levels WHERE user_id = ? AND guild_id = ?), 0) + ?,
?, ?, datetime('now'))
`);
return stmt.run(userId, guildId, userId, guildId, xpAmount, newLevel, totalMessages);
}
getLeaderboard(guildId, limit = 10, offset = 0) {
return this.db.prepare(`
SELECT user_id, xp, level
FROM user_levels
WHERE guild_id = ?
ORDER BY xp DESC
LIMIT ? OFFSET ?
`).all(guildId, limit, offset);
}
}

Config Manager

class ConfigManager {
static getDefaultConfig() {
return {
language: 'es',
welcome: {
enabled: false,
channel_id: null,
message: '¡Bienvenido {user} a {server}!',
image_url: null
},
tickets: {
enabled: false,
category_id: null,
panel_channel_id: null,
log_channel_id: null,
staff_role_id: null,
categories: [
{
id: 'support',
name: 'Soporte Técnico',
description: 'Ayuda técnica general',
emoji: '🔧'
},
{
id: 'report',
name: 'Reportar Bug',
description: 'Reportar errores o problemas',
emoji: '🐛'
}
]
},
levels: {
enabled: true,
xp_per_message: { min: 15, max: 25 },
cooldown: 60000,
excluded_channels: []
}
};
}
static async getGuildConfig(guildId) {
const data = db.getGuildConfig(guildId);
if (!data) {
const defaultConfig = this.getDefaultConfig();
db.setGuildConfig(guildId, defaultConfig);
return defaultConfig;
}
return JSON.parse(data.config);
}
static async updateGuildConfig(guildId, updates) {
const current = await this.getGuildConfig(guildId);
const merged = { ...current, ...updates };
db.setGuildConfig(guildId, merged);
return merged;
}
}

Level Manager

class LevelManager {
static calculateLevel(xp) {
return Math.floor(Math.sqrt(xp / 100));
}
static calculateXPForLevel(level) {
return level * level * 100;
}
static async addXP(userId, guildId, interaction) {
const config = await ConfigManager.getGuildConfig(guildId);
if (!config.levels.enabled) return false;
// Verificar cooldown
const userData = db.getUserLevel(userId, guildId);
if (userData && userData.last_xp_gain) {
const timeDiff = Date.now() - new Date(userData.last_xp_gain).getTime();
if (timeDiff < config.levels.cooldown) return false;
}
// Calcular XP
const { min, max } = config.levels.xp_per_message;
const xpGain = Math.floor(Math.random() * (max - min + 1)) + min;
// Actualizar base de datos
const currentXP = userData ? userData.xp : 0;
const newXP = currentXP + xpGain;
const newLevel = this.calculateLevel(newXP);
const oldLevel = userData ? userData.level : 0;
db.addXP(userId, guildId, xpGain);
// Verificar level up
if (newLevel > oldLevel) {
await this.handleLevelUp(userId, guildId, newLevel, interaction);
}
return { xpGain, newLevel, levelUp: newLevel > oldLevel };
}
static async handleLevelUp(userId, guildId, newLevel, interaction) {
const t = getTranslation(guildId);
const user = await interaction.client.users.fetch(userId);
// Mensaje de level up
const embed = new EmbedBuilder()
.setTitle(t('levels.levelUp.title'))
.setDescription(t('levels.levelUp.description', { user, level: newLevel }))
.setColor('#00FF00')
.setThumbnail(user.displayAvatarURL());
if (interaction.channel) {
await interaction.channel.send({ embeds: [embed] });
}
// Roles automáticos (si están configurados)
await this.checkAutoRoles(userId, guildId, newLevel, interaction);
}
}

🔒 Seguridad y Validación

Sanitización de Datos

class DataValidator {
static sanitizeString(str, maxLength = 2000) {
if (typeof str !== 'string') return '';
return str.slice(0, maxLength).replace(/[<>@#&]/g, '');
}
static validateGuildId(guildId) {
return /^\d{17,19}$/.test(guildId);
}
static validateUserId(userId) {
return /^\d{17,19}$/.test(userId);
}
static validateConfig(config) {
const schema = {
language: ['es', 'en'],
welcome: {
enabled: 'boolean',
channel_id: 'string|null',
message: 'string',
image_url: 'string|null'
}
};
return this.validateSchema(config, schema);
}
}

Rate Limiting

class RateLimiter {
constructor() {
this.limits = new Map();
}
isLimited(key, limit = 5, window = 60000) {
const now = Date.now();
const userLimits = this.limits.get(key) || [];
// Limpiar entradas viejas
const validLimits = userLimits.filter(time => now - time < window);
if (validLimits.length >= limit) {
return true;
}
validLimits.push(now);
this.limits.set(key, validLimits);
return false;
}
}

📈 Monitoreo y Logs

Sistema de Logs

class Logger {
static levels = {
ERROR: 0,
WARN: 1,
INFO: 2,
DEBUG: 3
};
static log(level, message, data = {}) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level: Object.keys(this.levels)[level],
message,
data
};
// Escribir a archivo
const logFile = `./logs/bot-${new Date().toISOString().split('T')[0]}.log`;
fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n');
// Console output
console.log(`[${logEntry.level}] ${timestamp} - ${message}`);
}
static error(message, data) { this.log(0, message, data); }
static warn(message, data) { this.log(1, message, data); }
static info(message, data) { this.log(2, message, data); }
static debug(message, data) { this.log(3, message, data); }
}

Métricas de Performance

class MetricsCollector {
static metrics = {
commandsExecuted: 0,
messagesProcessed: 0,
errorsCount: 0,
uptime: process.uptime()
};
static recordCommand(commandName) {
this.metrics.commandsExecuted++;
Logger.debug(`Command executed: ${commandName}`);
}
static recordError(error) {
this.metrics.errorsCount++;
Logger.error('Error recorded', { error: error.message, stack: error.stack });
}
static getMetrics() {
return {
...this.metrics,
uptime: process.uptime(),
memoryUsage: process.memoryUsage()
};
}
}

🔄 Backup y Recuperación

Sistema de Backup

class BackupManager {
static async createBackup() {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = `./data/backups/backup-${timestamp}.db`;
try {
await fs.copyFile('./data/bot.db', backupPath);
Logger.info(`Backup created: ${backupPath}`);
// Limpiar backups antiguos (mantener solo 7 días)
await this.cleanOldBackups();
return backupPath;
} catch (error) {
Logger.error('Failed to create backup', error);
throw error;
}
}
static async cleanOldBackups(daysToKeep = 7) {
const backupDir = './data/backups';
const files = await fs.readdir(backupDir);
const cutoffDate = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);
for (const file of files) {
const filePath = path.join(backupDir, file);
const stats = await fs.stat(filePath);
if (stats.mtime.getTime() < cutoffDate) {
await fs.unlink(filePath);
Logger.info(`Old backup deleted: ${file}`);
}
}
}
static async restoreFromBackup(backupPath) {
try {
await fs.copyFile(backupPath, './data/bot.db');
Logger.info(`Database restored from: ${backupPath}`);
return true;
} catch (error) {
Logger.error('Failed to restore backup', error);
return false;
}
}
}

🔌 Extensibilidad

Plugin System (Futuro)

class PluginManager {
static plugins = new Map();
static async loadPlugin(pluginPath) {
try {
const plugin = await import(pluginPath);
const instance = new plugin.default();
this.plugins.set(instance.name, instance);
Logger.info(`Plugin loaded: ${instance.name}`);
return true;
} catch (error) {
Logger.error(`Failed to load plugin: ${pluginPath}`, error);
return false;
}
}
static async executeHook(hookName, ...args) {
for (const [name, plugin] of this.plugins) {
if (typeof plugin[hookName] === 'function') {
try {
await plugin[hookName](...args);
} catch (error) {
Logger.error(`Plugin ${name} hook ${hookName} failed`, error);
}
}
}
}
}

Event Hooks

// Hooks disponibles para plugins
const hooks = {
'message.create': async (message) => {
await PluginManager.executeHook('onMessage', message);
},
'member.join': async (member) => {
await PluginManager.executeHook('onMemberJoin', member);
},
'command.execute': async (interaction) => {
await PluginManager.executeHook('onCommand', interaction);
}
};

📊 Optimización de Performance

Connection Pooling

class DatabasePool {
constructor(maxConnections = 5) {
this.connections = [];
this.maxConnections = maxConnections;
this.currentConnections = 0;
}
async getConnection() {
if (this.connections.length > 0) {
return this.connections.pop();
}
if (this.currentConnections < this.maxConnections) {
this.currentConnections++;
return new Database('./data/bot.db');
}
// Esperar por una conexión disponible
return new Promise(resolve => {
const check = setInterval(() => {
if (this.connections.length > 0) {
clearInterval(check);
resolve(this.connections.pop());
}
}, 10);
});
}
releaseConnection(connection) {
this.connections.push(connection);
}
}

Cache Layer

class CacheManager {
constructor(ttl = 300000) { // 5 minutos por defecto
this.cache = new Map();
this.ttl = ttl;
}
set(key, value) {
const expires = Date.now() + this.ttl;
this.cache.set(key, { value, expires });
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expires) {
this.cache.delete(key);
return null;
}
return item.value;
}
clear() {
this.cache.clear();
}
}

Esta documentación técnica está dirigida a desarrolladores que quieran extender o modificar el bot. Para uso básico, consulta las guías de usuario en otras secciones.