Módulo 3: Conversión de HTML a Theme de Grav

Introducción

En este módulo aprenderás a transformar cualquier plantilla HTML estática en un theme funcional de Grav. El proceso requiere analizar el HTML, identificar componentes reutilizables y reorganizarlo siguiendo la estructura de Grav.


1. Análisis Estructural Profundo de Plantillas HTML

1.1 Teoría: Entendiendo la Estructura HTML

Antes de convertir una plantilla HTML a Grav, debes analizar su anatomía para entender:

  1. Estructura general: ¿Qué secciones tiene? (header, main, sidebar, footer)
  2. Elementos repetidos: ¿Qué aparece en todas las páginas?
  3. Elementos variables: ¿Qué cambia entre páginas?
  4. Dependencias: ¿Qué librerías CSS/JS usa?

Anatomía típica de una plantilla HTML:

<!DOCTYPE html>
<html>
<head>
    <!-- Meta tags, título, CSS -->
</head>
<body>
    <header>
        <!-- Logo, navegación -->
    </header>

    <main>
        <!-- Contenido principal (cambia por página) -->
    </main>

    <aside>
        <!-- Sidebar (opcional) -->
    </aside>

    <footer>
        <!-- Copyright, enlaces -->
    </footer>

    <!-- Scripts JS -->
</body>
</html>

Pregunta clave: ¿Qué partes son fijas (iguales en todas las páginas) y qué partes son dinámicas (cambian según la página)?

1.2 Ejemplo Práctico: Analizando una Plantilla Real

Supongamos que tienes esta plantilla HTML simple:

index.html (original):

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mi Sitio Web - Inicio</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>

    <!-- Header -->
    <header class="site-header">
        <div class="container">
            <div class="logo">
                <img src="images/logo.png" alt="Logo">
            </div>
            <nav class="main-nav">
                <ul>
                    <li><a href="index.html">Inicio</a></li>
                    <li><a href="about.html">Nosotros</a></li>
                    <li><a href="services.html">Servicios</a></li>
                    <li><a href="contact.html">Contacto</a></li>
                </ul>
            </nav>
        </div>
    </header>

    <!-- Hero Section -->
    <section class="hero">
        <div class="container">
            <h1>Bienvenido a Nuestro Sitio</h1>
            <p>Ofrecemos las mejores soluciones para tu negocio</p>
            <a href="#" class="btn btn-primary">Comenzar</a>
        </div>
    </section>

    <!-- Content -->
    <main class="content">
        <div class="container">
            <h2>Nuestros Servicios</h2>
            <div class="row">
                <div class="col-md-4">
                    <div class="service-card">
                        <h3>Diseño Web</h3>
                        <p>Creamos sitios web modernos y responsive</p>
                    </div>
                </div>
                <div class="col-md-4">
                    <div class="service-card">
                        <h3>SEO</h3>
                        <p>Optimizamos tu posicionamiento en buscadores</p>
                    </div>
                </div>
                <div class="col-md-4">
                    <div class="service-card">
                        <h3>Marketing</h3>
                        <p>Estrategias efectivas para tu marca</p>
                    </div>
                </div>
            </div>
        </div>
    </main>

    <!-- Footer -->
    <footer class="site-footer">
        <div class="container">
            <p>&copy; 2024 Mi Sitio Web. Todos los derechos reservados.</p>
            <div class="social-links">
                <a href="#">Facebook</a>
                <a href="#">Twitter</a>
                <a href="#">Instagram</a>
            </div>
        </div>
    </footer>

    <script src="js/jquery.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

Análisis de la estructura:

Elemento Tipo Observación
<head> Fijo Mismo en todas las páginas, solo cambia <title>
<header> Fijo Logo y navegación idénticos en todo el sitio
Hero Section Variable Solo en homepage, no en otras páginas
<main> Variable Contenido diferente en cada página
<footer> Fijo Igual en todas las páginas
Scripts Fijo Mismas librerías JS en todo el sitio

Conclusión del análisis:

  • Elementos fijos → Irán en el template base (base.html.twig)
  • Elementos variables → Irán en templates específicos que extienden el base
  • Hero section → Puede ser un partial opcional

2. Identificación Sistemática de Componentes Reutilizables

2.1 Teoría: ¿Qué es un Componente Reutilizable?

Un componente reutilizable es una porción de HTML que:

  1. Se repite en múltiples páginas (header, footer, navegación)
  2. Puede incluirse en diferentes contextos (tarjetas, botones, modales)
  3. Mantiene su estructura pero puede cambiar su contenido

Tipos de componentes en Grav:

  • Partials: Fragmentos incluibles con {% include %}

    • Ejemplos: header, footer, navegación, breadcrumbs
  • Bloques: Secciones sobrescribibles con {% block %}

    • Ejemplos: content, stylesheets, javascripts
  • Macros: Funciones Twig reutilizables (avanzado)

    • Ejemplos: botones, badges, alerts

2.2 Identificando Componentes en el HTML

Revisa tu plantilla y pregúntate:

¿Se repite este código en otras páginas? → Partial
¿Tiene una función específica y aislada? → Partial
¿Su contenido varía pero su estructura no? → Partial con parámetros
¿Aparece solo en ciertas páginas? → Incluir condicionalmente

2.3 Ejemplo Práctico: Extrayendo Componentes

Del HTML anterior, identificamos:

1. Header (componente fijo):

<header class="site-header">
    <div class="container">
        <div class="logo">
            <img src="images/logo.png" alt="Logo">
        </div>
        <nav class="main-nav">
            <ul>
                <li><a href="index.html">Inicio</a></li>
                <li><a href="about.html">Nosotros</a></li>
                <li><a href="services.html">Servicios</a></li>
                <li><a href="contact.html">Contacto</a></li>
            </ul>
        </nav>
    </div>
</header>

Acción: Crear templates/partials/header.html.twig

2. Footer (componente fijo):

<footer class="site-footer">
    <div class="container">
        <p>&copy; 2024 Mi Sitio Web. Todos los derechos reservados.</p>
        <div class="social-links">
            <a href="#">Facebook</a>
            <a href="#">Twitter</a>
            <a href="#">Instagram</a>
        </div>
    </div>
</footer>

Acción: Crear templates/partials/footer.html.twig

3. Service Card (componente repetido):

<div class="col-md-4">
    <div class="service-card">
        <h3>Diseño Web</h3>
        <p>Creamos sitios web modernos y responsive</p>
    </div>
</div>

Acción: Este patrón se repite 3 veces → Puede convertirse en un bucle con datos dinámicos

4. Hero Section (componente opcional):

<section class="hero">
    <div class="container">
        <h1>Bienvenido a Nuestro Sitio</h1>
        <p>Ofrecemos las mejores soluciones para tu negocio</p>
        <a href="#" class="btn btn-primary">Comenzar</a>
    </div>
</section>

Acción: Crear templates/partials/hero.html.twig e incluir solo si existe


3. Separación Estratégica en Secciones y Layouts

3.1 Teoría: Estructura de Templates en Grav

La separación estratégica sigue esta jerarquía:

base.html.twig              ← Estructura HTML común (skeleton)
    ↓ extends
default.html.twig           ← Template genérico (páginas simples)
    ↓ extends
blog.html.twig              ← Template específico (listado blog)
item.html.twig              ← Template específico (artículo individual)

Principio de separación:

  1. Base template: HTML común (DOCTYPE, head, body, header, footer)
  2. Layout templates: Estructuras de página (1 columna, 2 columnas, grid)
  3. Specific templates: Páginas específicas (homepage, blog, contacto)
  4. Partials: Componentes reutilizables (header, footer, cards)

3.2 Ejemplo Práctico: Creando la Estructura

Paso 1: Template Base

templates/partials/base.html.twig:

<!DOCTYPE html>
<html lang="{{ grav.language.getActive ?: 'es' }}">
<head>
    {% block head %}
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <title>
            {% if page.title %}{{ page.title }} | {% endif %}{{ site.title }}
        </title>

        {% block stylesheets %}
            {{ assets.css()|raw }}
        {% endblock %}
    {% endblock head %}
</head>
<body>

    {% block header %}
        {% include 'partials/header.html.twig' %}
    {% endblock %}

    {% block hero %}
        {# Hero opcional - se define en templates específicos #}
    {% endblock %}

    {% block content %}
        {# Contenido principal - obligatorio en todos los templates #}
    {% endblock %}

    {% block footer %}
        {% include 'partials/footer.html.twig' %}
    {% endblock %}

    {% block javascripts %}
        {{ assets.js()|raw }}
    {% endblock %}

</body>
</html>

Paso 2: Template Default (páginas simples)

templates/default.html.twig:

{% extends 'partials/base.html.twig' %}

{% block content %}
    <main class="content">
        <div class="container">
            <article class="page">
                <h1>{{ page.title }}</h1>
                <div class="page-content">
                    {{ page.content|raw }}
                </div>
            </article>
        </div>
    </main>
{% endblock %}

Paso 3: Template Homepage (con hero)

templates/home.html.twig:

{% extends 'partials/base.html.twig' %}

{% block hero %}
    {% include 'partials/hero.html.twig' %}
{% endblock %}

{% block content %}
    <main class="content">
        <div class="container">
            <h2>Nuestros Servicios</h2>
            <div class="row">
                {% for service in page.header.services %}
                    <div class="col-md-4">
                        <div class="service-card">
                            <h3>{{ service.title }}</h3>
                            <p>{{ service.description }}</p>
                        </div>
                    </div>
                {% endfor %}
            </div>
        </div>
    </main>
{% endblock %}

Paso 4: Partials

templates/partials/header.html.twig:

<header class="site-header">
    <div class="container">
        <div class="logo">
            {% if theme_config.logo %}
                <img src="{{ theme_config.logo }}" alt="{{ site.title }}">
            {% else %}
                <span class="site-title">{{ site.title }}</span>
            {% endif %}
        </div>
        <nav class="main-nav">
            <ul>
                {% for item in pages.children.visible %}
                    <li class="{% if item.active %}active{% endif %}">
                        <a href="{{ item.url }}">{{ item.menu }}</a>
                    </li>
                {% endfor %}
            </ul>
        </nav>
    </div>
</header>

templates/partials/footer.html.twig:

<footer class="site-footer">
    <div class="container">
        <p>&copy; {{ current_year }} {{ site.title }}. Todos los derechos reservados.</p>

        {% if theme_config.social_links %}
            <div class="social-links">
                {% for social in theme_config.social_links %}
                    <a href="{{ social.url }}" target="_blank">{{ social.platform }}</a>
                {% endfor %}
            </div>
        {% endif %}
    </div>
</footer>

templates/partials/hero.html.twig:

<section class="hero">
    <div class="container">
        <h1>{{ page.header.hero.title }}</h1>
        <p>{{ page.header.hero.subtitle }}</p>
        {% if page.header.hero.button %}
            <a href="{{ page.header.hero.button.url }}" class="btn btn-primary">
                {{ page.header.hero.button.text }}
            </a>
        {% endif %}
    </div>
</section>

4. Inventario Completo de Assets y Dependencias

4.1 Teoría: Catalogando Assets

Antes de migrar, debes hacer un inventario completo de todos los recursos:

Tipos de assets:

  1. CSS: Frameworks (Bootstrap), archivos personalizados, fuentes
  2. JavaScript: Librerías (jQuery), plugins, scripts propios
  3. Imágenes: Logo, iconos, imágenes de contenido
  4. Fuentes: Custom fonts (WOFF, WOFF2)
  5. Otros: Videos, PDFs, archivos descargables

Preguntas clave:

  • ¿Qué librerías externas usa? (CDN o local)
  • ¿Qué assets son críticos para la primera carga?
  • ¿Qué puede cargarse de forma diferida?
  • ¿Hay dependencias entre archivos? (jQuery antes de Bootstrap)

4.2 Ejemplo Práctico: Inventario de la Plantilla

Del HTML original, identificamos:

CSS:

1. css/bootstrap.min.css      → Framework CSS (puede usar CDN)
2. css/style.css              → Estilos personalizados (migrar a theme)

JavaScript:

1. js/jquery.min.js           → Librería externa (CDN recomendado)
2. js/bootstrap.min.js        → Depende de jQuery
3. js/main.js                 → Script personalizado (migrar a theme)

Imágenes:

1. images/logo.png            → Logo del sitio
2. images/*                   → Otras imágenes (migrar a theme/images/)

Organización en Grav:

mi-theme/
├── css/
│   └── style.css            ← css/style.css (renombrado)
├── js/
│   └── main.js              ← js/main.js
├── images/
│   └── logo.png             ← images/logo.png
└── fonts/                   ← Si hay fuentes personalizadas

4.3 Ejemplo: Registrando Assets en PHP

mi-theme.php:

<?php
namespace Grav\Theme;

use Grav\Common\Theme;

class MiTheme extends Theme
{
    public static function getSubscribedEvents()
    {
        return [
            'onThemeInitialized' => ['onThemeInitialized', 0]
        ];
    }

    public function onThemeInitialized()
    {
        if (!$this->isAdmin()) {
            $this->enable([
                'onTwigSiteVariables' => ['onTwigSiteVariables', 0]
            ]);
        }
    }

    public function onTwigSiteVariables()
    {
        // === CSS ===

        // Bootstrap desde CDN
        $this->grav['assets']->addCss(
            'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css',
            ['priority' => 100]
        );

        // CSS personalizado
        $this->grav['assets']->addCss('theme://css/style.css', ['priority' => 90]);

        // === JavaScript ===

        // jQuery desde CDN
        $this->grav['assets']->addJs(
            'https://code.jquery.com/jquery-3.6.0.min.js',
            ['priority' => 100, 'group' => 'bottom']
        );

        // Bootstrap JS (depende de jQuery)
        $this->grav['assets']->addJs(
            'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js',
            ['priority' => 90, 'group' => 'bottom']
        );

        // Script personalizado (se carga último)
        $this->grav['assets']->addJs(
            'theme://js/main.js',
            ['priority' => 80, 'group' => 'bottom']
        );
    }
}

Ventajas de usar CDN:

  • Menor carga en tu servidor
  • Usuarios pueden tenerlo en caché
  • Actualización centralizada

Cuándo usar archivos locales:

  • Intranet sin acceso a internet
  • Control total sobre versiones
  • Customización de librerías

5. Organización Optimizada para Desarrollo en Grav

5.1 Teoría: Mejores Prácticas de Organización

Una buena organización facilita:

  • Mantenimiento: Encontrar y editar código rápidamente
  • Escalabilidad: Añadir nuevas funcionalidades sin romper nada
  • Colaboración: Otros desarrolladores entienden la estructura
  • Performance: Assets optimizados y carga eficiente

Principios de organización:

  1. Separación de responsabilidades: Cada archivo hace una cosa
  2. Nomenclatura consistente: Nombres claros y predecibles
  3. Estructura modular: Componentes independientes
  4. Documentación básica: README con instrucciones

5.2 Estructura Final Recomendada

mi-theme/
├── blueprints.yaml              # Configuración Admin
├── mi-theme.yaml                # Valores por defecto
├── mi-theme.php                 # Lógica PHP
├── README.md                    # Documentación
├── thumbnail.jpg                # Preview del theme
│
├── templates/
│   ├── partials/
│   │   ├── base.html.twig       # Template base
│   │   ├── header.html.twig     # Cabecera
│   │   ├── footer.html.twig     # Pie de página
│   │   ├── hero.html.twig       # Hero section
│   │   └── navigation.html.twig # Menú (si es complejo)
│   │
│   ├── default.html.twig        # Páginas generales
│   ├── home.html.twig           # Homepage
│   ├── blog.html.twig           # Listado de blog
│   └── item.html.twig           # Artículo individual
│
├── css/
│   ├── style.css                # Estilos principales
│   └── components/              # (opcional) CSS por componente
│       ├── header.css
│       ├── footer.css
│       └── cards.css
│
├── js/
│   ├── main.js                  # Script principal
│   └── components/              # (opcional) JS por componente
│       └── menu.js
│
├── images/
│   ├── logo.png
│   └── icons/
│       └── ...
│
└── fonts/                       # (opcional) Fuentes custom
    └── custom-font.woff2

5.3 Ejemplo: Workflow de Conversión Completo

Paso a paso para convertir HTML a Grav:

1. Preparación:

# Crear theme
cd user/themes/
mkdir mi-theme
cd mi-theme

# Crear estructura
mkdir -p templates/partials css js images

2. Migrar assets:

# Copiar archivos del HTML original
cp /ruta/plantilla-html/css/style.css css/
cp /ruta/plantilla-html/js/main.js js/
cp -r /ruta/plantilla-html/images/* images/

3. Crear configuración básica:

blueprints.yaml:

name: Mi Theme
version: 1.0.0
description: "Theme convertido desde HTML"
author:
  name: Tu Nombre

mi-theme.yaml:

enabled: true
logo: ''

4. Convertir HTML a Twig:

  • Copiar el HTML original
  • Identificar secciones (header, main, footer)
  • Crear base.html.twig con estructura común
  • Extraer partials (header, footer)
  • Crear templates específicos (home, default)
  • Reemplazar contenido estático por variables Twig

5. Registrar assets:

Crear mi-theme.php y registrar CSS/JS como vimos anteriormente.

6. Configurar página de inicio:

user/pages/01.home/home.md:

---
title: Inicio
hero:
    title: Bienvenido a Nuestro Sitio
    subtitle: Ofrecemos las mejores soluciones
    button:
        text: Comenzar
        url: /servicios

services:
    - title: Diseño Web
      description: Creamos sitios modernos
    - title: SEO
      description: Optimizamos tu posicionamiento
    - title: Marketing
      description: Estrategias efectivas
---

# Contenido adicional de la página

Aquí puedes escribir más contenido que aparecerá después de los servicios.

7. Activar y probar:

# En Admin → Themes → Mi Theme → Activar
# O editar user/config/system.yaml:
pages:
  theme: mi-theme

5.4 Checklist de Conversión

Assets migrados:

  • [ ] CSS copiados y registrados
  • [ ] JavaScript copiados y registrados
  • [ ] Imágenes en carpeta correcta
  • [ ] Fuentes (si las hay)

Templates creados:

  • [ ] base.html.twig funcional
  • [ ] Partials (header, footer) extraídos
  • [ ] default.html.twig para páginas simples
  • [ ] Templates específicos (home, blog, etc.)

Configuración:

  • [ ] blueprints.yaml con metadatos
  • [ ] [theme].yaml con valores por defecto
  • [ ] [theme].php registra assets correctamente

Contenido dinámico:

  • [ ] Variables Twig reemplazan texto estático
  • [ ] Navegación generada desde páginas
  • [ ] Imágenes usan rutas dinámicas

Testing:

  • [ ] Todas las páginas se ven correctamente
  • [ ] CSS/JS cargan sin errores
  • [ ] Responsive funciona
  • [ ] Sin enlaces rotos

Ejercicio Práctico: Conversión Completa

Objetivo: Convertir una plantilla HTML Bootstrap en un theme de Grav.

Plantilla de partida: Landing page simple con:

  • Header con logo y menú
  • Hero section con imagen de fondo
  • Sección de 3 características
  • Footer con redes sociales

Pasos:

  1. Descargar plantilla HTML gratuita (ej. Bootstrap templates)
  2. Crear estructura del theme en Grav
  3. Identificar componentes reutilizables
  4. Crear base.html.twig y partials
  5. Registrar assets en PHP
  6. Configurar página de inicio con contenido dinámico
  7. Probar responsive y funcionalidad

Resumen del Módulo

Has aprendido a:

Analizar estructura HTML: Identificar elementos fijos vs variables
Extraer componentes: Crear partials reutilizables
Separar en layouts: Sistema de herencia con base/templates específicos
Inventariar assets: Catalogar CSS, JS, imágenes y dependencias
Organizar eficientemente: Estructura óptima para desarrollo en Grav