function paginacion($cSql, $leyenda, $tamPagina = 10, $maxIndex = 10, $leyendaPaginas = "Página %s de %s", $queryString = "")
{
    /* devuelve un array con ( n reg, n paginas, cSQL limitada, navegación ) */
    $paginaActual = 0;
    if ($queryString) {
        $nTemp = strpos($queryString, "PaginaActual=");
        if ($nTemp > 0) {
            $paginaActual = (int) substr($queryString, $nTemp + 13);
        }
    }
    if (!$paginaActual) {
        $paginaActual = por_defecto(si_es_key($_REQUEST, "PaginaActual"), mRequest("PaginaActual"));
        if ($paginaActual == "") {
            $paginaActual = 1;
        }
    }
    $rsTemp = mysql_query($cSql);
    $totalReg = mysql_num_rows($rsTemp);
    $cNavega = "";
    if ($totalReg == 0) {
        // no hay páginas
        $totalPaginas = 0;
        $paginaActual = 0;
    } elseif ($totalReg <= $tamPagina) {
        // solo hay una página
        $totalPaginas = 1;
        $paginaActual = 1;
    } else {
        // hay más de una página
        $totalPaginas = ceil($totalReg / $tamPagina);
        if ($paginaActual > $totalPaginas) {
            $paginaActual = $totalPaginas;
        }
        // calculamos las leyendas si hay
        if ($leyenda != "") {
            $npag = 1;
            $aCampos = explode(",", $leyenda);
            while ($npag <= $totalPaginas && ($aTemp = mysql_fetch_array($rsTemp))) {
                $cTemp = "";
                foreach ($aCampos as $tCampo) {
                    $cTemp .= $aTemp[trim($tCampo)] . " ";
                }
                $aLeyendas[$npag - 1] .= " - {$cTemp}";
                $aLeyendas[$npag] = $cTemp;
                /* TODO mirar error */
                @mysql_data_seek($rsTemp, $npag * $tamPagina);
                $npag++;
            }
            // falta el ultimo
            mysql_data_seek($rsTemp, $totalReg - 1);
            $aTemp = mysql_fetch_array($rsTemp, MYSQL_ASSOC);
            $cTemp = "";
            foreach ($aCampos as $tCampo) {
                $cTemp .= $aTemp[trim($tCampo)] . " ";
            }
            $aLeyendas[$totalPaginas] .= " - {$cTemp}";
        }
        // cambiamos la SQL.
        if (strstr(strtoupper($cSql), " LIMIT ") === false) {
            $cSql .= " LIMIT " . max(0, ($paginaActual - 1) * $tamPagina) . ",{$tamPagina}";
        }
        // Construir barra de navegacion
        $webBase = _paginacion_base($queryString);
        if ($totalPaginas <= $maxIndex) {
            // todas las páginas caben.
            for ($nCont = 1; $nCont <= $totalPaginas; $nCont++) {
                $temp = $leyenda ? " title='{$aLeyendas[$nCont]}'" : "";
                $cNavega .= "&nbsp;<a href='{$webBase}PaginaActual={$nCont}'{$temp} >{$nCont}</a>\n";
            }
        } else {
            // construir barra limitada
            if ($paginaActual > $totalPaginas - $maxIndex) {
                $nDesde = $totalPaginas - $maxIndex;
                $nHasta = $totalPaginas;
            } else {
                $nDesde = floor($paginaActual / $maxIndex) * $maxIndex + 1;
                $nHasta = $nDesde + $maxIndex - 1;
            }
            $nSiguiente = min($totalPaginas, $nDesde == 1 ? $maxIndex : $nDesde + $maxIndex);
            $nAnterior = max(1, $nDesde - $maxIndex);
            // boton de ir al principio (siempre 1)
            if ($nDesde > 1) {
                $temp = $leyenda ? " title='{$aLeyendas[1]}' " : "";
                $cNavega .= "<a class='paginacion_bottom' href='{$webBase}PaginaActual=1'{$temp};>&lt;&lt;</a>";
            }
            // boton de ir al anterior
            if ($nDesde != 1) {
                /* boton anterior */
                $temp = $leyenda ? " title='{$aLeyendas[$nAnterior]}' " : "";
                $cNavega .= "&nbsp;<a class='paginacion_anterior' href='{$webBase}PaginaActual={$nAnterior}'{$temp}>&lt;</a>";
            }
            // poner las paginas
            $cNavega .= "<span class='paginacion_paginas'>\n";
            for ($nCont = $nDesde; $nCont <= $nHasta; $nCont++) {
                $temp = $leyenda ? " title='{$aLeyendas[$nCont]}' " : "";
                $cNavega .= "&nbsp;<a href='{$webBase}PaginaActual={$nCont}'{$temp}>{$nCont}</a>\n";
            }
            $cNavega .= "</span>\n";
            // botón de siguiente
            if ($nHasta < $totalPaginas) {
                $temp = $leyenda ? " title='{$aLeyendas[$nSiguiente]}' " : "";
                $cNavega .= "&nbsp;<a class='paginacion_siguiente' href='{$webBase}PaginaActual={$nSiguiente}' {$temp}'>&gt;</a>\n";
            }
            // botón de ultimo
            if ($nHasta < $totalPaginas) {
                $temp = $leyenda ? " title='{$aLeyendas[$ntotalPaginas]}' " : "";
                $cNavega .= "&nbsp;<a class='paginacion_top' href='{$webBase}PaginaActual={$totalPaginas}' {$temp}>&gt;&gt;</a>\n";
            }
        }
    }
    if ($totalPaginas <= 0) {
        $cPagina = "&nbsp;";
    } else {
        // se genera la paginación. Ej: 5 de 6
        $cPagina = sprintf($leyendaPaginas, $paginaActual, $totalPaginas) . " |" . $cNavega;
    }
    $aTemp = array($totalReg, $totalPaginas, $cSql, $cPagina);
    return $aTemp;
}
function ddlib_edicion($aTabla, $cSQL = "", $aOpciones)
{
    /*
    
    aTabla
       acceso
       defecto
       verifica
       tipo
          separadortabla
          separador
          hidden
          fijo
          htmldespues
    
          adjunto directorio
          imagen directorio
    
          lista   ->[lista]
          listasql SQL
          listafuncion funcion_a_llamar
                     $formatolista
          checkbox
          checkboxes
               utiliza el campo etiquetas
    
          infofijo información
          infofuncion funcion
          readonly
          info
    
          htmlfijo HTML-bruto
          htmlfuncion funcion
    
          nuevopassword size max
    
          textos cols=40 fila=8
          cadena size=40 max=(size or 40)
    
          -->sino un input=text
       adicional
       atributos
       id
       clase
    
    cSQL
       si existe, es una modificación
       sino una inserción (registro nuevo
    
    aOpciones
       titulo
       tituloSinHTML
       metodo
       enviar
       hidden
       tablaID
       tablaClase
       prefijoId (campo)
       volver
    */
    $prefijoID = si_es_key($aOpciones, "prefijoID", "campo") . "_";
    // titulo
    $cResul = "";
    if (isset($aOpciones["titulo"])) {
        $cResul .= $aOpciones["tituloSinHTML"] == true ? $aOpciones["titulo"] : "<h2><span>{$aOpciones[titulo]}</span></h2>\n";
    }
    // Se usará mas adelante
    $cEnviar = por_defecto($aOpciones['enviar'], t("Enviar"));
    $cMetodo = por_defecto($aOpciones["metodo"], "post");
    $cEnctype = $cMetodo == "post" ? 'multipart/form-data' : 'application/x-www-form-urlencoded';
    $cResul .= "\n<form name='editar' method='{$cMetodo}' action='" . _ddlib_script() . "'  enctype='{$cEnctype}'  >\n";
    $cHidden = "";
    $aAfter = array();
    if (isset($aOpciones["hidden"])) {
        foreach ($aOpciones["hidden"] as $name => $value) {
            $cHidden .= "<input type='hidden' name='{$name}' value='{$value}' />\n";
        }
    }
    if (isset($aOpciones["siguienteAccion"])) {
        $temp = array("c", "a", "i", "order", "PaginaActual");
        foreach ($aOpciones["siguienteAccion"] as $i => $value) {
            $cHidden .= "<input type='hidden' name='" . $temp[$i] . "' value='{$value}' />\n";
        }
    }
    // se revisan los campos para ver ocultos, obligatorios y demás
    foreach ($aTabla as $k => $dd) {
        list($tipo) = explode(" ", $dd["tipo"]);
        $tipo = strtolower($tipo);
        // campos hidden
        if ($tipo == "hidden") {
            $cHidden .= sprintf("<input type='hidden' value='%s' name='%s'/>", $dd["value"] || $aDatos[$dd["campo"]], $dd['campo']);
        }
        if ($tipo == "htmldespues") {
            $aAfter[] = por_defecto($dd["funcion"], substr($dd["tipo"], 12));
        }
        // Buscamos campos obligatorios
        if ($aCelda["acceso"] !== false and $aCelda["verifica"] == "no_vacio") {
            $lObligatorio = true;
        }
    }
    if ($lObligatorio) {
        $cResul .= "<div class='obligatorio'>*" . t("beherrezkoa") . "</div>";
    }
    // calcular la cabecera (pie incluido ) y luego el cuerpo.
    $atributosTabla = "class='" . si_es_key($aOpciones, "tablaClase", "edicion") . "'";
    $ultimaTabla = "";
    // obtener los valores
    if ($cSQL != "") {
        /* en un modificación los valores se obtienen por una consulta SQL */
        $aDatos = mysql_query_registro($cSQL);
    } else {
        /* en una adición de obtienen con los valores por defecto */
        $aDatos = array();
        foreach ($aTabla as $dd) {
            if (isset($dd["campo"])) {
                $aDatos[$dd["campo"]] = isset($dd["defecto"]) ? $dd["defecto"] : "";
            } else {
                if (is_array($dd["campos"]) && is_array($dd["defectos"])) {
                    foreach ($dd["campos"] as $campo => $tipo) {
                        $aDatos[$campo] = $dd["defectos"]["campo"];
                    }
                }
            }
        }
    }
    // empieza el bucle para dibujar los valores
    $nCont = 1;
    $nTabla = 1;
    if ($aDatos) {
        $cTable = "";
        foreach ($aTabla as $k => $dd) {
            if ($dd["acceso"] === false) {
                continue;
            }
            // obtener el tipo de campo
            $aParametros = explode(" ", $dd["tipo"]);
            $cTipo = strtolower($aParametros[0]);
            switch ($cTipo) {
                // casos especiales
                case "htmldespues":
                case "hidden":
                case "fijo":
                    break;
                    // separador
                // separador
                case "separadortabla":
                    if ($ultimaTabla) {
                        $cTabla .= "\n</table>";
                    }
                    $ultimaTabla = por_defecto($aOpciones["tablaID"], "tabla-{$nCont}");
                    $nTabla++;
                    $cTabla .= "\n<table {$atributosTabla} id='{$ultimaTabla}'>\n";
                case "separador":
                    if (!$ultimaTabla) {
                        $ultimaTabla = por_defecto($aOpciones["tablaID"], "tabla-{$nCont}");
                        $cTabla .= "\n<table {$atributosTabla} id='{$ultimaTabla}'>\n";
                    }
                    $atributos = _ddlib_atributos($dd, "{$prefijoID}{$nCont}");
                    $cTabla .= "\n<tr class='separador'>\n";
                    $cTabla .= "<th colspan='2' {$atributos}>{$dd[cabecera]}</th></tr>\n";
                    break;
                    // adjuntos e imagenes
                // adjuntos e imagenes
                default:
                    if ($visualizar = ddlib_editarCampo($dd, $aDatos, "{$prefijoID}{$nCont}")) {
                        if (!$ultimaTabla) {
                            $ultimaTabla = por_defecto($aOpciones["tablaID"], "tabla-{$nCont}");
                            $cTabla .= "\n<table {$atributosTabla} id='{$ultimaTabla}'>\n";
                        }
                        $obligatorio = $dd["verifica"] == "no_vacio" ? " class='obligatorio'" : "";
                        $adicional = $dd["adicional"] ? "<span class='adicional'>{$dd[adicional]}</span>" : "";
                        if (isset($dd["campos"])) {
                            // los campos multiples no tienen etiqueta
                            $cTabla .= "\n <tr><th {$obligatorio}>{$dd[cabecera]}</th>";
                        } else {
                            $cTabla .= "\n <tr><th><label for='{$prefijoID}{$nCont}' {$obligatorio}>{$dd[cabecera]}</label></th>";
                        }
                        $cTabla .= "\n     <td>{$visualizar} {$adicional}</td></tr>";
                    }
            }
            $nCont++;
        }
        // fin del bucle foreach del dd
        // botones de Enviar y Volver.
        $cTabla .= "<tr><td class='botones-enviar' colspan='2'><input type='submit' value='{$cEnviar}' class='boton' />";
        if ($aOpciones["volver"] === true) {
            $cTabla .= "<a href='" . strtr($_SERVER["HTTP_REFERER"], array("&" => "&amp;")) . "' class='boton'>Volver</a>";
        }
        $cTabla .= "</td></tr>\n";
    }
    $cResul .= $cTabla . "</table>\n" . $cHidden . "\n" . "</form>\n";
    foreach ($aAfter as $funcion) {
        $cResul .= call_user_func($funcion, $aDatos);
    }
    return $cResul;
}
/**
* Devuelve un control según el tipo especificado)
*/
function mControl($dd, $opciones = false)
{
    if (!$opciones) {
        $opciones = array();
    }
    $id = isset($dd['id']) ? $dd['id'] : $dd['campo'];
    $valor = isset($opciones['valor']) ? $opciones['valor'] : false;
    $parametros = explode(" ", $dd["tipo"]);
    $tipo = $parametros[0];
    $atributos = array("id" => $id, "name" => $dd['campo']);
    if (isset($dd['clase'])) {
        $atributos['class'] = $dd['clase'];
    }
    if (isset($opciones['valor']) && $tipo != "texto") {
        $atributos['value'] = $opciones['valor'];
    }
    if (isset($dd['atributos']) && is_array($dd['atributos'])) {
        $atributos = $atributos + $dd["atributos"];
    }
    $cAtributos = mImplode(" %s='%s'", $atributos);
    $resto = substr($dd["tipo"], strpos($dd["tipo"], " ") + 1);
    switch ($tipo) {
        case "numero":
            if (isset($parametros[1])) {
                $min = si_es_key($parametros, 1, 0);
                $max = si_es_key($parametros, 2, 10);
                $campo = "<select {$cAtributos}>\n";
                for ($opcion = $min; $opcion < $max; $opcion++) {
                    $selected = $valor == $opcion ? ' selected' : '';
                    $campo .= "<option value='{$opcion}'{$selected}>{$opcion}</option>\n";
                }
                $campo .= "</select>\n";
            } else {
                $campo = "<input type='text' {$cAtributos}>";
            }
            break;
        case "url":
            $max = si_es_key($parametros, 1, 30);
            $size = si_es_key($parametros, 2, $max);
            $campo = "<input type='text' size='{$size}' maxlength='{$max}' {$cAtributos} >";
            break;
        case "cadena":
            $max = si_es_key($parametros, 1, 30);
            $size = si_es_key($parametros, 2, $max);
            $campo = "<input type='text' size='{$size}' maxlength='{$max}' {$cAtributos} >";
            break;
        case "texto":
            $cols = si_es_key($parametros, 1, 60);
            $filas = si_es_key($parametros, 2, 8);
            $campo = "<textarea cols='{$cols}' rows='{$filas}' {$cAtributos}>{$valor}</textarea>";
            break;
        case "lista":
            $lista = mExplode("|", ":", $resto);
            if (count($lista) < 5) {
                $campo = "";
                unset($atributos['id']);
                $cAtributos = mImplode(" %s='%s'", $atributos);
                foreach ($lista as $opcion => $opcionLabel) {
                    $checked = $valor == $opcion ? ' checked' : '';
                    $campo .= "<input value='{$opcion}' type='radio'{$checked} {$cAtributos}><span class='radio-label'> {$opcionLabel} </span>";
                }
            } else {
                $campo = "<select {$cAtributos}>\n";
                foreach ($lista as $opcion => $opcionLabel) {
                    $selected = $valor == $opcion ? ' selected' : '';
                    $campo .= "<option value='{$opcion}'{$selected}>{$opcionLabel}</option>\n";
                }
                $campo .= "</select>\n";
            }
            break;
        case "checkbox":
        case "booleano":
            $value = si_es_key($aOpciones, "value", 1);
            $campo = "<input type='checkbox' {$atributos}" . ($valor ? " selected " : "") . ">";
            break;
    }
    if (isset($dd["adicional"])) {
        $campo .= " <span class='control-adicional'>{$dd['adicional']}</span>";
    }
    // construir la label
    $etiqueta = isset($dd["cabecera"]) ? $dd["cabecera"] : $dd["campo"];
    if (isset($dd["obligatorio"])) {
        $label = "<label for='{$id}' class='control-obligatorio'>{$etiqueta}<span class='control-obligatorio'>(*)</span></label>";
    } else {
        $label = "<label for='{$id}'>{$etiqueta}</label>";
    }
    // maquetar label y control según estilo
    $estilos = array("dl" => "<dt>%s</dt>\n<dd>%s</dd>", "p" => '<p>%s %s</p>', "p-br" => '<p>%s <br> %s</p>', "table" => '<tr><th>%s</th><td>%s</td></tr>');
    if (!isset($opciones["estilo"]) || !isset($estilos[$opciones['estilo']])) {
        $estilo = $estilos['dl'];
    } else {
        $estilo = $estilos[$opciones['estilo']];
    }
    return "\n" . sprintf($estilo, $label, $campo);
}
function maquetador_extrae_modulo($cadena)
{
    /* @TODO mejorar expresiones regulares para dobles espacios */
    $cadena = strtr($cadena, array("<%" => "", "%>" => ""));
    $cuando = "";
    if (preg_match('/WHEN ([^ ]*) (PUT|PHP) (.*)/i', $cadena, $aTemp)) {
        return array("modulo" => $aTemp[2], "accion" => $aTemp[3], "condicional" => $aTemp[1]);
    } elseif (preg_match("/WHEN (CONTROLADOR:(?:[^ ]*)(?: ACCION:(?:[^ ].*))?) (.*)/i", $cadena, $aTemp)) {
        $cuando = $aTemp[1];
        $cadena = $aTemp[2];
    } elseif (preg_match('/WHEN ([^ ]*) (.*)/i', $cadena, $aTemp)) {
        $cuando = $aTemp[1];
        $cadena = $aTemp[2];
    } elseif (preg_match('/HOME (.*)/i', $cadena, $aTemp)) {
        $cuando = "home";
        $cadena = $aTemp[1];
    } elseif (preg_match('/ONCE (.*)/i', $cadena, $aTemp)) {
        $cuando = "once " . $aTemp[1];
        $cadena = $aTemp[1];
    }
    $aTemp = preg_split("#[,\\(\\)]#", $cadena);
    return array("modulo" => $aTemp[0], "accion" => si_es_key($aTemp, 1), "id" => si_es_key($aTemp, 2), "condicional" => $cuando);
}