<?php

/**
 * SCRIPT DE ANÁLISIS AUTOMÁTICO DE MÉTRICA ESPAÑOLA
 * 
 * Este script analiza versos en español calculando sílabas gramaticales y métricas,
 * considerando fenómenos poéticos como sinalefa, sinéresis, diéresis y ajustes por acentuación.
 * 
 * @category   NaturalLanguageProcessing
 * @package    Corpus
 * @subpackage Preprocessing
 * @author     Jaume d'Urgell <jaume@durgell.com>
 * @author     Endika Tapia <endikatlg@gmail.com>
 * @copyright  Jaume d'Urgell 陈建军
 * @license    MIT
 * @version    3.2.0
 * @since      2025-11-04
 * @link       https://durgell.com/metrica/
 * @see        https://durgell.com/metrica/
 */

// =============================================================================
// CONFIGURACIÓN INICIAL Y MANEJO DE ERRORES
// =============================================================================

/**
 * Configuración de cabeceras HTTP para correcta interpretación de caracteres
 * Establece el tipo de contenido como HTML con codificación UTF-8
 */
header('Content-Type: text/html; charset=UTF-8');

ini_set('display_errors'1);
ini_set('display_startup_errors'1);
error_reporting(E_ALL);

//declare(strict_types=1);

/**
 * Configuración de codificación interna para funciones de cadena multibyte
 * Asegura el correcto manejo de caracteres especiales del español
 */
mb_internal_encoding("UTF-8");
mb_regex_encoding("UTF-8");

/**
 * Variables globales para manejo de estado
 * 
 * @var string $error Mensaje de error para mostrar al usuario
 * @var string $versoOriginal Texto del verso ingresado por el usuario
 * @var array $listaDeDieresis Almacena casos de diéresis detectados para presentación
 */
$error "";
$versoOriginal "";
$listaDeDieresis = [];

/**
 * Configuración de visualización de errores (SOLO PARA DESARROLLO)
 * En producción deberían desactivarse estas opciones
 */
// ini_set('display_errors', 1);
// ini_set('display_startup_errors', 1);
// error_reporting(E_ALL);
ini_set('display_errors'0); // Esto es para despliegue en producción.

// =============================================================================
// PROCESAMIENTO DE ENTRADA DEL USUARIO
// =============================================================================

/**
 * Procesa el formulario cuando se envía via POST
 * Realiza validaciones básicas de entrada
 */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
// Obtiene y limpia el verso del formulario
    
$versoOriginal = isset($_POST['verso']) ? trim((string)$_POST['verso']) : '';
    
    
// Validación: el verso no puede estar vacío
    
if ($versoOriginal === "") {
        
$error "Por favor, escribe un verso (no puede quedar en blanco).";
    } 
    
// Validación: longitud máxima del verso
    
elseif (mb_strlen($versoOriginal'UTF-8') > 256) {
        
$error "El verso no puede superar los 256 caracteres.";
    }
    
// Si no hay errores, continúa el análisis en las secciones posteriores
}

?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cálculo automático de Métrica Española</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=DM+Serif+Text&display=swap" rel="stylesheet">
    <style>
        /* =============================================================================
           ESTILOS CSS PARA LA INTERFAZ WEB
           ============================================================================= */
        
        /* Reset básico para márgenes y padding */
        * {
            box-sizing: border-box;
        }
        
        /* Estilos generales del cuerpo */
        body { 
            font-family: Arial, sans-serif; 
            margin: 0 1em; 
            background: #ddd; 
            font-size: 16px;
            line-height: 1.5;
        }
        
        /* Encabezado principal */
        h1 {
            font-family: "DM Serif Text", serif;
            font-weight: 800;
            font-style: normal;
            font-size: 32px;
            line-height: 1;
            text-align: center;
            margin: 16px 0px;
        }
        
        /* Contenedor del formulario */
        .form-container {
            display: flex;
            flex-direction: column;
            gap: 1em;
        }
        
        /* Campo de entrada de texto */
        input { 
            width: 100%; 
            height: 50px; 
            font-size: 1em;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 6px;
        }
        
        /* Contenedor de botones */
        .botones-container {
            display: flex;
            gap: 1em;
            width: 100%;
        }
        
        /* Estilos base para botones */
        button { 
            border: none;
            border-radius: 8px;
            padding: 14px 0px;
            font-size: 1em;
            font-weight: 600;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            cursor: pointer;
            transition: all 0.2s cubic-bezier(0.08, 0.52, 0.52, 1);
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
            flex: 1;
        }
        
        /* Botón de analizar - Estilos específicos */
        .btn-analizar {
            background: linear-gradient(135deg, #27ae60 0%, #219653 100%);
            color: white;
        }
        
        /* Botón de ejemplo - Estilos específicos */
        .btn-ejemplo {
            background: linear-gradient(135deg, #1877F2 0%, #166FE5 100%);
            color: white;
        }
        
        /* Botón de borrar - Estilos específicos */
        .btn-borrar {
            background: linear-gradient(135deg, #E74C3C 0%, #C0392B 100%);
            color: white;
        }
        
        /* Efectos hover para botones */
        .btn-analizar:hover {
            background: linear-gradient(135deg, #219653 0%, #1e8449 100%);
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        
        .btn-ejemplo:hover {
            background: linear-gradient(135deg, #166FE5 0%, #1461C8 100%);
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        
        .btn-borrar:hover {
            background: linear-gradient(135deg, #C0392B 0%, #A93226 100%);
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        
        /* Efectos active para botones */
        .btn-analizar:active {
            background: linear-gradient(135deg, #1e8449 0%, #196f3d 100%);
            transform: translateY(0);
        }
        
        .btn-ejemplo:active {
            background: linear-gradient(135deg, #1461C8 0%, #1257B4 100%);
            transform: translateY(0);
        }
        
        .btn-borrar:active {
            background: linear-gradient(135deg, #A93226 0%, #922B21 100%);
            transform: translateY(0);
        }
        
        /* Pie de página */
        .footer {
            line-height: 1.4;
            padding: 0 0 1em 0;
            margin-top: 10px;
            text-align: center;
            color: #444;
            font-weight: normal;
            font-size: small;
        }
        
        /* Información de proceso */
        .proceso { 
            background: #fff; 
            padding: 1px 1em;
            border-radius: 8px; 
            box-shadow: 0 0 5px #ccc; 
            margin-top: 1em; 
            word-wrap: break-word;
            overflow-wrap: break-word;
            line-height: 1.4;
        }
        
        /* Área de resultados */
        .resultado { 
            background: #fff; 
            padding: 12px 1em;
            border-radius: 8px; 
            box-shadow: 0 0 5px #ccc; 
            margin-top: 1em; 
            word-wrap: break-word;
            overflow-wrap: break-word;
            line-height: 1.4;
        }
        
        /* Panel informativo */
        .panel { 
            background: #fff; 
            padding: 1px 1em;
            border-radius: 8px; 
            box-shadow: 0 0 5px #ccc; 
            margin-top: 1em; 
            word-wrap: break-word;
            overflow-wrap: break-word;
            line-height: 1.4;
        }
        
        /* Panel informativo */
        .mit { 
            background: #fff; 
            padding: 1px 1em;
            border-radius: 8px; 
            box-shadow: 0 0 5px #ccc; 
            margin-top: 1em; 
            word-wrap: break-word;
            overflow-wrap: break-word;
            line-height: 1.4;
        }
        
        .justificado {
            text-align: justify;
            hyphens: auto;
            -webkit-hyphens: auto; /* para compatibilidad con Chrome/Safari */
            -ms-hyphens: auto;      /* para navegadores antiguos de Microsoft */
            text-justify: inter-word;
        }
        
        .video{
            width: 100%;
            margin: 16px auto 0px;
            border-radius: 16px; 
            display: block;
            border: solid 2px #000;
        }
        
          /* Espacio de presentación entre sílabas */
        .gap {
            color: #888;
            margin: 0 2px;
        }
        /* Manejo de márgenes en el área de resultados */
        .resultado > *:first-child {
            margin-top: 0;
        }
        
        .resultado > *:last-child {
            margin-bottom: 0;
        }
        
        /* Etiquetas destacadas */
        .etiqueta { 
            font-weight: bold; 
        }
        
        /* Texto monoespaciado para silabeos */
        .monoespaciado { 
            font-family: "SFMono-Regular", Menlo, Consolas, monospace; 
            display: inline;
            max-width: 100%;
            overflow-x: auto;
        }
        
        /* Línea separadora */
        hr {
            border: none;
            border-top: 1px dotted #888;
        }
        
        /* Estilo para código QR */
        .qr_code {
          width: 80px;
          float: left;
          padding: 0 0 8px 0;
          vertical-align: middle;
        }
        
        /* Marcar palabras sin con pronunciación desconocida */
        .not-found {
            color: #b00;
            background: rgba(255, 200, 200, 0.3);
            padding: 0 .15em;
            border-radius: 3px;
            font-weight: 600;
        }
    
        /* =============================================================================
           MEDIA QUERIES PARA DISPOSITIVOS DE ESCRITORIO
           ============================================================================= */
        @media (min-width: 768px) {
            body {
                margin: 0;
                max-width: 800px;
                margin-left: auto;
                margin-right: auto;
            }
            
            .video {
                width: 50%;
                border: solid 1px #000;
            }
            
            .qr_code {
              width: 100px;
              padding: 0 0 12px 0;
            }
            
            /* Encabezado principal */
            h1 {
                font-family: "DM Serif Text", serif;
                font-weight: 400;
                font-style: normal;
                font-size: 45px;
                line-height: 1;
                text-align: center;
                margin: 16px 0px;
            }
            .form-container {
                flex-direction: row;
                align-items: center;
                flex-direction: column;
                gap: 1em;
            }
            
            .salto {
                width: 50%;    
            }
            
            input {
                height: 40px;
                font-size: 1.05em;
            }
            
            .proceso {
                width: 50%;
                margin: 16px auto 0px;
                gap: 1em;
            }
            
            .botones-container {
                width: 50%;
                flex-shrink: 0;
                gap: 1em;
            }
            
            button {
                width: auto;
                padding: 10px 28px;
                white-space: nowrap;
            }
        }
    </style>
</head>
<body>
    <!-- =============================================================================
         ESTRUCTURA HTML DE LA INTERFAZ
         ============================================================================= -->
    <video class="video" poster="cabecera.webp" controls loop>
        <source src="carmen-de-burgos.mp4" type="video/mp4">
        Tu navegador no soporta el elemento de vídeo.
    </video>
    <h1>
        <a href="https://durgell.com/metrica/" style="display: block; text-decoration: none; color: inherit;">
            Cálculo automático<br/>de Métrica Española
        </a>
    </h1>

    <!-- Mostrar mensajes de error si existen -->
    <?php if ($error): ?>
    <div style="color: #c00; background: #fff0f0; border: 1px solid #c66; padding: 1em; margin-bottom: 1em; border-radius: 8px;">
        <?php echo htmlspecialchars($errorENT_QUOTES ENT_SUBSTITUTE'UTF-8'); ?>
    </div>
    <?php endif; ?>

    <!-- Formulario principal -->
    <form method="post" if="formulario" autocomplete="off">
        <div class="form-container">
            <input type="text" 
                   name="verso" 
                   id="verso"
                   class="salto"
                   required 
                   autofocus 
                   maxlength="256" 
                   placeholder="Escribe un verso aquí."
                   value="<?= isset($_POST['verso']) ? htmlspecialchars($_POST['verso'], ENT_QUOTES ENT_SUBSTITUTE'UTF-8') : '' ?>"
                   autocomplete="off">
            <div class="botones-container">
                <button type="submit" class="btn-analizar" id="btn-analizar">Analizar</button>
                <button type="button" class="btn-ejemplo" id="btn-ejemplo">Ejemplos</button>
                <button type="button" class="btn-borrar" id="btn-borrar">Borrar</button>
            </div>
        </div>
    </form>

<?php

// =============================================================================
// ESTABLECIMIENTO DE LA CONEXIÓN A LA BASE DE DATOS
// =============================================================================

// Módulo externo, por razones de seguridad.
//
// Usamos @include_once en lugar de require_once para que no yerre si el archivo
// no está, sin embargo en tal caso deberían insertarse aquí los parámetros para
// la configuración del acceso a la base de datos.
//
@include_once 'datos-de-conexion.php';

/** Contenido del script externo: datos_de_conexion.php
 *
 *  $host = 'localhost';
 *  $dbname = 'nombre_de_la_base_de_datos';
 *  $user = 'nombre_del_usuario_autorizado';
 *  $pass = 'contraseña';
 */

// Crear conexión PDO
try {
    
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4"$user$pass, [
        
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    
]);
} catch (
PDOException $e) {
    die(
"Error de conexión a la base de datos: " $e->getMessage());
}

// =============================================================================
// PRE-CARGA DE DATOS
// =============================================================================

/**
 * Versos de ejemplo, para realizar pruebas al azar.
 */
//
// Usamos @include_once en lugar de require_once para que no yerre si el archivo
// no está. También comprobamos si existe la función versoAleatorio, para que el
// sistema no genere un error en caso de que dicho fichero no esté. Así se mantiene
// la posibilidad de utilizar este script de forma independiente.
//
@include_once 'versos-de-ejemplo.php';

if (!
function_exists('versoAleatorio')) {
    function 
versoAleatorio() {
        
$tablaDeVersosDeEjemplo = [
            
"En el río azul confluïa entre sueños."
        
];
        return 
$tablaDeVersosDeEjemplo[array_rand($tablaDeVersosDeEjemplo)];
    }
}

// =============================================================================
// FUNCIONES DE ANÁLISIS LINGÜÍSTICO
// =============================================================================

/**
 * Limpia una palabra eliminando signos de puntuación
 * 
 * @param string $palabra La palabra a limpiar
 * @return string Palabra sin signos de puntuación, manteniendo letras y caracteres especiales españoles
 */
function limpiarPalabra($palabra) {
    return 
preg_replace('/[^\p{L}áéíóúäëïöüÿÁÉÍÓÚÜÑñ]/u'''$palabra);
}

/**
 * Determina si un carácter en una posición específica es vocal fonológica
 * 
 * Considera reglas especiales del español como:
 * - 'y' al final de palabra es vocal
 * - 'u' muda después de 'q' o 'g'
 * - Vocales con diéresis
 * 
 * @param string $palabraMin Palabra en minúsculas
 * @param int $pos Posición del carácter a evaluar
 * @return bool True si es vocal fonológica, false en caso contrario
 */
function esVocalFonologica($palabraMin$pos) {
    
$len mb_strlen($palabraMin'UTF-8');
    
$c mb_substr($palabraMin$pos1'UTF-8');
    
$prev = ($pos 0) ? mb_substr($palabraMin$pos-11'UTF-8') : '';
    
$next = ($pos $len-1) ? mb_substr($palabraMin$pos+11'UTF-8') : '';
    
    
// 'y' al final de palabra se considera vocal
    
if ($c === 'y') return ($pos === $len-1);
    
    
// Las vocales con diéresis siempre son vocales fonológicas
    
if ($c === 'ü' || $c === 'ï') return true;
    
    
// 'u' muda después de 'q' o 'g' seguida de vocal
    
if ($c === 'u' && ($prev === 'q' || $prev === 'g') && $next !== '') {
        if (
preg_match('/[eéií]/u'$next)) return false;
    }
    
    
// Cualquier otra vocal
    
return (bool)preg_match('/[aeiouáéíóú]/u'$c);
}

/**
 * Divide una palabra en sílabas aplicando las reglas del español
 * 
 * @param string $palabra Palabra a silabear
 * @return array Array de sílabas
 */
function silabearPalabra($palabra) {
    
// Reemplazar dígrafos con caracteres especiales temporales
    
$p $palabra;
    
$p preg_replace('/ch/iu''§'$p);  // ch -> §
    
$p preg_replace('/ll/iu''Ł'$p);  // ll -> Ł
    
$p preg_replace('/rr/iu''Ŕ'$p);  // rr -> Ŕ
    
$p preg_replace('/qu([ieíé])/iu''Q$1'$p);  // que, qui -> Qe, Qi
    
$p preg_replace('/gu([ieíé])/iu''G$1'$p);  // gue, gui -> Ge, Gi
    
$p preg_replace('/gü([ieíé])/iu''Ğu$1'$p); // güe, güi -> Ğue, Ğui
    
    // División silábica básica: separar después de cada vocal
    
$sil preg_split(
        
'/(?<=[aeiouáéíóúüäëïöÿAEIOUÁÉÍÓÚÜÄËÏÖŸ])(?=[^aeiouáéíóúüäëïöÿAEIOUÁÉÍÓÚÜÄËÏÖŸ])/u',
        
$p
    
);
    
    
// Aplicar correcciones sucesivas
    
$sil corregirPrefijosIniciales($sil);
    
$sil corregirInicioSConsonante($sil);
    
$sil corregirInicioCC($sil);
    
$sil dividirAtaqueInterno($sil);
    
$sil separarHiatos($sil);
    
$sil reabsorberConsonanteSuelta($sil);
    
$sil reabsorberSFinalPlural($sil);
    
    
// Restaurar los dígrafos originales
    
$sil array_map(function($s){
        
$s str_replace('§','ch',$s);
        
$s str_replace('Ł','ll',$s);
        
$s str_replace('Ŕ','rr',$s);
        
$s str_replace('Q','qu',$s);
        
$s str_replace('G','gu',$s);
        
$s str_replace('Ğu','gü',$s);
        return 
$s;
    }, 
$sil);
    
    return 
array_values(array_filter($sil, fn($s) => $s !== ''));
}

/**
 * Separa hiatos en las sílabas
 * 
 * Los hiatos ocurren cuando dos vocales que normalmente formarían diptongo
 * se pronuncian en sílabas separadas
 * 
 * @param array $silabas Array de sílabas a procesar
 * @return array Sílabas con hiatos separados
 */
function separarHiatos($silabas) {
    
$res = [];
    foreach (
$silabas as $s) {
        
// Solo procesar sílabas con dos o más vocales consecutivas
        
if (preg_match('/[aeiouáéíóúäëïöüÿ]{2,}/iu'$s)) {
            
$buffer '';
            
$len mb_strlen($s,'UTF-8');
            for (
$i=0$i<$len$i++) {
                
$ch mb_substr($s,$i,1,'UTF-8');
                
$buffer .= $ch;
                if (
$i<$len-1) {
                    
$next mb_substr($s,$i+1,1,'UTF-8');
                    
// Si hay hiato entre caracteres actual y siguiente, separar
                    
if (esHiato($ch,$next)) {
                        
$res[] = $buffer;
                        
$buffer '';
                    }
                }
            }
            if (
$buffer!==''$res[] = $buffer;
        } else {
            
$res[] = $s;
        }
    }
    return 
$res;
}

/**
 * Determina si dos vocales forman hiato
 * 
 * @param string $v1 Primera vocal
 * @param string $v2 Segunda vocal
 * @return bool True si forman hiato, false si forman diptongo
 */
function esHiato($v1,$v2) {
    
$abiertas = ['a','á','e','é','o','ó'];
    
$cerradas = ['i','í','u','ú','ü'];
    
$v1l mb_strtolower($v1,'UTF-8');
    
$v2l mb_strtolower($v2,'UTF-8');
    
    
// Dos vocales abiertas siempre forman hiato
    
if (in_array($v1l,$abiertas) && in_array($v2l,$abiertas)) return true;
    
    
// Vocal cerrada tónica seguida de vocal abierta
    
if (in_array($v1l,['í','ú']) && in_array($v2l,$abiertas)) return true;
    
    
// Vocal abierta seguida de vocal cerrada tónica
    
if (in_array($v1l,$abiertas) && in_array($v2l,['í','ú'])) return true;
    
    return 
false;
}

/**
 * Corrige prefijos iniciales como "in-" + consonante
 * 
 * @param array $silabas Array de sílabas
 * @return array Sílabas corregidas
 */
function corregirPrefijosIniciales($silabas) {
    if (
count($silabas) < 2) return $silabas;
    
    
$primera $silabas[0];
    
$segunda $silabas[1];
    
    
// Prefijos como "in-" seguidos de consonante nasal o líquida
    
if (preg_match('/^(?i)i$/u'$primera) || preg_match('/^(?i)í$/u'$primera)) {
        if (
preg_match('/^[nmrNMR][^aeiouáéíóúüäëïöÿAEIOUÁÉÍÓÚÄËÏÖÜŸ]/u'$segunda)) {
            
$c mb_substr($segunda01'UTF-8');
            
$silabas[0] .= $c;
            
$silabas[1] = mb_substr($segunda1null'UTF-8');
            if (
$silabas[1] === '') {
                
array_splice($silabas11);
            }
        }
    }
    return 
$silabas;
}

/**
 * Corrige la 's' seguida de consonante al inicio de sílaba
 * 
 * @param array $silabas Array de sílabas
 * @return array Sílabas corregidas
 */
function corregirInicioSConsonante($silabas) {
    
$res = [];
    for (
$i=0$i<count($silabas); $i++) {
        
$s $silabas[$i];
        
// Si la sílaba empieza con 's' + consonante y no es la primera sílaba
        
if ($i>&& preg_match('/^[sS][bcdfghjklmnñpqrtvwxyz]/u'$s)) {
            
$res[count($res)-1] .= mb_substr($s,0,1,'UTF-8');
            
$resto mb_substr($s,1,null,'UTF-8');
            if (
$resto !== ''$res[] = $resto;
        } else {
            
$res[] = $s;
        }
    }
    return 
$res;
}

/**
 * Corrige grupos de dos consonantes al inicio de sílaba
 * 
 * @param array $silabas Array de sílabas
 * @return array Sílabas corregidas
 */
function corregirInicioCC($silabas) {
    
// Grupos consonánticos permitidos en español
    
$gruposPermitidos = ['br','bl','cr','cl','dr','fl','fr','gr','gl','pr','pl','tr','tl','ch',
                         
'Br','Bl','Cr','Cl','Dr','Fl','Fr','Gr','Gl','Pr','Pl','Tr','Tl','Ch',
                         
'BR','BL','CR','CL','DR','FL','FR','GR','GL','PR','PL','TR','TL','CH',
                         
'bR','bL','cR','cL','dR','fL','fR','gR','gL','pR','pL','tR','tL','cH'];
    
$resultado = [];

    foreach (
$silabas as $s) {
        
// Si la sílaba empieza con dos consonantes
        
if (preg_match('/^[^aeiouáéíóúüäëïöÿ]{2}/iu'$s)) {
            
$grupo mb_substr($s02'UTF-8');
            
// Si no es un grupo permitido, separar las consonantes
            
if (!in_array($grupo$gruposPermitidos)) {
                
$resultado[] = mb_substr($s01'UTF-8');
                
$resultado[] = mb_substr($s1null'UTF-8');
                continue;
            }
        }
        
$resultado[] = $s;
    }

    return 
$resultado;
}

/**
 * Divide sílabas con ataque interno complejo
 * 
 * @param array $silabas Array de sílabas
 * @return array Sílabas divididas
 */
function dividirAtaqueInterno($silabas) {
    
$res = [];
    foreach (
$silabas as $s) {
        
$len mb_strlen($s,'UTF-8');
        
$cut = -1;
        
        
// Buscar punto de división después de vocal
        
for ($i=0$i<$len-1$i++) {
            
$ch mb_substr($s,$i,1,'UTF-8');
            if (
preg_match('/[aeiouáéíóúäëïöüÿ]/iu',$ch)) {
                
$rest mb_substr($s,$i+1,null,'UTF-8');
                
// Si lo que sigue es un grupo consonántico que puede iniciar sílaba
                
if (preg_match('/^(Ŕ|r|l|§|[bcdfghjklmnñpqrtvwxyz][rl])/iu',$rest)) {
                    
$cut $i+1;
                    break;
                }
            }
        }
        
        
// Aplicar división si se encontró punto de corte
        
if ($cut>0) {
            
$res[] = mb_substr($s,0,$cut,'UTF-8');
            
$res[] = mb_substr($s,$cut,null,'UTF-8');
        } else {
            
$res[] = $s;
        }
    }
    return 
$res;
}

/**
 * Reabsorbe consonantes sueltas en la sílaba anterior
 * 
 * @param array $silabas Array de sílabas
 * @return array Sílabas corregidas
 */
function reabsorberConsonanteSuelta($silabas) {
    
$i=0;
    while (
$i<count($silabas)) {
        
$s=$silabas[$i];
        
// Si la sílaba es una sola consonante
        
if (mb_strlen($s,'UTF-8')===&& preg_match('/[bcdfghjklmnñpqrtvwxyz]/iu',$s)) {
            if (
$i>0) {
                
// Unir a la sílaba anterior
                
$silabas[$i-1].=$s;
                
array_splice($silabas,$i,1);
                continue;
            }
        }
        
$i++;
    }
    return 
$silabas;
}

/**
 * Reabsorbe la 's' final de plural en la sílaba anterior
 * 
 * @param array $silabas Array de sílabas
 * @return array Sílabas corregidas
 */
function reabsorberSFinalPlural($silabas) {
    
$n=count($silabas);
    if (
$n>=2) {
        
$last=$silabas[$n-1];
        
// Si la última sílaba es solo 's' (plural)
        
if ($last==='s'||$last==='S') {
            
$silabas[$n-2].=$last;
            
array_pop($silabas);
        }
    }
    return 
$silabas;
}

/**
 * Genera el silabeo gramatical completo de un verso
 * 
 * @param string $verso El verso a analizar
 * @return string Silabeo gramatical con separadores '/'
 */
function versoSilabasGramaticales($verso) {
    
$palabras preg_split('/\s+/'trim($verso));
    
$silabas = [];
    foreach (
$palabras as $p) {
        
$p limpiarPalabra($p);
        if (
$p === '') continue;
        
$sils silabearPalabra($p);
        foreach (
$sils as $s) {
            if (
$s !== ''$silabas[] = $s;
        }
    }
    return 
implode('/'$silabas);
}

/**
 * Cuenta el número total de sílabas gramaticales en un verso
 * 
 * @param string $verso El verso a analizar
 * @return int Número de sílabas gramaticales
 */
function contarSilabasGramaticales($verso) {
    
$palabras preg_split('/\s+/'trim($verso));
    
$total 0;
    foreach (
$palabras as $p) {
        
$p limpiarPalabra($p);
        if (
$p === '') continue;
        
$sils silabearPalabra($p);
        foreach (
$sils as $s) {
            if (
$s !== ''$total++;
        }
    }
    return 
$total;
}

/**
 * Genera el silabeo métrico considerando fenómenos poéticos
 * 
 * @param string $verso El verso a analizar
 * @return string Silabeo métrico con marcadores especiales
 */
function versoSilabasMetricas($verso) {
    
$palabras preg_split('/\s+/'trim($verso));
    
$tokens = [];
    
    for (
$i 0$i count($palabras); $i++) {
        
$p limpiarPalabra($palabras[$i]);
        if (
$p === '') continue;
        
        
$silabasPalabra silabearPalabra($p);
        if (empty(
$silabasPalabra)) $silabasPalabra = [$p];
        
        
// Aplicar sinéresis (unión de vocales en diptongo)
        
$silabasPalabra aplicarSineresis($silabasPalabra);
        
        foreach (
$silabasPalabra as $s$tokens[] = $s;
        
        
// Verificar sinalefa entre palabras
        
if ($i count($palabras) - 1) {
            
$a limpiarPalabra($palabras[$i]);
            
$b limpiarPalabra($palabras[$i+1]);
            
            if (
$a !== '' && $b !== '') {
                
$ult ultimoCharFonetico($a);
                
$pri primerCharFonetico($b);
                
                
// Tratar 'y' como 'i' para efectos de sinalefa
                
if (mb_strtolower($a'UTF-8') === 'y'$ult 'i';
                if (
mb_strtolower($b'UTF-8') === 'y'$pri 'i';
                
                
// Si hay vocal final y vocal inicial, posible sinalefa
                
if (preg_match('/[aeiouáéíóúäëïöüÿ]/u'$ult) && preg_match('/[aeiouáéíóúäëïöüÿ]/u'$pri)) {
                    
// No hay sinalefa si hay signo de puntuación
                    
if (preg_match('/[.,;:!?]$/u'$palabras[$i])) {
                        
$tokens[] = '__SEP__';
                    } else {
                        
$tokens[] = '__SINALEFA__';
                    }
                } else {
                    
$tokens[] = '__SEP__';
                }
            }
        }
    }
    
    
// Construir el resultado final con los separadores apropiados
    
$res '';
    
$needSep false;
    for (
$i 0$i count($tokens); $i++) {
        
$t $tokens[$i];
        if (
$t === '__SEP__') {
            
$needSep '/';
            continue;
        }
        if (
$t === '__SINALEFA__') {
            
$needSep '‿';
            continue;
        }
        if (
$res !== '' && $needSep !== false) {
            
$res .= $needSep;
        }
        
$res .= $t;
        
$needSep '/';
    }
    return 
$res;
}

/**
 * Obtiene el último carácter fonético de una palabra
 * 
 * @param string $w Palabra a analizar
 * @return string Último carácter fonético (ignorando puntuación)
 */
function ultimoCharFonetico($w) {
    
$w trim($w);
    
$len mb_strlen($w'UTF-8');
    for (
$i $len 1$i >= 0$i--) {
        
$ch mb_substr($w$i1'UTF-8');
        if (
preg_match('/[a-záéíóúäëïöüÿñ]/iu'$ch)) return mb_strtolower($ch'UTF-8');
    }
    return 
'';
}

/**
 * Obtiene el primer carácter fonético de una palabra
 * 
 * @param string $w Palabra a analizar
 * @return string Primer carácter fonético (ignorando puntuación y 'h' muda)
 */
function primerCharFonetico($w) {
    
$w trim($w);
    
$len mb_strlen($w'UTF-8');
    
$j 0;
    
    
// Saltar caracteres no fonéticos iniciales
    
while ($j $len) {
        
$ch mb_substr($w$j1'UTF-8');
        if (
preg_match('/[a-záéíóúäëïöüÿñ]/iu'$ch)) break;
        
$j++;
    }
    
    if (
$j >= $len) return '';
    
$ch mb_substr($w$j1'UTF-8');
    
    
// Si es 'h' muda, tomar el siguiente carácter
    
if (mb_strtolower($ch'UTF-8') === 'h' && $j $len) {
        
$next mb_substr($w$j 11'UTF-8');
        if (
preg_match('/[a-záéíóúäëïöüÿñ]/iu'$next)) return mb_strtolower($next'UTF-8');
    }
    
    return 
mb_strtolower($ch'UTF-8');
}

/**
 * Identifica núcleos vocálicos en una palabra con sus posiciones
 * 
 * @param string $p Palabra a analizar
 * @return array Array de núcleos con posiciones inicio y fin
 */
function nucleosVocalicosConPos($p) {
    
$n mb_strlen($p'UTF-8');
    
$nucleos = [];
    
$i 0;
    
    while (
$i $n) {
        
$ch mb_substr($p$i1'UTF-8');
        if (!
esVocal($ch)) {
            
$i++;
            continue;
        }
        
        
$ini $i;
        
$j $i 1;
        
        
// Agrupar vocales consecutivas que formen diptongo
        
while ($j $n) {
            
$v1 mb_substr($p$j 11'UTF-8');
            
$v2 mb_substr($p$j1'UTF-8');
            if (!
esVocal($v2)) break;
            if (
esHiatoEntre($v1$v2)) {
                
$nucleos[] = ['ini' => $ini'fin' => $j 1];
                
$ini $j;
            }
            
$j++;
        }
        
$nucleos[] = ['ini' => $ini'fin' => $j 1];
        
$i $j;
    }
    return 
$nucleos;
}

/**
 * Determina si un carácter es vocal
 * 
 * @param string $c Carácter a evaluar
 * @return bool True si es vocal
 */
function esVocal($c) {
    
$c mb_strtolower($c'UTF-8');
    return 
in_array($c, ['a','e','i','o','u','á','é','í','ó','ú','ü'], true);
}

/**
 * Determina si una vocal es abierta
 * 
 * @param string $c Carácter a evaluar
 * @return bool True si es vocal abierta
 */
function esVocalAbierta($c) {
    
$c mb_strtolower($c'UTF-8');
    return 
in_array($c, ['a','e','o','á','é','ó'], true);
}

/**
 * Determina si una vocal es cerrada tónica
 * 
 * @param string $c Carácter a evaluar
 * @return bool True si es vocal cerrada tónica
 */
function esCerradaTonica($c) {
    
$c mb_strtolower($c'UTF-8');
    return 
in_array($c, ['í','ú'], true);
}

/**
 * Determina si dos vocales forman hiato
 * 
 * @param string $v1 Primera vocal
 * @param string $v2 Segunda vocal
 * @return bool True si forman hiato
 */
function esHiatoEntre($v1$v2) {
    
$v1 mb_strtolower($v1'UTF-8');
    
$v2 mb_strtolower($v2'UTF-8');
    
    
// Dos vocales abiertas
    
if (esVocalAbierta($v1) && esVocalAbierta($v2)) return true;
    
    
// Vocal cerrada tónica + vocal abierta
    
if (esCerradaTonica($v1) && esVocalAbierta($v2)) return true;
    
    
// Vocal abierta + vocal cerrada tónica
    
if (esVocalAbierta($v1) && esCerradaTonica($v2)) return true;
    
    return 
false;
}

/**
 * Aplica sinéresis (unión de vocales en diptongo) a las sílabas
 * 
 * @param array $silabas Array de sílabas
 * @return array Sílabas con sinéresis aplicada
 */
function aplicarSineresis($silabas) {
    
$resultado = [];
    
    foreach (
$silabas as $s) {
        if (!empty(
$resultado)) {
            
$ultima $resultado[count($resultado)-1];
            
$ultChar mb_substr($ultima, -11'UTF-8');
            
$len mb_strlen($s'UTF-8');
            
$j 0;
            
            
// Saltar 'h' muda inicial
            
if ($len && mb_strtolower(mb_substr($s01'UTF-8')) === 'h') {
                
$j 1;
            }
            
            
$priChar mb_substr($s$j1'UTF-8');
            
$ultCharLower mb_strtolower($ultChar'UTF-8');
            
$priCharLower mb_strtolower($priChar'UTF-8');
            
            
$vocalesAbiertas = ['a','á','e','é','o','ó'];
            
$vocalesCerradasTonica = ['í','ú'];
            
            
// Condiciones para sinéresis
            
if (preg_match('/[aeiouáéíóúü]$/iu'$ultima) &&
                
preg_match('/^[aeiouáéíóúü]/iu'$priChar)) {
                
                
// Vocal abierta + vocal abierta
                
if (in_array($ultCharLower$vocalesAbiertastrue) &&
                    
in_array($priCharLower$vocalesAbiertastrue)) {
                    
$resultado[count($resultado)-1] = $ultima '⁔' $s;
                    continue;
                }
                
                
// Vocal cerrada tónica + vocal abierta
                
if (in_array($ultCharLower$vocalesCerradasTonicatrue) &&
                    
in_array($priCharLower$vocalesAbiertastrue)) {
                    
$resultado[count($resultado)-1] = $ultima '⁔' $s;
                    continue;
                }
                
                
// Vocal abierta + vocal cerrada tónica
                
if (in_array($ultCharLower$vocalesAbiertastrue) &&
                    
in_array($priCharLower$vocalesCerradasTonicatrue)) {
                    
$resultado[count($resultado)-1] = $ultima '⁔' $s;
                    continue;
                }
            }
        }
        
$resultado[] = $s;
    }
    return 
$resultado;
}

/**
 * Encuentra el índice del núcleo tónico mediante tildes
 * 
 * @param string $p Palabra a analizar
 * @return int Índice del núcleo tónico, -1 si no se encuentra
 */
function indiceNucleoTonicoPorTilde($p) {
    
$nucleos nucleosVocalicosConPos($p);
    
$n mb_strlen($p'UTF-8');
    
    
// Buscar caracteres con tilde
    
for ($i 0$i $n$i++) {
        
$ch mb_substr($p$i1'UTF-8');
        if (
preg_match('/[áéíóúÁÉÍÓÚ]/u'$ch)) {
            foreach (
$nucleos as $idx => $r) {
                if (
$i >= $r['ini'] && $i <= $r['fin']) {
                    return 
$idx;
                }
            }
        }
    }
    return -
1;
}

/**
 * Determina el tipo de palabra (aguda/llana) cuando no hay tilde
 * 
 * @param string $palabra Palabra a analizar
 * @return string 'llana' o 'aguda'
 */
function tipoSinTilde($palabra) {
    return 
preg_match('/[aeiouáéíóúnsy]$/u'$palabra) ? 'llana' 'aguda';
}

/**
 * Determina el tipo de palabra final (aguda, llana, esdrújula)
 * 
 * @param string $palabra Palabra a analizar
 * @return string Tipo de palabra: 'aguda', 'llana', 'esdrújula' o 'sobreesdrújula'
 */
function tipoPalabraFinal($palabra) {
    
$p mb_strtolower(limpiarPalabra($palabra), 'UTF-8');
    
$nucleos nucleosVocalicosConPos($p);
    
$num count($nucleos);
    
    
// Palabras monosílabas son agudas
    
if ($num <= 1) {
        return 
'aguda';
    }
    
    
// Buscar tilde para determinar sílaba tónica
    
$idxTilde indiceNucleoTonicoPorTilde($p);
    if (
$idxTilde !== -1) {
        
$desdeFinal $num $idxTilde;
        if (
$desdeFinal === 1) return 'aguda';
        if (
$desdeFinal === 2) return 'llana';
        if (
$desdeFinal === 3) return 'esdrújula';
        return 
'sobreesdrújula';
    }
    
    
// Sin tilde, aplicar reglas por terminación
    
return tipoSinTilde($p);
}

/**
 * Calcula el ajuste métrico final según el tipo de palabra final
 * 
 * @param string $verso El verso completo
 * @return int Ajuste: +1 (aguda), 0 (llana), -1 (esdrújula/sobreesdrújula)
 */
function ajusteFinal($verso) {
    
$palabras preg_split('/\s+/'trim($verso));
    
$ultima limpiarPalabra(end($palabras));
    
$tipo tipoPalabraFinal($ultima);
    
    if (
$tipo === 'aguda') return 1;
    if (
$tipo === 'esdrújula' || $tipo === 'sobreesdrújula') return -1;
    return 
0;
}

/**
 * Cuenta el número total de sílabas métricas en un verso
 * 
 * @param string $verso El verso a analizar
 * @return int Número de sílabas métricas
 */
function contarSilabasMetricas($verso) {
    
$silabeo versoSilabasMetricas($verso);
    if (!
is_string($silabeo) || trim($silabeo) === '') {
        return 
0;
    }
    
$partes preg_split('/[\/]/u'$silabeo, -1PREG_SPLIT_NO_EMPTY);
    return 
is_array($partes) ? count($partes) : 0;
}

/**
 * Genera mensaje descriptivo para variaciones métricas
 * 
 * @param int $valor Valor del ajuste (-1, 0, 1)
 * @return string Mensaje descriptivo
 */
function mensajeVariacion(int $valor): string {
    return match (
$valor) {
        -
=> "se le resta una sílaba métrica",
        
0  => "el número de sílabas métricas no varía",
        
1  => "se le añade una sílaba métrica",
        default => 
"valor no esperado",
    };
}

// =============================================================================
// MANEJO DE DIÉRESIS MÉTRICA
// =============================================================================

/**
 * Lista de palabras con diéresis ortográfica (no suman sílabas adicionales)
 */
$palabras_con_dieresis = [
    
'aconcagüino','adagüe','agüe','agüé','agüen','agüera','agüeran','agüeras','agüere','agüeren','agüeres','agüero','agüeros','agüío','agüista','agüita','agüite','agüizote','alengüe','alengüé','alengüéis','alengüemos','alengüen','alengües','ambigüedad','ambigüedades','amortigüe','angüejo','antigüedad','antigüedades','antigüeño','apacigüe','apacigüé','apacigüéis','apacigüemos','apacigüen','apacigües','apirgüinarse','aragüeño','aragüirá','argüe','argüendera','argüendero','argüí','argüía','argüid','argüidor','argüir','argüís','argüitivo','atestigüe','atestigüé','atestigüéis','atestigüemos','atestigüen','atestigües','avergüence','avergüencen','avergüences','avergüenza','avergüenzan','avergüenzas','avergüenzo','averigüe','averigüé','averigüéis','averigüemos','averigüen','averigües','averigüetas','bilingüe','bilingües','bilingüismo','bilingüismos','camagüe','camagüeyano','camagüira','cangüeso','cangüesos','chagüite','changüí','chigüil','chigüín','chiquigüite','chirigüe','cigüeña','cigüeñal','cigüeñales','cigüeñas','cigüeñato','cigüeño','cigüeños','cigüeñuela','cigüeñuelas','cigüete','colchagüino','coligüe','cologüina','comayagüense','contigüidad','corregüela','curamagüey','degüella','degüellan','degüellas','degüelle','degüellen','degüelles','degüello','desagüe','desagüé','desagüéis','desagüemos','desagüen','desagües','deslengüe','deslengüé','deslengüéis','deslengüemos','deslengüen','deslengües','desvergüenza','desvergüenzas','empigüela','empigüelaba','empigüelabais','empigüelábamos','empigüelaban','empigüelabas','empigüelad','empigüelada','empigüeladas','empigüelado','empigüelados','empigüeláis','empigüelamos','empigüelan','empigüelando','empigüelar','empigüelara','empigüelará','empigüelarais','empigüeláramos','empigüelaran','empigüelarán','empigüelaras','empigüelarás','empigüelare','empigüelaré','empigüelareis','empigüelaréis','empigüelaremos','empigüeláremos','empigüelaren','empigüelares','empigüelaría','empigüelaríais','empigüelaríamos','empigüelarían','empigüelarías','empigüelaron','empigüelas','empigüelase','empigüelaseis','empigüelásemos','empigüelasen','empigüelases','empigüelaste','empigüelasteis','empigüele','empigüelé','empigüeléis','empigüelemos','empigüelen','empigüeles','empigüelo','empigüeló','enagüetas','enagüillas','engüera','engüeran','engüerar','engüeras','engüere','engüeren','engüeres','engüero','enjagüe','enjagües','etnolingüística','exangüe','exangües','exigüidad','extralingüístico','fagüeño','fagüeños','fragüe','fragüé','fragüéis','fragüemos','fragüen','fragües','fragüín','gargüero','gregüescos','guargüero','güecho','güechos','güegüecho','güeldo','güeldrés','güelfa','güelfas','güelfo','güelfos','güeña','güeñas','güera','güérmeces','güero','güeros','güey','güila','güillín','güillines','güilota','güimba','güin','güincha','güinche','güines','güipil','güira','güiras','güirila','güirís','güiro','güisaro','güisquería','güisqui','güisquil','güito','halagüeña','halagüeñamente','halagüeñas','halagüeño','halagüeños','higüela','higüera','higüero','higüeros','higüeyano','igüedo','jagüel','jagüey','jagüilla','jigüe','lengüecita','lengüeta','lengüetada','lengüetadas','lengüetas','lengüetazo','lengüeteada','lengüetear','lengüetería','lengüeterías','lengüetero','lengüicorta','lengüicortas','lengüicorto','lengüicortos','lengüilarga','lengüilargas','lengüilargo','lengüilargos','ligüística','ligüísticas','ligüístico','ligüísticos','lingüista','lingüistas','lingüística','lingüísticas','lingüístico','lingüísticos','macagüita','macagüitas','machigüe','magüeta','magüetas','magüeto','magüetos','majagüero','majagüeros','managüense','manigüero','mayagüezano','mengüe','mengüé','mengüéis','mengüemos','mengüen','mengües','metalingüísticamente','metalingüístico','monolingüe','multilingüe','multilingües','nacarigüe','nacarigües','nagüero','nicaragüense','nicaragüenses','paragüera','paragüeras','paragüería','paragüerías','paragüero','paragüeros','pedigüeña','pedigüeñas','pedigüeñería','pedigüeño','pedigüeños','pichagüero','pingüe','pingüedinosa','pingüedinosas','pingüedinoso','pingüedinosos','pingües','pingüino','pingüinos','piragüero','piragüeros','piragüismo','piragüista','pirgüín','pirgüines','plurilingüe','plurilingües','plurilingüismo','psicolingüística','psicolingüístico','quinquelingüe','quinquelingües','rancagüino','reargüí','reargüía','reargüíais','reargüíamos','reargüían','reargüías','reargüid','reargüida','reargüidas','reargüido','reargüidos','reargüimos','reargüió','reargüirá','reargüirán','reargüirás','reargüiré','reargüiréis','reargüiremos','reargüiría','reargüiríais','reargüiríamos','reargüirían','reargüirías','reargüís','reargüiste','reargüisteis','redargüir','regüeldo','regüeldos','rigüe','rompezaragüelles','sangüeño','sangüeños','sangüesa','sangüesas','sangüeso','sangüesos','santigüe','saragüete','saragüetes','sinvergüencería','sinvergüencerías','sinvergüenza','sinvergüenzas','sociolingüística','sociolingüísticas','sociolingüístico','sociolingüísticos','subigüela','subigüelas','tegüe','terigüela','terigüelas','tigüilote','tigüilotes','trarigüe','trarigües','trilingüe','trilingües','ungüentaria','ungüentarias','ungüentario','ungüentarios','ungüento','ungüentos','veragüense','vergüenza','vergüenzas','verigüeto','verigüetos','yangüés','yegüería','yegüerías','yegüerío','yegüeriza','yegüerizas','yegüerizo','yegüerizos','yegüero','yegüeros','zagüía','zaragüelles','zarigüeya'
];

/**
 * Separa vocales con diéresis métrica insertando separadores
 * 
 * @param string $cadena Texto a procesar
 * @return string Texto con separadores añadidos para diéresis métrica
 */
function separarVocalesConDieresis($cadena) {
    
$vocales_normales 'aeiouyAEIOUY';
    
$vocales_dieresis 'äëïöüÿÄËÏÖÜŸ';
    
    
$patron '/([' preg_quote($vocales_normales) . '])([' preg_quote($vocales_dieresis) . '])|([' preg_quote($vocales_dieresis) . '])([' preg_quote($vocales_normales) . '])/u';
    
    
$resultado preg_replace_callback($patron, function($coincidencias) {
        if (!empty(
$coincidencias[1]) && !empty($coincidencias[2])) {
            return 
$coincidencias[1] . '/' $coincidencias[2];
        }
        if (!empty(
$coincidencias[3]) && !empty($coincidencias[4])) {
            return 
$coincidencias[3] . '/' $coincidencias[4];
        }
        return 
$coincidencias[0];
    }, 
$cadena);
    
    return 
$resultado;
}

/**
 * Acumula pares de diéresis para presentación posterior
 * 
 * @param array $array Array actual de diéresis
 * @param mixed $valor1 Primer valor del par
 * @param mixed $valor2 Segundo valor del par
 * @return array Array actualizado
 */
function acumularDieresis($array$valor1$valor2) {
    
$array[] = [$valor1$valor2];
    return 
$array;
}

/**
 * Detecta y cuenta diéresis métricas en un texto
 * 
 * @param string $texto Texto a analizar
 * @param array $palabras_permitidas Palabras con diéresis ortográfica (no cuentan)
 * @return int Número de diéresis métricas encontradas
 */
function dieresisMetricas(string $texto, array $palabras_permitidas): int {
    
$texto mb_strtolower($texto'UTF-8');
    
$permitidas array_flip(array_map('mb_strtolower'$palabras_permitidas));
    
$total 0;
    global 
$listaDeDieresis;

    if (
preg_match_all('/\b[\wáéíóúäëïöüÿñ\'\-]+\b/u'$texto$matchesPREG_OFFSET_CAPTURE)) {
        foreach (
$matches[0] as $match) {
            
$palabra mb_strtolower($match[0]);
            
// Saltar palabras con diéresis ortográfica
            
if (isset($permitidas[$palabra])) {
                continue;
            }
            
// Contar diéresis métricas
            
$total += preg_match_all('/[äëïöüÿ]/u'$palabra);
            if (
preg_match_all('/[äëïöüÿ]/u'$palabra) == 1) {
                
$sinSeparar versoSilabasMetricas($palabra);
                
$separada separarVocalesConDieresis(versoSilabasMetricas($palabra));
                
$listaDeDieresis acumularDieresis($listaDeDieresis$sinSeparar$separada);
                
// echo '<b>DEPURACIÓN</b><br>';
                // echo 'Evento número: '.$total.'<br>';
                // echo 'Sin separar la diéresis: '.$sinSeparar.'<br>';
                // echo 'Separando la diéresis: '.$separada.'<br>';
                // echo '<hr>';
            
}
        }
    }

    return 
$total;
}

// =============================================================================
// CONSULTA A LA BASE DE DATOS PARA ESTABLECER LA PRONUNCIACIÓN
// =============================================================================

/**
 * Convierte un texto en su equivalente fonético aproximado
 * buscando cada palabra en la tabla `pronunciacion`.
 *
 * Si no se encuentra la palabra, se marca con un <span class="not-found">.
 *
 * @param string $texto Texto original a convertir.
 * @param PDO    $pdo   Conexión PDO a la base de datos.
 * @return string Texto transformado (HTML escapado y seguro).
 */
function convertirAPronunciacion(string $textoPDO $pdo): string {

    
// Normalizamos el texto de entrada, suprimiendo los puntos y comas que pudiera contener.
    
$texto str_replace(['('')''['']''{''}''¡''!''¿''?''.'','';'], ''$texto);

    
// Preparamos la consulta una sola vez
    
$stmt $pdo->prepare('SELECT IPA FROM pronunciacion WHERE LOWER(palabra) = LOWER(?) LIMIT 1');

    
// Dividimos el texto conservando espacios y puntuación
    
$tokens preg_split('/(\b)/u'$texto, -1PREG_SPLIT_DELIM_CAPTURE);

    
$resultado '';

    foreach (
$tokens as $token) {
        
// Detectamos palabras (letras y acentos)
        
if (preg_match('/^\p{L}+$/u'$token)) {
            
$stmt->execute([$token]);
            
$fila $stmt->fetch(PDO::FETCH_ASSOC);

            if (
$fila && !empty(trim($fila['IPA']))) {
                
// Si se encuentra, usamos la pronunciación escapada
                
$resultado .= htmlspecialchars($fila['IPA'], ENT_QUOTES ENT_SUBSTITUTE'UTF-8');
            } else {
                
// Si no se encuentra, la envolvemos en <span class="not-found">
                
$resultado .= '<span class="not-found">' .
                              
htmlspecialchars($tokenENT_QUOTES ENT_SUBSTITUTE'UTF-8') .
                              
'</span>';
            }
        } else {
            
// Cualquier otro símbolo (espacio, puntuación...)
            
$resultado .= htmlspecialchars($tokenENT_QUOTES ENT_SUBSTITUTE'UTF-8');
        }
    }
    
    
$resultado '['.$resultado.']'// convención internacional para marcar el uso de pronunciación fonética.

    
return $resultado;
}

// =============================================================================
// PROCESAMIENTO PRINCIPAL Y PRESENTACIÓN DE RESULTADOS
// =============================================================================

/**
 * Contador de diéresis métricas para el verso actual
 */
$contarDieresis dieresisMetricas($versoOriginal$palabras_con_dieresis);

/**
 * Procesamiento principal cuando se envía el formulario sin errores
 */
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["verso"])) {
    
$versoOriginal trim((string)$_POST["verso"]);
    
$palabras preg_split('/\s+/'$versoOriginal);
    
    
// Cálculos principales
    
$silabasGramaticales contarSilabasGramaticales($versoOriginal);
    
$ultimaPalabraOriginal end($palabras);
    
$ultimaPalabraLimpia   limpiarPalabra($ultimaPalabraOriginal);
    
$tipoFinal tipoPalabraFinal($ultimaPalabraLimpia);
    
$ajuste ajusteFinal($versoOriginal);
    
$SilabasMetricas contarSilabasMetricas($versoOriginal) + $ajuste $contarDieresis;
    
$versoGram versoSilabasGramaticales($versoOriginal);
    
$versoMetric versoSilabasMetricas($versoOriginal);
    
$versoPronunciado convertirAPronunciacion($versoOriginal$pdo);

    
// Configuración de mensajes según el tipo de palabra final
    
$claseDeCierre = [
      
'aguda' => '. (Verso oxítono; ',
      
'llana' => '. (Verso paroxítono; ',
      
'esdrújula' => '. (Verso proparoxítono; ',
      
'sobreesdrújula' => '. (Verso proparoxítono; '
    
][$tipoFinal] ?? '';
    
    
// Clasificación poética del verso por número de sílabas
    
$analisisPoetico = match ($SilabasMetricas) {
        default => 
'',
        
2       => 'bisílabo, de arte menor)',
        
3       => 'trisílabo, de arte menor)',
        
4       => 'tetrasílabo, de arte menor)',
        
5       => 'pentasílabo, de arte menor)',
        
6       => 'hexasílabo, de arte menor)',
        
7       => 'heptasílabo, de arte menor)',
        
8       => 'octosílabo, de arte menor)',
        
9       => 'eneasílabo, de arte mayor)',
        
10      => 'decasílabo, de arte mayor)',
        
11      => 'endecasílabo, de arte mayor)',
        
12      => 'dodecasílabo, de arte mayor)',
        
13      => 'tridecasílabo, de arte mayor)',
        
14      => 'alejandrino, de arte mayor)',
        
15      => 'pentadecasílabo, de arte mayor)',
        
16      => 'hexadecasílabo, de arte mayor)',
        
17      => 'heptadecasílabo, de arte mayor)',
        
18      => 'octodecasílabo, de arte mayor)',
        
19      => 'eneadecasílabo, de arte mayor)',
        
20      => 'icosasílabo, de arte mayor)',
        
21      => 'unveintisílabo, de arte mayor)',
        
22      => 'docosílabo, de arte mayor)'
    
};

/**
 * Convierte números a su representación textual en español
 * 
 * @param int $numero Número a convertir (0-999)
 * @return string Representación textual del número
 */
function numeroALetras($numero) {
    if (!
is_numeric($numero) || $numero || $numero 999) {
        return 
"Error: El número debe ser un entero entre 0 y 999.";
    }
    
$numero = (int) $numero;
    
    
$unidades = array('''uno''dos''tres''cuatro''cinco''seis''siete''ocho''nueve');
    
$decenas_especiales = array('diez''once''doce''trece''catorce''quince''dieciséis''diecisiete''dieciocho''diecinueve');
    
$decenas_base = array('''''veinte''treinta''cuarenta''cincuenta''sesenta''setenta''ochenta''noventa');
    
$centenas = array('''ciento''doscientos''trescientos''cuatrocientos''quinientos''seiscientos''setecientos''ochocientos''novecientos');
    
    if (
$numero == 0) {
        return 
'cero';
    }
    
    
$texto '';
    
    
// Procesar centenas
    
if ($numero >= 100) {
        
$c floor($numero 100);
        
$resto $numero 100;
        
        
// Caso especial: "cien" en lugar de "ciento"
        
if ($c == && $resto == 0) {
            
$texto 'cien';
            return 
$texto;
        }
        
        
$texto .= $centenas[$c];
        
        if (
$resto 0) {
            
$texto .= ' ';
            
$numero $resto;
        } else {
            return 
$texto;
        }
    }
    
    
// Procesar decenas y unidades
    
if ($numero 10) {
        
$texto .= $unidades[$numero];
    } elseif (
$numero 20) {
        
$texto .= $decenas_especiales[$numero 10];
    } else {
        
$d floor($numero 10);
        
$u $numero 10;
        
        
// Casos especiales con "veinti"
        
if ($d == && $u 0) {
            
$texto .= 'veinti' $unidades[$u];
        } else {
            
$texto .= $decenas_base[$d];
            if (
$u 0) {
                
$texto .= ' y ' $unidades[$u];
            }
        }
    }
    
    return 
trim($texto);
}

/**
 * Prepara el silabeo métrico para presentación con diéresis separadas
 */
$silabeoPresentableConDieresisSeparadas $versoMetric;
if (
is_array($listaDeDieresis) && !empty($listaDeDieresis)) {
    foreach (
$listaDeDieresis as $par) {
        if (
count($par) === 2) {
            [
$buscar$reemplazar] = $par;

            
// Construimos el patrón regex insensible a mayúsculas
            
$pattern '/' preg_quote($buscar'/') . '/i';

            
$silabeoPresentableConDieresisSeparadas preg_replace_callback(
                
$pattern,
                function (
$matches) use ($buscar$reemplazar) {
                    
$original $matches[0];

                    
// Si todo el fragmento estaba en mayúsculas → mantenerlo así
                    
if (mb_strtoupper($original'UTF-8') === $original) {
                        return 
mb_strtoupper($reemplazar'UTF-8');
                    }
                    
// Si solo la primera letra estaba en mayúscula → capitalizar
                    
elseif (mb_strtoupper(mb_substr($original01'UTF-8')) === mb_substr($original01'UTF-8')) {
                        return 
mb_strtoupper(mb_substr($reemplazar01'UTF-8')) .
                               
mb_substr($reemplazar1null'UTF-8');
                    }
                    
// En cualquier otro caso → devolver en minúsculas
                    
else {
                        return 
mb_strtolower($reemplazar'UTF-8');
                    }
                },
                
$silabeoPresentableConDieresisSeparadas
            
);
        }
    }
}

?>
<!-- =============================================================================
     PRESENTACIÓN DE RESULTADOS
     ============================================================================= -->
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && $error === '') { ?>
    <div class="proceso" id="proceso" style="display:block;">
        <p class="justificado" style="margin-bottom: -13px;"><span class="etiqueta">Razonamiento de IA…</span></p>
        <p id="razonamiento" class="justificado" style="display:block;">Iniciando el análisis…</p>
    </div>
    <div class="resultado" id="resultado" style="display:none;">
        <h2>Resultado</h2>
        <p><span class="etiqueta">Verso analizado:</span> <?= htmlspecialchars($versoOriginalENT_QUOTES ENT_SUBSTITUTE'UTF-8'?></p>
        <hr>
        <p><span class="etiqueta">Pronunciación fonética detallada (AFI):</span> <?= $versoPronunciado ?></p>
        <hr>
        <p><span class="etiqueta">Número de sílabas gramaticales:</span> <?= numeroALetras($silabasGramaticales?>.</p>
        <p><strong>Silabeo gramatical:</strong> <span class="monoespaciado"><?= str_replace('/''<span class="gap">/</span>'htmlspecialchars($versoGramENT_QUOTES ENT_SUBSTITUTE'UTF-8')) ?></span></p>
        <hr>
        <p><span class="etiqueta">Número de sílabas métricas:</span> <?= numeroALetras($SilabasMetricas?><?= $claseDeCierre ?><?= $analisisPoetico ?></p>
        <p><strong>Silabeo métrico:</strong> <span class="monoespaciado"><?= str_replace('/''<span class="gap">/</span>'htmlspecialchars($silabeoPresentableConDieresisSeparadasENT_QUOTES ENT_SUBSTITUTE'UTF-8')) ?></span></p>
        <p class="justificado"><span style="display:inline-block">● Dado</span> que el verso termina con una <span class="etiqueta">palabra <?= htmlspecialchars($tipoFinalENT_QUOTES ENT_SUBSTITUTE'UTF-8'?></span>: <?= mensajeVariacion($ajuste?>.</p>
        
        <!-- Análisis de fenómenos métricos detectados -->
        <?php
        
// Análisis de sinalefas
        
echo match (substr_count($versoMetric"‿")) {
            
0    => '',
            
1    => '<p class="justificado"><span style="display:inline-block">● El</span> texto presenta una <span class="etiqueta">una sinalefa</span>, señalizada con un ‿, que resta una sílaba métrica.</p>',
            default => 
'<p class="justificado"><span style="display:inline-block">● El</span> texto presenta <span class="etiqueta">'.numeroALetras(substr_count($versoMetric"‿")).' sinalefas</span>, señalizadas con sendas: ‿, que restan otras tantas sílabas métricas.</p>',
        };
        
        
// Explicación adicional sobre sinalefas opcionales
        
echo match (substr_count($versoMetric"‿")) { 
            
0    => '<p class="justificado"><span style="display:inline-block">● No</span> se ha encontrado ninguna <span class="etiqueta">sinalefa</span>.</p>',
            
1    => '<p class="justificado"><span style="display:inline-block">● Téngase</span> en cuenta que la persona autora podría tomarse la licencia de ignorar una sinalefa cuando esta se produzca entre vocales tónicas, o entre una vocal átona y una tónica, según su criterio discrecional. En tal caso, no se descontaría ninguna sílaba &mdash;en lo que respecta a las sinalefas&mdash; al calcular la métrica de este verso (esta aplicación sí las ha descontado). <span class="etiqueta"><i>cf.</i> Dialefa.</p></span>',
            default => 
'<p class="justificado"><span style="display:inline-block">● Téngase</span> en cuenta que la persona autora podría tomarse la licencia de ignorar alguna &mdash;o incluso todas&mdash; las sinalefas que se produzcan entre vocales tónicas, o entre una vocal átona y una tónica, según su criterio discrecional, en cuyo caso, hasta '.numeroALetras(substr_count($versoMetric"‿")).' sílabas podrían no ser descontadas al calcular la métrica de este verso (esta aplicación sí las ha descontado). <span class="etiqueta"><i>cf.</i> Dialefa.</p></span>',
        };
        
        
// Análisis de sinéresis
        
echo match (substr_count($versoMetric"⁔")) {
            
0    => '<p class="justificado"><span style="display:inline-block">● No</span> se ha encontrado ninguna <span class="etiqueta">sinéresis</span>.</p>',
            
1    => '<p class="justificado"><span style="display:inline-block">● El</span> texto presenta <span class="etiqueta">una sinéresis</span>, señalizada con un ⁔, cuyo cómputo queda sujeto al criterio discrecional de la persona autora, quien decide si esa sílaba debe o no ser descontada (esta aplicación sí lo ha hecho).</p>',
            default => 
'<p class="justificado"><span style="display:inline-block">● El</span> texto presenta <span class="etiqueta">'.numeroALetras(substr_count($versoMetric"⁔")).' sinéresis</span>, señalizadas con sendas ⁔, cuyo cómputo queda sujeto al criterio discrecional de la persona autora, quien determina cuántas de esas sílabas deben descontarse al calcular la métrica de este verso (esta aplicación las ha descontado todas).</p>',
        };
        
        
// Análisis de diéresis métricas
        
echo match ($contarDieresis) {
            
0    => '<p class="justificado"><span style="display:inline-block">● No</span> se ha encontrado ninguna <span class="etiqueta">diéresis métrica</span>.</p>',
            
1    => '<p class="justificado"><span style="display:inline-block">● Se</span> ha detectado un caso de <span class="etiqueta">diéresis métrica</span>, señalizado con una ¨ en una palabra que no lleva diéresis ortográfica, por lo que esta aplicación ha sumado una sílaba en el cómputo de la métrica.</p>',
            default => 
'<p class="justificado"><span style="display:inline-block">● Se</span> han detectado '.numeroALetras($contarDieresis).' casos de <span class="etiqueta">diéresis métrica</span>, señalizados con sendos ¨ en palabras que no llevan diéresis ortográfica, por lo que esta aplicación ha sumado '.numeroALetras($contarDieresis).' sílabas en el cómputo de la métrica.</p>',
        };
        
?>
    </div>
    <div class="panel" id="panel" style="display:none;">
        <h2>Información</h2>
        <p class="justificado">La <span class="etiqueta">métrica</span> es el conjunto de reglas que determinan la medida, el ritmo y la estructura de los versos en la poesía en español. Se basa en el número de sílabas métricas, los acentos y las pausas del verso. Resulta útil porque permite al poeta crear musicalidad, equilibrio y emoción en sus composiciones, y al lector apreciar mejor el ritmo y la intención expresiva del poema. En nuestro caso, sobre la métrica de la Lengua Española.</p>
        <p class="justificado">La <span class="etiqueta">sinalefa</span> es un fenómeno métrico del verso español que consiste en unir en una sola sílaba métrica la vocal final de una palabra con la vocal inicial de la siguiente, incluso si hay una o más vocales contiguas. Su función es ajustar el cómputo silábico del verso sin alterar el ritmo natural del habla, favoreciendo la fluidez y la musicalidad del poema.</p>
        <p class="justificado">La <span class="etiqueta">dialefa (o azeuxis)</span> es un recurso métrico que consiste en mantener separadas, en el cómputo silábico, las vocales finales e iniciales de dos palabras consecutivas que normalmente formarían una sinalefa. Es decir, se evita la unión natural de sonidos para conservar una sílaba más en el verso, generalmente por razones rítmicas, expresivas o para resaltar la pausa entre ambas palabras.</p>
        <p class="justificado">La <span class="etiqueta">sinéresis</span> se trata de un recurso métrico que consiste en unir en una sola sílaba métrica dos vocales que normalmente formarían un hiato dentro de una misma palabra. Con ella se reduce el número de sílabas del verso y se favorece su medida, sin alterar demasiado la pronunciación natural, aunque a veces modifica levemente el ritmo o la musicalidad del poema.</p>
        <p class="justificado">La <span class="etiqueta">diéresis métrica:</span> es una figura poética que consiste en separar en dos sílabas las vocales de un diptongo dentro de una palabra, aumentando así el cómputo silábico del verso. Se marca a veces con el signo ¨ sobre la vocal débil (ü) para indicar su pronunciación separada. Se diferencia de la diéresis ortográfica, que solo sirve para señalar que la “u” debe pronunciarse en combinaciones como “güe” o “güi”, sin afectar la métrica del verso.</p>
        <p class="justificado">El <span class="etiqueta">AFI</span>, o <span class="etiqueta">Alfabeto Fonético Internacional</span>, es un sistema de símbolos creado para representar de forma precisa los sonidos de todas las lenguas del mundo. Cada signo corresponde a un sonido específico, independientemente de cómo se escriba en una lengua. Se usa en lingüística y enseñanza de idiomas para mostrar cómo se pronuncian realmente las palabras, evitando las ambigüedades de la ortografía.</p>
    </div>
    <script> // Este apartado es solo a efectos de presenctación de resultados en pantalla.
        (function(){
        const _0xA=['507265706172616369c3b36e2079207365676d656e74616369c3b36e2064656c20746578746f2e','4c696d7069657a61206f72746f6772c3a16669636120792070756e7475616369c3b36e2e','446976697369c3b36e2064656c20706f656d6120656e20766572736f732e','4964656e7469666963616369c3b36e2064652070616c61627261732079206163656e746f732e','44657465726d696e616369c3b36e2064656c206163656e746f2066696e616c2e','53696c6162656f206f72746f6772c3a16669636f20696e696369616c2e','41706c6963616369c3b36e2064652073696e616c6566617320656e7472652070616c61627261732e','41706c6963616369c3b36e2064652073696ec3a9726573697320696e7465726e61732e','41706c6963616369c3b36e206465206469c3a9726573697320706fc3a974696361732e','436f727265636369c3b36e20706f72207469706f206465206163656e746f2066696e616c2e','43c3b36d7075746f20746f74616c2064652073c3ad6c61626173206dc3a97472696361732e','436c617369666963616369c3b36e2064656c207469706f20646520766572736f2e','5472616e73637269706369c3b36e20666f6ec3a97469636120656e204146492e','56657269666963616369c3b36e207920636f686572656e6369612064656c20616ec3a16c697369732e','4576616c75616369c3b36e2072c3ad746d696361207920657374696cc3ad73746963612066696e616c2e'
        ];
        function _0xB(h){try{return decodeURIComponent(h.replace(/(..)/g,'%$1'))}catch(e){try{var s='';for(var i=0;i<h.length;i+=2){s+=String.fromCharCode(parseInt(h.substr(i,2),16))}return s}catch(e2){return''}}}
        const _0xC=_0xA.map(_0xB),_0xD=document.getElementById('proceso'),_0xE=document.getElementById('resultado'),_0xF=document.getElementById('razonamiento'),_0xH=document.getElementById('panel');
        function _0xG(){_0xD.style.display='block';_0xE.style.display='none';_0xH.style.display='none';var i=0;var T=3000;var P=Math.max(1,Math.floor(T/_0xC.length));_0xF.textContent=_0xC[0]||'Iniciando el análisis…';i=1;var ID=setInterval(function(){if(i<_0xC.length){_0xF.textContent=_0xC[i++];return}clearInterval(ID);setTimeout(function(){_0xD.style.display='none';_0xE.style.display='block';_0xH.style.display='block'},300)},P)}
        window.addEventListener('load',_0xG);
        })();
    </script>
<?php ?>
<?php 
?>
    <div class="mit" id="licenciaMIT" style="display:none;">
        <h2>Licencia MIT</h2>
        <p class="justificado"><span class="etiqueta">&copy; <?= date('Y'?> Jaume d'Urgell 陈建军</span></p>
        <p class="justificado">Por la presente se concede permiso, libre de cargos, a cualquier persona que obtenga una copia de este programa y de los archivos de documentación asociados (el "Programa"), a utilizar el Programa sin restricción, incluyendo sin limitación los derechos a usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar, y/o vender copias del Programa, y a permitir a las personas a las que se les proporcione el Programa a hacer lo mismo, sujeto a las siguientes condiciones:</p>
        <p class="justificado">EL PROGRAMA SE PROPORCIONA "COMO ESTÁ", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A GARANTÍAS DE COMERCIALIZACIÓN, IDONEIDAD PARA UN PROPÓSITO PARTICULAR E INCUMPLIMIENTO. EN NINGÚN CASO LOS AUTORES O PROPIETARIOS DE LOS DERECHOS DE AUTOR SERÁN RESPONSABLES DE NINGUNA RECLAMACIÓN, DAÑOS U OTRAS RESPONSABILIDADES, YA SEA EN UNA ACCIÓN DE CONTRATO, AGRAVIO O CUALQUIER OTRO MOTIVO, DERIVADAS DE, FUERA DE O EN CONEXIÓN CON EL PROGRAMA O SU USO U OTRO TIPO DE ACCIONES EN EL PROGRAMA.</p>
    </div>

    <!-- Pie de página con información de copyright -->
    <p class="footer">
        <strong>I+D+i sobre Procesamiento del Lenguaje Natural.</strong></br>
        &copy; <?= date("Y"?> Jaume d'Urgell 陈建军. <a href="https://durgell.com/">Página web</a>. <a href="mailto:jaume@durgell.com">Correo</a>.</br>
        Software libre (<a href="javascript:void(0)" onclick="mit()">licencia MIT</a>). <a href="https://durgell.com/metrica/codigo-fuente.php">Código fuente</a>.
    </p>

    <!-- =============================================================================
         SCRIPT JAVASCRIPT PARA INTERACTIVIDAD
         ============================================================================= -->
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const input = document.getElementById('verso');
            const btnEjemplo = document.getElementById('btn-ejemplo');
            const btnBorrar = document.getElementById('btn-borrar');
            const resultado = document.getElementById('resultado');
            
            if (input) {
                // Enfocar el campo de entrada al cargar la página
                input.focus();
                
                // Colocar cursor al final si hay texto existente
                const length = input.value.length;
                input.setSelectionRange(length, length);
                
                // Configurar funcionalidad del botón ejemplo
                if (btnEjemplo) {
                    btnEjemplo.addEventListener('click', function() {
                        input.value = '<?php echo versoAleatorio(); ?>';
                        // document.getElementById('formulario').submit();
                        document.getElementById('btn-analizar').click();
                    });
                }
                
                // Configurar funcionalidad del botón borrar
                if (btnBorrar) {
                    btnBorrar.addEventListener('click', function() {
                        input.value = '';
                        input.focus();
                        
                        // Ocultar resultados anteriores
                        if (resultado) {
                            try {resultado.style.display = 'none';} catch (error) {}
                            try {panel.style.display = 'none';} catch (error) {}
                        }
                            try {licenciaMIT.style.display = 'none';} catch (error) {}
                    });
                }
            }
            
            // Cuando la ventana recupere el foco, el input también lo hace.
            window.addEventListener("focus", () => {
                input.focus();
            });
        });
        
        function mit() {
            try {resultado.style.display = 'none';} catch (error) {}
            try {panel.style.display = 'none';} catch (error) {}
            try {licenciaMIT.style.display = 'block';} catch (error) {}
        }
    </script>
</body>
</html>