CCCPaste

camino al ajuste

#Requires AutoHotkey v2.0
#SingleInstance Force
#WinActivateForce

; ===== CONFIGURACIÓN ACELERADA =====
SendMode "Input"
SetWorkingDir A_ScriptDir

; ===== DISTRIBUCIÓN POR PERFIL =====
global symbols := Map("cherry", 32, "bell", 26, "lemon", 17, "star", 13, "diamond", 7, "seven", 4, "joker", 1)
global symbolValues := Map("cherry", 2, "bell", 4, "lemon", 7, "star", 12, "diamond", 24, "seven", 48, "joker", 0, "three_jokers", 80)
global spinning := false
global imgPath := "imgs\"
global soundPath := "sounds\"
global spinDuration := 500
global spinInterval := 30
global img1, img2, img3
global lastWin := false
global statsFile := "slot_stats.ini"
global detailedStatsFile := "detailed_stats.ini"
global configFile := "slot_config.ini"
global savedSettingsFile := "AfinacionesGuardadas.ini" ; <<< NUEVA VARIABLE PARA AFINACIONES
global autoSpin := false
global autoSpinCount := 0
global maxAutoSpins := 1000
global lastUpdateCount := 0
global jackpotValue := 100
global jackpotCounter := 0
global testingMode := true  ; ← ACTIVADO PARA PRUEBAS
global DetailedStatsGui := ""
global ReportEdit := ""
global symbolWin := ""


; ===== ESTADÍSTICAS DE SISTEMAS DE AYUDA (SIMPLIFICADAS) =====
global pityActivations := 0, pityWins := 0, pityPesos := 0
global multiplierActivationsCount := 0, multiplierWins := 0, multiplierPesosExtra := 0
global jokerActivations := 0, jokerWins := 0, jokerPesos := 0
global jackpotActivations := 0, jackpotWins := 0, jackpotPesos := 0

; ===== NUEVAS VARIABLES PARA ANÁLISIS AVANZADO =====
global symbolFrequency := Map()
global combinationStats := Map()
global volatilityStats := Map()
global rtpBreakdown := Map()

; ===== SISTEMA DE MULTIPLICADORES =====
global multiplierProbability := 25
global multiplierValue := 2.0
global multiplierActivations := 0
global totalMultiplierPesos := 0

; ===== SISTEMA DE PERFILES RTP =====
global RTPProfiles := Map("RTP35", 35.0, "RTP40", 40.0, "RTP45", 45.0, "RTP50", 50.0)
global enableJokerSounds := false
global currentProfile := "RTP40"
global targetRTP := RTPProfiles[currentProfile]

; ===== SISTEMA PITY COUNTER =====
global pityCounter := 0
global pityThreshold := 25
global originalPityThreshold := 20

; ===== SISTEMA BONUS MEJORADO =====
global bonusLights := 0
global maxBonusGiros := 20
global bonusLetters := ["B", "O", "N", "U", "S"]
global letterOrder := []
global bonusThresholds := []

; ===== INICIALIZAR ESTADISTICAS =====
global totalSpins := 0
global totalWins := 0
global totalPesos := 0
global winStats := Map()
global pesosStats := Map()

; ===== ESTADISTICAS DETALLADAS =====
global detailedTotalSpins := 0
global detailedTotalWins := 0
global detailedTotalPesos := 0
global detailedJackpotsWon := 0
global detailedCompensations := 0
global detailedCompensationCount := 0
global detailedWinStats := Map()
global detailedPesosStats := Map()
global detailedJokerRespines := 0
global detailedJokerRespinWins := 0
global detailedJokerRespinPesos := 0

; ===== NUEVAS ESTADISTICAS AVANZADAS =====
global currentLosingStreak := 0
global longestLosingStreak := 0
global symbolWinCounts := Map()
global symbolRTPContributions := Map()
global jokerRTPContribution := 0

; ===== INICIALIZAR LISTA DE SÍMBOLOS =====
global symbolList := []

; ===== VARIABLES RTP MEJORADO =====
global safetyNetThreshold := 40
global safetyNetActive := false
global safetyNetBoost := false
global jokerEffectivenessRTP35 := 35
global jokerEffectivenessRTP40 := 25
global jokerEffectivenessRTP45 := 45
global jokerEffectivenessRTP50 := 65

; ===== SISTEMA DE RESCATE DE VOLATILIDAD MEJORADO =====
global rescueSpinsWithoutWin := 0
global lastRescueSpin := 0
global rescueMinSpinsBetween := 200
global rescueActivations := 0
global rescuePesos := 0
global rescueSymbols := ["lemon", "star", "bell"]  ; Símbolos naturales para rescates
global rescueActive := false  ; Nueva variable para controlar estado

; ===== INTERFAZ GRAFICA =====
MyGui := Gui("+AlwaysOnTop +Resize +OwnDialogs -MaximizeBox", "Tragamonedas JACKPOT 5000 giros")
MyGui.MarginX := 10
MyGui.MarginY := 10
MyGui.SetFont("s12", "Arial")
MyGui.Opt("+OwnDialogs")

; Slot machine frame
MyGui.Add("GroupBox", "w340 h150 Center", "Tragamonedas")
MyGui.Add("Picture", "vSlot1 xp+10 yp+25 w100 h100 Border", imgPath . "question.png")
MyGui.Add("Picture", "vSlot2 x+10 w100 h100 Border", imgPath . "question.png")
MyGui.Add("Picture", "vSlot3 x+10 w100 h100 Border", imgPath . "question.png")

; Mostrar jackpot actual en una línea
MyGui.Add("Text", "xm y+10 w180 Center", "Contador Jackpot:")
JackpotCounterText := MyGui.Add("Text", "x+0 yp w160 Center", "0/5000")

; Logo Joker Giro Gratis - CENTRADO
MyGui.Add("Picture", "vJokerLogo xm+80 y+5 w180 h35", imgPath . "joker_giro_gratis_apagado.png")

; Display de BONUS
MyGui.Add("Picture", "vBonusSlot1 xm+125 y+10 w20 h30", imgPath . "bonus_empty.png")
MyGui.Add("Picture", "vBonusSlot2 x+2 w20 h30", imgPath . "bonus_empty.png")
MyGui.Add("Picture", "vBonusSlot3 x+2 w20 h30", imgPath . "bonus_empty.png")
MyGui.Add("Picture", "vBonusSlot4 x+2 w20 h30", imgPath . "bonus_empty.png")
MyGui.Add("Picture", "vBonusSlot5 x+2 w20 h30", imgPath . "bonus_empty.png")

; Botones de giro en una línea
SpinBtn := MyGui.Add("Button", "xm y+20 w100 h25", "1 Moneda")
SpinBtn.OnEvent("Click", StartSpin)
SpinBtn.Opt("+Default")

Spin1000Btn := MyGui.Add("Button", "x+10 w100 h25", "1000")
Spin1000Btn.OnEvent("Click", Start1000Spins)

Spin10000Btn := MyGui.Add("Button", "x+10 w100 h25", "10000")
Spin10000Btn.OnEvent("Click", Start10000Spins)

; === NUEVO BOTÓN ULTRA-RÁPIDO ===
UltraTestBtn := MyGui.Add("Button", "xm y+10 w340 h30 cFFFFFF Background009900", "🔥 PRUEBA ULTRA-RÁPIDA 50000")
UltraTestBtn.OnEvent("Click", StartUltraTest)

; === NUEVO BOTÓN EXPORTAR DATOS ===
ExportBtn := MyGui.Add("Button", "xm y+5 w165 h25 cFFFFFF Background3366FF", "📊 EXPORTAR CSV")
ExportBtn.OnEvent("Click", ExportTestData)

; === NUEVO BOTÓN EXPORTAR TXT ===
ExportTxtBtn := MyGui.Add("Button", "x+10 yp w165 h25 cFFFFFF BackgroundFF6600", "📝 EXPORTAR TXT")
ExportTxtBtn.OnEvent("Click", ExportTestResults)

; Boton de seleccion de perfil
ProfileBtn := MyGui.Add("Button", "xm y+10 w340 h30", "Seleccionar Perfil RTP")
ProfileBtn.OnEvent("Click", ShowProfileMenu)

; Indicador de perfil actual
CurrentProfileText := MyGui.Add("Text", "xm y+5 w340 Center cFF9900", "Perfil Actual: RTP 40%")

; Estadisticas
MyGui.Add("GroupBox", "xm y+10 w340 h190 Center", "Estadisticas")  ; ← Reducir altura
MyGui.Add("Text", "xp+10 yp+25 w120", "Monedas totales:")
TotalSpinsText := MyGui.Add("Text", "vTotalSpinsText x+5 w50", "0")
MyGui.Add("Text", "xp-125 y+5 w120", "Pesos ganados:")
TotalPesosText := MyGui.Add("Text", "vTotalPesosText x+5 w50", "0")
MyGui.Add("Text", "xp-125 y+5 w120", "Jugador gana:")
PlayerWinPercent := MyGui.Add("Text", "vPlayerWinPercent x+5 w70", "0%")
MyGui.Add("Text", "xp-125 y+5 w120", "Maquina gana:")
MachineWinPercent := MyGui.Add("Text", "vMachineWinPercent x+5 w70", "100%")
MyGui.Add("Text", "xp-125 y+5 w120", "Giros sin ganar:")
PityCounterText := MyGui.Add("Text", "vPityCounterText x+5 w70", "0/20")

; === NUEVO INDICADOR DE RESCATE ===
MyGui.Add("Text", "xp-125 y+5 w120", "Rescates:")
RescueCounterText := MyGui.Add("Text", "vRescueCounterText x+5 w70", "0")

StatsBtn := MyGui.Add("Button", "xm+20 y+15 w140 h35", "Ver Detalles")
StatsBtn.OnEvent("Click", ShowStats)

ResetBtn := MyGui.Add("Button", "x+20 w140 h35", "Resetear Stats")
ResetBtn.OnEvent("Click", ResetStats)

; Atajo de teclado para abrir el menú de estadísticas detalladas
9::ShowDetailedStats

MyGui.OnEvent("Close", GuiClose)
MyGui.Show("AutoSize Center")

; Inicializar después de crear la GUI
UpdateSymbolList()
LoadStats()
LoadDetailedStats()
InitializeAdvancedStats()
RandomizeBonusOrder()
RandomizeBonusThresholds()
LoadConfig()
LoadAfinacionesGuardadas() ; <--- ¡NUEVA LLAMADA PARA CARGAR AFINACIONES!

; Inicializar RTP de sesión
UpdateJokerStatus()

;
; ===== FUNCIÓN PARA CARGAR AFINACIONES GUARDADAS (AfinacionesGuardadas.ini) =====
;
LoadAfinacionesGuardadas() {
    ; Declarar variables globales necesarias
    global savedSettingsFile, currentProfile, symbols, pityThreshold, jokerEffectivenessRTP35
    global jokerEffectivenessRTP40, jokerEffectivenessRTP45, jokerEffectivenessRTP50, multiplierProbability
    global multiplierValue, safetyNetThreshold, targetRTP, RTPProfiles, CurrentProfileText
    global PityCounterText, MyGui, testingMode, symbolList

    ; 1. Comprobar si el archivo de afinaciones existe
    if (!FileExist(savedSettingsFile)) {
        if (testingMode) {
            OutputDebug("⚠️ Archivo de afinaciones '" . savedSettingsFile . "' no encontrado. Usando configuración por defecto/slot_config.ini.`n")
        }
        return
    }

    ; 2. Leer el perfil actualmente activo del archivo de afinaciones.
    ;    Asumimos que el perfil activo está guardado en la sección "Afinacion" bajo la clave "CurrentProfile".
    savedProfile := IniRead(savedSettingsFile, "Afinacion", "CurrentProfile", currentProfile)

    ; 3. Cargar configuraciones guardadas si el perfil existe como sección en el INI.
    ;    Los valores se leerán bajo una sección que coincide con el nombre del perfil (e.g., [RTP40])
    sectionName := savedProfile

    ; Leer valores, usando "" como default para saber si fueron encontrados
    loadedPity := IniRead(savedSettingsFile, sectionName, "PityThreshold", "")
    loadedProb := IniRead(savedSettingsFile, sectionName, "MultiplierProbability", "")
    loadedValue := IniRead(savedSettingsFile, sectionName, "MultiplierValue", "")

    ; 4. Aplicar los nuevos valores SOLO si se encontraron
    if (loadedPity != "") {
        pityThreshold := Integer(loadedPity)
    }
    if (loadedProb != "") {
        multiplierProbability := Integer(loadedProb)
    }
    ; Asegurarse de que el valor del multiplicador se lea como Float
    if (loadedValue != "") {
        multiplierValue := Float(loadedValue)
    }

    ; 5. Si el perfil guardado es diferente al actual, sobrescribir y recargar la configuración base.
    ;    Esto asegura que los defaults de símbolos y otros parámetros se actualicen correctamente
    if (savedProfile != currentProfile) {
        currentProfile := savedProfile
        LoadConfig() ; Se re-ejecuta LoadConfig() para aplicar los defaults del nuevo perfil (símbolos, etc.).
    }

    ; 6. Mensaje de depuración y actualización de interfaz
    if (testingMode) {
        OutputDebug("✅ Afinaciones Guardadas cargadas. Perfil: " . currentProfile . ". Pity: " . pityThreshold . ". Prob Multi: " . multiplierProbability . ". Valor Multi: " . multiplierValue . "`n")
    }

    ; Re-actualizar display después de posibles cambios en pityThreshold y targetRTP
    targetRTP := RTPProfiles.Has(currentProfile) ? RTPProfiles[currentProfile] : 0.0
    CurrentProfileText.Value := "Perfil Actual: " . currentProfile . " (" . targetRTP . "%)"
    PityCounterText.Value := pityCounter . "/" . pityThreshold
    UpdateSymbolList() ; Asegurar que la lista de símbolos se actualice.
}


; ===== FUNCIONES =====

; ===== FUNCIÓN EXPORTAR RESULTADOS TXT CORREGIDA =====
ExportTestResults(*) {
    global currentProfile, targetRTP, totalSpins, totalPesos, detailedTotalSpins, detailedTotalPesos
    global multiplierProbability, multiplierValue, testingMode

    if (totalSpins = 0) {
        MsgBox("No hay datos para exportar. Realiza algunos giros primero.", "Sin Datos", "48")
        return
    }

    ; ✅ CALCULAR RTP CON SEGURIDAD
    currentRTP := 0
    if (totalSpins > 0) {
        currentRTP := (totalPesos / totalSpins) * 100
    }

    deviation := currentRTP - targetRTP

    ; Crear nombre de archivo con timestamp
    timestamp := FormatTime(, "yyyy-MM-dd_HH-mm-ss")
    filename := "Resultado_Precision_" . currentProfile . "_" . timestamp . ".txt"

    content := "=== RESULTADO PRECISIÓN " . currentProfile . " ===`n"
    content .= "Timestamp: " . timestamp . "`n"
    content .= "Perfil: " . currentProfile . " (Objetivo: " . targetRTP . "%)`n"
    content .= "Giros totales: " . totalSpins . "`n"
    content .= "RTP Final: " . Round(currentRTP, 3) . "%`n"
    content .= "Desviación: " . Round(deviation, 3) . "%`n"
    content .= "Multiplicador final: " . multiplierProbability . "% / " . multiplierValue . "x`n"
    content .= "Precisión: " . (Abs(deviation) <= 0.5 ? "✅ EXCELENTE" : (Abs(deviation) <= 1.0 ? "⚠️ ACEPTABLE" : "❌ INACEPTABLE")) . "`n"
    content .= "`n"
    content .= "=== DETALLES ADICIONALES ===`n"
    content .= "Giros detallados: " . detailedTotalSpins . "`n"
    content .= "Pesos detallados: " . detailedTotalPesos . "`n"
    content .= "Fecha de análisis: " . FormatTime(, "dd/MM/yyyy HH:mm:ss") . "`n"

    try {
        FileAppend(content, filename)
        if (testingMode) {
            OutputDebug("📊 RESULTADO EXPORTADO: " . filename . "`n")
        }
        MsgBox("✅ Archivo TXT exportado correctamente:`n" . filename, "Exportación Exitosa", "64")
    } catch Error as e {
        OutputDebug("❌ ERROR exportando: " . e.Message . "`n")
        MsgBox("❌ Error al exportar archivo TXT:`n" . e.Message, "Error de Exportación", "16")
    }
}
; ===== SISTEMA DE RESCATE DE VOLATILIDAD MEJORADO =====
CheckVolatilityRescue() {
    global rescueSpinsWithoutWin, lastRescueSpin, rescueMinSpinsBetween, totalSpins
    global currentProfile, rescueActivations, rescuePesos, rescueSymbols
    global img1, img2, img3, symbolWin, symbolValues, totalPesos, detailedTotalPesos
    global totalWins, detailedTotalWins, winStats, detailedWinStats, pesosStats, detailedPesosStats
    global pityCounter, currentLosingStreak, MyGui, imgPath, rescueActive

    ; Evitar activación múltiple
    if (rescueActive) {
        return false
    }

    ; Umbral de activación: 80 giros sin victoria y mínimo 200 giros entre rescates
    if (rescueSpinsWithoutWin >= 45 && (totalSpins - lastRescueSpin) >= 120) {
        rescueActive := true

        ; 🎯 MODIFICACIÓN CRÍTICA IMPLEMENTADA: SOLO SÍMBOLOS DE BAJO VALOR
        lowValueRescueSymbols := ["cherry", "bell"]  ; Solo símbolos de 2-4 pesos

        ; Seleccionar símbolo de bajo valor para el rescate
        rescueSymbol := lowValueRescueSymbols[Random(1, lowValueRescueSymbols.Length)]

        ; El premio será el valor natural del símbolo (2-4 pesos)
        rescuePrize := symbolValues[rescueSymbol]

        if (rescuePrize > 0) {
            ; Forzar combinación ganadora
            img1 := rescueSymbol
            img2 := rescueSymbol
            img3 := rescueSymbol
            symbolWin := rescueSymbol

            ; Actualizar display visual inmediatamente
            MyGui["Slot1"].Value := imgPath . rescueSymbol . ".png"
            MyGui["Slot2"].Value := imgPath . rescueSymbol . ".png"
            MyGui["Slot3"].Value := imgPath . rescueSymbol . ".png"

            ; Procesar la victoria
            HandleRescueWin(rescueSymbol, rescuePrize)

            ; Actualizar contadores
            rescueActivations++
            rescuePesos += rescuePrize
            lastRescueSpin := totalSpins
            rescueSpinsWithoutWin := 0
            pityCounter := 0
            currentLosingStreak := 0

            ; Actualizar display de estadísticas
            UpdateStatsDisplay()

            ; Mostrar notificación en modo testing
            if (testingMode) {
                OutputDebug("🎯 RESCATE ACTIVADO: " . rescueSymbol . " - " . rescuePrize . " pesos`n")
            }

            rescueActive := false
            return true
        }
        rescueActive := false
    }
    return false
}

GetRescuePrize() {
    global rescueSymbols, symbolValues

    ; Seleccionar símbolo natural para el rescate
    rescueSymbol := rescueSymbols[Random(1, rescueSymbols.Length)]

    ; Pagar el valor EXACTO de la figura
    return symbolValues[rescueSymbol]
}

HandleRescueWin(symbol, prize) {
    global totalWins, detailedTotalWins, winStats, detailedWinStats
    global totalPesos, detailedTotalPesos, pesosStats, detailedPesosStats
    global lastWin, pityCounter, currentLosingStreak, rescueSpinsWithoutWin

    lastWin := true
    totalWins++
    detailedTotalWins++
    pityCounter := 0
    currentLosingStreak := 0
    rescueSpinsWithoutWin := 0

    ; Registrar estadísticas
    winStats[symbol] := winStats.Has(symbol) ? winStats[symbol] + 1 : 1
    detailedWinStats[symbol] := detailedWinStats.Has(symbol) ? detailedWinStats[symbol] + 1 : 1

    pesosStats[symbol] := pesosStats.Has(symbol) ? pesosStats[symbol] + prize : prize
    detailedPesosStats[symbol] := detailedPesosStats.Has(symbol) ? detailedPesosStats[symbol] + prize : prize

    totalPesos += prize
    detailedTotalPesos += prize

    ; Actualizar display
    UpdateStatsDisplay()
    UpdateJokerStatus()
}

; ===== NUEVA FUNCIÓN: INICIALIZAR ESTADÍSTICAS AVANZADAS =====
InitializeAdvancedStats() {
    global symbolFrequency, combinationStats, volatilityStats, rtpBreakdown, symbols
    global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos, rescueActive

    ; Inicializar frecuencia de símbolos
    symbolFrequency := Map()
    for symbol in symbols {
        symbolFrequency[symbol] := 0
    }

    ; Inicializar estadísticas de combinaciones
    combinationStats := Map()

    ; Inicializar métricas de volatilidad
    volatilityStats := Map()
    volatilityStats["CurrentLosingStreak"] := 0
    volatilityStats["LongestLosingStreak"] := 0
    volatilityStats["CurrentWinningStreak"] := 0
    volatilityStats["LongestWinningStreak"] := 0
    volatilityStats["SpinsBetweenWins"] := []
    volatilityStats["WinDistribution"] := Map()

    ; Inicializar desglose de RTP
    rtpBreakdown := Map()
    rtpBreakdown["Base_Symbols"] := 0
    rtpBreakdown["Pity_System"] := 0
    rtpBreakdown["Joker_Respins"] := 0
    rtpBreakdown["Multipliers"] := 0
    rtpBreakdown["Jackpot"] := 0
    rtpBreakdown["Rescue_System"] := 0
    rtpBreakdown["Total"] := 0

    ; Inicializar sistema de rescate
    rescueSpinsWithoutWin := 0
    lastRescueSpin := 0
    rescueActivations := 0
    rescuePesos := 0
    rescueActive := false
}

UpdateSymbolList() {
    global symbols, symbolList
    symbolList := []
    for symbol, count in symbols {
        Loop count {
            symbolList.Push(symbol)
        }
    }
}

LoadStats() {
    global statsFile, totalSpins, totalWins, totalPesos, winStats, pesosStats, symbols, jackpotCounter, pityCounter
    global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos

    if FileExist(statsFile) {
        totalSpins := Integer(IniRead(statsFile, "Stats", "TotalSpins", "0"))
        totalWins := Integer(IniRead(statsFile, "Stats", "TotalWins", "0"))
        totalPesos := Integer(IniRead(statsFile, "Stats", "TotalPesos", "0"))
        jackpotCounter := Integer(IniRead(statsFile, "Stats", "JackpotCounter", "0"))
        pityCounter := Integer(IniRead(statsFile, "Stats", "PityCounter", "0"))
        rescueSpinsWithoutWin := Integer(IniRead(statsFile, "Rescue", "SpinsWithoutWin", "0"))
        lastRescueSpin := Integer(IniRead(statsFile, "Rescue", "LastRescueSpin", "0"))
        rescueActivations := Integer(IniRead(statsFile, "Rescue", "Activations", "0"))
        rescuePesos := Integer(IniRead(statsFile, "Rescue", "TotalPesos", "0"))

        for symbol in symbols {
            winStats[symbol] := Integer(IniRead(statsFile, "Symbols", symbol, "0"))
            pesosStats[symbol] := Integer(IniRead(statsFile, "Pesos", symbol, "0"))
        }
        winStats["three_jokers"] := Integer(IniRead(statsFile, "Symbols", "three_jokers", "0"))
        pesosStats["three_jokers"] := Integer(IniRead(statsFile, "Pesos", "three_jokers", "0"))
    } else {
        ; Inicializar valores si no existen
        totalSpins := 0
        totalWins := 0
        totalPesos := 0
        jackpotCounter := 0
        pityCounter := 0
        rescueSpinsWithoutWin := 0
        lastRescueSpin := 0
        rescueActivations := 0
        rescuePesos := 0

        for symbol in symbols {
            winStats[symbol] := 0
            pesosStats[symbol] := 0
        }
        winStats["three_jokers"] := 0
        pesosStats["three_jokers"] := 0
    }

    ; Actualizar la interfaz
    UpdateStatsDisplay()
}

SaveStats() {
    global statsFile, totalSpins, totalWins, totalPesos, winStats, pesosStats, jackpotCounter, pityCounter
    global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos

    IniWrite(totalSpins, statsFile, "Stats", "TotalSpins")
    IniWrite(totalWins, statsFile, "Stats", "TotalWins")
    IniWrite(totalPesos, statsFile, "Stats", "TotalPesos")
    IniWrite(jackpotCounter, statsFile, "Stats", "JackpotCounter")
    IniWrite(pityCounter, statsFile, "Stats", "PityCounter")
    IniWrite(rescueSpinsWithoutWin, statsFile, "Rescue", "SpinsWithoutWin")
    IniWrite(lastRescueSpin, statsFile, "Rescue", "LastRescueSpin")
    IniWrite(rescueActivations, statsFile, "Rescue", "Activations")
    IniWrite(rescuePesos, statsFile, "Rescue", "TotalPesos")

    for symbol, count in winStats {
        IniWrite(count, statsFile, "Symbols", symbol)
        IniWrite(pesosStats[symbol], statsFile, "Pesos", symbol)
    }
}

LoadDetailedStats() {
    global detailedStatsFile, detailedTotalSpins, detailedTotalWins, detailedTotalPesos, detailedJackpotsWon, detailedWinStats, detailedPesosStats, symbols, detailedCompensations, detailedCompensationCount, multiplierActivations, totalMultiplierPesos, detailedJokerRespines, detailedJokerRespinWins, detailedJokerRespinPesos
    global currentLosingStreak, longestLosingStreak
    global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos

    if FileExist(detailedStatsFile) {
        detailedTotalSpins := Integer(IniRead(detailedStatsFile, "Stats", "TotalSpins", "0"))
        detailedTotalWins := Integer(IniRead(detailedStatsFile, "Stats", "TotalWins", "0"))
        detailedTotalPesos := Integer(IniRead(detailedStatsFile, "Stats", "TotalPesos", "0"))
        detailedJackpotsWon := Integer(IniRead(detailedStatsFile, "Stats", "JackpotsWon", "0"))
        detailedCompensations := Integer(IniRead(detailedStatsFile, "Stats", "TotalCompensations", "0"))
        detailedCompensationCount := Integer(IniRead(detailedStatsFile, "Stats", "CompensationCount", "0"))
        multiplierActivations := Integer(IniRead(detailedStatsFile, "Multipliers", "ActivationCount", "0"))
        totalMultiplierPesos := Integer(IniRead(detailedStatsFile, "Multipliers", "TotalPesos", "0"))
        detailedJokerRespines := Integer(IniRead(detailedStatsFile, "Joker", "Respines", "0"))
        detailedJokerRespinWins := Integer(IniRead(detailedStatsFile, "Joker", "RespinWins", "0"))
        detailedJokerRespinPesos := Integer(IniRead(detailedStatsFile, "Joker", "RespinPesos", "0"))
        rescueSpinsWithoutWin := Integer(IniRead(detailedStatsFile, "Rescue", "SpinsWithoutWin", "0"))
        lastRescueSpin := Integer(IniRead(detailedStatsFile, "Rescue", "LastRescueSpin", "0"))
        rescueActivations := Integer(IniRead(detailedStatsFile, "Rescue", "Activations", "0"))
        rescuePesos := Integer(IniRead(detailedStatsFile, "Rescue", "TotalPesos", "0"))

        for symbol in symbols {
            detailedWinStats[symbol] := Integer(IniRead(detailedStatsFile, "Symbols", symbol, "0"))
            detailedPesosStats[symbol] := Integer(IniRead(detailedStatsFile, "Pesos", symbol, "0"))
        }
        detailedWinStats["three_jokers"] := Integer(IniRead(detailedStatsFile, "Symbols", "three_jokers", "0"))
        detailedPesosStats["three_jokers"] := Integer(IniRead(detailedStatsFile, "Pesos", "three_jokers", "0"))

        ; Cargar nuevas estadísticas avanzadas
        currentLosingStreak := Integer(IniRead(detailedStatsFile, "AdvancedMetrics", "CurrentLosingStreak", "0"))
        longestLosingStreak := Integer(IniRead(detailedStatsFile, "AdvancedMetrics", "LongestLosingStreak", "0"))

    } else {
        ; Inicializar valores si no existen
        detailedTotalSpins := 0
        detailedTotalWins := 0
        detailedTotalPesos := 0
        detailedJackpotsWon := 0
        detailedCompensations := 0
        detailedCompensationCount := 0
        multiplierActivations := 0
        totalMultiplierPesos := 0
        currentLosingStreak := 0
        longestLosingStreak := 0
        rescueSpinsWithoutWin := 0
        lastRescueSpin := 0
        rescueActivations := 0
        rescuePesos := 0

        for symbol in symbols {
            detailedWinStats[symbol] := 0
            detailedPesosStats[symbol] := 0
        }
        detailedWinStats["three_jokers"] := 0
        detailedPesosStats["three_jokers"] := 0
    }
}

SaveDetailedStats() {
    global detailedStatsFile, detailedTotalSpins, detailedTotalWins, detailedTotalPesos, detailedJackpotsWon, detailedWinStats, detailedPesosStats, detailedCompensations, detailedCompensationCount, multiplierActivations, totalMultiplierPesos, detailedJokerRespines, detailedJokerRespinWins, detailedJokerRespinPesos
    global longestLosingStreak, symbolRTPContributions, jokerRTPContribution, symbolValues
    global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos

    RTP := 0
    if (detailedTotalSpins > 0) {
        RTP := Round((detailedTotalPesos / detailedTotalSpins) * 100, 2)
    }

    ; Calcular RTP por símbolo
    symbolRTPContributions := Map()
    for symbol, pesos in detailedPesosStats {
        if (detailedTotalPesos > 0) {
            symbolRTPContributions[symbol] := Round((pesos / detailedTotalPesos) * RTP, 2)
        } else {
            symbolRTPContributions[symbol] := 0
        }
    }

    ; Calcular contribución del Joker
    jokerRTPContribution := 0
    if (detailedTotalPesos > 0) {
        jokerRTPContribution := Round((detailedJokerRespinPesos / detailedTotalPesos) * RTP, 2)
    }

    IniWrite(detailedTotalSpins, detailedStatsFile, "Stats", "TotalSpins")
    IniWrite(detailedTotalWins, detailedStatsFile, "Stats", "TotalWins")
    IniWrite(detailedTotalPesos, detailedStatsFile, "Stats", "TotalPesos")
    IniWrite(detailedJackpotsWon, detailedStatsFile, "Stats", "JackpotsWon")
    IniWrite(detailedCompensations, detailedStatsFile, "Stats", "TotalCompensations")
    IniWrite(detailedCompensationCount, detailedStatsFile, "Stats", "CompensationCount")
    IniWrite(RTP, detailedStatsFile, "Stats", "RTP")
    IniWrite(multiplierActivations, detailedStatsFile, "Multipliers", "ActivationCount")
    IniWrite(totalMultiplierPesos, detailedStatsFile, "Multipliers", "TotalPesos")
    IniWrite(detailedJokerRespines, detailedStatsFile, "Joker", "Respines")
    IniWrite(detailedJokerRespinWins, detailedStatsFile, "Joker", "RespinWins")
    IniWrite(detailedJokerRespinPesos, detailedStatsFile, "Joker", "RespinPesos")
    IniWrite(rescueSpinsWithoutWin, detailedStatsFile, "Rescue", "SpinsWithoutWin")
    IniWrite(lastRescueSpin, detailedStatsFile, "Rescue", "LastRescueSpin")
    IniWrite(rescueActivations, detailedStatsFile, "Rescue", "Activations")
    IniWrite(rescuePesos, detailedStatsFile, "Rescue", "TotalPesos")

    for symbol, count in detailedWinStats {
        IniWrite(count, detailedStatsFile, "Symbols", symbol)
        IniWrite(detailedPesosStats[symbol], detailedStatsFile, "Pesos", symbol)
    }

    ; Escribir nuevas métricas avanzadas
    IniWrite(longestLosingStreak, detailedStatsFile, "AdvancedMetrics", "LongestLosingStreak")
    IniWrite(currentLosingStreak, detailedStatsFile, "AdvancedMetrics", "CurrentLosingStreak")

    ; Escribir contribución RTP por símbolo
    for symbol, rtp in symbolRTPContributions {
        IniWrite(rtp, detailedStatsFile, "RTPBySymbol", symbol)
    }

    ; Escribir contribución RTP del Joker
    IniWrite(jokerRTPContribution, detailedStatsFile, "RTPBySymbol", "joker_respin_rtp")
}

LoadConfig() {
    ; DECLARAR EXPLÍCITAMENTE TODAS LAS VARIABLES GLOBALES
    global currentProfile, symbols, pityThreshold, jokerEffectivenessRTP35, jokerEffectivenessRTP40
    global jokerEffectivenessRTP45, jokerEffectivenessRTP50, multiplierProbability, multiplierValue
    global safetyNetThreshold, targetRTP, RTPProfiles, CurrentProfileText, MyGui, PityCounterText
    global rescueSpinsWithoutWin, rescueMinSpinsBetween

    currentProfile := IniRead(configFile, "Config", "CurrentProfile", "RTP40")

    ; CONFIGURACIÓN MEJORADA
    if (currentProfile = "RTP35") {
    symbols := Map("cherry", 25, "bell", 26, "lemon", 17, "star", 12, "diamond", 8, "seven", 6, "joker", 1)
    pityThreshold := 25
    jokerEffectivenessRTP35 := 20
    multiplierProbability := 10
    multiplierValue := 2.0
    safetyNetThreshold := 45
    rescueSpinsWithoutWin := 60
    rescueMinSpinsBetween := 200
    LoadAfinacionesGuardadas()
}else if (currentProfile = "RTP40") {
    symbols := Map("cherry", 30, "bell", 25, "lemon", 19, "star", 12, "diamond", 7, "seven", 5, "joker", 2)
    pityThreshold := 25
    jokerEffectivenessRTP40 := 20
    multiplierProbability := 10
    multiplierValue := 2.2
    safetyNetThreshold := 50
    rescueSpinsWithoutWin := 80
    rescueMinSpinsBetween := 250
} else if (currentProfile = "RTP45") {
    symbols := Map("cherry", 25, "bell", 22, "lemon", 20, "star", 15, "diamond", 9, "seven", 7, "joker", 2)
    pityThreshold := 25
    jokerEffectivenessRTP45 := 20
    multiplierProbability := 6
    multiplierValue := 2.5
    safetyNetThreshold := 50
    rescueSpinsWithoutWin := 80
    rescueMinSpinsBetween := 250
} else if (currentProfile = "RTP50") {
        symbols := Map("cherry", 26, "bell", 22, "lemon", 22, "star", 14, "diamond", 7, "seven", 6, "joker", 3)
        pityThreshold := 25
        jokerEffectivenessRTP50 := 65
        multiplierProbability := 40
        multiplierValue := 3.2
        safetyNetThreshold := 25
    }

    ; Leer valores de configuración si existen
    pityThreshold := Integer(IniRead(configFile, "Config", "PityThreshold", pityThreshold))
    multiplierProbability := Integer(IniRead(configFile, "Multipliers", "ActivationProbability", multiplierProbability))
    multiplierValue := Float(IniRead(configFile, "Multipliers", "MultiplierValue", multiplierValue))

    targetRTP := RTPProfiles[currentProfile]
    CurrentProfileText.Value := "Perfil Actual: " . currentProfile . " (" . targetRTP . "%)"

    if (currentProfile = "RTP35")
        CurrentProfileText.Opt("c007ACC")
    else if (currentProfile = "RTP40")
        CurrentProfileText.Opt("cFF9900")
    else if (currentProfile = "RTP45")
        CurrentProfileText.Opt("cFF3366")
    else if (currentProfile = "RTP50")
        CurrentProfileText.Opt("c00FF00")

    MyGui.Title := "Tragamonedas AHK - JACKPOT a 5000 giros"
    PityCounterText.Value := pityCounter . "/" . pityThreshold

    UpdateSymbolList()  ; ← ACTUALIZAR LISTA DE SÍMBOLOS
}

SaveConfig() {
    global configFile, currentProfile, pityThreshold, multiplierProbability, multiplierValue

    IniWrite(currentProfile, configFile, "Config", "CurrentProfile")
    IniWrite(pityThreshold, configFile, "Config", "PityThreshold")
    IniWrite(multiplierProbability, configFile, "Multipliers", "ActivationProbability")
    IniWrite(multiplierValue, configFile, "Multipliers", "MultiplierValue")
}

RandomizeBonusOrder() {
    global bonusLetters, letterOrder
    letterOrder := ["B", "O", "N", "U", "S"]
}

RandomizeBonusThresholds() {
    global bonusThresholds
    bonusThresholds := []

    bonusThresholds.Push(Random(3, 5))
    bonusThresholds.Push(Random(7, 9))
    bonusThresholds.Push(Random(11, 13))
    bonusThresholds.Push(Random(15, 17))
    bonusThresholds.Push(20)
}

UpdateBonusLights() {
    global pityCounter, bonusThresholds, letterOrder, imgPath, MyGui

    lettersToShow := 0
    Loop 5 {
        if (pityCounter >= bonusThresholds[A_Index]) {
            lettersToShow := A_Index
        }
    }

    ; Apagar todas las luces primero
    Loop 5 {
        MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_empty.png"
    }

    ; Encender las luces en ORDEN CONSECUTIVO
    Loop lettersToShow {
        letter := letterOrder[A_Index]
        if (letter = "B")
            MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_B_red.png"
        else if (letter = "O")
            MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_O_blue.png"
        else if (letter = "N")
            MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_N_green.png"
        else if (letter = "U")
            MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_U_yellow.png"
        else if (letter = "S")
            MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_S_purple.png"
    }
}

ShowProfileMenu(*) {
    try {
        ProfileMenu := Menu()
        ProfileMenu.Add("RTP 35%", SetRTPProfile.Bind("RTP35"))
        ProfileMenu.Add("RTP 40%", SetRTPProfile.Bind("RTP40"))
        ProfileMenu.Add("RTP 45%", SetRTPProfile.Bind("RTP45"))
        ProfileMenu.Add("RTP 50%", SetRTPProfile.Bind("RTP50"))
        ProfileMenu.Show()
    }
}

SetRTPProfile(profileName, *) {
    ; DECLARAR EXPLÍCITAMENTE TODAS LAS VARIABLES GLOBALES
    global currentProfile, targetRTP, RTPProfiles, symbols, MyGui
    global pityThreshold, jokerEffectivenessRTP35, jokerEffectivenessRTP40
    global jokerEffectivenessRTP45, jokerEffectivenessRTP50, multiplierProbability, multiplierValue
    global safetyNetThreshold
	global rescueSpinsWithoutWin, rescueMinSpinsBetween

    currentProfile := profileName
    targetRTP := RTPProfiles[profileName]

   ; === CONFIGURACIÓN OPTIMIZADA GAMIFICACIÓN ===
   if (currentProfile = "RTP35") {
    symbols := Map("cherry", 25, "bell", 26, "lemon", 17, "star", 12, "diamond", 8, "seven", 6, "joker", 1)
    pityThreshold := 25
    jokerEffectivenessRTP35 := 20
    multiplierProbability := 10
    multiplierValue := 2.0
    safetyNetThreshold := 45
    rescueSpinsWithoutWin := 60
    rescueMinSpinsBetween := 200
    LoadAfinacionesGuardadas()
} else if (currentProfile = "RTP40") {
    symbols := Map("cherry", 30, "bell", 25, "lemon", 19, "star", 12, "diamond", 7, "seven", 5, "joker", 2)
    pityThreshold := 25
    jokerEffectivenessRTP40 := 20
    multiplierProbability := 10
    multiplierValue := 2.2
    safetyNetThreshold := 50
    rescueSpinsWithoutWin := 80
    rescueMinSpinsBetween := 250
} else if (currentProfile = "RTP45") {
    symbols := Map("cherry", 25, "bell", 22, "lemon", 20, "star", 15, "diamond", 9, "seven", 7, "joker", 2)
    pityThreshold := 25
    jokerEffectivenessRTP45 := 20
    multiplierProbability := 6
    multiplierValue := 2.5
    safetyNetThreshold := 50
    rescueSpinsWithoutWin := 80
    rescueMinSpinsBetween := 250
} else if (currentProfile = "RTP50") {
        symbols := Map("cherry", 26, "bell", 22, "lemon", 22, "star", 14, "diamond", 7, "seven", 6, "joker", 3)
        pityThreshold := 25
        jokerEffectivenessRTP50 := 65
        multiplierProbability := 40
        multiplierValue := 3.2
        safetyNetThreshold := 25
    }

    UpdateSymbolList()
    UpdateProfileDisplay()
    ResetStats(0)

    ; GUARDAR CONFIGURACIÓN
    SaveConfig()

    MyGui.Opt("+OwnDialogs")
    MsgBox("RTP configurado al " . targetRTP . "%", "Perfil Cambiado", "64")
    MyGui.Title := "Tragamonedas AHK - JACKPOT a 5000 giros"
    UpdateJokerStatus()
}

ResetProfileSettings() {
    global currentProfile, pityThreshold

    if (currentProfile = "RTP35") {
        pityThreshold := 25
    } else if (currentProfile = "RTP40") {
        pityThreshold := 25
    } else if (currentProfile = "RTP45") {
        pityThreshold := 25
    } else if (currentProfile = "RTP50") {
        pityThreshold := 25
    }

    SaveConfig()
}

UpdateProfileDisplay() {
    global currentProfile, targetRTP, CurrentProfileText, MyGui

    CurrentProfileText.Value := "Perfil Actual: " . currentProfile . " (" . targetRTP . "%)"
    if (currentProfile = "RTP35")
        CurrentProfileText.Opt("c007ACC")
    else if (currentProfile = "RTP40")
        CurrentProfileText.Opt("cFF9900")
    else if (currentProfile = "RTP45")
        CurrentProfileText.Opt("cFF3366")
    else if (currentProfile = "RTP50")
        CurrentProfileText.Opt("c00FF00")

    MyGui.Title := "Tragamonedas AHK - JACKPOT a 5000 giros"
}

CalculatePlayerWinPercent() {
    global totalSpins, totalPesos
    if (totalSpins = 0)
        return "0%"
    playerWinPercent := (totalPesos / totalSpins) * 100
    return Round(playerWinPercent, 1) . "%"
}

CalculateMachineWinPercent() {
    global totalSpins, totalPesos
    if (totalSpins = 0)
        return "100%"
    machineWinPercent := ((totalSpins - totalPesos) / totalSpins) * 100
    return Round(machineWinPercent, 1) . "%"
}

UpdateWinPercentages() {
    PlayerWinPercent.Value := CalculatePlayerWinPercent()
    MachineWinPercent.Value := CalculateMachineWinPercent()
}

UpdateJokerStatus() {
    global currentRTP, totalSpins, totalPesos, MyGui, imgPath

    if (totalSpins > 0) {
        currentRTP := (totalPesos / totalSpins) * 100
    } else {
        currentRTP := 0
    }

    if (currentRTP < targetRTP) {
        MyGui["JokerLogo"].Value := imgPath . "joker_giro_gratis_prendido.png"
    } else {
        MyGui["JokerLogo"].Value := imgPath . "joker_giro_gratis_apagado.png"
    }
}

Start1000Spins(*) {
    global spinning, autoSpin, testingMode, autoSpinCount, maxAutoSpins
    if spinning
        return
    autoSpin := true
    testingMode := true
    autoSpinCount := 0
    maxAutoSpins := 1000
    SpinBtn.Enabled := false
    Spin1000Btn.Enabled := false
    Spin10000Btn.Enabled := false
    SetTimer(AutoSpinLoop, 80)
}

Start10000Spins(*) {
    global spinning, autoSpin, testingMode, autoSpinCount, maxAutoSpins
    if spinning
        return
    autoSpin := true
    testingMode := true
    autoSpinCount := 0
    maxAutoSpins := 50000
    SpinBtn.Enabled := false
    Spin1000Btn.Enabled := false
    Spin10000Btn.Enabled := false
    SetTimer(AutoSpinLoop, 80)
}

StartUltraTest(*) {
    global spinning, autoSpin, testingMode, autoSpinCount, maxAutoSpins
    global symbols, symbolList  ; ← Asegurar que estas globales estén declaradas
    if spinning
        return
     ; === AGREGAR ESTA LÍNEA CRÍTICA ===
    UpdateSymbolList()  ; ← ¡ACTUALIZAR LA LISTA DE SÍMBOLOS!

    MyGui.Opt("+OwnDialogs")
    MsgBox("🚀 INICIANDO PRUEBA ULTRA-RÁPIDA`n`n• 50,000 giros en 5-9 minutos`n• RTP teórico puro (sin ajustes dinámicos)`n• Sin animaciones ni sonidos`n• Resultados válidos al 100%", "Modo Pruebas Rápido", "64")

    autoSpin := true
    testingMode := true
    autoSpinCount := 0
    maxAutoSpins := 50000
    SpinBtn.Enabled := false
    Spin1000Btn.Enabled := false
    Spin10000Btn.Enabled := false
    UltraTestBtn.Enabled := false
    UltraTestBtn.Text := "🔥 PROCESANDO... " . autoSpinCount . "/50000"

    SetTimer(UltraSpinLoop, 1)
}

UltraSpinLoop() {
    ; DECLARAR TODAS LAS VARIABLES GLOBALES UNA POR UNA
    global autoSpin, autoSpinCount, maxAutoSpins, spinning, totalSpins, detailedTotalSpins
    global img1, img2, img3, symbolWin, symbolList, targetRTP, totalPesos
    global detailedJokerRespines, detailedJokerRespinWins, UltraTestBtn
    global symbolFrequency, combinationStats, jokerActivations
    global jackpotCounter, jackpotValue, detailedJackpotsWon, pityCounter
    global detailedTotalPesos, testingMode, UpdateBonusLights, symbolValues
    global detailedJokerRespinPesos, jokerWins, jokerPesos
    global jackpotActivations, jackpotWins, jackpotPesos
    global currentLosingStreak, longestLosingStreak
    global currentProfile, safetyNetActive, safetyNetThreshold
    global rescueSpinsWithoutWin, lastRescueSpin, rescueMinSpinsBetween, rescueActivations, rescuePesos

    if spinning || autoSpinCount >= maxAutoSpins {
        if (autoSpinCount >= maxAutoSpins) {
            SetTimer(UltraSpinLoop, 0)
            autoSpin := false
            testingMode := false
            lastUpdateCount := 0

            SaveStats()
            SaveDetailedStats()

            ; ✅ NUEVO: EXPORTAR RESULTADOS AUTOMÁTICAMENTE - AGREGADO
            if (testingMode) {
                ExportTestResults()
            }

            SpinBtn.Enabled := true
            Spin1000Btn.Enabled := true
            Spin10000Btn.Enabled := true
            UltraTestBtn.Enabled := true
            UltraTestBtn.Text := "🔥 PRUEBA ULTRA-RÁPIDA 50000"

            currentRTP := (totalPesos / totalSpins) * 100
            MyGui.Opt("+OwnDialogs")
            MsgBox("✅ PRUEBA COMPLETADA`n`nGiros: " . maxAutoSpins . "`nRTP Final: " . Round(currentRTP, 2) . "%`nDesviación: " . Round(currentRTP - targetRTP, 2) . "%`n`n📊 Datos guardados en archivos INI", "Resultados Ultra-Rápidos", "64")
        }
        return
    }

    Critical true

    totalSpins++
    detailedTotalSpins++
    autoSpinCount++

        ; ACTUALIZACIÓN GARANTIZADA CADA 100 GIROS
    if (Mod(autoSpinCount, 100) == 0) {
        UltraTestBtn.Text := "🔥 PROCESANDO... " . autoSpinCount . "/50000"
        UpdateStatsDisplay()
    }

    randIndex := Random(1, symbolList.Length)
    img1 := symbolList[randIndex]
    randIndex := Random(1, symbolList.Length)
    img2 := symbolList[randIndex]
    randIndex := Random(1, symbolList.Length)
    img3 := symbolList[randIndex]

    symbolFrequency[img1] := symbolFrequency.Get(img1, 0) + 1
    symbolFrequency[img2] := symbolFrequency.Get(img2, 0) + 1
    symbolFrequency[img3] := symbolFrequency.Get(img3, 0) + 1

    symbolWin := CheckWinCombination(img1, img2, img3)

    ; ===== VERIFICAR RESCATE DE VOLATILIDAD MEJORADO =====
    if (!symbolWin || symbolWin = false) {
        rescueSpinsWithoutWin++
        if (CheckVolatilityRescue()) {
            ; El rescate ya manejó la victoria, continuar al siguiente giro
            Critical false
            return
        }
    } else {
        rescueSpinsWithoutWin := 0
    }

    if (symbolWin is String && symbolWin != "") {
        HandleTripleWinUltra()
    } else if (img1 = "joker" || img2 = "joker" || img3 = "joker") {
        currentRTP := 0
        if (totalSpins > 0) {
            currentRTP := (totalPesos / totalSpins) * 100
        }

        if (currentRTP < targetRTP) {
            jokerActivations += 1
            detailedJokerRespines++

            if (img1 != "joker") {
                randIndex := Random(1, symbolList.Length)
                img1 := symbolList[randIndex]
            }
            if (img2 != "joker") {
                randIndex := Random(1, symbolList.Length)
                img2 := symbolList[randIndex]
            }
            if (img3 != "joker") {
                randIndex := Random(1, symbolList.Length)
                img3 := symbolList[randIndex]
            }

            symbolWin := CheckWinCombination(img1, img2, img3)
            if (symbolWin is String && symbolWin != "") {
                ; ✅ BLOQUE CORREGIDO - CONTADORES DE JOKER FIJOS
                pesosGanados := symbolValues.Has(symbolWin) ? symbolValues[symbolWin] : 2
                HandleTripleWinUltra()
                detailedJokerRespinWins += 1  ; ← SOLO 1 VICTORIA POR RESPIN
                detailedJokerRespinPesos += pesosGanados
                jokerWins += 1
                jokerPesos += pesosGanados
            } else {
                HandleLossUltra()
            }
        } else {
            HandleLossUltra()
        }
    } else {
        HandleLossUltra()
    }

    ; VERIFICACIÓN JACKPOT
    if (jackpotCounter >= 5000) {
        totalPesos += jackpotValue
        detailedTotalPesos += jackpotValue
        detailedJackpotsWon++
        jackpotCounter := 0
        pityCounter := 0
        UpdateBonusLights()

        ; ✅ CONTADORES DE SISTEMAS DE AYUDA
        jackpotActivations += 1
        jackpotWins += 1
        jackpotPesos += jackpotValue
    }

    Critical false
}

HandleTripleWinUltra() {
    global lastWin, totalWins, detailedTotalWins, winStats, detailedWinStats, pityCounter
    global totalPesos, detailedTotalPesos, pesosStats, detailedPesosStats, symbolWin, symbolValues
    global totalMultiplierPesos, multiplierActivations, multiplierValue, multiplierProbability
    global detailedJokerRespinPesos, combinationStats, multiplierActivationsCount, multiplierWins, multiplierPesosExtra
    global jokerWins, jokerPesos
    global currentLosingStreak, safetyNetActive, safetyNetBoost
    global rescueSpinsWithoutWin

    lastWin := true
    currentLosingStreak := 0
    rescueSpinsWithoutWin := 0
    totalWins++
    detailedTotalWins++
    pityCounter := 0

    pesosGanados := symbolValues.Has(symbolWin) ? symbolValues[symbolWin] : 2

    combinationStats[symbolWin] := combinationStats.Get(symbolWin, 0) + 1

    if (symbolWin = "three_jokers") {
        winStats["three_jokers"] := winStats.Has("three_jokers") ? winStats["three_jokers"] + 1 : 1
        detailedWinStats["three_jokers"] := detailedWinStats.Has("three_jokers") ? detailedWinStats["three_jokers"] + 1 : 1
    } else {
        winStats[symbolWin] := winStats.Has(symbolWin) ? winStats[symbolWin] + 1 : 1
        detailedWinStats[symbolWin] := detailedWinStats.Has(symbolWin) ? detailedWinStats[symbolWin] + 1 : 1

        if (Random(1, 100) <= (100 / multiplierProbability)) {
            multiplierActivations++
            multiplierActivationsCount++
            baseValue := symbolValues.Has(symbolWin) ? symbolValues[symbolWin] : 0
            multipliedValue := Round(baseValue * multiplierValue)
            totalMultiplierPesos += (multipliedValue - baseValue)
            multiplierPesosExtra += (multipliedValue - baseValue)
            multiplierWins += 1
            pesosGanados := multipliedValue
        }
    }

    totalPesos += pesosGanados
    detailedTotalPesos += pesosGanados

    if (symbolWin = "three_jokers") {
        pesosStats["three_jokers"] := pesosStats.Has("three_jokers") ? pesosStats["three_jokers"] + pesosGanados : pesosGanados
        detailedPesosStats["three_jokers"] := detailedPesosStats.Has("three_jokers") ? detailedPesosStats["three_jokers"] + pesosGanados : pesosGanados
    } else {
        pesosStats[symbolWin] := pesosStats.Has(symbolWin) ? pesosStats[symbolWin] + pesosGanados : pesosGanados
        detailedPesosStats[symbolWin] := detailedPesosStats.Has(symbolWin) ? detailedPesosStats[symbolWin] + pesosGanados : pesosGanados
    }
}

HandleLossUltra() {
    global lastWin, pityCounter, jackpotCounter, totalPesos, detailedTotalPesos, detailedCompensations, detailedCompensationCount, testingMode, currentProfile, totalSpins, pityThreshold, pityActivations, pityWins, pityPesos
    global currentLosingStreak, longestLosingStreak
    global safetyNetActive, safetyNetThreshold
    global rescueSpinsWithoutWin

    lastWin := false
    currentLosingStreak++
    if (currentLosingStreak > longestLosingStreak) {
        longestLosingStreak := currentLosingStreak
    }
    pityCounter++
    jackpotCounter++
    rescueSpinsWithoutWin++  ; ← CONTAR GIROS SIN GANAR

    ; ✅ VERIFICAR SAFETY NET PARA RTP35
    if (currentProfile = "RTP35" && currentLosingStreak >= safetyNetThreshold && !safetyNetActive) {
        safetyNetActive := false
        ; En modo ultra, simplemente forzar una victoria
        symbolWin := "bell"
        HandleTripleWinUltra()
        safetyNetActive := false
        return
    }

    if (pityCounter >= pityThreshold) {
        pityCounter := 0
        compensation := 4  ; ← COMPENSACIÓN FIJA DE 4 PESOS

        if (compensation > 0) {
            detailedCompensations += compensation
            detailedCompensationCount++
            totalPesos += compensation
            detailedTotalPesos += compensation

            pityActivations += 1
            pityWins += 1
            pityPesos += compensation
        }
    }
}

ExportTestData(*) {
    global currentProfile, targetRTP, detailedTotalSpins, detailedTotalWins, detailedTotalPesos
    global symbolFrequency, combinationStats, symbols, symbolValues
    global detailedCompensations, detailedJokerRespines, detailedJokerRespinWins, detailedJokerRespinPesos
    global multiplierActivations, totalMultiplierPesos, detailedJackpotsWon, jackpotValue
    global longestLosingStreak, currentLosingStreak
    global pityActivations, pityWins, pityPesos, multiplierActivationsCount, multiplierWins, multiplierPesosExtra
    global jokerActivations, jokerWins, jokerPesos, jackpotActivations, jackpotWins, jackpotPesos
    global detailedPesosStats, detailedWinStats
    global rescueSpinsWithoutWin, rescueActivations, rescuePesos

    try {
        timestamp := FormatTime(, "yyyy-MM-dd_HH-mm-ss")
        filename := "RTP_Analysis_" . currentProfile . "_" . timestamp . ".csv"

        ; ✅ CALCULAR RTP CON SEGURIDAD
        currentRTP := 0
        if (detailedTotalSpins > 0) {
            currentRTP := (detailedTotalPesos / detailedTotalSpins) * 100
        }

        csvContent := ""

        csvContent .= "ANÁLISIS RTP - PERFIL " . currentProfile . "`n"
        csvContent .= "Timestamp,RTP_Teórico,RTP_Calculado,Total_Giros,Total_Victorias,Total_Pesos`n"
        csvContent .= timestamp . "," . targetRTP . "," . Round(currentRTP, 2) . "," . detailedTotalSpins . "," . detailedTotalWins . "," . detailedTotalPesos . "`n`n"

        ; ✅ SECCIÓN FRECUENCIA CORREGIDA - EVITA DIVISIÓN POR CERO
        csvContent .= "FRECUENCIA DE SÍMBOLOS`n"
        csvContent .= "Símbolo,Veces_Salió,Probabilidad_Real,Probabilidad_Teórica`n"

        totalSymbolAppearances := 0
        for symbol, count in symbolFrequency {
            totalSymbolAppearances += count
        }

        if (totalSymbolAppearances = 0) {
            ; Si no hay datos, mostrar valores en cero
            for symbol, count in symbolFrequency {
                theoreticalProbability := symbols.Has(symbol) ? (symbols[symbol] / 100) * 100 : 0
                csvContent .= symbol . "," . count . ",0%," . theoreticalProbability . "%`n"
            }
        } else {
            ; Calcular probabilidades normalmente
            for symbol, count in symbolFrequency {
                realProbability := (count / totalSymbolAppearances) * 100
                theoreticalProbability := symbols.Has(symbol) ? (symbols[symbol] / 100) * 100 : 0
                csvContent .= symbol . "," . count . "," . Round(realProbability, 2) . "%," . theoreticalProbability . "%`n"
            }
        }
        csvContent .= "`n"

        ; ✅ SECCIÓN COMBINACIONES GANADORAS CON SEGURIDAD
        csvContent .= "COMBINACIONES GANADORAS`n"
        csvContent .= "Combinación,Veces,Probabilidad,Pesos_Generados,Valor_Promedio`n"

        totalWins := detailedTotalWins
        if (totalWins = 0) {
            ; Si no hay victorias, mostrar combinaciones en cero
            for combination, count in combinationStats {
                pesosGenerados := detailedPesosStats.Has(combination) ? detailedPesosStats[combination] : 0
                csvContent .= combination . "," . count . ",0%," . pesosGenerados . ",0`n"
            }
        } else {
            ; Calcular probabilidades normalmente
            for combination, count in combinationStats {
                probability := (count / totalWins) * 100
                pesosGenerados := detailedPesosStats.Has(combination) ? detailedPesosStats[combination] : 0
                valorPromedio := (count > 0) ? (pesosGenerados / count) : 0
                csvContent .= combination . "," . count . "," . Round(probability, 2) . "%," . pesosGenerados . "," . Round(valorPromedio, 2) . "`n"
            }
        }
        csvContent .= "`n"

        ; ✅ SISTEMAS DE AYUDA CON VERIFICACIONES
        csvContent .= "SISTEMAS DE AYUDA`n"
        csvContent .= "Sistema,Activaciones,Victorias,Tasa_Éxito,Pesos_Generados`n"

        ; Pity Counter
        pitySuccessRate := (pityActivations > 0) ? ((pityWins / pityActivations) * 100) : 0
        csvContent .= "Pity_Counter," . pityActivations . "," . pityWins . "," . Round(pitySuccessRate, 1) . "%," . pityPesos . "`n"

        ; Multiplicador
        multiplierSuccessRate := (multiplierActivationsCount > 0) ? ((multiplierWins / multiplierActivationsCount) * 100) : 0
        csvContent .= "Multiplicador," . multiplierActivationsCount . "," . multiplierWins . "," . Round(multiplierSuccessRate, 1) . "%," . multiplierPesosExtra . "`n"

        ; Joker Respin
        jokerSuccessRate := (jokerActivations > 0) ? ((jokerWins / jokerActivations) * 100) : 0
        csvContent .= "Joker_Respin," . jokerActivations . "," . jokerWins . "," . Round(jokerSuccessRate, 1) . "%," . jokerPesos . "`n"

        ; Jackpot
        jackpotSuccessRate := (jackpotActivations > 0) ? ((jackpotWins / jackpotActivations) * 100) : 0
        csvContent .= "Jackpot," . jackpotActivations . "," . jackpotWins . "," . Round(jokerSuccessRate, 1) . "%," . jackpotPesos . "`n"

        ; Sistema de Rescate
        rescueSuccessRate := (rescueActivations > 0) ? 100 : 0  ; Siempre tiene éxito cuando se activa
        csvContent .= "Rescate_Volatilidad," . rescueActivations . "," . rescueActivations . "," . rescueSuccessRate . "%," . rescuePesos . "`n"

        csvContent .= "`n"

        ; ✅ VOLATILIDAD
        csvContent .= "VOLATILIDAD`n"
        csvContent .= "Métrica,Valor`n"
        csvContent .= "Racha_Pérdida_Más_Larga," . longestLosingStreak . "`n"
        csvContent .= "Racha_Pérdida_Actual," . currentLosingStreak . "`n"
        csvContent .= "Giros_Sin_Victoria_Actual," . rescueSpinsWithoutWin . "`n"

        avgSpinsBetweenWins := (detailedTotalWins > 0) ? (detailedTotalSpins / detailedTotalWins) : 0
        csvContent .= "Giros_Promedio_Entre_Victorias," . Round(avgSpinsBetweenWins, 1) . "`n"

        winRate := (detailedTotalSpins > 0) ? ((detailedTotalWins / detailedTotalSpins) * 100) : 0
        csvContent .= "Tasa_de_Victoria," . Round(winRate, 2) . "%`n"
        csvContent .= "`n"

        ; ✅ DESGLOSE RTP CON VERIFICACIONES
        csvContent .= "DESGLOSE RTP`n"
        csvContent .= "Componente,Contribución_RTP`n"

        ; Calcular con seguridad contra división por cero
        if (detailedTotalSpins > 0) {
            baseRTP := ((detailedTotalPesos - detailedCompensations - detailedJokerRespinPesos - totalMultiplierPesos - (detailedJackpotsWon * jackpotValue) - rescuePesos) / detailedTotalSpins) * 100
            pityRTP := (detailedCompensations / detailedTotalSpins) * 100
            jokerRTP := (detailedJokerRespinPesos / detailedTotalSpins) * 100
            multiplierRTP := (totalMultiplierPesos / detailedTotalSpins) * 100
            jackpotRTP := ((detailedJackpotsWon * jackpotValue) / detailedTotalSpins) * 100
            rescueRTP := (rescuePesos / detailedTotalSpins) * 100
        } else {
            baseRTP := 0
            pityRTP := 0
            jokerRTP := 0
            multiplierRTP := 0
            jackpotRTP := 0
            rescueRTP := 0
        }

        csvContent .= "Símbolos_Base," . Round(baseRTP, 2) . "%`n"
        csvContent .= "Sistema_Pity," . Round(pityRTP, 2) . "%`n"
        csvContent .= "Joker_Respins," . Round(jokerRTP, 2) . "%`n"
        csvContent .= "Multiplicadores," . Round(multiplierRTP, 2) . "%`n"
        csvContent .= "Jackpot," . Round(jackpotRTP, 2) . "%`n"
        csvContent .= "Rescate_Volatilidad," . Round(rescueRTP, 2) . "%`n"
        csvContent .= "TOTAL," . Round(currentRTP, 2) . "%`n"

        FileAppend(csvContent, filename)

        MyGui.Opt("+OwnDialogs")
        MsgBox("📈 DATOS EXPORTADOS`n`nArchivo: " . filename . "`n`nContiene análisis completo para afinación RTP", "Exportación Completada", "64")

    } catch Error as e {
        MyGui.Opt("+OwnDialogs")
        MsgBox("❌ ERROR al exportar datos:`n" . e.Message . "`nLínea: " . e.Line, "Error de Exportación", "16")
    }
}

UpdateStatsDisplay() {
    global totalSpins, totalWins, totalPesos, jackpotCounter, pityCounter, pityThreshold, multiplierActivations, rescueActivations

    TotalSpinsText.Value := totalSpins
    TotalPesosText.Value := totalPesos
    JackpotCounterText.Value := jackpotCounter . "/5000"
    PityCounterText.Value := pityCounter . "/" . pityThreshold
    RescueCounterText.Value := rescueActivations  ; ← NUEVO: Mostrar rescates
    UpdateWinPercentages()
}

StartSpin(*) {
    global spinning, totalSpins, detailedTotalSpins, SpinBtn, Spin1000Btn, Spin10000Btn, imgPath, spinInterval, spinDuration
    global currentProfile

    if spinning
        return

    spinning := true
    SpinBtn.Enabled := false
    SpinBtn.Text := "Girando..."
    Spin1000Btn.Enabled := false
    Spin10000Btn.Enabled := false

    totalSpins++
    detailedTotalSpins++

    UpdateStatsDisplay()
    SaveStats()

    Loop 3
        MyGui["Slot" . A_Index].Value := imgPath . "question.png"

    SetTimer(SpinSlots, spinInterval)
    SetTimer(StopSpin, -spinDuration)
}

SpinSlots() {
    global symbolList, imgPath, MyGui, img1, img2, img3

    randIndex := Random(1, symbolList.Length)
    img1 := symbolList[randIndex]

    randIndex := Random(1, symbolList.Length)
    img2 := symbolList[randIndex]

    randIndex := Random(1, symbolList.Length)
    img3 := symbolList[randIndex]

    Loop 3 {
        currentImg := (A_Index = 1) ? img1 : (A_Index = 2) ? img2 : img3
        imgFile := imgPath . currentImg . ".png"
        fallback := imgPath . "question.png"

        if FileExist(imgFile)
            MyGui["Slot" . A_Index].Value := imgFile
        else
            MyGui["Slot" . A_Index].Value := fallback
    }
}

StopSpin() {
    global spinning, MyGui, imgPath, img1, img2, img3, symbolWin, jackpotCounter, pityCounter, pityThreshold
    global SpinBtn, Spin1000Btn, Spin10000Btn, lastWin
    global currentLosingStreak, longestLosingStreak
    global currentProfile, rescueSpinsWithoutWin

    SetTimer(SpinSlots, 0)
    Critical true

    try {
        MyGui["Slot1"].Value := imgPath . img1 . ".png"
        MyGui["Slot2"].Value := imgPath . img2 . ".png"
        MyGui["Slot3"].Value := imgPath . img3 . ".png"
    }
    Sleep 30

    symbolWin := CheckWinCombination(img1, img2, img3)

    ; ===== VERIFICAR RESCATE DE VOLATILIDAD MEJORADO =====
    if (!symbolWin || symbolWin = false) {
        rescueSpinsWithoutWin++
        if (CheckVolatilityRescue()) {
            ; El rescate manejó la victoria completamente
            spinning := false
            SpinBtn.Enabled := true
            SpinBtn.Text := "1 Moneda"
            SpinBtn.Opt("+Default")
            if (!autoSpin) {
                Spin1000Btn.Enabled := true
                Spin10000Btn.Enabled := true
            }

            ; Mostrar mensaje discreto en modo normal
            if (!testingMode && !autoSpin) {
                SetTimer(ShowRescueMessage, -500)
            }

            SaveStats()
            SaveDetailedStats()
            Critical false
            return
        }
    } else {
        rescueSpinsWithoutWin := 0
    }

    if (symbolWin is String && symbolWin != "") {
        HandleTripleWin()
    }

    if (jackpotCounter >= 5000) {
        HandleJackpotWin()
    }

    if (!symbolWin || symbolWin = false) {
        if (img1 = "joker" || img2 = "joker" || img3 = "joker") {
            HandleJokerRespin()
        } else {
            HandleLoss()
        }
    }

    if (!lastWin) {
        currentLosingStreak++
    } else {
        if (currentLosingStreak > longestLosingStreak) {
            longestLosingStreak := currentLosingStreak
        }
        currentLosingStreak := 0
    }

    PityCounterText.Value := pityCounter . "/" . pityThreshold
    JackpotCounterText.Value := jackpotCounter . "/5000"
    UpdateBonusLights()
    UpdateWinPercentages()
    UpdateJokerStatus()

    spinning := false
    SpinBtn.Enabled := true
    SpinBtn.Text := "1 Moneda"
    SpinBtn.Opt("+Default")
    if (!autoSpin) {
        Spin1000Btn.Enabled := true
        Spin10000Btn.Enabled := true
    }

    SaveStats()
    SaveDetailedStats()
    Critical false
}

ShowRescueMessage() {
    MyGui.Opt("+OwnDialogs")
    MsgBox("¡Combinación ganadora!`n`nHas obtenido una victoria.", "¡Ganaste!", "64")
}

CheckWinCombination(reel1, reel2, reel3) {
    symbolsInReels := [reel1, reel2, reel3]
    symbolCounts := Map()
    jokerCount := 0

    for symbol in symbolsInReels {
        if (symbol = "joker") {
            jokerCount++
        } else {
            symbolCounts[symbol] := symbolCounts.Has(symbol) ? symbolCounts[symbol] + 1 : 1
        }
    }

    for symbol, count in symbolCounts {
        if (count = 3) {
            return symbol
        }
    }

    for symbol, count in symbolCounts {
        if (count = 2 && jokerCount >= 1) {
            return symbol
        }
    }

    for symbol, count in symbolCounts {
        if (count = 1 && jokerCount >= 2) {
            return symbol
        }
    }

    if (jokerCount = 3) {
        return "three_jokers"
    }

    return false
}

; ===== NUEVAS FUNCIONES RTP35 MEJORADO =====

CheckSafetyNet() {
    global currentLosingStreak, safetyNetThreshold, safetyNetActive, safetyNetBoost
    global symbols, currentProfile, totalSpins

    ; Solo activar para RTP35/RTP40/RTP45/RTP50 y si no está ya activo
    if ((currentProfile != "RTP35" && currentProfile != "RTP40" && currentProfile != "RTP45" && currentProfile != "RTP50") || safetyNetActive || currentLosingStreak < safetyNetThreshold) {
        return
    }

    safetyNetActive := true
    safetyNetBoost := true

    ; ✅ DISTRIBUCIÓN DE EMERGENCIA TEMPORAL POR PERFIL
    if (currentProfile = "RTP35") {
        emergencySymbols := Map(
            "cherry", 25,    ; -3 temporalmente
            "bell", 22,      ; -2
            "lemon", 14,     ; -2
            "star", 18,      ; +3 (aumentar significativamente)
            "diamond", 12,   ; +3 (más oportunidades alto valor)
            "seven", 8,      ; +1 (más símbolos premium)
            "joker", 1       ; = (mantener)
        )
    } else if (currentProfile = "RTP40") {
        emergencySymbols := Map(
            "cherry", 28,    ; -3 temporalmente
            "bell", 23,      ; -3
            "lemon", 15,     ; -2
            "star", 15,      ; +3 (aumentar significativamente)
            "diamond", 10,   ; +2 (más oportunidades alto valor)
            "seven", 6,      ; +1 (más símbolos premium)
            "joker", 1       ; = (mantener)
        )
    } else if (currentProfile = "RTP45") {
        emergencySymbols := Map(
            "cherry", 38,    ; -2 temporalmente
            "bell", 23,      ; -2
            "lemon", 14,     ; -1
            "star", 10,      ; +2 (aumentar significativamente)
            "diamond", 6,    ; +1 (más oportunidades alto valor)
            "seven", 5,      ; +1 (más símbolos premium)
            "joker", 2       ; = (mantener)
        )
    } else if (currentProfile = "RTP50") {
        emergencySymbols := Map(
            "cherry", 45,    ; -3 temporalmente
            "bell", 26,      ; -2
            "lemon", 16,     ; -2
            "star", 12,      ; +2 (aumentar significativamente)
            "diamond", 7,    ; +1 (más oportunidades alto valor)
            "seven", 6,      ; +1 (más símbolos premium)
            "joker", 2       ; = (mantener)
        )
    }

    ; Aplicar distribución de emergencia
    ApplyEmergencyDistribution(emergencySymbols)

    ; Mostrar notificación
    MyGui.Opt("+OwnDialogs")
    MsgBox("🛡️ SISTEMA DE SEGURIDAD " . currentProfile . " ACTIVADO`n`nDespués de " . currentLosingStreak . " giros sin ganar`nProbabilidades premium aumentadas temporalmente`n¡La suerte está por cambiar!", "Red de Seguridad", "64")

    ; Restaurar después de 10 giros o primera victoria
    SetTimer(RestoreOriginalDistribution, 10000)
}

ApplyEmergencyDistribution(emergencySymbols) {
    global symbols
    symbols := emergencySymbols.Clone()
    UpdateSym