Blog / Portafolio — Arquitectura con AltoRouter

Este blog funciona como portafolio técnico y hub de documentación, construido en PHP utilizando AltoRouter para el enrutamiento y una estructura de carpetas por categorías dentro de /blog. Cada post es un archivo PHP independiente con metadatos internos, y el sistema genera de forma dinámica: rutas, listados, búsqueda, paginación y URLs limpias.

Ver blog en vivo

Objetivo del sistema

Diseñar un blog-portafolio que cumpla con:

Tecnologías y herramientas

Estructura de carpetas

public_html/
└── web/
    └── jair-rivera.qodexia.site/
        ├── blog/
        │   ├── react/
        │   │   ├── index.php
        │   │   └── <post-react-*.php>
        │   ├── php/
        │   ├── docker/
        │   ├── laravel/
        │   ├── vps-hosting/
        │   ├── webs/
        │   │   ├── index.php
        │   │   └── planeta-fiscal.php
        │   │   └── real-estate-demo.php
        │   ├── _init.php
        │   ├── _sidebar.php
        │   └── index.php        // listado general del blog
        ├── routes/
        │   ├── categories.php
        │   ├── posts-react.php
        │   ├── posts-php.php
        │   ├── posts-docker.php
        │   ├── posts-laravel.php
        │   ├── posts-vps-hosting.php
        │   └── posts-webs.php
        ├── home.php
        ├── index.php            // Front controller con AltoRouter
        ├── composer.json
        └── vendor/              // AltoRouter + autoload

Flujo 1 — Enrutamiento principal con AltoRouter

El archivo index.php en la raíz actúa como front controller:

// Configuración base
require __DIR__ . '/vendor/autoload.php';

$router = new AltoRouter();
$router->setBasePath('');

// Rutas base
$router->map('GET', '/', function () {
    require __DIR__ . '/home.php';
});

$router->map('GET', '/blog', function () {
    require __DIR__ . '/blog/index.php';
});

// Rutas externas (categorías + posts)
require __DIR__ . '/routes/categories.php';
require __DIR__ . '/routes/posts-react.php';
require __DIR__ . '/routes/posts-php.php';
require __DIR__ . '/routes/posts-docker.php';
require __DIR__ . '/routes/posts-laravel.php';
require __DIR__ . '/routes/posts-vps-hosting.php';
require __DIR__ . '/routes/posts-webs.php';

// Resolución de la ruta
$match = $router->match();

if ($match && is_callable($match['target'])) {
    call_user_func_array($match['target'], $match['params']);
} else {
    http_response_code(404);
    echo "<p>404 — Página no encontrada</p>";
}

De esta forma, el router:

Flujo 2 — Rutas dinámicas para posts por categoría

Cada archivo routes/posts-*.php se encarga de registrar las rutas de los posts de una categoría, leyendo los archivos de la carpeta correspondiente:

// Ejemplo: routes/posts-webs.php

$categoryFolder = 'webs';
$categoryBase   = '/blog/' . $categoryFolder;
$postsDir       = __DIR__ . '/../blog/' . $categoryFolder;

$files = glob($postsDir . '/*.php') ?: [];

foreach ($files as $filePath) {
    $file = basename($filePath);

    if (in_array(strtolower($file), ['index.php'])) {
        continue; // no es un post
    }

    $code = @file_get_contents($filePath);
    if ($code === false) continue;

    // 1) Obtener $postSlug desde el archivo del post
    $slug = null;
    if (preg_match('/\$postSlug\s*=\s*[\'"](.*?)[\'"]\s*;/i', $code, $m)) {
        $slug = trim($m[1]);
    }

    // Fallback: usar nombre de archivo sin .php
    if ($slug === null || $slug === '') {
        $slug = basename($file, '.php');
    }

    $slug = trim($slug, "/ \t\n\r\0\x0B");

    // 2) Registrar ruta dinámica en AltoRouter
    $router->map('GET', $categoryBase . '/' . $slug, function () use ($categoryFolder, $file) {
        require __DIR__ . '/../blog/' . $categoryFolder . '/' . $file;
    });
}

Esto permite que:

Flujo 3 — Metadatos dentro de cada post

Cada post define su propio slug y fecha al inicio del archivo, antes del HTML:

<?php
$postSlug = 'planeta-fiscal-demo';
$postDate = '2025-11-18';
?>
<!DOCTYPE html>
<html lang="es">
<head>
    <title>Planeta Fiscal — Sitio demo contable</title>
    <meta name="description"
          content="Landing y sitio comercial para una firma contable con planes y membresías." />
    ...
</head>

Estos metadatos son reutilizados en:

Flujo 4 — Listado, búsqueda y paginación por categoría

Dentro de cada carpeta de categoría, el archivo index.php se encarga de:

A nivel conceptual el flujo es:

// 1. Cargar posts
$files = glob($postsDir . '/*.php') ?: [];
$posts = [];

// 2. Recorrer archivos, extraer metadatos, slug y fecha
// 3. Ordenar por timestamp descendente
usort($posts, fn($a, $b) => $b['timestamp'] <=> $a['timestamp']);

// 4. Si hay búsqueda (?q=), filtrar por título / descripción / slug
// 5. Si NO hay búsqueda, aplicar paginación (10 por página)
// 6. Pasar $postsPage al template para pintarlos

Flujo 5 — Interfaz del listado y sidebar reutilizable

El HTML de cada index.php de categoría:

Resultados y aprendizajes

Este sistema de blog demuestra:

Regresar