Files
amayo/prisma/schema.prisma
2025-11-24 16:16:01 -06:00

1205 lines
37 KiB
Plaintext

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
/**
* -----------------------------------------------------------------------------
* Modelo para el Servidor (Guild)
* -----------------------------------------------------------------------------
*/
model Guild {
id String @id
name String
prefix String @default("!")
staff Json?
aiRolePrompt String?
// Relaciones
alliances Alliance[]
partnerStats PartnershipStats[]
// ✅ CAMBIO: Ahora un Guild puede tener MÚLTIPLES configuraciones de embed.
embedConfigs EmbedConfig[]
BlockV2Config BlockV2Config[]
// ✅ NUEVAS RELACIONES
allianceChannels AllianceChannel[]
pointsHistory PointHistory[]
EconomyItem EconomyItem[]
InventoryEntry InventoryEntry[]
ShopOffer ShopOffer[]
ItemMutation ItemMutation[]
EconomyWallet EconomyWallet[]
ShopPurchase ShopPurchase[]
// Nuevas relaciones para el motor de minijuegos
gameAreas GameArea[]
minigameRuns MinigameRun[]
playerProgress PlayerProgress[]
Mob Mob[]
PlayerState PlayerState[]
PlayerEquipment PlayerEquipment[]
ActionCooldown ActionCooldown[]
SmeltJob SmeltJob[]
ScheduledMobAttack ScheduledMobAttack[]
// Nuevas relaciones para sistemas de engagement
Achievement Achievement[]
PlayerAchievement PlayerAchievement[]
Quest Quest[]
QuestProgress QuestProgress[]
PlayerStats PlayerStats[]
PlayerStreak PlayerStreak[]
AuditLog AuditLog[]
PlayerStatusEffect PlayerStatusEffect[]
DeathLog DeathLog[]
}
/**
* -----------------------------------------------------------------------------
* Modelo para el Usuario
* -----------------------------------------------------------------------------
* Representa a un usuario de Discord de manera global.
*/
model User {
id String @id
// Relaciones
partnerStats PartnershipStats[]
createdAlliances Alliance[]
// ✅ NUEVA RELACIÓN
pointsHistory PointHistory[]
InventoryEntry InventoryEntry[]
EconomyWallet EconomyWallet[]
ShopPurchase ShopPurchase[]
// Nuevas relaciones para el motor de minijuegos
minigameRuns MinigameRun[]
playerProgress PlayerProgress[]
PlayerState PlayerState[]
PlayerEquipment PlayerEquipment[]
ActionCooldown ActionCooldown[]
SmeltJob SmeltJob[]
ScheduledMobAttack ScheduledMobAttack[]
// Nuevas relaciones para sistemas de engagement
PlayerAchievement PlayerAchievement[]
QuestProgress QuestProgress[]
PlayerStats PlayerStats[]
PlayerStreak PlayerStreak[]
AuditLog AuditLog[]
PlayerStatusEffect PlayerStatusEffect[]
DeathLog DeathLog[]
}
/**
* -----------------------------------------------------------------------------
* Modelo para las Estadísticas de Alianza (Leaderboard)
* -----------------------------------------------------------------------------
* Almacena los puntos de un usuario EN UN SERVIDOR específico.
* Se gana 1 punto por mensaje en los canales registrados.
*/
model PartnershipStats {
// Puntos acumulados totales.
totalPoints Int @default(0)
// Puntos para la tabla de clasificación semanal.
weeklyPoints Int @default(0)
// Puntos para la tabla de clasificación mensual.
monthlyPoints Int @default(0)
// Fecha del último reinicio para controlar los contadores.
lastWeeklyReset DateTime @default(now())
lastMonthlyReset DateTime @default(now())
// --- Relaciones y Clave Primaria ---
user User @relation(fields: [userId], references: [id])
userId String
guild Guild @relation(fields: [guildId], references: [id])
guildId String
// Un usuario solo puede tener un registro de estadísticas por servidor.
@@id([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Modelo para la Alianza (El mensaje publicado)
* -----------------------------------------------------------------------------
* Guarda la referencia al mensaje de alianza, pero no su contenido.
* El contenido se construye dinámicamente usando EmbedConfig y PartnershipStats.
*/
model Alliance {
id String @id @default(cuid())
channelId String
messageId String @unique
createdAt DateTime @default(now())
// --- Relaciones ---
guild Guild @relation(fields: [guildId], references: [id])
guildId String
creator User @relation(fields: [creatorId], references: [id])
creatorId String
}
/**
* -----------------------------------------------------------------------------
* Modelo para Canales de Alianza
* -----------------------------------------------------------------------------
* Gestiona qué canales están configurados para otorgar puntos y qué bloque enviar
*/
model AllianceChannel {
id String @id @default(cuid())
channelId String @unique // ID del canal de Discord
// Configuración del canal
blockConfigName String // Nombre del BlockV2Config a enviar
isActive Boolean @default(true)
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// --- Relaciones ---
guild Guild @relation(fields: [guildId], references: [id])
guildId String
// Historial de puntos otorgados en este canal
pointsHistory PointHistory[]
// Un canal solo puede estar en un servidor
@@unique([guildId, channelId])
}
/**
* -----------------------------------------------------------------------------
* Modelo para Historial de Puntos
* -----------------------------------------------------------------------------
* Registra cada vez que un usuario gana puntos con fecha y hora
*/
model PointHistory {
id String @id @default(cuid())
// Información del punto otorgado
points Int @default(1)
timestamp DateTime @default(now())
messageId String // ID del mensaje que generó el punto
// --- Relaciones ---
user User @relation(fields: [userId], references: [id])
userId String
guild Guild @relation(fields: [guildId], references: [id])
guildId String
allianceChannel AllianceChannel @relation(fields: [channelId], references: [id])
channelId String
}
/**
* -----------------------------------------------------------------------------
* Modelo para la Configuración del Embed
* -----------------------------------------------------------------------------
*/
model EmbedConfig {
id String @id @default(cuid())
// ✅ NUEVO: Un nombre único para identificar este embed dentro del servidor.
// Ejemplos: "alianza", "bienvenida", "reglas"
name String
// Campos del Embed (título, descripción, color, etc.)
color String?
title String?
url String?
authorName String?
authorIconURL String?
authorURL String?
description String?
thumbnailURL String?
imageURL String?
footerText String?
footerIconURL String?
fields String? @default("[]")
// --- Relación ---
guild Guild @relation(fields: [guildId], references: [id])
// ✅ CAMBIO: Quitamos '@unique' para permitir que un guildId aparezca múltiples veces.
guildId String
// ✅ NUEVO: Asegura que el 'name' sea único por cada servidor.
// No puedes tener dos embeds llamados "alianza" en el mismo servidor.
@@unique([guildId, name])
}
/**
* -----------------------------------------------------------------------------
* Modelo para la Configuración de Bloques V2
* -----------------------------------------------------------------------------
*/
model BlockV2Config {
id String @id @default(cuid())
// ✅ Nombre único dentro de cada servidor
name String
// Configuración en JSON (embed + componentes, botones, etc.)
config Json
// Relación con el servidor
guild Guild @relation(fields: [guildId], references: [id])
guildId String
// 🔒 Asegura que un nombre no se repita dentro del mismo servidor
@@unique([guildId, name])
}
/**
* -----------------------------------------------------------------------------
* Economía: Catálogo de Ítems
* -----------------------------------------------------------------------------
* - Definición global o por servidor (guildId opcional)
* - Uso masivo de JSON para banderas y configuraciones opcionales
* - Campos de fecha para disponibilidad/adquisición/uso
* - maxPerInventory permite limitar cuántos puede tener un usuario
*/
model EconomyItem {
id String @id @default(cuid())
// Clave estable única por servidor (o global si guildId es null)
key String
name String
description String?
category String?
icon String?
// Si es apilable (stackable). Si es false, puedes manejar instancias en state JSON del inventario
stackable Boolean @default(true)
// Límite duro por inventario (p. ej. 1 o 2); null = ilimitado
maxPerInventory Int?
// Ámbito opcional por servidor. Si es null, el ítem es global
guildId String?
guild Guild? @relation(fields: [guildId], references: [id])
// Ventanas de disponibilidad (para adquirir) y de uso (para poder usarse)
availableFrom DateTime?
availableTo DateTime?
usableFrom DateTime?
usableTo DateTime?
// Etiquetas libres (requiere PostgreSQL)
tags String[]
// Propiedades dinámicas: banderas como breakable/craftable, chestRewards, eventCurrency, passiveEffects, shop, etc.
props Json?
// Cualquier metadato adicional (para extensiones futuras)
metadata Json?
// Relaciones
recipes ItemRecipe[]
inventories InventoryEntry[]
shopOffers ShopOffer[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
RecipeIngredient RecipeIngredient[]
SmeltJob SmeltJob[]
@@unique([guildId, key])
@@index([guildId])
}
/**
* -----------------------------------------------------------------------------
* Economía: Inventario por Usuario x Servidor x Ítem
* -----------------------------------------------------------------------------
* - "state" JSON permite almacenar durabilidad, instancias, efectos/mutaciones aplicadas, expiraciones, etc.
* - Clave única por (userId, guildId, itemId) para stacks; id sintético para relaciones hijas
*/
model InventoryEntry {
id String @id @default(cuid())
userId String
guildId String
itemId String
quantity Int @default(0)
// JSON flexible: { instances:[{ durability: 50, effects:[...], expiresAt:null }], notes:"..." }
state Json?
acquiredAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relaciones
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
item EconomyItem @relation(fields: [itemId], references: [id])
mutations InventoryItemMutation[]
@@unique([userId, guildId, itemId])
@@index([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Economía: Recetas de Crafteo
* -----------------------------------------------------------------------------
*/
model ItemRecipe {
id String @id @default(cuid())
// Ítem resultante de la receta y su cantidad
productItemId String
productQuantity Int @default(1)
product EconomyItem @relation(fields: [productItemId], references: [id])
ingredients RecipeIngredient[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Una receta por ítem resultante (si necesitas variantes, añade un campo "variant" en metadata)
@@unique([productItemId])
}
model RecipeIngredient {
id String @id @default(cuid())
recipeId String
itemId String
quantity Int
recipe ItemRecipe @relation(fields: [recipeId], references: [id])
item EconomyItem @relation(fields: [itemId], references: [id])
@@unique([recipeId, itemId])
}
/**
* -----------------------------------------------------------------------------
* Economía: Ofertas de Tienda por Servidor
* -----------------------------------------------------------------------------
* - price en JSON permite monedas nativas y/o ítems como pago mixto
* Ejemplo: { "coins": 500, "items": [{ "itemKey": "iron", "qty": 3 }] }
*/
model ShopOffer {
id String @id @default(cuid())
guildId String
itemId String
enabled Boolean @default(true)
price Json // { coins?: number, items?: [{ itemKey?: string, itemId?: string, qty: number }], extra?: any }
startAt DateTime?
endAt DateTime?
// Límite de compras por usuario (null = sin límite)
perUserLimit Int?
// Stock global de la oferta (null = ilimitado)
stock Int?
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id])
item EconomyItem @relation(fields: [itemId], references: [id])
ShopPurchase ShopPurchase[]
// Evita duplicados del mismo ítem en ventana exacta (puedes ajustar según tu flujo)
@@unique([guildId, itemId, startAt, endAt])
@@index([guildId])
}
/**
* -----------------------------------------------------------------------------
* Economía: Mutaciones/Efectos adicionales aplicables a ítems
* -----------------------------------------------------------------------------
* - Catálogo de mutaciones opcional (global o por servidor)
* - Pueden vincularse a entradas de inventario
*/
model ItemMutation {
id String @id @default(cuid())
key String
name String
description String?
effects Json // Definición de efectos/bonos: libre
metadata Json?
guildId String?
guild Guild? @relation(fields: [guildId], references: [id])
inventories InventoryItemMutation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([guildId, key])
}
model InventoryItemMutation {
id String @id @default(cuid())
inventoryId String
mutationId String
appliedAt DateTime @default(now())
data Json?
inventory InventoryEntry @relation(fields: [inventoryId], references: [id])
mutation ItemMutation @relation(fields: [mutationId], references: [id])
@@index([inventoryId])
}
/**
* -----------------------------------------------------------------------------
* Economía: Billetera por Usuario x Servidor (moneda base)
* -----------------------------------------------------------------------------
*/
model EconomyWallet {
id String @id @default(cuid())
userId String
guildId String
coins Int @default(0)
metadata Json?
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, guildId])
@@index([guildId])
}
/**
* -----------------------------------------------------------------------------
* Economía: Historial de compras de la tienda
* -----------------------------------------------------------------------------
*/
model ShopPurchase {
id String @id @default(cuid())
offerId String
userId String
guildId String
qty Int @default(1)
offer ShopOffer @relation(fields: [offerId], references: [id])
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
createdAt DateTime @default(now())
@@index([offerId])
@@index([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Minijuegos: Áreas (Minas, Lagunas, Arenas de pelea, Campos de cultivo)
* -----------------------------------------------------------------------------
* - Extensible vía JSON: requirements/rewards/mobs por nivel
*/
model GameArea {
id String @id @default(cuid())
// Clave única por servidor (o global si guildId es null)
key String
name String
type String // "MINE" | "LAGOON" | "FIGHT" | "FARM" | otros
guildId String?
guild Guild? @relation(fields: [guildId], references: [id])
// Configuración general del área (cooldowns, multiplicadores, etc.)
config Json?
metadata Json?
levels GameAreaLevel[]
runs MinigameRun[]
progress PlayerProgress[]
deathLogs DeathLog[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([guildId, key])
@@index([guildId])
}
model GameAreaLevel {
id String @id @default(cuid())
areaId String
level Int
// Requisitos para participar (tipo de herramienta, tier mínimo, etc.)
requirements Json?
// Tabla de recompensas (ítems/monedas) con pesos
rewards Json?
// Tabla de mobs con pesos
mobs Json?
metadata Json?
availableFrom DateTime?
availableTo DateTime?
area GameArea @relation(fields: [areaId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([areaId, level])
}
/**
* -----------------------------------------------------------------------------
* Minijuegos: Mobs (usados en minas, lagunas/pesca, peleas)
* -----------------------------------------------------------------------------
*/
model Mob {
id String @id @default(cuid())
key String
name String
category String?
guildId String?
guild Guild? @relation(fields: [guildId], references: [id])
stats Json? // { hp, attack, defense, luckModifiers, ... }
drops Json? // Reward table específica del mob (opcional)
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ScheduledMobAttack ScheduledMobAttack[]
@@unique([guildId, key])
@@index([guildId])
}
/**
* -----------------------------------------------------------------------------
* Minijuegos: Registro de partidas/ejecuciones
* -----------------------------------------------------------------------------
*/
model MinigameRun {
id String @id @default(cuid())
userId String
guildId String
areaId String
level Int
toolItemId String?
success Boolean
result Json // { rewards:..., mobs:..., tool:{ durabilityDelta, broken }, notes }
startedAt DateTime @default(now())
finishedAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
area GameArea @relation(fields: [areaId], references: [id])
@@index([userId, guildId])
@@index([areaId])
@@index([startedAt])
}
/**
* -----------------------------------------------------------------------------
* Minijuegos: Progreso del jugador por área
* -----------------------------------------------------------------------------
*/
model PlayerProgress {
id String @id @default(cuid())
userId String
guildId String
areaId String
highestLevel Int @default(1)
metadata Json?
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
area GameArea @relation(fields: [areaId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, guildId, areaId])
@@index([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Estado del Jugador (HP, stats base) por servidor
* -----------------------------------------------------------------------------
*/
model PlayerState {
id String @id @default(cuid())
userId String
guildId String
hp Int @default(100)
maxHp Int @default(100)
stats Json? // { attack, defense, strength, luck, ... }
metadata Json?
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Equipamiento del Jugador por servidor (arma, armadura, capa, etc.)
* -----------------------------------------------------------------------------
*/
model PlayerEquipment {
id String @id @default(cuid())
userId String
guildId String
weaponItemId String?
armorItemId String?
capeItemId String?
// accesorios u otros slots libres
accessories Json?
metadata Json?
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Cooldowns por acción arbitraria (clave libre)
* -----------------------------------------------------------------------------
*/
model ActionCooldown {
id String @id @default(cuid())
userId String
guildId String
key String
until DateTime
metadata Json?
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
@@unique([userId, guildId, key])
@@index([until])
}
/**
* -----------------------------------------------------------------------------
* Fundición (smelting) con cooldown/tiempo de preparación
* -----------------------------------------------------------------------------
*/
model SmeltJob {
id String @id @default(cuid())
userId String
guildId String
// entradas y parámetros de fundición
inputs Json // { items: [{ itemKey, qty }], extra?: any }
outputItemId String
outputQty Int @default(1)
startedAt DateTime @default(now())
readyAt DateTime
status String @default("pending") // pending|ready|claimed|cancelled
metadata Json?
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
outputItem EconomyItem @relation(fields: [outputItemId], references: [id])
@@index([userId, guildId])
@@index([readyAt])
}
/**
* -----------------------------------------------------------------------------
* Ataques programados de mobs al jugador (para eventos sin comandos)
* -----------------------------------------------------------------------------
*/
model ScheduledMobAttack {
id String @id @default(cuid())
userId String
guildId String
mobId String
scheduleAt DateTime
processedAt DateTime?
status String @default("scheduled") // scheduled|processing|done|failed
metadata Json?
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
mob Mob @relation(fields: [mobId], references: [id])
@@index([scheduleAt])
@@index([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Sistema de Logros (Achievements)
* -----------------------------------------------------------------------------
*/
model Achievement {
id String @id @default(cuid())
key String
name String
description String
icon String?
category String // "mining", "crafting", "combat", "economy", "exploration"
// Requisitos para desbloquear (JSON flexible)
requirements Json // { type: "mine_count", value: 100 }
// Recompensas al desbloquear
rewards Json? // { coins: 500, items: [...], title: "..." }
guildId String?
guild Guild? @relation(fields: [guildId], references: [id])
// Logros desbloqueados por usuarios
unlocked PlayerAchievement[]
hidden Boolean @default(false) // logros secretos
points Int @default(10) // puntos que otorga el logro
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([guildId, key])
@@index([guildId])
}
model PlayerAchievement {
id String @id @default(cuid())
userId String
guildId String
achievementId String
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
achievement Achievement @relation(fields: [achievementId], references: [id])
progress Int @default(0) // progreso actual hacia el logro
unlockedAt DateTime? // null si aún no está desbloqueado
notified Boolean @default(false) // si ya se notificó al usuario
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, guildId, achievementId])
@@index([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Sistema de Misiones (Quests)
* -----------------------------------------------------------------------------
*/
model Quest {
id String @id @default(cuid())
key String
name String
description String
icon String?
// Tipo de misión
type String // "daily", "weekly", "event", "permanent"
category String // "mining", "combat", "economy", "exploration"
// Requisitos
requirements Json // { type: "mine", count: 10 }
// Recompensas
rewards Json // { coins: 500, items: [...], xp: 100 }
// Disponibilidad
startAt DateTime?
endAt DateTime?
guildId String?
guild Guild? @relation(fields: [guildId], references: [id])
progress QuestProgress[]
active Boolean @default(true)
repeatable Boolean @default(false) // si se puede repetir después de completar
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([guildId, key])
@@index([guildId])
@@index([type])
}
model QuestProgress {
id String @id @default(cuid())
userId String
guildId String
questId String
progress Int @default(0) // progreso actual
completed Boolean @default(false)
claimed Boolean @default(false) // si ya reclamó recompensa
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
quest Quest @relation(fields: [questId], references: [id])
completedAt DateTime?
claimedAt DateTime?
expiresAt DateTime? // para misiones diarias/semanales
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, guildId, questId, expiresAt])
@@index([userId, guildId])
@@index([questId])
}
/**
* -----------------------------------------------------------------------------
* Sistema de Estadísticas del Jugador
* -----------------------------------------------------------------------------
*/
model PlayerStats {
id String @id @default(cuid())
userId String
guildId String
// Stats de minijuegos
minesCompleted Int @default(0)
fishingCompleted Int @default(0)
fightsCompleted Int @default(0)
farmsCompleted Int @default(0)
// Stats de combate
mobsDefeated Int @default(0)
damageDealt Int @default(0)
damageTaken Int @default(0)
timesDefeated Int @default(0)
// Stats de economía
totalCoinsEarned Int @default(0)
totalCoinsSpent Int @default(0)
itemsCrafted Int @default(0)
itemsSmelted Int @default(0)
itemsPurchased Int @default(0)
// Stats de items
chestsOpened Int @default(0)
itemsConsumed Int @default(0)
itemsEquipped Int @default(0)
// Récords personales
highestDamageDealt Int @default(0)
longestWinStreak Int @default(0)
currentWinStreak Int @default(0)
mostCoinsAtOnce Int @default(0)
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, guildId])
@@index([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Efectos de Estado del Jugador (Status Effects)
* -----------------------------------------------------------------------------
* Almacena efectos temporales como FATIGUE (reduce daño/defensa), BLEED, BUFFS, etc.
* type: clave tipo string flexible (ej: "FATIGUE", "BLESSING", "POISON")
* stacking: se puede permitir múltiples efectos del mismo tipo si cambias la unique compuesta.
*/
model PlayerStatusEffect {
id String @id @default(cuid())
userId String
guildId String
type String
// magnitud genérica (ej: 0.15 para 15%); interpretación depende del tipo
magnitude Float @default(0)
// duración controlada por expiresAt; si null = permanente hasta eliminación manual
expiresAt DateTime?
data Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
// Un efecto único por tipo (puedes quitar esta línea si quieres stackeables):
@@unique([userId, guildId, type])
@@index([userId, guildId])
@@index([guildId])
@@index([expiresAt])
}
/**
* -----------------------------------------------------------------------------
* Sistema de Rachas (Streaks)
* -----------------------------------------------------------------------------
*/
model PlayerStreak {
id String @id @default(cuid())
userId String
guildId String
currentStreak Int @default(0)
longestStreak Int @default(0)
lastActiveDate DateTime @default(now())
totalDaysActive Int @default(0)
// Recompensas reclamadas por día
rewardsClaimed Json? // { day3: true, day7: true, etc }
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, guildId])
@@index([userId, guildId])
}
/**
* -----------------------------------------------------------------------------
* Log de Auditoría
* -----------------------------------------------------------------------------
*/
model AuditLog {
id String @id @default(cuid())
userId String
guildId String
action String // "buy", "craft", "trade", "equip", "mine", "fight", etc.
target String? // ID del item/mob/área afectado
details Json? // detalles adicionales
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
createdAt DateTime @default(now())
@@index([userId, guildId])
@@index([action])
@@index([createdAt])
}
/**
* -----------------------------------------------------------------------------
* Log de Muertes (DeathLog)
* -----------------------------------------------------------------------------
* Auditoría de penalizaciones al morir para trazabilidad y balance.
*/
model DeathLog {
id String @id @default(cuid())
userId String
guildId String
areaId String?
areaKey String?
level Int?
goldLost Int @default(0)
percentApplied Float @default(0) // porcentaje calculado de penalización
autoDefeatNoWeapon Boolean @default(false)
fatigueMagnitude Float? // 0.15 = 15%
fatigueMinutes Int? // minutos aplicados
metadata Json?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
guild Guild @relation(fields: [guildId], references: [id])
area GameArea? @relation(fields: [areaId], references: [id])
@@index([userId, guildId])
@@index([createdAt])
}
/**
* -----------------------------------------------------------------------------
* Sistema de Feature Flags
* -----------------------------------------------------------------------------
* Control de features para rollouts progresivos, A/B testing y toggles
* Permite activar/desactivar funcionalidades sin deployar código
*/
model FeatureFlag {
id String @id @default(cuid())
name String @unique
description String?
status String @default("disabled") // enabled|disabled|rollout|maintenance
// Nivel de aplicación: global, guild, user, channel
target String @default("global")
// Estrategia de rollout (para status = rollout)
rolloutStrategy String? // percentage|whitelist|blacklist|gradual|random
// Configuración de la estrategia (JSON)
rolloutConfig String? // JSON serializado
// Fechas de inicio/fin
startDate DateTime?
endDate DateTime?
// Metadata adicional
metadata String? // JSON serializado
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status])
@@index([target])
}
/**
* -----------------------------------------------------------------------------
* Music System: Listening History
* -----------------------------------------------------------------------------
* Tracks every song listened to by users for personalized recommendations.
* Redis: Last 50 songs per user (hot data)
* Prisma: Full history (cold storage)
*/
model ListeningHistory {
id String @id @default(cuid())
userId String
guildId String
// Track information
trackId String
title String
author String
duration Int
source String @default("youtube")
// Listening metrics
playedAt DateTime @default(now())
completedAt DateTime?
listenedMs Int @default(0)
score Int @default(0)
skipped Boolean @default(false)
skipReason String?
@@index([userId, playedAt])
@@index([guildId, playedAt])
@@index([userId, score])
}
/**
* -----------------------------------------------------------------------------
* Music System: User Preferences
* -----------------------------------------------------------------------------
*/
model UserMusicPreferences {
userId String @id
favoriteArtists Json @default("[]")
skipPatterns Json @default("{}")
recentTrends Json @default("[]")
autoplayEnabled Boolean @default(true)
totalPlays Int @default(0)
totalSkips Int @default(0)
lastUpdated DateTime @default(now())
@@index([lastUpdated])
}
/**
* -----------------------------------------------------------------------------
* Music System: Playlists
* -----------------------------------------------------------------------------
*/
model MusicPlaylist {
id String @id @default(cuid())
userId String
guildId String
name String
description String?
isDefault Boolean @default(false) // true for "Me gusta" playlist
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tracks PlaylistTrack[]
@@unique([userId, guildId, name])
@@index([userId, guildId])
@@index([userId, isDefault])
}
model PlaylistTrack {
id String @id @default(cuid())
playlistId String
trackId String // encoded track from Lavalink
title String
author String
duration Int
thumbnail String?
url String?
addedAt DateTime @default(now())
playlist MusicPlaylist @relation(fields: [playlistId], references: [id], onDelete: Cascade)
@@index([playlistId])
@@index([trackId])
}
/**
* -----------------------------------------------------------------------------
* Music System: Track Likes
* -----------------------------------------------------------------------------
*/
model TrackLike {
id String @id @default(cuid())
userId String
guildId String
trackId String
title String
author String
thumbnail String?
likedAt DateTime @default(now())
@@unique([userId, guildId, trackId])
@@index([userId, guildId])
@@index([trackId])
}