De Muysc cubun - Lengua Muisca

m
m
 
(No se muestran 4 ediciones intermedias del mismo usuario)
Línea 1: Línea 1:
 
-- ============================================================
 
-- ============================================================
 
-- Diego F. Gómez -- 2024-2026
 
-- Diego F. Gómez -- 2024-2026
-- Módulo de conjugación para verbos muysca en mediawiki
+
-- Módulo de conjugación para verbos muysca en lua para mediawiki
 
-- ============================================================
 
-- ============================================================
  
Línea 20: Línea 20:
 
     series = {
 
     series = {
 
         s1 = {
 
         s1 = {
             title = "Serie de Flexión Verbal (Independiente)",
+
             title = "Serie de Flexión Verbal",
 
             headers = {"Perfectivo", "Imperfectivo", "Irrealis", "Destinativo"}
 
             headers = {"Perfectivo", "Imperfectivo", "Irrealis", "Destinativo"}
 
         },
 
         },
Línea 121: Línea 121:
 
             },
 
             },
 
             applyRule = true,
 
             applyRule = true,
             allowInfix = true
+
             allowInfix = true,
 +
            hasNegation = true -- Habilita filas negativas
 
         },
 
         },
 
         {
 
         {
Línea 129: Línea 130:
 
             persons = {{TEXTS.persons.sg1, "cha-"}, {TEXTS.persons.sg2, "ma-"}, {TEXTS.persons.sg3, ""}, {TEXTS.persons.pl1, "chi-"}, {TEXTS.persons.pl2, "mi-"}},
 
             persons = {{TEXTS.persons.sg1, "cha-"}, {TEXTS.persons.sg2, "ma-"}, {TEXTS.persons.sg3, ""}, {TEXTS.persons.pl1, "chi-"}, {TEXTS.persons.pl2, "mi-"}},
 
             suffixes = {
 
             suffixes = {
                -- Resultativa: -ua para .su, -ia para .sq
 
 
                 perf = (classInfo.type == "su." and "-ua" or "-ia"),  
 
                 perf = (classInfo.type == "su." and "-ua" or "-ia"),  
                -- Factual: -suca para .su, -sca para .sq
 
 
                 imperf = (classInfo.type == "su." and "-suca" or "-sca"),
 
                 imperf = (classInfo.type == "su." and "-suca" or "-sca"),
                -- Irrealis: -nynga para .su, -nga para .sq
 
 
                 irreal = (classInfo.type == "su." and "-nynga" or "-nga"),
 
                 irreal = (classInfo.type == "su." and "-nynga" or "-nga"),
                -- Prospectivo: -nynguepqua para .su, -nguepqua para .sq
 
 
                 prospectivo = (classInfo.type == "su." and "-nynguepqua" or "-nguepqua")
 
                 prospectivo = (classInfo.type == "su." and "-nynguepqua" or "-nguepqua")
 
             },
 
             },
Línea 163: Línea 160:
 
             local label, prefix, personalSuffix = pData[1], pData[2], pData[3]
 
             local label, prefix, personalSuffix = pData[1], pData[2], pData[3]
 
              
 
              
             -- 1. Regla Especial de Prefijo (z- -> i-)
+
            ------------------------------------------------------------
 +
             -- 1. LÓGICA DE PREFIJOS (Sujeto y Desambiguación)
 +
            ------------------------------------------------------------
 +
 
 +
            -- 1.1 Regla Especial de Prefijo (z- -> i-)
 +
            -- Solo para Serie 1, solo si la raíz comienza con n, s, z, t o ch,
 +
            -- y sólo para verbos intransitivos, pues los transitivos poseen el infijo -b- , -g-, etc. que nunca será coronal.
 
             if s.applyRule and label == TEXTS.persons.sg1 then
 
             if s.applyRule and label == TEXTS.persons.sg1 then
                 if firstChar == "n" or firstChar == "z" or firstChar == "t" or firstTwo == "ch" then
+
                 if (firstChar == "n" or firstChar == "s" or firstChar == "z" or firstChar == "t" or firstTwo == "ch") and classInfo.isIntransitive then
 
                     prefix = "i-"
 
                     prefix = "i-"
 
                 end
 
                 end
 
             end
 
             end
  
             -- 2. LÓGICA DEL INFIJO -b- Y CAMBIOS EN LA RAÍZ
+
            -- 1.2 LÓGICA DE LA A- INTRANSITIVA (Desambiguación)
 +
            local intransitivePrefix = ""
 +
            -- Si es intransitivo y estamos en Serie 2 (3.ª persona) o Serie 3 (Imperativos)
 +
            -- Se añade 'a-' para marcar la intransitividad/neutralidad
 +
            if classInfo.isIntransitive then
 +
                if (s.id == "S2" and label == TEXTS.persons.sg3) or s.id == "S3" then
 +
                    intransitivePrefix = "a-"
 +
                end
 +
            end
 +
 
 +
            ------------------------------------------------------------
 +
             -- 2. LÓGICA DE RAÍZ E INFIJOS
 +
            ------------------------------------------------------------
 +
 
 
             local currentInfix = ""
 
             local currentInfix = ""
 
             local currentRoot = root  
 
             local currentRoot = root  
 
              
 
              
 +
            -- 2.1 LÓGICA DEL INFIJO -b- Y CAMBIOS EN LA RAÍZ
 +
            -- Solo se aplica infijo si es transitivo Y NO es intransitivo (neutraliza casos como aguquy)
 
             if s.allowInfix and classInfo.isTransitive and not classInfo.isIntransitive then
 
             if s.allowInfix and classInfo.isTransitive and not classInfo.isIntransitive then
 
                 if prefix ~= "" then
 
                 if prefix ~= "" then
Línea 181: Línea 199:
 
                     elseif r1 == "n" then
 
                     elseif r1 == "n" then
 
                         currentInfix = "m-"
 
                         currentInfix = "m-"
                    -- Ley de la H corregida: Solo aplica a clase su.
 
 
                     elseif r1 == "h" and classInfo.type == "su." then
 
                     elseif r1 == "h" and classInfo.type == "su." then
 
                         local vocal = root:sub(2,2)
 
                         local vocal = root:sub(2,2)
Línea 195: Línea 212:
 
             end
 
             end
  
          -- 3. LÓGICA DE SUFIJOS
+
            ------------------------------------------------------------
 +
            -- 3. LÓGICA DE SUFIJOS Y REGLAS ESPECIALES
 +
            ------------------------------------------------------------
 +
 
 +
            -- 3.1 Inicialización de sufijos base
 
             local perfSuffix = personalSuffix or (s.suffixes and s.suffixes.perf) or ""
 
             local perfSuffix = personalSuffix or (s.suffixes and s.suffixes.perf) or ""
 
             local imperfSuffix = (s.suffixes and s.suffixes.imperf) or ""
 
             local imperfSuffix = (s.suffixes and s.suffixes.imperf) or ""
Línea 202: Línea 223:
 
             local destinativoSuffix = (s.suffixes and s.suffixes.destinativo) or ""
 
             local destinativoSuffix = (s.suffixes and s.suffixes.destinativo) or ""
 
              
 
              
 +
            -- 3.2 Inicialización de raíces por tiempo
 
             local rootPerf = currentRoot
 
             local rootPerf = currentRoot
 
             local rootImperf = currentRoot
 
             local rootImperf = currentRoot
Línea 208: Línea 230:
 
             local rootDestinativo = currentRoot
 
             local rootDestinativo = currentRoot
  
             -- 4. LÓGICA DE RAÍCES EN -I (Serie 2)
+
             -- 3.3 REGLA ESPECÍFICA PARA CLASE "su." (ej. nypquasuca > nypqua-o)
 +
            -- En la Serie 1, el perfectivo añade una -o si la raíz termina en -a
 +
            if classInfo.type == "su." and s.id == "S1" then
 +
                if root:sub(-1):lower() == "a" then
 +
                    perfSuffix = "-o"
 +
                end
 +
            end
 +
 
 +
            -- 3.4 LÓGICA DE RAÍCES EN -I (Serie 2)
 
             if classInfo.hasRootI and s.id == "S2" then
 
             if classInfo.hasRootI and s.id == "S2" then
 
                 rootPerf = currentRoot .. "i"
 
                 rootPerf = currentRoot .. "i"
Línea 216: Línea 246:
 
             end
 
             end
  
             -- 5. LÓGICA DE VERBOS CON RAÍZ HISTÓRICA EN IQUY (z/i)
+
             -- 3.5 LÓGICA DE VERBOS CON RAÍZ HISTÓRICA EN IQUY (z/i)
 
             if classInfo.isQuy then
 
             if classInfo.isQuy then
 
                 if s.id == "S1" then
 
                 if s.id == "S1" then
Línea 230: Línea 260:
 
             end
 
             end
  
             -- 6. LÓGICA DE VERBOS CON RAÍZ HISTÓRICA EN -QUY SIMPLE (ej: ua-squa)
+
             -- 3.6 LÓGICA DE VERBOS CON RAÍZ HISTÓRICA EN -QUY SIMPLE (ej: ua-squa)
 
             if classInfo.isQuySimple then
 
             if classInfo.isQuySimple then
 
                 if s.id == "S1" then
 
                 if s.id == "S1" then
Línea 242: Línea 272:
 
             end
 
             end
 
              
 
              
            -- EXPLICACIÓN IQUY: El prospectivo siempre hereda la raíz del irrealis
 
            -- (Si el irrealis cambió a 'i', el prospectivo también debe hacerlo)
 
 
             rootProspectivo = rootIrreal
 
             rootProspectivo = rootIrreal
  
             -- 7. CONSTRUCCIÓN FINAL DE LA FILA (Sin duplicados)
+
             ------------------------------------------------------------
 +
            -- 4. ENSAMBLAJE FINAL Y NEGACIÓN
 +
            ------------------------------------------------------------
 +
 
 +
            -- 4.1 CONSTRUCCIÓN DE FILA AFIRMATIVA
 
             table.insert(serieData.rows, {
 
             table.insert(serieData.rows, {
 
                 label = label,
 
                 label = label,
                 perf = prefix .. currentInfix .. rootPerf .. perfSuffix,
+
                status = "pos", -- Marca para estilo visual
                 imperf = prefix .. currentInfix .. rootImperf .. imperfSuffix,
+
                 perf = prefix .. currentInfix .. intransitivePrefix .. rootPerf .. perfSuffix,
                 irreal = prefix .. currentInfix .. rootIrreal .. irrealSuffix,
+
                 imperf = prefix .. currentInfix .. intransitivePrefix .. rootImperf .. imperfSuffix,
                 prospectivo = prefix .. currentInfix .. rootProspectivo .. prospectivoSuffix,
+
                 irreal = prefix .. currentInfix .. intransitivePrefix .. rootIrreal .. irrealSuffix,
                 destinativo = prefix .. currentInfix .. rootDestinativo .. destinativoSuffix
+
                 prospectivo = prefix .. currentInfix .. intransitivePrefix .. rootProspectivo .. prospectivoSuffix,
 +
                 destinativo = prefix .. currentInfix .. intransitivePrefix .. rootDestinativo .. destinativoSuffix
 
             })
 
             })
 +
 +
            -- 4.2 LÓGICA DE NEGACIÓN (Solo para Serie 1)
 +
            if s.hasNegation then
 +
                -- REGLA DE RAÍZ PURA: La negación ignora extensiones históricas en S1
 +
                local negRoot = currentRoot
 +
               
 +
                table.insert(serieData.rows, {
 +
                    label = "", -- Se puede dejar vacío para usar rowspan o marcar con (-)
 +
                    status = "neg",
 +
                    perf = prefix .. currentInfix .. negRoot .. "-za",
 +
                    imperf = prefix .. currentInfix .. negRoot .. imperfSuffix .. "-za",
 +
                    irreal = prefix .. currentInfix .. negRoot .. "-zi-nga",
 +
                    destinativo = prefix .. currentInfix .. negRoot .. "-za-n iua"
 +
                })
 +
            end
 
         end
 
         end
 
         table.insert(results.series, serieData)
 
         table.insert(results.series, serieData)
Línea 270: Línea 318:
  
 
     local tableHtml = wrapper:tag("table"):addClass("wikitable")
 
     local tableHtml = wrapper:tag("table"):addClass("wikitable")
         :css("width", "100%"):css("max-width", "600px"):css("margin", "0")
+
         :css("width", "100%"):css("max-width", "700px"):css("margin", "0")
  
 
     local headerRow = tableHtml:tag("tr")
 
     local headerRow = tableHtml:tag("tr")
Línea 283: Línea 331:
 
     end
 
     end
  
     for _, row in ipairs(serie.rows) do
+
     for i, row in ipairs(serie.rows) do
 
         local tr = tableHtml:tag("tr")
 
         local tr = tableHtml:tag("tr")
         tr:tag("td"):css("font-weight", "bold"):wikitext(row.label)
+
          
 +
        -- Estética para negación
 +
        if row.status == "neg" then
 +
            tr:css("background-color", "#f8f9fa"):css("color", "#54595d")
 +
            local tdLabel = tr:tag("td"):css("text-align", "right"):css("font-size", "0.9em")
 +
                :css("font-style", "italic"):wikitext("neg.")
 +
        else
 +
            tr:tag("td"):css("font-weight", "bold"):wikitext(row.label)
 +
        end
 +
 
 
         tr:tag("td"):wikitext(row.perf)
 
         tr:tag("td"):wikitext(row.perf)
 
          
 
          
        -- Si es la Serie 1, dibuja sus 4 columnas
 
 
         if serie.id == "S1" then
 
         if serie.id == "S1" then
 
             tr:tag("td"):wikitext(row.imperf)
 
             tr:tag("td"):wikitext(row.imperf)
 
             tr:tag("td"):wikitext(row.irreal)
 
             tr:tag("td"):wikitext(row.irreal)
 
             tr:tag("td"):wikitext(row.destinativo)
 
             tr:tag("td"):wikitext(row.destinativo)
           
 
        -- Si es la Serie 2, dibuja sus 4 columnas
 
 
         elseif serie.id == "S2" then
 
         elseif serie.id == "S2" then
 
             tr:tag("td"):wikitext(row.imperf)
 
             tr:tag("td"):wikitext(row.imperf)
Línea 307: Línea 361:
 
     local args = frame:getParent().args
 
     local args = frame:getParent().args
 
     local pageName = mw.title.getCurrentTitle().text
 
     local pageName = mw.title.getCurrentTitle().text
   
 
    -- Prioridad de captura de clase/categoría
 
 
     local claseInput = args.clase or args.tipo or args.cat or args[1] or ""
 
     local claseInput = args.clase or args.tipo or args.cat or args[1] or ""
   
 
 
     local data = p.getConjugations(pageName, claseInput, args.raiz)
 
     local data = p.getConjugations(pageName, claseInput, args.raiz)
  
Línea 317: Línea 368:
 
     end
 
     end
  
     local mainDiv = mw.html.create("div"):addClass("mw-collapsible"):addClass("mw-collapsed")
+
    -- Contenedor de la línea del botón (alineado a la derecha)
         :css("border", "1px solid #a2a9b1"):css("padding", "15px"):css("background-color", "#fcfcfc")
+
     local buttonWrapper = mw.html.create("div")
         :attr("data-expandtext", TEXTS.ui.expand):attr("data-collapsetext", TEXTS.ui.collapse)
+
         :css("display", "flex")
 +
        :css("justify-content", "flex-end")
 +
         :css("margin-bottom", "10px")
  
     mainDiv:tag("div"):css("font-weight", "bold"):css("border-bottom", "1px solid #a2a9b1")
+
     -- El Botón Azul (mw-customtoggle)
         :css("margin-bottom", "15px"):css("padding-bottom", "5px")
+
    buttonWrapper:tag("div")
 +
        :addClass("mw-customtoggle-muyscaParadigma") -- ID vinculado al contenido
 +
        :addClass("mw-ui-button mw-ui-progressive")  -- Estilo azul de MediaWiki
 +
        :css("border-radius", "20px")                -- Bordes bien redondeados
 +
        :css("padding", "4px 15px")
 +
        :css("font-weight", "bold")
 +
        :css("cursor", "pointer")
 +
        :wikitext(TEXTS.ui.expand)
 +
 
 +
    -- Contenedor del Paradigma (el que se oculta/muestra)
 +
    local contentDiv = mw.html.create("div")
 +
        :addClass("mw-collapsible mw-collapsed")
 +
        :attr("id", "mw-customcollapsible-muyscaParadigma") -- ID para el toggle
 +
        :css("border", "1px solid #c8ccd1")
 +
        :css("border-radius", "8px")
 +
        :css("padding", "20px")
 +
        :css("background-color", "#fcfcfc")
 +
        :css("margin-top", "10px")
 +
 
 +
    -- El Título (ahora dentro del contenido desplegable)
 +
    contentDiv:tag("div")
 +
        :css("font-weight", "bold")
 +
        :css("border-bottom", "2px solid #3366cc")
 +
         :css("margin-bottom", "20px")
 +
        :css("padding-bottom", "5px")
 +
        :css("font-size", "1.2em")
 
         :wikitext(TEXTS.ui.main_title .. data.root .. " (" .. data.classInfo.raw .. ")")
 
         :wikitext(TEXTS.ui.main_title .. data.root .. " (" .. data.classInfo.raw .. ")")
  
     local contentDiv = mainDiv:tag("div"):addClass("mw-collapsible-content")
+
     -- Dibujar las tablas de las series
 
     for _, serie in ipairs(data.series) do
 
     for _, serie in ipairs(data.series) do
 
         contentDiv:node(drawTable(serie))
 
         contentDiv:node(drawTable(serie))
 
     end
 
     end
     return tostring(mainDiv)
+
 
 +
     return tostring(buttonWrapper) .. tostring(contentDiv)
 
end
 
end
  
 
return p
 
return p

Revisión actual - 09:10 1 abr 2026

La documentación para este módulo puede ser creada en Módulo:Conjugador/doc

-- ============================================================
-- Diego F. Gómez -- 2024-2026
-- Módulo de conjugación para verbos muysca en lua para mediawiki
-- ============================================================

local p = {}

-- ============================================================
-- DICCIONARIO DE TEXTOS 
-- ============================

local TEXTS = {
    ui = {
        expand = "Conjugar",
        collapse = "Ocultar",
        error_root = "Error: No se pudo extraer la raíz de ",
        main_title = "Paradigma verbal: ",
        col_person = "Persona"
    },
    series = {
        s1 = {
            title = "Serie de Flexión Verbal",
            headers = {"Perfectivo", "Imperfectivo", "Irrealis", "Destinativo"}
        },
        s2 = {
            title = "Serie de Nominalización",
            headers = {"Resultativa", "Factual", "Irrealis", "Prospectiva"}
        },
        s3 = {
            title = "Modo Imperativo",
            headers = {"Forma Única"}
        }
    },
    persons = {
        sg1 = "1.ª sg.", sg2 = "2.ª sg.", sg3 = "3.ª",
        pl1 = "1.ª pl.", pl2 = "2.ª pl.",
        imp_sg = "2.ª sg. (Imp.)", 
        imp_pl = "2.ª pl. (Imp.)"
    }
}

-- ============================================================
-- LÓGICA PRIVADA
-- ============================================================

local function cleanPageName(pageName)
    if not mw then return pageName:gsub("%s*%b()", ""):gsub("%d+", ""):gsub("^%s*(.-)%s*$", "%1") end
    pageName = mw.text.trim(pageName or "")
    pageName = mw.ustring.gsub(pageName, "%s*%b()", "")
    pageName = mw.ustring.gsub(pageName, "%d+", "")
    return mw.text.trim(pageName)
end

local function getRoot(pageName, clase, manualRoot)
    if manualRoot and manualRoot ~= "" then return manualRoot end
    local fullVerb = cleanPageName(pageName)
    local root = fullVerb
    if clase == "sq." then root = fullVerb:gsub("squa$", "")
    elseif clase == "su." then root = fullVerb:gsub("suca$", "") end
    if root == fullVerb then root = fullVerb:gsub("s[uq]ua$", "") end
    return root
end

-- ============================================================
-- NUEVA LÓGICA DE CLASE (Parsing)
-- ============================================================

local function parseVerbClass(raw)
    raw = (raw or ""):lower()
    local info = {
        type = "",             -- "sq." o "su."
        isTransitive = false,
        isIntransitive = false,
        isQuy = false,         -- Para iquy.
        isQuySimple = false,   -- Para quy. (ua-squa)
        hasRootI = false,      -- Para raíces en i. (hui-a)
        raw = raw
    }
    
    if raw:find("sq") then info.type = "sq." end
    if raw:find("su") then info.type = "su." end
    if raw:find("tr%.") then info.isTransitive = true end
    if raw:find("intr%.") then info.isIntransitive = true end
    
    -- Detectamos raíces históricas
    if raw:find("iquy") then 
        info.isQuy = true 
    elseif raw:find("quy%.") then
        info.isQuySimple = true
    end
    
    if raw:find(" i%.") or raw:find(" i ") or raw:sub(-2) == " i" then
        info.hasRootI = true
    end
    
    return info
end

-- ============================================================
-- EL CEREBRO: Lógica Pura
-- ============================================================

function p.getConjugations(pageName, claseRaw, manualRoot)
    local classInfo = parseVerbClass(claseRaw)
    local root = getRoot(pageName, classInfo.type, manualRoot)
    
    local results = { root = root, classInfo = classInfo, series = {} }

    -- Configuración de Series
    local config = {
        {
            id = "S1", -- Flexión Verbal
            title = TEXTS.series.s1.title, 
            headers = TEXTS.series.s1.headers,
            persons = {{TEXTS.persons.sg1, "z-"}, {TEXTS.persons.sg2, "m-"}, {TEXTS.persons.sg3, "a-"}, {TEXTS.persons.pl1, "chi-"}, {TEXTS.persons.pl2, "mi-"}},
            suffixes = {
                perf = "", 
                imperf = (classInfo.type == "su." and "-suca" or "-squa"),
                irreal = (classInfo.type == "su." and "-nynga" or "-nga"),
                destinativo = "-iua"
            },
            applyRule = true,
            allowInfix = true,
            hasNegation = true -- Habilita filas negativas
        },
        {
            id = "S2", -- Nominalización
            title = TEXTS.series.s2.title, 
            headers = TEXTS.series.s2.headers,
            persons = {{TEXTS.persons.sg1, "cha-"}, {TEXTS.persons.sg2, "ma-"}, {TEXTS.persons.sg3, ""}, {TEXTS.persons.pl1, "chi-"}, {TEXTS.persons.pl2, "mi-"}},
            suffixes = {
                perf = (classInfo.type == "su." and "-ua" or "-ia"), 
                imperf = (classInfo.type == "su." and "-suca" or "-sca"),
                irreal = (classInfo.type == "su." and "-nynga" or "-nga"),
                prospectivo = (classInfo.type == "su." and "-nynguepqua" or "-nguepqua")
            },
            applyRule = false,
            allowInfix = false
        },
        {
            id = "S3", -- Imperativos
            title = TEXTS.series.s3.title, 
            headers = TEXTS.series.s3.headers,
            persons = {
                {TEXTS.persons.imp_sg, "", "-u"}, 
                {TEXTS.persons.imp_pl, "", "-ua"}
            },
            applyRule = false,
            allowInfix = false
        }
    }

    local firstChar = root:sub(1,1):lower()
    local firstTwo = root:sub(1,2):lower()

    for _, s in ipairs(config) do
        local serieData = { id = s.id, title = s.title, headers = s.headers, rows = {} }
        
        for _, pData in ipairs(s.persons) do
            local label, prefix, personalSuffix = pData[1], pData[2], pData[3]
            
            ------------------------------------------------------------
            -- 1. LÓGICA DE PREFIJOS (Sujeto y Desambiguación)
            ------------------------------------------------------------

            -- 1.1 Regla Especial de Prefijo (z- -> i-)
            -- Solo para Serie 1, solo si la raíz comienza con n, s, z, t o ch, 
            -- y sólo para verbos intransitivos, pues los transitivos poseen el infijo -b- , -g-, etc. que nunca será coronal.
            if s.applyRule and label == TEXTS.persons.sg1 then
                if (firstChar == "n" or firstChar == "s" or firstChar == "z" or firstChar == "t" or firstTwo == "ch") and classInfo.isIntransitive then
                    prefix = "i-"
                end
            end

            -- 1.2 LÓGICA DE LA A- INTRANSITIVA (Desambiguación)
            local intransitivePrefix = ""
            -- Si es intransitivo y estamos en Serie 2 (3.ª persona) o Serie 3 (Imperativos)
            -- Se añade 'a-' para marcar la intransitividad/neutralidad
            if classInfo.isIntransitive then
                if (s.id == "S2" and label == TEXTS.persons.sg3) or s.id == "S3" then
                    intransitivePrefix = "a-"
                end
            end

            ------------------------------------------------------------
            -- 2. LÓGICA DE RAÍZ E INFIJOS
            ------------------------------------------------------------

            local currentInfix = ""
            local currentRoot = root 
            
            -- 2.1 LÓGICA DEL INFIJO -b- Y CAMBIOS EN LA RAÍZ
            -- Solo se aplica infijo si es transitivo Y NO es intransitivo (neutraliza casos como aguquy)
            if s.allowInfix and classInfo.isTransitive and not classInfo.isIntransitive then
                if prefix ~= "" then
                    local r1 = firstChar
                    if r1 == "u" then
                        currentInfix = "g-"
                    elseif r1 == "n" then
                        currentInfix = "m-"
                    elseif r1 == "h" and classInfo.type == "su." then
                        local vocal = root:sub(2,2)
                        currentInfix = "m" .. vocal .. "-"
                        currentRoot = "h" .. root:sub(3)
                    elseif r1 == "b" then
                        currentInfix = "m-"
                        currentRoot = "m" .. root:sub(2)
                    else
                        currentInfix = (prefix == "m-" and "m-" or "b-")
                    end
                end
            end

            ------------------------------------------------------------
            -- 3. LÓGICA DE SUFIJOS Y REGLAS ESPECIALES
            ------------------------------------------------------------

            -- 3.1 Inicialización de sufijos base
            local perfSuffix = personalSuffix or (s.suffixes and s.suffixes.perf) or ""
            local imperfSuffix = (s.suffixes and s.suffixes.imperf) or ""
            local irrealSuffix = (s.suffixes and s.suffixes.irreal) or ""
            local prospectivoSuffix = (s.suffixes and s.suffixes.prospectivo) or ""
            local destinativoSuffix = (s.suffixes and s.suffixes.destinativo) or ""
            
            -- 3.2 Inicialización de raíces por tiempo
            local rootPerf = currentRoot
            local rootImperf = currentRoot
            local rootIrreal = currentRoot
            local rootProspectivo = currentRoot
            local rootDestinativo = currentRoot

            -- 3.3 REGLA ESPECÍFICA PARA CLASE "su." (ej. nypquasuca > nypqua-o)
            -- En la Serie 1, el perfectivo añade una -o si la raíz termina en -a
            if classInfo.type == "su." and s.id == "S1" then
                if root:sub(-1):lower() == "a" then
                    perfSuffix = "-o"
                end
            end

            -- 3.4 LÓGICA DE RAÍCES EN -I (Serie 2)
            if classInfo.hasRootI and s.id == "S2" then
                rootPerf = currentRoot .. "i"
                rootImperf = currentRoot .. "i"
                rootIrreal = currentRoot .. "i"
                rootProspectivo = currentRoot .. "i"
            end

            -- 3.5 LÓGICA DE VERBOS CON RAÍZ HISTÓRICA EN IQUY (z/i)
            if classInfo.isQuy then
                if s.id == "S1" then
                    perfSuffix = "quy"
                elseif s.id == "S2" then
                    rootPerf = currentRoot .. "z"
                    perfSuffix = "-a"
                    rootImperf = currentRoot .. "i"
                    rootIrreal = currentRoot .. "i"
                elseif s.id == "S3" then
                    rootPerf = currentRoot .. "z"
                end
            end

            -- 3.6 LÓGICA DE VERBOS CON RAÍZ HISTÓRICA EN -QUY SIMPLE (ej: ua-squa)
            if classInfo.isQuySimple then
                if s.id == "S1" then
                    perfSuffix = "quy"
                elseif s.id == "S2" then
                    rootPerf = currentRoot .. "c"
                    perfSuffix = "-a"
                elseif s.id == "S3" then
                    rootPerf = currentRoot .. "c"
                end
            end
            
            rootProspectivo = rootIrreal

            ------------------------------------------------------------
            -- 4. ENSAMBLAJE FINAL Y NEGACIÓN
            ------------------------------------------------------------

            -- 4.1 CONSTRUCCIÓN DE FILA AFIRMATIVA
            table.insert(serieData.rows, {
                label = label,
                status = "pos", -- Marca para estilo visual
                perf = prefix .. currentInfix .. intransitivePrefix .. rootPerf .. perfSuffix,
                imperf = prefix .. currentInfix .. intransitivePrefix .. rootImperf .. imperfSuffix,
                irreal = prefix .. currentInfix .. intransitivePrefix .. rootIrreal .. irrealSuffix,
                prospectivo = prefix .. currentInfix .. intransitivePrefix .. rootProspectivo .. prospectivoSuffix,
                destinativo = prefix .. currentInfix .. intransitivePrefix .. rootDestinativo .. destinativoSuffix
            })

            -- 4.2 LÓGICA DE NEGACIÓN (Solo para Serie 1)
            if s.hasNegation then
                -- REGLA DE RAÍZ PURA: La negación ignora extensiones históricas en S1
                local negRoot = currentRoot
                
                table.insert(serieData.rows, {
                    label = "", -- Se puede dejar vacío para usar rowspan o marcar con (-)
                    status = "neg",
                    perf = prefix .. currentInfix .. negRoot .. "-za",
                    imperf = prefix .. currentInfix .. negRoot .. imperfSuffix .. "-za",
                    irreal = prefix .. currentInfix .. negRoot .. "-zi-nga",
                    destinativo = prefix .. currentInfix .. negRoot .. "-za-n iua"
                })
            end
        end
        table.insert(results.series, serieData)
    end
    return results
end

-- ============================================================
-- LA VISTA: Usado por la Wiki
-- ============================================================
local function drawTable(serie)
    local wrapper = mw.html.create("div"):css("margin-bottom", "1.5em"):css("font-size", "14px")
    wrapper:tag("div"):css("font-weight", "bold"):css("font-size", "1.1em")
        :css("margin-bottom", "0.4em"):css("color", "#202122"):wikitext(serie.title)

    local tableHtml = wrapper:tag("table"):addClass("wikitable")
        :css("width", "100%"):css("max-width", "700px"):css("margin", "0")

    local headerRow = tableHtml:tag("tr")
    headerRow:tag("th"):wikitext(TEXTS.ui.col_person)
    
    if serie.id == "S3" then
        headerRow:tag("th"):wikitext(serie.headers[1])
    else
        for _, hName in ipairs(serie.headers) do
            headerRow:tag("th"):wikitext(hName)
        end
    end

    for i, row in ipairs(serie.rows) do
        local tr = tableHtml:tag("tr")
        
        -- Estética para negación
        if row.status == "neg" then
            tr:css("background-color", "#f8f9fa"):css("color", "#54595d")
            local tdLabel = tr:tag("td"):css("text-align", "right"):css("font-size", "0.9em")
                :css("font-style", "italic"):wikitext("neg.")
        else
            tr:tag("td"):css("font-weight", "bold"):wikitext(row.label)
        end

        tr:tag("td"):wikitext(row.perf)
        
        if serie.id == "S1" then
            tr:tag("td"):wikitext(row.imperf)
            tr:tag("td"):wikitext(row.irreal)
            tr:tag("td"):wikitext(row.destinativo)
        elseif serie.id == "S2" then
            tr:tag("td"):wikitext(row.imperf)
            tr:tag("td"):wikitext(row.irreal)
            tr:tag("td"):wikitext(row.prospectivo)
        end
    end
    return wrapper
end

function p.render(frame)
    local args = frame:getParent().args
    local pageName = mw.title.getCurrentTitle().text
    local claseInput = args.clase or args.tipo or args.cat or args[1] or ""
    local data = p.getConjugations(pageName, claseInput, args.raiz)

    if data.root == "" or data.root == pageName then
        return "<span style='color:red;'>" .. TEXTS.ui.error_root .. pageName .. ".</span>"
    end

    -- Contenedor de la línea del botón (alineado a la derecha)
    local buttonWrapper = mw.html.create("div")
        :css("display", "flex")
        :css("justify-content", "flex-end")
        :css("margin-bottom", "10px")

    -- El Botón Azul (mw-customtoggle)
    buttonWrapper:tag("div")
        :addClass("mw-customtoggle-muyscaParadigma") -- ID vinculado al contenido
        :addClass("mw-ui-button mw-ui-progressive")   -- Estilo azul de MediaWiki
        :css("border-radius", "20px")                -- Bordes bien redondeados
        :css("padding", "4px 15px")
        :css("font-weight", "bold")
        :css("cursor", "pointer")
        :wikitext(TEXTS.ui.expand)

    -- Contenedor del Paradigma (el que se oculta/muestra)
    local contentDiv = mw.html.create("div")
        :addClass("mw-collapsible mw-collapsed")
        :attr("id", "mw-customcollapsible-muyscaParadigma") -- ID para el toggle
        :css("border", "1px solid #c8ccd1")
        :css("border-radius", "8px")
        :css("padding", "20px")
        :css("background-color", "#fcfcfc")
        :css("margin-top", "10px")

    -- El Título (ahora dentro del contenido desplegable)
    contentDiv:tag("div")
        :css("font-weight", "bold")
        :css("border-bottom", "2px solid #3366cc")
        :css("margin-bottom", "20px")
        :css("padding-bottom", "5px")
        :css("font-size", "1.2em")
        :wikitext(TEXTS.ui.main_title .. data.root .. " (" .. data.classInfo.raw .. ")")

    -- Dibujar las tablas de las series
    for _, serie in ipairs(data.series) do
        contentDiv:node(drawTable(serie))
    end

    return tostring(buttonWrapper) .. tostring(contentDiv)
end

return p