local addonName, addonTable = ...
local prefijoAddon = "PremadeCDs"

-- ==========================================
-- 1. MOTOR DE EVENTOS (LIMPIO Y SEGURO)
-- ==========================================
local fCore = CreateFrame("Frame")
fCore:RegisterEvent("ADDON_LOADED")
fCore:RegisterEvent("PLAYER_ENTERING_WORLD")
fCore:RegisterEvent("GROUP_ROSTER_UPDATE")
fCore:RegisterEvent("CHAT_MSG_ADDON")
fCore:RegisterEvent("ZONE_CHANGED_NEW_AREA")
fCore:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
fCore:RegisterEvent("TRAIT_CONFIG_UPDATED") 
fCore:RegisterEvent("SPELL_UPDATE_COOLDOWN")
fCore:RegisterEvent("SPELL_UPDATE_CHARGES") 
-- AÑADIDO: Ahora escuchamos a "player" y a "pet"
fCore:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player", "pet")
fCore:RegisterUnitEvent("UNIT_PET", "player")
fCore:RegisterEvent("SPELLS_CHANGED")

C_ChatInfo.RegisterAddonMessagePrefix(prefijoAddon)


addonTable.db = {}

-- ==========================================
-- MEDIA / LIBSHAREDMEDIA COMPATIBILITY LAYER
-- ==========================================
local function SafeGetLSM()
    if type(LibStub) ~= "function" then return nil end
    local ok, lib = pcall(LibStub, "LibSharedMedia-3.0", true)
    if ok then return lib end
    return nil
end

local function MergeNamedMediaLists(...)
    local merged, seen = {}, {}
    local function addEntry(entry)
        if type(entry) ~= "table" then return end
        local name = entry.name or entry.label or entry.key
        local path = entry.path or entry.file
        if type(name) ~= "string" or name == "" or type(path) ~= "string" or path == "" then return end
        local key = string.lower(name)
        if seen[key] then return end
        seen[key] = true
        table.insert(merged, { name = name, label = entry.label or name, path = path })
    end
    for i = 1, select("#", ...) do
        local list = select(i, ...)
        if type(list) == "table" then
            for _, entry in ipairs(list) do addEntry(entry) end
        end
    end
    table.sort(merged, function(a, b)
        return string.lower(a.label or a.name) < string.lower(b.label or b.name)
    end)
    return merged
end

local function BuildLSMEntries(lsm, mediaType)
    local out = {}
    if not (lsm and lsm.List and lsm.Fetch) then return out end
    local ok, names = pcall(lsm.List, lsm, mediaType)
    if not ok or type(names) ~= "table" then return out end
    for _, name in ipairs(names) do
        local fetched
        local okFetch = pcall(function()
            fetched = lsm:Fetch(mediaType, name, true) or lsm:Fetch(mediaType, name)
        end)
        if okFetch and type(fetched) == "string" and fetched ~= "" then
            table.insert(out, { name = name, label = name, path = fetched })
        end
    end
    return out
end

local ASIAN_LOCALES = { koKR = true, zhCN = true, zhTW = true, jaJP = true }

local function IsAsianLocale(locale)
    if addonTable.NormalizeLocale then
        locale = addonTable.NormalizeLocale(locale)
    end
    return ASIAN_LOCALES[locale] == true
end

local function GetBlizzardDefaultFontPath()
    local candidates = { GameFontNormal, SystemFont_Shadow_Med1, NumberFontNormal }
    for _, obj in ipairs(candidates) do
        if obj and obj.GetFont then
            local path = select(1, obj:GetFont())
            if type(path) == "string" and path ~= "" then
                return path
            end
        end
    end
    return "Fonts\\FRIZQT__.TTF"
end

addonTable.IsAsianLocale = addonTable.IsAsianLocale or IsAsianLocale
addonTable.GetRecommendedFontNameForLocale = addonTable.GetRecommendedFontNameForLocale or function(locale)
    return IsAsianLocale(locale) and "Blizzard Default" or "Friz Quadrata"
end

local function EnsureMediaAPI()
    local media = addonTable.Media or {}
    local lsm = SafeGetLSM()
    local defaultFontPath = "Fonts\\FRIZQT__.TTF"
    local clientFontPath = GetBlizzardDefaultFontPath()
    local defaultTexturePath = "Interface\\TargetingFrame\\UI-StatusBar"
    local defaultBackgroundPath = "Interface\\Buttons\\WHITE8X8"
    local defaultBorderPath = "Interface\\Tooltips\\UI-Tooltip-Border"

    local defaults = {
        fonts = {
            { name = "Blizzard Default", label = "Blizzard Default", path = clientFontPath },
            { name = "Friz Quadrata", label = "Friz Quadrata", path = defaultFontPath },
            { name = "Arial Narrow", label = "Arial Narrow", path = "Fonts\\ARIALN.TTF" },
            { name = "Morpheus", label = "Morpheus", path = "Fonts\\MORPHEUS.TTF" },
            { name = "Skurri", label = "Skurri", path = "Fonts\\skurri.ttf" },
        },
        textures = {
            { name = "Blizzard", label = "Blizzard", path = defaultTexturePath },
            { name = "Minimalist", label = "Minimalist", path = defaultTexturePath },
        },
        backgrounds = {
            { name = "Solid", label = "Solid", path = defaultBackgroundPath },
        },
        borders = {
            { name = "Tooltip", label = "Tooltip", path = defaultBorderPath },
        },
    }

    local origGetFonts = media.GetAvailableFonts
    local origGetTextures = media.GetAvailableTextures
    local origGetBackgrounds = media.GetAvailableBackgrounds
    local origGetBorders = media.GetAvailableBorders
    local origLoadFromDB = media.LoadFromDB
    local origApplyFont = media.ApplyFont
    local origApplyTexture = media.ApplyTexture

    local function safeCall(method, self)
        if type(method) ~= "function" then return nil end
        local ok, result = pcall(method, self)
        if ok and type(result) == "table" then return result end
        return nil
    end

    function media:GetAvailableFonts()
        return MergeNamedMediaLists(
            safeCall(origGetFonts, self),
            BuildLSMEntries(lsm, "font"),
            defaults.fonts
        )
    end

    function media:GetAvailableTextures()
        return MergeNamedMediaLists(
            safeCall(origGetTextures, self),
            BuildLSMEntries(lsm, "statusbar"),
            defaults.textures
        )
    end

    function media:GetAvailableBackgrounds()
        return MergeNamedMediaLists(
            safeCall(origGetBackgrounds, self),
            BuildLSMEntries(lsm, "background"),
            defaults.backgrounds
        )
    end

    function media:GetAvailableBorders()
        return MergeNamedMediaLists(
            safeCall(origGetBorders, self),
            BuildLSMEntries(lsm, "border"),
            defaults.borders
        )
    end

    local function findPath(list, requestedName, fallbackPath)
        local target = (type(requestedName) == "string" and requestedName ~= "" and requestedName) or nil
        if target then
            for _, entry in ipairs(list or {}) do
                if entry.name == target then return entry.path end
            end
        end
        if list and list[1] and list[1].path then return list[1].path end
        return fallbackPath
    end

    function media:ResolveFontPath(name)
        return findPath(self:GetAvailableFonts(), name, defaultFontPath)
    end

    function media:ResolveTexturePath(name)
        return findPath(self:GetAvailableTextures(), name, defaultTexturePath)
    end

    function media:ResolveBackgroundPath(name)
        return findPath(self:GetAvailableBackgrounds(), name, defaultBackgroundPath)
    end

    function media:ResolveBorderPath(name)
        return findPath(self:GetAvailableBorders(), name, defaultBorderPath)
    end

    function media:LoadFromDB(profile)
        if type(origLoadFromDB) == "function" then
            pcall(origLoadFromDB, self, profile)
        end
        local cfg = type(profile) == "table" and profile or {}
        local effectiveLocale = addonTable.NormalizeLocale and addonTable.NormalizeLocale(cfg.language) or cfg.language
        local fontName = cfg.fontName
        if fontName == nil or fontName == "" then
            fontName = addonTable.GetRecommendedFontNameForLocale and addonTable.GetRecommendedFontNameForLocale(effectiveLocale) or "Friz Quadrata"
        elseif IsAsianLocale(effectiveLocale) and fontName == "Friz Quadrata" then
            fontName = "Blizzard Default"
        end
        self.currentFontName = fontName or self.currentFontName or "Friz Quadrata"
        self.currentTextureName = cfg.textureName or self.currentTextureName or "Blizzard"
        self.currentBackgroundName = cfg.menuBackgroundName or self.currentBackgroundName or "Solid"
        self.currentBorderName = cfg.menuBorderName or self.currentBorderName or "Tooltip"
        self.currentFontPath = self:ResolveFontPath(self.currentFontName)
        self.currentTexturePath = self:ResolveTexturePath(self.currentTextureName)
        self.currentBackgroundPath = self:ResolveBackgroundPath(self.currentBackgroundName)
        self.currentBorderPath = self:ResolveBorderPath(self.currentBorderName)
    end

    function media:ApplyFont(fontString, size, flags)
        local path = self.currentFontPath or self:ResolveFontPath(self.currentFontName)
        if type(origApplyFont) == "function" then
            pcall(origApplyFont, self, fontString, size, flags)
        end
        if not (fontString and fontString.SetFont) then return end
        local _, oldSize, oldFlags = fontString:GetFont()
        local ok = pcall(fontString.SetFont, fontString, path or defaultFontPath, size or oldSize or 12, flags or oldFlags or "OUTLINE")
        if not ok then
            pcall(fontString.SetFont, fontString, defaultFontPath, size or oldSize or 12, flags or oldFlags or "OUTLINE")
        end
    end

    function media:ApplyTexture(target)
        local path = self.currentTexturePath or self:ResolveTexturePath(self.currentTextureName)
        if type(origApplyTexture) == "function" then
            pcall(origApplyTexture, self, target)
        end
        if not target then return end
        if target.SetStatusBarTexture then
            pcall(target.SetStatusBarTexture, target, path or defaultTexturePath)
        elseif target.SetTexture then
            pcall(target.SetTexture, target, path or defaultTexturePath)
        end
    end

    addonTable.Media = media
end

EnsureMediaAPI()

-- ==========================================
-- LOCALE COMPATIBILITY LAYER (ruRU)
-- ==========================================
do
    local function CopyLocaleTable(src)
        local out = {}
        if type(src) ~= "table" then return out end
        for k, v in pairs(src) do out[k] = v end
        return out
    end

    local function ResolveSavedLanguage()
        local saved = _G.PremadeCDsDB
        if type(saved) ~= "table" then return nil end
        local playerName = UnitName("player")
        if not playerName then return nil end
        local charToProfile = saved.charToProfile or {}
        local profileName = charToProfile[playerName] or "Default"
        local profiles = saved.profiles or {}
        local profile = profiles[profileName] or profiles.Default
        return profile and profile.language or nil
    end

    local SUPPORTED_LOCALES = {
        enUS = true, esES = true, frFR = true, deDE = true,
        koKR = true, zhCN = true, zhTW = true, jaJP = true,
        ruRU = true,
    }

    addonTable.NormalizeLocale = function(locale)
        if locale == "Auto" or locale == nil or locale == "" then
            local savedLocale = ResolveSavedLanguage()
            if savedLocale and savedLocale ~= "" and savedLocale ~= "Auto" then
                locale = savedLocale
            else
                locale = GetLocale()
            end
        end
        if locale == "enGB" then locale = "enUS" end
        if locale == "esMX" then locale = "esES" end
        if locale == "zhSG" then locale = "zhCN" end
        if not SUPPORTED_LOCALES[locale] then
            locale = "enUS"
        end
        return locale
    end

    addonTable.GetDict = function(locale)
        local normalized = addonTable.NormalizeLocale(locale)
        local base = (addonTable.locales and addonTable.locales.enUS) or {}
        local selected = (addonTable.locales and addonTable.locales[normalized]) or base
        local dict = CopyLocaleTable(base)
        for k, v in pairs(selected) do dict[k] = v end
        return dict
    end
end

local db = addonTable.db
local spellDB = addonTable.spellDB
local L = addonTable.L

local function GetReadyLabel()
    local current = addonTable.L
    if type(current) == "table" then
        L = current
    end
    local ready = L and L.READY
    if type(ready) ~= "string" or ready == "" then
        ready = "READY"
    end
    return ready
end

local function UpdateReadyText(row)
    if not row or not row.timerText then return end
    if db and db.showListo and (row.cdExpiration == 0 or row.cdExpiration <= GetTime()) then
        row.timerText:SetText(GetReadyLabel())
    else
        row.timerText:SetText("")
    end
end
local cortesBasePorClase = addonTable.cortesBasePorClase
local mapaClases = addonTable.mapaClases or {}
local specsDetectadas = {} 
local talentosDetectados = {} 
local ultimoSyncEnviado = {} 
addonTable.throttleDB = {}
local modoTestActivo = false

local barrasActivas, ordenBarras, iconosActivos = {}, {}, {}
local anclasGrupo = {}
local unidadesGrupo = {"player", "party1", "party2", "party3", "party4"}
local ultimoCorteAnunciado = nil 

-- ==========================================
-- LECTORES INTELIGENTES Y ALIAS
-- ==========================================
local function ObtenerIDReal(spellID)
    if addonTable.spellAliases and addonTable.spellAliases[spellID] then
        return addonTable.spellAliases[spellID]
    end
    return spellID
end

local function ObtenerIDAprendido(spellID)
    if not spellID then return nil end
    if IsPlayerSpell(spellID) then return spellID end
    
    local ok, knownPet = pcall(IsSpellKnown, spellID, true)
    if ok and knownPet then return spellID end
    
    if addonTable.spellAliases then
        for cloneID, baseID in pairs(addonTable.spellAliases) do
            if baseID == spellID then
                if IsPlayerSpell(cloneID) then return cloneID end
                local ok2, knownPetClone = pcall(IsSpellKnown, cloneID, true)
                if ok2 and knownPetClone then return cloneID end
            end
        end
    end
    return nil
end

local function ObtenerCD(info, nombre)
    if not info then return 15 end
    if type(info.cd) == "table" then
        local spec = specsDetectadas[nombre] or 0
        if nombre == UnitName("player") then
            local idx = GetSpecialization()
            if idx then spec = GetSpecializationInfo(idx) end
        end
        return info.cd[spec] or info.cd.default or 15
    end
    return info.cd or 15
end

local function GuardarPosicionAncla(self)
    self:StopMovingOrSizing()
    local point, _, relativePoint, xOfs, yOfs = self:GetPoint()
    if db then
        db.anchors = db.anchors or {}
        db.anchors[self:GetName()] = {point, relativePoint, xOfs, yOfs}
    end
end

addonTable.GuardarTodasLasAnclas = function()
    if not db then return end
    db.anchors = db.anchors or {}
    local function saveA(f)
        if f then
            local point, _, relativePoint, xOfs, yOfs = f:GetPoint()
            db.anchors[f:GetName()] = {point, relativePoint, xOfs, yOfs}
        end
    end
    saveA(PremadeCDsAnchorCortes)
    saveA(PremadeCDsAnchorCCs)
    for _, u in ipairs(unidadesGrupo) do saveA(_G["PremadeCDsAnchor_"..u]) end
end

local anchorCortes = CreateFrame("Frame", "PremadeCDsAnchorCortes", UIParent)
anchorCortes:SetSize(150, 20); anchorCortes:SetMovable(true); anchorCortes:EnableMouse(true); anchorCortes:RegisterForDrag("LeftButton")
anchorCortes:SetScript("OnDragStart", anchorCortes.StartMoving); anchorCortes:SetScript("OnDragStop", GuardarPosicionAncla)
anchorCortes.bg = anchorCortes:CreateTexture(nil, "BACKGROUND"); anchorCortes.bg:SetAllPoints(); anchorCortes.bg:SetColorTexture(1, 0, 0, 0.5)
anchorCortes.text = anchorCortes:CreateFontString(nil, "ARTWORK", "GameFontNormal"); anchorCortes.text:SetPoint("CENTER"); anchorCortes.text:SetText("Anchor: CORTES"); anchorCortes:Hide()

local anchorCCs = CreateFrame("Frame", "PremadeCDsAnchorCCs", UIParent)
anchorCCs:SetSize(150, 20); anchorCCs:SetMovable(true); anchorCCs:EnableMouse(true); anchorCCs:RegisterForDrag("LeftButton")
anchorCCs:SetScript("OnDragStart", anchorCCs.StartMoving); anchorCCs:SetScript("OnDragStop", GuardarPosicionAncla)
anchorCCs.bg = anchorCCs:CreateTexture(nil, "BACKGROUND"); anchorCCs.bg:SetAllPoints(); anchorCCs.bg:SetColorTexture(0, 1, 0, 0.5)
anchorCCs.text = anchorCCs:CreateFontString(nil, "ARTWORK", "GameFontNormal"); anchorCCs.text:SetPoint("CENTER"); anchorCCs.text:SetText("Anchor: CCs GRUPO"); anchorCCs:Hide()

for i, unit in ipairs(unidadesGrupo) do
    local f = CreateFrame("Frame", "PremadeCDsAnchor_"..unit, UIParent)
    f:SetSize(80, 30); f:SetMovable(true); f:EnableMouse(true); f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving); f:SetScript("OnDragStop", GuardarPosicionAncla)
    f.bg = f:CreateTexture(nil, "BACKGROUND"); f.bg:SetAllPoints(); f.bg:SetColorTexture(0, 0.5, 1, 0.5)
    f.text = f:CreateFontString(nil, "ARTWORK", "GameFontNormal"); f.text:SetPoint("CENTER"); f.text:SetText("Anchor: " .. unit)
    f:Hide()
    anclasGrupo[unit] = f
    iconosActivos[unit] = {}
end

local function ObtenerUnidadDeMarco(frame)
    if not frame then return nil end
    if type(frame.unit) == "string" then return frame.unit end
    if frame.GetAttribute and frame:GetAttribute("unit") then return frame:GetAttribute("unit") end
    return nil
end

local function FrameCoincideUnidad(frame, unitBuscada)
    if not frame then return false end
    local frameUnit = ObtenerUnidadDeMarco(frame)
    if frameUnit and UnitIsUnit(frameUnit, unitBuscada) then return true end
    return false
end

local function GetPartyUnitFrame(unit)
    local function IsValid(f) return f and (modoTestActivo or f:IsVisible()) end

    if _G["DandersPartyHeader"] or _G["DUI_PlayerFrame"] or _G["DandersPlayerFrame"] then
        for i = 1, 5 do
            local f = _G["DUI_PartyFrame"..i] or _G["DUI_UnitFrameParty"..i] or _G["DandersPartyHeaderUnitButton"..i] or _G["DUI_PartyGroup1UnitButton"..i]
            if IsValid(f) and FrameCoincideUnidad(f, unit) then return f end 
        end
        if unit == "player" then
            local p = _G["DUI_PlayerFrame"] or _G["DandersPlayerFrame"] or _G["DandersPartyHeaderUnitButton0"]
            if IsValid(p) and (not ObtenerUnidadDeMarco(p) or FrameCoincideUnidad(p, "player")) then return p end 
        end
        if modoTestActivo then
            if unit == "player" then
                local f = _G["DandersPartyHeaderUnitButton1"] or _G["DUI_PartyFrame1"] or _G["DUI_UnitFrameParty1"]
                if IsValid(f) then return f end
            end
            local idx = string.match(unit, "party(%d)")
            if idx then
                local f = _G["DUI_PartyFrame"..idx] or _G["DUI_UnitFrameParty"..idx] or _G["DandersPartyHeaderUnitButton"..idx] or _G["DUI_PartyGroup1UnitButton"..idx]
                if IsValid(f) then return f end
            end
        end
    end

    if _G["ElvUI"] then
        for i = 1, 5 do
            local f = _G["ElvUF_PartyGroup1UnitButton"..i]
            if IsValid(f) and FrameCoincideUnidad(f, unit) then return f end 
        end
        if unit == "player" then 
            local p = _G["ElvUF_Player"]
            if IsValid(p) and (not ObtenerUnidadDeMarco(p) or FrameCoincideUnidad(p, "player")) then return p end 
        end
    end

    if _G["Grid2LayoutFrame"] then
        for i = 1, 5 do
            local f = _G["Grid2LayoutHeader1UnitButton"..i]
            if IsValid(f) and FrameCoincideUnidad(f, unit) then return f end 
        end
    end

    for i = 1, 5 do
        local f = _G["CompactPartyFrameMember"..i]
        if IsValid(f) and FrameCoincideUnidad(f, unit) then return f end 
    end
    if unit == "player" then 
        local p = _G["PlayerFrame"]
        if IsValid(p) and (not ObtenerUnidadDeMarco(p) or FrameCoincideUnidad(p, "player")) then return p end 
    end

    return nil
end

addonTable.RestaurarAnclas = function()
    if not db then return end
    db.anchors = db.anchors or {}
    
    local anclasEstaticas = {PremadeCDsAnchorCortes = anchorCortes, PremadeCDsAnchorCCs = anchorCCs}
    for nombreAncla, frame in pairs(anclasEstaticas) do
        frame:ClearAllPoints()
        if db.anchors[nombreAncla] then
            local data = db.anchors[nombreAncla]
            frame:SetPoint(data[1], UIParent, data[2], data[3], data[4])
        else
            if nombreAncla == "PremadeCDsAnchorCortes" then frame:SetPoint("CENTER", -200, 100)
            elseif nombreAncla == "PremadeCDsAnchorCCs" then frame:SetPoint("CENTER", 200, 0) end
        end
    end

    for _, u in ipairs(unidadesGrupo) do
        local ancla = anclasGrupo[u]
        ancla:ClearAllPoints()
        if db.anchorMode == "Manual" and db.anchors[ancla:GetName()] then
            local data = db.anchors[ancla:GetName()]
            ancla:SetPoint(data[1], UIParent, data[2], data[3], data[4])
        else
            local idx = tonumber(u:match("party(%d)") or 0)
            if u:match("player") then idx = 0 end
            ancla:SetPoint("CENTER", -100, 50 - (idx * 50))
        end
    end
end

local function ActualizarAnclajesDinamicos()
    if not db then return end
    
    if db.anchorMode == "Manual" then
        for unit, ancla in pairs(anclasGrupo) do
            ancla.hasValidAnchor = true
        end
        return
    end
    
    local oX = db.offsetX or 0
    local oY = db.offsetY or 0

    for unit, ancla in pairs(anclasGrupo) do
        local targetFrame = GetPartyUnitFrame(unit)
        ancla:ClearAllPoints()
        
        if targetFrame and (modoTestActivo or targetFrame:IsVisible()) then
            ancla.hasValidAnchor = true
            ancla:SetSize(1, 1)
            
            if db.anchorSide == "RIGHT" then
                ancla:SetPoint("TOPLEFT", targetFrame, "TOPRIGHT", 5 + oX, oY)
            elseif db.anchorSide == "LEFT" then
                ancla:SetPoint("TOPRIGHT", targetFrame, "TOPLEFT", -5 + oX, oY)
            elseif db.anchorSide == "TOP" then
                ancla:SetPoint("BOTTOMLEFT", targetFrame, "TOPLEFT", oX, 5 + oY)
            elseif db.anchorSide == "BOTTOM" then
                ancla:SetPoint("TOPLEFT", targetFrame, "BOTTOMLEFT", oX, -5 + oY)
            end
        else
            ancla.hasValidAnchor = false
            ancla:SetSize(80, 30)
            local idx = tonumber(unit:match("party(%d)") or 0)
            if unit:match("player") then idx = 0 end
            ancla:SetPoint("CENTER", -100, 50 - (idx * 50))
        end
    end
end

local NOMBRES_TEST = {}

local function LimpiarNombreSeguro(nombreCompuesto)
    if type(nombreCompuesto) ~= "string" then return "" end
    local namePart = strsplit("-", nombreCompuesto)
    return namePart or nombreCompuesto
end

local function ObtenerCooldownSeguro(spellID)
    local cdInfo = C_Spell.GetSpellCooldown(spellID)
    if not cdInfo then return 0, 0, false end
    
    local ok1, dur = pcall(function() return cdInfo.duration + 0 end)
    local ok2, start = pcall(function() return cdInfo.startTime + 0 end)
    
    if ok1 and ok2 and type(dur) == "number" and type(start) == "number" then
        return start, dur, true
    end
    return 0, 0, false
end

local function ActualizarVisualCargas(ic, isLocalPlayer)
    if not ic then return end
    
    local isOnCD = (ic.cdExpiration and ic.cdExpiration > GetTime())
    
    ic.chargesText:SetText("")

    if isOnCD then
        ic.icon:SetDesaturated(true)
        ic.icon:SetVertexColor(0.5, 0.5, 0.5)
    else
        if isLocalPlayer then
            local isUsable, notEnoughMana = C_Spell.IsSpellUsable(ic.spellID)
            if isUsable or notEnoughMana then
                ic.icon:SetDesaturated(false)
                ic.icon:SetVertexColor(1, 1, 1)
            else
                ic.icon:SetDesaturated(true)
                ic.icon:SetVertexColor(0.5, 0.5, 0.5)
            end
        else
            ic.icon:SetDesaturated(false)
            ic.icon:SetVertexColor(1, 1, 1)
        end
    end
end

local function DeberiaMostrarHechizo(unit, spellID)
    local info = spellDB[spellID]
    if not info or db.disabledSpells[spellID] then return false end
    
    local classFile
    if modoTestActivo then
        for _, mock in ipairs(NOMBRES_TEST) do
            if mock.unit == unit then classFile = mock.class; break end
        end
    else
        _, classFile = UnitClass(unit)
    end

    if classFile and info.clase ~= classFile then return false end
    if modoTestActivo then return true end
    
    local name = UnitName(unit)
    if not name then return false end

    if unit == "player" then
        return ObtenerIDAprendido(spellID) ~= nil
    else
        if talentosDetectados[name] then
            return talentosDetectados[name][spellID] == true
        end
        
        if info.spec == 0 then return true end
        if specsDetectadas[name] then
            if type(info.spec) == "table" then
                for _, specID in ipairs(info.spec) do
                    if specsDetectadas[name] == specID then return true end
                end
                return false
            else
                return specsDetectadas[name] == info.spec 
            end
        end
    end
    
    return false
end

local function DeberiaEstarActivoEnZona()
    if anchorCortes:IsShown() or anchorCCs:IsShown() then return true end
    local _, instanceType = GetInstanceInfo()
    if instanceType == "party" then return db.activeDungeon end
    if instanceType == "raid" then return db.activeRaid end
    if instanceType == "arena" or instanceType == "pvp" then return db.activePvp end
    if instanceType == "none" then return db.activeWorld end
    return true
end

local function EsNombreActivoEnGrupo(nombreBuscar)
    if anchorCortes:IsShown() then return true end
    if not nombreBuscar then return false end
    
    local miNombre = UnitName("player")
    if miNombre and (miNombre == nombreBuscar or LimpiarNombreSeguro(miNombre) == nombreBuscar) then return true end
    
    for i=1, 4 do
        local n = UnitName("party"..i)
        if n and (n == nombreBuscar or LimpiarNombreSeguro(n) == nombreBuscar) then return true end
    end
    return false
end

local function ObtenerUnidadPorNombre(nombreBuscado)
    if type(nombreBuscado) ~= "string" then return nil end
    if modoTestActivo then
        for _, mock in ipairs(NOMBRES_TEST) do
            if string.match(mock.name, nombreBuscado) or string.match(nombreBuscado, mock.name) then return mock.unit end
        end
    end
    local nombreLimpio = LimpiarNombreSeguro(nombreBuscado)
    if UnitName("player") == nombreLimpio or UnitName("player") == nombreBuscado then return "player" end
    for i=1, 4 do if UnitName("party"..i) == nombreLimpio or UnitName("party"..i) == nombreBuscado then return "party"..i end end
    return nil
end

local function EnviarMiInfo()
    local specIndex = GetSpecialization()
    local specID = specIndex and GetSpecializationInfo(specIndex) or 0
    local miNombre = UnitName("player")
    if miNombre and specID then specsDetectadas[miNombre] = specID end
    
    local _, miClase = UnitClass("player")
    local misHechizos = {}
    for sIDRaw, info in pairs(spellDB) do
        if info.clase == miClase then
            local sID = ObtenerIDReal(sIDRaw)
            if ObtenerIDAprendido(sID) then
                misHechizos[sID] = true
            end
        end
    end
    if miNombre then talentosDetectados[miNombre] = misHechizos end
    
    if IsInGroup() then
        local channel = IsInRaid() and "RAID" or "PARTY"
        C_ChatInfo.SendAddonMessage(prefijoAddon, "SPEC#"..tostring(specID), channel)
        
        local str = "TALENTS#"
        local num = 0
        for id, _ in pairs(misHechizos) do
            if num > 0 then str = str .. "," end
            str = str .. tostring(id)
            num = num + 1
        end
        
        if num > 0 then
            C_ChatInfo.SendAddonMessage(prefijoAddon, str, channel)
        else
            C_ChatInfo.SendAddonMessage(prefijoAddon, "TALENTS#NONE", channel)
        end
    end
end

local function AnunciarProximoCorte(classFile, isPlayer)
    if db and db.ttsEnabled == false then return end
    
    local rutaBase = "Interface\\AddOns\\"..addonName.."\\Sounds\\"
    
    if isPlayer then
        PlaySoundFile(rutaBase .. "YO.mp3", "Master")
    elseif classFile then
        PlaySoundFile(rutaBase .. classFile .. ".mp3", "Master")
    end
end

SLASH_PREMADECDS3 = "/premadetts"
SlashCmdList["PREMADECDS3"] = function()
    local _, myClass = UnitClass("player")
    AnunciarProximoCorte(myClass, true)
    C_Timer.After(2, function()
        AnunciarProximoCorte(myClass, false)
    end)
end

local function EvaluarProximoCorte()
    if not IsInGroup() and not modoTestActivo then return end

    local listos = {}
    local enCD = {}
    local ahora = GetTime()

    for barKey, row in pairs(barrasActivas) do
        local senderName = string.match(barKey, "^(.-)_%d+$")
        
        local unit = ObtenerUnidadPorNombre(senderName)
        local debeMostrar = true
        if unit then debeMostrar = DeberiaMostrarHechizo(unit, row.spellID) end
        
        if not db.disabledSpells[row.spellID] and (EsNombreActivoEnGrupo(senderName) or modoTestActivo) and debeMostrar then
            local role = "DAMAGER"
            local classFile = "UNKNOWN"
            local spec = specsDetectadas[senderName] or 0

            if modoTestActivo then
                for _, mock in ipairs(NOMBRES_TEST) do
                    if mock.name == senderName then
                        classFile = mock.class
                        if mock.unit == "player" then unit = "player" end
                        break
                    end
                end
            elseif unit then
                role = UnitGroupRolesAssigned(unit)
                _, classFile = UnitClass(unit)
            end

            local score = 1000 
            local isMelee = false
            if role == "TANK" or classFile == "ROGUE" or classFile == "WARRIOR" or classFile == "DEMONHUNTER" or classFile == "DEATHKNIGHT" or classFile == "MONK" or classFile == "PALADIN" then
                isMelee = true
            end
            if spec == 255 or spec == 263 or spec == 103 then isMelee = true end

            if isMelee then score = 2000 end

            local info = { 
                name = senderName, 
                cdExpiration = row.cdExpiration, 
                priority = score,
                classFile = classFile,
                isPlayer = (unit == "player")
            }
            if row.cdExpiration == 0 or row.cdExpiration <= ahora then
                table.insert(listos, info)
            else
                table.insert(enCD, info)
            end
        end
    end

    local ganador = nil
    if #listos > 0 then
        table.sort(listos, function(a, b)
            if a.priority == b.priority then return a.name < b.name end
            return a.priority > b.priority
        end)
        ganador = listos[1]
    elseif #enCD > 0 then
        table.sort(enCD, function(a, b)
            if math.abs(a.cdExpiration - b.cdExpiration) < 0.1 then 
                if a.priority == b.priority then return a.name < b.name end
                return a.priority > b.priority 
            end
            return a.cdExpiration < b.cdExpiration
        end)
        ganador = enCD[1]
    end

    if ganador then
        if ganador.name ~= ultimoCorteAnunciado then
            ultimoCorteAnunciado = ganador.name
            AnunciarProximoCorte(ganador.classFile, ganador.isPlayer)
        end
    else
        ultimoCorteAnunciado = nil
    end
end

local function ActualizarTodoEnVivo()
    if not db then return end
    if addonTable.Media and addonTable.Media.LoadFromDB then addonTable.Media:LoadFromDB(db) end
    local estaActivo = DeberiaEstarActivoEnZona()
    
    ActualizarAnclajesDinamicos()
    
    local barrasOrdenadas = {}
    for barKey, row in pairs(barrasActivas) do
        row.barKey = barKey
        local senderName = string.match(barKey, "^(.-)_%d+$")
        local unit = ObtenerUnidadPorNombre(senderName)
        
        local debeMostrar = true
        if unit then debeMostrar = DeberiaMostrarHechizo(unit, row.spellID) end
        
        if db.disabledSpells[row.spellID] or not estaActivo or (not EsNombreActivoEnGrupo(senderName) and not modoTestActivo) or not db.showCortes or not debeMostrar then
            row:Hide()
        else
            table.insert(barrasOrdenadas, row)
        end
    end

    table.sort(barrasOrdenadas, function(a, b)
        local remA = (a.cdExpiration and a.cdExpiration > GetTime()) and (a.cdExpiration - GetTime()) or 0
        local remB = (b.cdExpiration and b.cdExpiration > GetTime()) and (b.cdExpiration - GetTime()) or 0
        if db.sortMode == "CD_ASC" then return remA < remB
        elseif db.sortMode == "CD_DESC" then return remA > remB
        else return a.barKey < b.barKey end
    end)

    local prevRow = nil
    for i, row in ipairs(barrasOrdenadas) do
        row:Show(); row:SetSize(db.width, db.height); row.icon:SetSize(db.height - 2, db.height - 2)
        
        if db.pixelPerfect then
            row.icon:SetPoint("TOPLEFT", row, "TOPLEFT", 1, -1)
            row.icon:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 1, 1)
            row.bg:Show()
        else
            row.icon:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
            row.icon:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
            row.bg:Hide()
        end

        if addonTable.Media and addonTable.Media.ApplyFont then 
            addonTable.Media:ApplyFont(row.text, db.nameFontSize); addonTable.Media:ApplyFont(row.timerText, db.fontSizeName)
        else
            row.text:SetFont("Fonts\\FRIZQT__.TTF", db.nameFontSize, "OUTLINE"); row.timerText:SetFont("Fonts\\FRIZQT__.TTF", db.fontSizeName, "OUTLINE")
        end
        if addonTable.Media and addonTable.Media.ApplyTexture then addonTable.Media:ApplyTexture(row.bar) end
        
        if row.cdExpiration == 0 or row.cdExpiration <= GetTime() then
            UpdateReadyText(row)
            row.bar:SetScript("OnUpdate", nil)
            row.bar:SetMinMaxValues(0, row.cdTotal)
            row.bar:SetValue(row.cdTotal)
            row.timerText:SetTextColor(1, 1, 1)
            row.cdExpiration = 0
        end
        
        row:ClearAllPoints()
        if not prevRow then row:SetPoint("TOP", anchorCortes, "BOTTOM", 0, 0) else row:SetPoint("TOP", prevRow, "BOTTOM", 0, -2) end
        prevRow = row
    end

    local visiblesCC = {}
    for unit, lista in pairs(iconosActivos) do
        local isUnitValid = false
        local pName = ""
        local pClass = "UNKNOWN"
        
        if modoTestActivo then
            isUnitValid = (unit == "player") or unit:match("party")
            for _, mock in ipairs(NOMBRES_TEST) do 
                if mock.unit == unit then 
                    pName = mock.name
                    pClass = mock.class
                    break 
                end 
            end
        else
            isUnitValid = UnitExists(unit) or (unit == "player")
            pName = UnitName(unit) or ""
            _, pClass = UnitClass(unit)
        end
        
        for _, ic in ipairs(lista) do
            local info = spellDB[ic.spellID]
            if info and info.cat == "CCs" then
                if isUnitValid and estaActivo and db.showCCs and DeberiaMostrarHechizo(unit, ic.spellID) then
                    ic:Show(); ic:SetSize(db.ccSize, db.ccSize)
                    
                    if db.pixelPerfect then
                        ic.icon:SetPoint("TOPLEFT", 1, -1); ic.icon:SetPoint("BOTTOMRIGHT", -1, 1)
                        ic.bg:Show()
                    else
                        ic.icon:SetPoint("TOPLEFT", 0, 0); ic.icon:SetPoint("BOTTOMRIGHT", 0, 0)
                        ic.bg:Hide()
                    end

                    if ic.playerName then 
                        ic.playerName:SetText(strsub(pName, 1, 4))
                        if addonTable.Media and addonTable.Media.ApplyFont then addonTable.Media:ApplyFont(ic.playerName, 10) end 
                    end
                    
                    local isLocal = (unit == "player" and not modoTestActivo)
                    ActualizarVisualCargas(ic, isLocal)

                    ic.currentClass = pClass or "UNKNOWN"
                    ic.currentUnit = unit
                    table.insert(visiblesCC, ic)
                else
                    ic:Hide()
                end
            end
        end
    end
    
    table.sort(visiblesCC, function(a, b)
        if a.currentClass == b.currentClass then
            if a.currentUnit == b.currentUnit then return a.spellID < b.spellID end
            return a.currentUnit < b.currentUnit
        end
        return a.currentClass < b.currentClass
    end)
    
    local ccSpacing = db.ccSpacing or 2
    local cDir = db.ccGrowDirection or "RIGHT_DOWN"
    local growLeftCC = (cDir == "LEFT_DOWN" or cDir == "LEFT_UP")
    local growUpCC = (cDir == "RIGHT_UP" or cDir == "LEFT_UP")

    local pointYCC = growUpCC and "BOTTOM" or "TOP"
    local pointXCC = growLeftCC and "RIGHT" or "LEFT"
    local pointCC = pointYCC .. pointXCC
    local relYCC = growUpCC and "TOP" or "BOTTOM"
    local relPointCC = relYCC .. pointXCC

    local xMCC = growLeftCC and -1 or 1
    local yMCC = growUpCC and 1 or -1

    for i, ic in ipairs(visiblesCC) do
        ic:ClearAllPoints()
        local cPR = math.max(1, db.ccPerRow or 5)
        local col = (i - 1) % cPR; local row = math.floor((i - 1) / cPR)
        ic:SetPoint(pointCC, anchorCCs, relPointCC, xMCC * (col * (db.ccSize + ccSpacing)), yMCC * (row * (db.ccSize + ccSpacing)))
    end

    local spacing = db.iconSpacing or 2
    local gDir = db.growDirection or "AUTO"
    
    for unit, listaIconos in pairs(iconosActivos) do
        local ancla = anclasGrupo[unit]
        local visiblesDef, visiblesOfen = {}, {}
        local isUnitValid = modoTestActivo or (unit == "player") or UnitExists(unit)

        if isUnitValid and db.anchorMode == "Auto" then
            local targetFrame = GetPartyUnitFrame(unit)
            if not targetFrame or not targetFrame:IsVisible() then isUnitValid = false end
        end

        if isUnitValid and ancla and ancla.hasValidAnchor then
            for _, iconFrame in ipairs(listaIconos) do
                local info = spellDB[iconFrame.spellID]
                if info and info.cat ~= "CCs" then
                    if estaActivo and DeberiaMostrarHechizo(unit, iconFrame.spellID) then
                        if info.cat == "Defensivos" and db.showDefensivos then
                            iconFrame:Show(); iconFrame:SetSize(db.iconSize, db.iconSize); table.insert(visiblesDef, iconFrame)
                        elseif info.cat == "Ofensivos" and db.showOfensivos then
                            iconFrame:Show(); iconFrame:SetSize(db.iconSize, db.iconSize); table.insert(visiblesOfen, iconFrame)
                        else iconFrame:Hide() end
                        
                        if db.pixelPerfect then
                            iconFrame.icon:SetPoint("TOPLEFT", 1, -1); iconFrame.icon:SetPoint("BOTTOMRIGHT", -1, 1)
                            iconFrame.bg:Show()
                        else
                            iconFrame.icon:SetPoint("TOPLEFT", 0, 0); iconFrame.icon:SetPoint("BOTTOMRIGHT", 0, 0)
                            iconFrame.bg:Hide()
                        end

                        local isLocal = (unit == "player" and not modoTestActivo)
                        ActualizarVisualCargas(iconFrame, isLocal)
                        
                    else iconFrame:Hide() end
                end
            end
            
            local growLeft, growUp = false, false
            if gDir == "AUTO" then
                if db.anchorMode ~= "Manual" and db.anchorSide == "LEFT" then growLeft = true end
            elseif gDir == "LEFT_DOWN" then growLeft = true; growUp = false
            elseif gDir == "RIGHT_UP" then growLeft = false; growUp = true
            elseif gDir == "LEFT_UP" then growLeft = true; growUp = true
            end

            local pointY = growUp and "BOTTOM" or "TOP"
            local pointX = growLeft and "RIGHT" or "LEFT"
            local pointIcon = pointY .. pointX
            local xMult = growLeft and -1 or 1
            local yMult = growUp and 1 or -1
            local iPR = math.max(1, db.iconsPerRow or 4)

            local arr1 = db.defensivosFirst and visiblesDef or visiblesOfen
            local arr2 = db.defensivosFirst and visiblesOfen or visiblesDef

            for i, iconFrame in ipairs(arr1) do
                iconFrame:ClearAllPoints()
                local col = (i - 1) % iPR; local row = math.floor((i - 1) / iPR)
                iconFrame:SetPoint(pointIcon, ancla, pointIcon, xMult * (col * (db.iconSize + spacing)), yMult * (row * (db.iconSize + spacing)))
            end
            
            local filasArr1 = math.ceil(#arr1 / iPR)

            for i, iconFrame in ipairs(arr2) do
                iconFrame:ClearAllPoints()
                local col = (i - 1) % iPR; local row = math.floor((i - 1) / iPR) + filasArr1
                iconFrame:SetPoint(pointIcon, ancla, pointIcon, xMult * (col * (db.iconSize + spacing)), yMult * (row * (db.iconSize + spacing)))
            end
        else
            for _, iconFrame in ipairs(listaIconos) do 
                local info = spellDB[iconFrame.spellID]
                if info and info.cat ~= "CCs" then iconFrame:Hide() end
            end
        end
    end
end
addonTable.ActualizarTodoEnVivo = ActualizarTodoEnVivo

local function ObtenerOCrearBarra(senderName, spellID, claseFondo)
    if not senderName or not spellID then return nil end
    senderName = LimpiarNombreSeguro(senderName)
    local barKey = senderName .. "_" .. spellID
    if barrasActivas[barKey] then return barrasActivas[barKey] end

    local info = spellDB[spellID]
    local cdBase = ObtenerCD(info, senderName)

    local row = CreateFrame("Frame", nil, UIParent)
    row.spellID = spellID; row.cdTotal = math.max(0.1, cdBase)
    row.cdExpiration = 0 
    
    row.bg = row:CreateTexture(nil, "BACKGROUND"); row.bg:SetAllPoints(); row.bg:SetColorTexture(0, 0, 0, 1)
    row.icon = row:CreateTexture(nil, "ARTWORK")
    row.icon:SetPoint("TOPLEFT", row, "TOPLEFT", 1, -1)
    row.icon:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 1, 1)
    row.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    
    local spellInfo = C_Spell.GetSpellInfo(spellID)
    if spellInfo then row.icon:SetTexture(spellInfo.iconID) end

    row.bar = CreateFrame("StatusBar", nil, row)
    row.bar:SetPoint("TOPLEFT", row.icon, "TOPRIGHT", 1, 0); row.bar:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -1, 0)
    row.bar:SetStatusBarTexture("Interface\\Buttons\\WHITE8x8") 

    local c = (claseFondo and RAID_CLASS_COLORS[claseFondo]) or {r=0.5, g=0.5, b=0.5}
    row.bar:SetStatusBarColor(c.r, c.g, c.b)
    row.bar.bg = row.bar:CreateTexture(nil, "BACKGROUND"); row.bar.bg:SetAllPoints(); row.bar.bg:SetColorTexture(c.r * 0.3, c.g * 0.3, c.b * 0.3, 0.8)

    row.text = row.bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); row.text:SetPoint("LEFT", row.bar, "LEFT", 5, 0); row.text:SetText(senderName)
    row.timerText = row.bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); row.timerText:SetPoint("RIGHT", row.bar, "RIGHT", -5, 0)
    UpdateReadyText(row)

    barrasActivas[barKey] = row
    ActualizarTodoEnVivo()
    return row
end

local function IniciarBarraCorte(row, remaining)
    if not row or type(remaining) ~= "number" then return end
    
    local senderName = string.match(row.barKey, "^(.-)_%d+$")
    local info = spellDB[row.spellID]
    if info then row.cdTotal = ObtenerCD(info, senderName) end
    
    local maxCd = row.cdTotal
    local startTime = GetTime() - (maxCd - remaining)
    row.cdExpiration = GetTime() + remaining
    
    if row.lastInterruptTime and (GetTime() - row.lastInterruptTime) < 1.0 then
        row.kickSuccess = true
        row.timerText:SetTextColor(0.1, 1, 0.1)
    else
        row.kickSuccess = false
        row.kickCastTime = GetTime()
        row.timerText:SetTextColor(1, 1, 1) 
        
        C_Timer.After(0.5, function()
            if not row.kickSuccess and (GetTime() - row.kickCastTime) < 0.6 then
                row.timerText:SetTextColor(1, 0.2, 0.2) 
            end
        end)
    end
    
    row.bar:SetMinMaxValues(0, maxCd)
    row.bar:SetValue(remaining)
    row.timerText:SetText(string.format("%.1f", remaining))
    row.bar:SetScript("OnUpdate", function(self)
        local elapsed = GetTime() - startTime
        local rem = maxCd - elapsed
        if rem <= 0 then
            row.cdExpiration = 0
            self:SetScript("OnUpdate", nil); self:SetValue(maxCd); 
            UpdateReadyText(row)
            row.timerText:SetTextColor(1, 1, 1) 
            ActualizarTodoEnVivo() 
            EvaluarProximoCorte() 
        else
            self:SetValue(rem); row.timerText:SetText(string.format("%.1f", rem))
        end
    end)
    
    ActualizarTodoEnVivo() 
    C_Timer.After(0.1, EvaluarProximoCorte) 
end

local function ObtenerOCrearIcono(unit, spellID)
    if not unit or not iconosActivos[unit] then return nil end
    for _, iconFrame in ipairs(iconosActivos[unit]) do
        if iconFrame.spellID == spellID then return iconFrame end
    end

    local info = spellDB[spellID]
    local name = UnitName(unit) or ""
    local cdBase = ObtenerCD(info, name)

    local frame = CreateFrame("Frame", nil, UIParent)
    frame.spellID = spellID; frame.cdTotal = math.max(0.1, cdBase)
    frame.cdExpiration = 0 
    
    frame.bg = frame:CreateTexture(nil, "BACKGROUND"); frame.bg:SetAllPoints(); frame.bg:SetColorTexture(0, 0, 0, 1)
    frame.icon = frame:CreateTexture(nil, "ARTWORK"); 
    frame.icon:SetPoint("TOPLEFT", 1, -1); 
    frame.icon:SetPoint("BOTTOMRIGHT", -1, 1)
    frame.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    
    local spellInfo = C_Spell.GetSpellInfo(spellID)
    if spellInfo then frame.icon:SetTexture(spellInfo.iconID) end

    frame.cooldown = CreateFrame("Cooldown", nil, frame, "CooldownFrameTemplate")
    frame.cooldown:SetAllPoints(); frame.cooldown:SetDrawEdge(false); frame.cooldown:SetHideCountdownNumbers(false)

    frame.chargesText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    frame.chargesText:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -1, 1)
    frame.chargesText:SetText("")

    if info and info.cat == "CCs" then
        frame.playerName = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        frame.playerName:SetPoint("BOTTOM", frame, "BOTTOM", 0, 2)
    end

    frame:SetScript("OnUpdate", function(self)
        if self.cdExpiration and self.cdExpiration > 0 then
            if GetTime() >= self.cdExpiration then
                self.cdExpiration = 0
                local isLocal = (unit == "player" and not modoTestActivo)
                ActualizarVisualCargas(self, isLocal)
            end
        end
    end)

    table.insert(iconosActivos[unit], frame)
    ActualizarTodoEnVivo()
    return frame
end

local function LimpiarIconos(unit)
    if iconosActivos[unit] then
        for _, frame in ipairs(iconosActivos[unit]) do frame:Hide() end
    end
end

local function AutoPoblarUnidad(unit)
    local name = UnitName(unit); local _, classFile = UnitClass(unit)
    if not classFile or not name then LimpiarIconos(unit); return end
    
    LimpiarIconos(unit)
    
    for sIDRaw, info in pairs(spellDB) do
        local sID = ObtenerIDReal(sIDRaw)
        if sIDRaw == sID and info.clase == classFile then 
            if info.cat == "Cortes" then
                ObtenerOCrearBarra(name, sID, classFile)
            else
                ObtenerOCrearIcono(unit, sID) 
            end
        end
    end
    ActualizarTodoEnVivo()
end

local function EscanearGrupo()
    if not db then return end
    for i = 1, 4 do 
        if UnitExists("party" .. i) then AutoPoblarUnidad("party" .. i) else LimpiarIconos("party" .. i) end
    end
    AutoPoblarUnidad("player")
    EvaluarProximoCorte() 
end

local testTicker = nil
addonTable.IniciarModoTest = function()
    modoTestActivo = not modoTestActivo
    if modoTestActivo then
        ultimoCorteAnunciado = nil 
        anchorCortes:Show(); anchorCCs:Show()
        
        if db.anchorMode == "Manual" then
            for _, unit in ipairs(unidadesGrupo) do anclasGrupo[unit]:Show() end
        end

        local myName = UnitName("player") or "Player"
        local _, myClass = UnitClass("player")
        myClass = myClass or "WARRIOR"
        local myCorte = cortesBasePorClase[myClass] or 6552
        
        NOMBRES_TEST = {
            {name = myName,     class = myClass,       spellID = myCorte, unit = "player"},
            {name = "Tankerino",class = "PALADIN",     spellID = 96231,   unit = "party1"},
            {name = "Healerino",class = "DRUID",       spellID = nil,     unit = "party2"},
            {name = "MeleeGod", class = "DEATHKNIGHT", spellID = 47528,   unit = "party3"},
            {name = "RangoMax", class = "MAGE",        spellID = 2139,    unit = "party4"}
        }
        
        for i, mock in ipairs(NOMBRES_TEST) do
            if mock.spellID then
                local row = ObtenerOCrearBarra(mock.name, mock.spellID, mock.class)
                if row then
                    IniciarBarraCorte(row, i * 4) 
                end
            end
            
            for sID, info in pairs(spellDB) do
                if info.cat ~= "Cortes" and info.clase == mock.class then 
                    local ic = ObtenerOCrearIcono(mock.unit, sID) 
                    if ic and ic.cooldown then
                        local cdInfo = ObtenerCD(info, mock.name)
                        ic.cdExpiration = GetTime() + (cdInfo * 0.5) 
                        ic.cooldown:Show()
                        ic.cooldown:SetCooldown(GetTime() - (cdInfo * 0.5), cdInfo)
                    end
                end
            end
        end
        ActualizarTodoEnVivo()
        
        testTicker = C_Timer.NewTicker(4, function()
            if not modoTestActivo then return end
            for _, mock in ipairs(NOMBRES_TEST) do
                if mock.spellID then
                    local barKey = mock.name .. "_" .. mock.spellID
                    local r = barrasActivas[barKey]
                    if r and (r.cdExpiration == 0 or r.cdExpiration <= GetTime()) then
                        IniciarBarraCorte(r, ObtenerCD(spellDB[mock.spellID], mock.name))
                    end
                end
                
                if iconosActivos[mock.unit] then
                    for _, ic in ipairs(iconosActivos[mock.unit]) do
                        local sInfo = spellDB[ic.spellID]
                        if sInfo and sInfo.cat ~= "CCs" then
                            if ic:IsVisible() and not ic.cooldown:IsShown() then
                                local cdInfo = ObtenerCD(sInfo, mock.name)
                                ic.cdExpiration = GetTime() + cdInfo
                                ic.cooldown:Show()
                                ic.cooldown:SetCooldown(GetTime(), cdInfo)
                                ActualizarVisualCargas(ic, false)
                            end
                        end
                    end
                end
            end
        end)
        
        if addonTable.btnTestGlobal then addonTable.btnTestGlobal:SetText(L.CLOSE_TEST) end
        EvaluarProximoCorte() 
    else
        anchorCortes:Hide(); anchorCCs:Hide()
        for _, unit in ipairs(unidadesGrupo) do anclasGrupo[unit]:Hide() end
        if testTicker then testTicker:Cancel(); testTicker = nil end
        
        for _, mock in ipairs(NOMBRES_TEST) do
            if mock.spellID then
                local barKey = mock.name .. "_" .. mock.spellID
                local r = barrasActivas[barKey]
                if r then r:Hide(); r.cdExpiration = 0; r.bar:SetScript("OnUpdate", nil) end
            end
            LimpiarIconos(mock.unit)
        end
        
        if addonTable.btnTestGlobal then addonTable.btnTestGlobal:SetText(L.TEST) end
        EscanearGrupo()
        ActualizarTodoEnVivo()
    end
end

fCore:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" and (...) == addonName then
        
        local charName = UnitName("player")
        if not charName then return end
        
        PremadeCDsDB = PremadeCDsDB or {}
        PremadeCDsDB.profiles = PremadeCDsDB.profiles or {}
        PremadeCDsDB.charToProfile = PremadeCDsDB.charToProfile or {}
        
        if PremadeCDsDB.width and not PremadeCDsDB.profiles["Default"] then
            PremadeCDsDB.profiles["Default"] = {}
            for k, v in pairs(PremadeCDsDB) do
                if k ~= "profiles" and k ~= "charToProfile" then PremadeCDsDB.profiles["Default"][k] = v end
            end
        end
        if not PremadeCDsDB.profiles["Default"] then PremadeCDsDB.profiles["Default"] = { profileName = "Default" } end
        if not PremadeCDsDB.charToProfile[charName] then PremadeCDsDB.charToProfile[charName] = "Default" end
        
        local currentProfile = PremadeCDsDB.charToProfile[charName]
        if not PremadeCDsDB.profiles[currentProfile] then PremadeCDsDB.profiles[currentProfile] = { profileName = currentProfile } end

        if addonTable.GetDict then
            local currentLanguage = PremadeCDsDB.profiles[currentProfile] and PremadeCDsDB.profiles[currentProfile].language or nil
            addonTable.L = addonTable.GetDict(currentLanguage)
            L = addonTable.L
        end
        
        if addonTable.ConstruirUI then addonTable.ConstruirUI() end
        if addonTable.AplicarPerfil then addonTable.AplicarPerfil(currentProfile) end

    elseif event == "PLAYER_ENTERING_WORLD" then
        C_Timer.After(1, function()
            EscanearGrupo()
            EnviarMiInfo()
            if IsInGroup() then
                local channel = IsInRaid() and "RAID" or "PARTY"
                C_ChatInfo.SendAddonMessage(prefijoAddon, "REQ_SPEC", channel)
            end
            ActualizarTodoEnVivo()
        end)

    elseif event == "GROUP_ROSTER_UPDATE" or event == "ZONE_CHANGED_NEW_AREA" or event == "PLAYER_SPECIALIZATION_CHANGED" or event == "TRAIT_CONFIG_UPDATED" or event == "UNIT_PET" or event == "SPELLS_CHANGED" then
        C_Timer.After(1, function()
            EscanearGrupo()
            EnviarMiInfo()
            if IsInGroup() then
                local channel = IsInRaid() and "RAID" or "PARTY"
                C_ChatInfo.SendAddonMessage(prefijoAddon, "REQ_SPEC", channel)
            end
            ActualizarTodoEnVivo()
        end)

    elseif event == "CHAT_MSG_ADDON" then
        local pre, msg, _, sender = ...
        if pre == prefijoAddon then
            local sName = LimpiarNombreSeguro(sender)
            if msg == "REQ_SPEC" then
                EnviarMiInfo()
            elseif string.find(msg, "SPEC#") then
                local _, sIDStr = strsplit("#", msg)
                local sID = tonumber(sIDStr)
                if sID then specsDetectadas[sName] = sID; ActualizarTodoEnVivo() end
            elseif string.find(msg, "TALENTS#") then
                local idsStr = string.sub(msg, 9)
                talentosDetectados[sName] = {}
                if idsStr ~= "NONE" then
                    for idStr in string.gmatch(idsStr, "([^,]+)") do
                        local id = tonumber(idStr)
                        if id then talentosDetectados[sName][id] = true end
                    end
                end
                ActualizarTodoEnVivo()
            else
                local sIDStr, remStr, cat, class, specStr, curChStr, maxChStr = strsplit("#", msg)
                local idRaw = tonumber(sIDStr)
                local id = idRaw and ObtenerIDReal(idRaw) or nil 
                local rem = tonumber(remStr)
                local specID = tonumber(specStr)
                
                if specID and specID > 0 and specsDetectadas[sName] ~= specID then
                    specsDetectadas[sName] = specID
                    ActualizarTodoEnVivo()
                end

                if id and rem and spellDB[id] then
                    local info = spellDB[id]
                    if cat == "Cortes" then
                        local r = ObtenerOCrearBarra(sName, id, class)
                        if r then IniciarBarraCorte(r, rem) end
                    else
                        local u = ObtenerUnidadPorNombre(sName)
                        if u then
                            local ic = ObtenerOCrearIcono(u, id)
                            if ic then
                                local cdInfo = ObtenerCD(info, sName)
                                local fakeStart = GetTime() - (cdInfo - rem)
                                ic.cooldown:Show()
                                ic.cooldown:SetCooldown(fakeStart, math.max(0.1, cdInfo))
                                ic.cdExpiration = GetTime() + rem 
                                ActualizarVisualCargas(ic, false)
                            end
                        end
                    end
                end
            end
        end

    elseif event == "SPELL_UPDATE_COOLDOWN" or event == "SPELL_UPDATE_CHARGES" then
        if iconosActivos["player"] then
            for _, ic in ipairs(iconosActivos["player"]) do
                local idAprendido = ObtenerIDAprendido(ic.spellID) or ic.spellID
                local startReal, durReal, isValid = ObtenerCooldownSeguro(idAprendido)
                
                if isValid and durReal > 1.5 then
                    if ic.lastStart ~= startReal or ic.lastDur ~= durReal then
                        ic.cooldown:SetCooldown(startReal, durReal)
                        ic.lastStart = startReal; ic.lastDur = durReal
                    end
                    ic.cdExpiration = startReal + durReal
                else
                    if not (ic.cdExpiration and ic.cdExpiration > GetTime()) then
                        ic.cdExpiration = 0
                        ic.lastStart = 0; ic.lastDur = 0
                        ic.cooldown:Clear()
                    end
                end
                
                ActualizarVisualCargas(ic, true)
            end
        end

    -- AÑADIDO: El chequeo de "player" y "pet" para que lance el CD cuando muerde el perro
    elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
        local unit, _, sIDRaw = ...
        if (unit == "player" or unit == "pet") then
            local sID = sIDRaw and ObtenerIDReal(sIDRaw) or nil
            if sID and spellDB[sID] then
                local info = spellDB[sID]; local _, class = UnitClass("player")
                class = class or "UNKNOWN"
                
                local startReal, durReal, isValid = ObtenerCooldownSeguro(sIDRaw)
                if not isValid or durReal <= 2 then
                    startReal, durReal, isValid = ObtenerCooldownSeguro(sID)
                end
                
                local cdInfo = ObtenerCD(info, UnitName("player"))
                if not isValid or durReal <= 2 then
                    durReal = cdInfo
                    startReal = GetTime()
                end
                
                if IsInGroup() then
                    C_Timer.After(0.25, function()
                        local specIndex = GetSpecialization()
                        local mySpec = specIndex and GetSpecializationInfo(specIndex) or 0
                        local channel = IsInRaid() and "RAID" or "PARTY"
                        C_ChatInfo.SendAddonMessage(prefijoAddon, tostring(sID).."#"..tostring(durReal).."#"..tostring(info.cat).."#"..tostring(class).."#"..tostring(mySpec).."#0#0", channel)
                    end)
                end
                
                if info.cat == "Cortes" then
                    local r = ObtenerOCrearBarra(UnitName("player"), sID, class)
                    if r then IniciarBarraCorte(r, durReal) end
                else
                    local ic = ObtenerOCrearIcono("player", sID)
                    if ic then
                        ic.cooldown:Show()
                        ic.cooldown:SetCooldown(startReal, math.max(0.1, durReal))
                        ic.cdExpiration = startReal + durReal 
                        ic.lastStart = startReal
                        ic.lastDur = durReal
                        ActualizarVisualCargas(ic, true)
                    end
                end
            end
        end
    end
end)

local fSync = CreateFrame("Frame")
local pulseTimer = 0
fSync:SetScript("OnUpdate", function(self, elapsed)
    pulseTimer = pulseTimer + elapsed
    if pulseTimer > 1.5 then 
        pulseTimer = 0
        local enGrupo = IsInGroup()
        local _, miClase = UnitClass("player")
        
        for sIDRaw, info in pairs(spellDB) do
            if info.clase == miClase then
                local sID = ObtenerIDReal(sIDRaw)
                local idAprendido = ObtenerIDAprendido(sID)
                
                if idAprendido then
                    local startReal, durReal, isValid = ObtenerCooldownSeguro(idAprendido)
                    if isValid and durReal > 2 then
                        local rem = durReal - (GetTime() - startReal)
                        if rem > 0 then
                            local data = ultimoSyncEnviado[sID]
                            local expectedRem = data and (data.rem - (GetTime() - data.t)) or 0
                            if not data or math.abs(expectedRem - rem) > 1.0 then
                                ultimoSyncEnviado[sID] = { rem = rem, t = GetTime() }
                                
                                if enGrupo then
                                    local _, class = UnitClass("player")
                                    class = class or "UNKNOWN"
                                    local specIndex = GetSpecialization()
                                    local mySpec = specIndex and GetSpecializationInfo(specIndex) or 0
                                    local channel = IsInRaid() and "RAID" or "PARTY"
                                    C_ChatInfo.SendAddonMessage(prefijoAddon, tostring(sID).."#"..tostring(rem).."#"..tostring(info.cat).."#"..tostring(class).."#"..tostring(mySpec).."#0#0", channel)
                                end
                            end
                        end
                    end
                end
            end
        end
    end
end)

SLASH_PREMADECDS1 = "/premade"
SLASH_PREMADECDS2 = "/interrupts"
SlashCmdList["PREMADECDS"] = function()
    if addonTable.ToggleMenu then addonTable.ToggleMenu() end
end