Files
amayo/README/ANALISIS_DURABILIDAD_NO_DEGRADA.md

8.0 KiB

🔍 Análisis: Durabilidad No Degrada (Items se Rompen Inmediatamente)

Fecha: Octubre 2025
Problema reportado: Los items no degradan durabilidad gradualmente, sino que se rompen tras el primer uso.


🐛 Síntomas

  1. Usuario compra/recibe herramienta (pico, espada, caña).
  2. Al usar el item en minijuego, se rompe inmediatamente (instancesRemaining = 0).
  3. No hay degradación visible progresiva (ej: 100 → 95 → 90...).
  4. El jugador debe re-comprar constantemente tras cada uso.

🔬 Análisis del Código

Sistema de Durabilidad (Funcionamiento Esperado)

El sistema está correctamente implementado en teoría:

1. Añadir Items (economy/service.ts:addItemByKey)

// Para items NO stackable con durabilidad:
for (let i = 0; i < canAdd; i++) {
  if (maxDurability && maxDurability > 0) {
    state.instances.push({ durability: maxDurability }); // ✅ Inicializa correctamente
  } else {
    state.instances.push({});
  }
}

2. Reducir Durabilidad (minigames/service.ts:reduceToolDurability)

const inst = state.instances[0];
const max = maxConfigured;

// Si la instancia no tiene durabilidad inicial, la inicializamos
if (inst.durability == null) (inst as any).durability = max; // ✅ Fallback correcto

const current = Math.min(Math.max(0, inst.durability ?? max), max);
const next = current - delta; // Resta durabilityPerUse

if (next <= 0) {
  state.instances.shift(); // Rompe instancia
  brokenInstance = true;
} else {
  (inst as any).durability = next; // Actualiza durabilidad
  state.instances[0] = inst;
}

🔴 Problema Identificado

Causa Raíz: Items creados antes de implementar el sistema de durabilidad (o mediante seed incompleto) tienen state.instances vacíos o sin campo durability:

// ❌ Item problemático en base de datos
{
  "instances": [
    {},              // Sin durability definido
    {}
  ]
}

Cuando reduceToolDurability ejecuta:

  1. Lee instancia: inst = {}
  2. Inicializa: inst.durability = max (ej: 100)
  3. Calcula: current = 100, next = 100 - 5 = 95
  4. PERO: Como modificó inst (referencia local), no actualiza correctamente el array state.instances[0].

El bug está en la asignación:

(inst as any).durability = next;
state.instances[0] = inst;  // ✅ Esto DEBERÍA funcionar pero...

Si inst es un objeto nuevo creado por el fallback, la referencia se pierde.


🛠️ Solución Propuesta

Opción 1: Fix en reduceToolDurability (Rápido)

Modificar para asegurar que siempre se actualiza el objeto del array directamente:

const state = parseInvState(entry.state);
state.instances ??= [{}];
if (state.instances.length === 0) state.instances.push({});

const inst = state.instances[0];
const max = maxConfigured;

// Inicializar durabilidad si no existe (directamente en el array)
if (inst.durability == null) {
  state.instances[0].durability = max;
}

const current = Math.min(Math.max(0, state.instances[0].durability ?? max), max);
const next = current - delta;

if (next <= 0) {
  state.instances.shift(); // Rompe instancia
  brokenInstance = true;
} else {
  state.instances[0].durability = next; // Actualiza DIRECTO en array
}

Ventaja: Fix inmediato sin migración de datos.
Desventaja: No resuelve items ya en inventarios con state corrupto.


Opción 2: Migración de Inventarios (Completa)

Crear script que recorra todos los InventoryEntry y regenere state.instances con durabilidad correcta:

// scripts/fixItemDurability.ts
import { prisma } from '../src/core/database/prisma';

async function fixDurability() {
  const entries = await prisma.inventoryEntry.findMany({
    include: { item: true },
    where: { item: { stackable: false } } // Solo items no apilables
  });

  for (const entry of entries) {
    const props = entry.item.props as any;
    const breakable = props?.breakable;
    
    if (!breakable || breakable.enabled === false) continue;
    
    const maxDurability = breakable.maxDurability ?? 100;
    const state = (entry.state as any) ?? {};
    const instances = state.instances ?? [];
    
    // Regenerar instancias con durabilidad completa
    const fixed = instances.map((inst: any) => {
      if (inst.durability == null || inst.durability <= 0) {
        return { ...inst, durability: maxDurability };
      }
      return inst;
    });
    
    // Si no hay instancias pero quantity > 0, crearlas
    if (fixed.length === 0 && entry.quantity > 0) {
      for (let i = 0; i < entry.quantity; i++) {
        fixed.push({ durability: maxDurability });
      }
    }
    
    await prisma.inventoryEntry.update({
      where: { id: entry.id },
      data: {
        state: { ...state, instances: fixed },
        quantity: fixed.length
      }
    });
    
    console.log(`Fixed ${entry.item.key} for user ${entry.userId}`);
  }
  
  console.log('✅ Durability migration complete');
}

fixDurability()
  .then(() => process.exit(0))
  .catch(e => { console.error(e); process.exit(1); });

Ejecución:

npx ts-node scripts/fixItemDurability.ts

Ventaja: Resuelve todos los items existentes.
Desventaja: Requiere downtime o aviso a usuarios.


Opción 3: Comando Admin (Híbrido)

Crear comando !fix-durability [userId] que regenera instancias bajo demanda:

// src/commands/messages/admin/fixDurability.ts
export const command: CommandMessage = {
  name: 'fix-durability',
  run: async (message, args) => {
    const targetUserId = args[0] || message.author.id;
    const guildId = message.guild!.id;
    
    const entries = await prisma.inventoryEntry.findMany({
      where: { userId: targetUserId, guildId },
      include: { item: true }
    });
    
    let fixed = 0;
    for (const entry of entries) {
      if (entry.item.stackable) continue;
      
      const props = entry.item.props as any;
      const breakable = props?.breakable;
      if (!breakable || breakable.enabled === false) continue;
      
      const maxDur = breakable.maxDurability ?? 100;
      const state = (entry.state as any) ?? {};
      const instances = state.instances ?? [];
      
      const regenerated = instances.map((inst: any) => 
        inst.durability == null ? { ...inst, durability: maxDur } : inst
      );
      
      if (regenerated.length !== instances.length || 
          JSON.stringify(regenerated) !== JSON.stringify(instances)) {
        await prisma.inventoryEntry.update({
          where: { id: entry.id },
          data: { state: { ...state, instances: regenerated } }
        });
        fixed++;
      }
    }
    
    await message.reply(`✅ Regeneradas ${fixed} herramientas para <@${targetUserId}>`);
  }
};

Ventaja: Los usuarios pueden auto-fixear sin downtime.
Desventaja: Requiere que cada usuario ejecute el comando.


🎯 Recomendación

Implementar Opción 1 (fix en código) + Opción 3 (comando admin):

  1. Fix inmediato: Modificar reduceToolDurability para actualizar directamente state.instances[0].durability.
  2. Comando de rescate: Añadir !fix-durability para que usuarios con items corruptos puedan regenerarlos.
  3. Seed mejorado: Asegurar que seed.ts use addItemByKey en lugar de crear inventarios manualmente.

📋 Checklist de Implementación

  • Modificar reduceToolDurability para actualizar durabilidad directamente en array.
  • Crear comando !fix-durability para regeneración bajo demanda.
  • Validar que seed.ts use addItemByKey correctamente.
  • Añadir logs de debug temporales para confirmar degradación.
  • Testear con herramienta tier 1 y tier 2 (50+ durabilidad).

🧪 Validación Post-Fix

Ejecutar:

!inventario
# Ver durabilidad inicial (ej: 100/100)

!mina
# Debería reducir (ej: 95/100)

!mina
# Seguir reduciendo (ej: 90/100)

# Repetir hasta agotar instancia

Resultado esperado: Degradación gradual visible hasta romper instancia.


Archivo: README/ANALISIS_DURABILIDAD_NO_DEGRADA.md