Files
FrankZamora b79569c5e7 docs: add templates-unificados openspec specification
Defines unified listing templates architecture.

Key additions:
- Two new components: archive-header, post-grid
- 10-phase implementation sequence
- _page_visibility group pattern
- Graceful handling of missing content
- CSS pagination via CSSGeneratorService

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 19:14:09 -06:00

21 KiB

Especificacion de Templates Unificados para Blog/Archive

Purpose

Define la arquitectura para unificar todos los templates de listados (blog, categorias, tags, archives) usando la misma estructura que single.php, aprovechando el sistema de visibilidad existente para controlar que componentes mostrar en cada contexto. Incluye la creacion de dos nuevos componentes: archive-header y post-grid.

Requirements

Requirement: Template Unificado para Listados

All listing templates MUST use the same structure as single.php.

Scenario: Estructura base de templates de listado

  • WHEN se implementa home.php, archive.php, category.php o tag.php
  • THEN DEBE usar la misma estructura que single.php
  • AND DEBE llamar a roi_render_component() para cada componente
  • AND la visibilidad se controla via PageVisibilityHelper::shouldShow()

Scenario: Componentes que se llaman en templates de listado

  • WHEN se renderiza un template de listado
  • THEN DEBE llamar a roi_render_component('hero')
  • AND DEBE llamar a roi_render_component('archive-header')
  • AND DEBE llamar a roi_render_component('post-grid')
  • AND DEBE llamar a roi_render_component('table-of-contents') en sidebar
  • AND DEBE llamar a roi_render_component('cta-box-sidebar') en sidebar
  • AND DEBE llamar a roi_render_component('contact-form')
  • AND cada componente decide si renderiza segun show_on_archives

Scenario: Determinacion de sidebar en listados

  • WHEN se determina si mostrar sidebar en un listado
  • THEN DEBE usar roi_should_render_any_wrapper(['table-of-contents', 'cta-box-sidebar'])
  • AND si retorna true usar col-lg-9 para contenido principal
  • AND si retorna false usar col-lg-12 para contenido principal

Scenario: Paginacion en templates de listado

  • WHEN se muestra paginacion en un listado
  • THEN DEBE usar the_posts_pagination() de WordPress
  • AND DEBE aplicar estilos Bootstrap via CSS del componente post-grid

Scenario: CSS de paginacion generado por post-grid

  • WHEN PostGridRenderer renderiza la paginacion
  • THEN el CSS de paginacion DEBE generarse via CSSGeneratorService
  • AND DEBE aplicar estilos Bootstrap (nav-links, page-numbers)
  • AND los colores DEBEN ser configurables via grupo colors del schema

Requirement: Componente archive-header

The archive-header component MUST display dynamic title and description for archive pages.

Scenario: Ubicacion de archivos archive-header

  • WHEN se crea el componente archive-header
  • THEN schema DEBE estar en Schemas/archive-header.json
  • AND Renderer DEBE estar en Public/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderRenderer.php
  • AND FormBuilder DEBE estar en Admin/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderFormBuilder.php
  • AND FieldMapper DEBE estar en Admin/ArchiveHeader/Infrastructure/FieldMapping/ArchiveHeaderFieldMapper.php

Scenario: Namespaces PHP de archive-header

  • WHEN se definen los namespaces para archive-header
  • THEN Renderer DEBE usar namespace ROITheme\Public\ArchiveHeader\Infrastructure\Ui
  • AND FormBuilder DEBE usar namespace ROITheme\Admin\ArchiveHeader\Infrastructure\Ui
  • AND FieldMapper DEBE usar namespace ROITheme\Admin\ArchiveHeader\Infrastructure\FieldMapping

Scenario: Deteccion automatica de tipo de archivo

  • WHEN ArchiveHeaderRenderer detecta el tipo de pagina
  • THEN para categoria DEBE mostrar "Categoria: [nombre]"
  • AND para tag DEBE mostrar "Etiqueta: [nombre]"
  • AND para autor DEBE mostrar "Articulos de: [nombre]"
  • AND para fecha DEBE mostrar "Archivo: [Mes Ano]"
  • AND para busqueda DEBE mostrar "Resultados para: [termino]"
  • AND para blog home DEBE mostrar el valor de blog_title del schema

Scenario: Grupos del schema archive-header

  • WHEN se define el schema archive-header.json
  • THEN DEBE incluir grupo visibility con priority 10
  • AND DEBE incluir grupo content con priority 20
  • AND DEBE incluir grupo typography con priority 30
  • AND DEBE incluir grupo colors con priority 40
  • AND DEBE incluir grupo spacing con priority 50
  • AND DEBE incluir grupo behavior con priority 70
  • NOTE archive-header NO incluye visual_effects (priority 60) porque es un componente de texto simple sin sombras, bordes redondeados ni transiciones hover

Scenario: Campos obligatorios de visibility en archive-header

  • WHEN se define grupo visibility en schema
  • THEN DEBE incluir is_enabled como boolean con default true
  • AND DEBE incluir show_on_desktop como boolean con default true
  • AND DEBE incluir show_on_mobile como boolean con default true

Scenario: Campos de _page_visibility en archive-header

  • WHEN se configura visibilidad por tipo de pagina en FieldMapper
  • THEN DEBE mapear campo show_on_home en grupo _page_visibility con default false
  • AND DEBE mapear campo show_on_posts en grupo _page_visibility con default false
  • AND DEBE mapear campo show_on_pages en grupo _page_visibility con default false
  • AND DEBE mapear campo show_on_archives en grupo _page_visibility con default true
  • AND DEBE mapear campo show_on_search en grupo _page_visibility con default false
  • NOTE Los campos _page_visibility NO van en el schema JSON, se manejan via FieldMapper y VisibilityDefaults
  • NOTE show_on_archives en true porque este componente solo tiene sentido en archives

Scenario: Campos de content en archive-header

  • WHEN se define grupo content
  • THEN DEBE incluir blog_title como text con default "Blog"
  • AND DEBE incluir show_post_count como boolean con default true
  • AND DEBE incluir show_description como boolean con default true

Scenario: Campos de typography en archive-header

  • WHEN se define grupo typography
  • THEN DEBE incluir heading_level como select con options ["h1", "h2", "h3", "h4", "h5", "h6"] y default "h1"
  • AND DEBE incluir title_size como text con default "2rem"
  • AND DEBE incluir title_weight como text con default "700"
  • AND DEBE incluir description_size como text con default "1rem"

Scenario: Campos de colors en archive-header

  • WHEN se define grupo colors
  • THEN DEBE incluir title_color como color con default "#0E2337"
  • AND DEBE incluir description_color como color con default "#6b7280"
  • AND DEBE incluir count_bg_color como color con default "#FF8600"
  • AND DEBE incluir count_text_color como color con default "#ffffff"

Scenario: Campos de spacing en archive-header

  • WHEN se define grupo spacing
  • THEN DEBE incluir margin_top como text con default "2rem"
  • AND DEBE incluir margin_bottom como text con default "2rem"
  • AND DEBE incluir padding como text con default "1.5rem"

Scenario: Campos de behavior en archive-header

  • WHEN se define grupo behavior
  • THEN DEBE incluir is_sticky como boolean con default false
  • AND DEBE incluir sticky_offset como text con default "0"

Requirement: Componente post-grid

The post-grid component MUST display posts from the main WordPress loop in a grid layout.

Scenario: Ubicacion de archivos post-grid

  • WHEN se crea el componente post-grid
  • THEN schema DEBE estar en Schemas/post-grid.json
  • AND Renderer DEBE estar en Public/PostGrid/Infrastructure/Ui/PostGridRenderer.php
  • AND FormBuilder DEBE estar en Admin/PostGrid/Infrastructure/Ui/PostGridFormBuilder.php
  • AND FieldMapper DEBE estar en Admin/PostGrid/Infrastructure/FieldMapping/PostGridFieldMapper.php

Scenario: Namespaces PHP de post-grid

  • WHEN se definen los namespaces para post-grid
  • THEN Renderer DEBE usar namespace ROITheme\Public\PostGrid\Infrastructure\Ui
  • AND FormBuilder DEBE usar namespace ROITheme\Admin\PostGrid\Infrastructure\Ui
  • AND FieldMapper DEBE usar namespace ROITheme\Admin\PostGrid\Infrastructure\FieldMapping

Scenario: Diferencia entre post-grid y related-post

  • WHEN PostGridRenderer obtiene los posts
  • THEN DEBE usar global $wp_query para obtener posts del loop principal
  • AND NO DEBE crear su propio WP_Query como hace RelatedPostRenderer
  • AND DEBE llamar wp_reset_postdata() al finalizar si modifica el loop

Scenario: Grupos del schema post-grid

  • WHEN se define el schema post-grid.json
  • THEN DEBE incluir grupo visibility con priority 10
  • AND DEBE incluir grupo content con priority 20
  • AND DEBE incluir grupo typography con priority 30
  • AND DEBE incluir grupo colors con priority 40
  • AND DEBE incluir grupo spacing con priority 50
  • AND DEBE incluir grupo visual_effects con priority 60
  • AND DEBE incluir grupo layout con priority 80
  • AND DEBE incluir grupo media con priority 90

Scenario: Campos obligatorios de visibility en post-grid

  • WHEN se define grupo visibility en schema
  • THEN DEBE incluir is_enabled como boolean con default true
  • AND DEBE incluir show_on_desktop como boolean con default true
  • AND DEBE incluir show_on_mobile como boolean con default true

Scenario: Campos de _page_visibility en post-grid

  • WHEN se configura visibilidad por tipo de pagina en FieldMapper
  • THEN DEBE mapear campo show_on_home en grupo _page_visibility con default true
  • AND DEBE mapear campo show_on_posts en grupo _page_visibility con default false
  • AND DEBE mapear campo show_on_pages en grupo _page_visibility con default false
  • AND DEBE mapear campo show_on_archives en grupo _page_visibility con default true
  • AND DEBE mapear campo show_on_search en grupo _page_visibility con default true
  • NOTE Los campos _page_visibility NO van en el schema JSON, se manejan via FieldMapper y VisibilityDefaults
  • NOTE show_on_home en true para mostrar grid en pagina de blog principal
  • NOTE show_on_archives en true porque este componente es para listados
  • NOTE show_on_search en true para mostrar resultados de busqueda

Scenario: Campos de content en post-grid

  • WHEN se define grupo content
  • THEN DEBE incluir show_thumbnail como boolean con default true
  • AND DEBE incluir show_excerpt como boolean con default true
  • AND DEBE incluir show_meta como boolean con default true
  • AND DEBE incluir show_categories como boolean con default true
  • AND DEBE incluir excerpt_length como select con options ["10", "15", "20", "25", "30"] y default "20"
  • AND DEBE incluir read_more_text como text con default "Leer mas"
  • AND DEBE incluir no_posts_message como text con default "No se encontraron publicaciones"

Scenario: Campos de media en post-grid

  • WHEN se define grupo media
  • THEN DEBE incluir fallback_image como url con default ""
  • AND DEBE incluir fallback_image_alt como text con default "Imagen por defecto"
  • AND fallback_image_alt es obligatorio para accesibilidad WCAG

Scenario: Campos de typography en post-grid

  • WHEN se define grupo typography
  • THEN DEBE incluir heading_level como select con options ["h2", "h3", "h4", "h5", "h6"] y default "h3"
  • AND DEBE incluir card_title_size como text con default "1.1rem"
  • AND DEBE incluir card_title_weight como text con default "600"
  • AND DEBE incluir excerpt_size como text con default "0.9rem"
  • AND DEBE incluir meta_size como text con default "0.8rem"

Scenario: Campos de colors en post-grid

  • WHEN se define grupo colors
  • THEN DEBE incluir card_bg_color como color con default "#ffffff"
  • AND DEBE incluir card_title_color como color con default "#0E2337"
  • AND DEBE incluir card_hover_bg_color como color con default "#f9fafb"
  • AND DEBE incluir card_border_color como color con default "#e5e7eb"
  • AND DEBE incluir card_hover_border_color como color con default "#FF8600"
  • AND DEBE incluir excerpt_color como color con default "#6b7280"
  • AND DEBE incluir meta_color como color con default "#9ca3af"
  • AND DEBE incluir category_bg_color como color con default "#FFF5EB"
  • AND DEBE incluir category_text_color como color con default "#FF8600"

Scenario: Campos de spacing en post-grid

  • WHEN se define grupo spacing
  • THEN DEBE incluir grid_gap como text con default "1.5rem"
  • AND DEBE incluir card_padding como text con default "1.25rem"
  • AND DEBE incluir section_margin_top como text con default "0"
  • AND DEBE incluir section_margin_bottom como text con default "2rem"

Scenario: Campos de visual_effects en post-grid

  • WHEN se define grupo visual_effects
  • THEN DEBE incluir card_border_radius como text con default "0.5rem"
  • AND DEBE incluir card_shadow como text con default "0 1px 3px rgba(0,0,0,0.1)"
  • AND DEBE incluir card_hover_shadow como text con default "0 4px 12px rgba(0,0,0,0.15)"
  • AND DEBE incluir card_transition como text con default "all 0.3s ease"
  • AND DEBE incluir image_border_radius como text con default "0.375rem"

Scenario: Campos de layout en post-grid

  • WHEN se define grupo layout
  • THEN DEBE incluir columns_desktop como select con options ["2", "3", "4"] y default "3"
  • AND DEBE incluir columns_tablet como select con options ["1", "2", "3"] y default "2"
  • AND DEBE incluir columns_mobile como select con options ["1", "2"] y default "1"
  • AND DEBE incluir image_position como select con options ["top", "left", "none"] y default "top"

Requirement: Manejo Graceful de Contenido Faltante

The post-grid component MUST handle missing content gracefully.

Scenario: Post sin imagen destacada

  • WHEN un post no tiene thumbnail y show_thumbnail es true
  • THEN si fallback_image tiene valor DEBE mostrar esa imagen con fallback_image_alt
  • AND si fallback_image esta vacio DEBE omitir la imagen sin romper el layout
  • AND NO DEBE mostrar imagen rota o placeholder generico

Scenario: Post sin excerpt

  • WHEN un post no tiene excerpt y show_excerpt es true
  • THEN DEBE generar excerpt automatico desde post_content
  • AND DEBE respetar excerpt_length del schema
  • AND DEBE usar wp_trim_words() para truncar

Scenario: Post sin categorias

  • WHEN un post no tiene categorias y show_categories es true
  • THEN DEBE omitir la seccion de categorias
  • AND NO DEBE mostrar "Sin categoria" u otro texto placeholder

Scenario: No posts found - Query vacia

  • WHEN have_posts() retorna false en un template de listado
  • THEN post-grid DEBE mostrar mensaje configurable de "no hay posts"
  • AND el mensaje DEBE usar campo no_posts_message del schema con default "No se encontraron publicaciones"
  • AND DEBE aplicar estilos consistentes con el design system
  • AND NO DEBE romper el layout de la pagina

Requirement: Visibilidad por Tipo de Pagina

Components MUST respect the show_on_archives setting in _page_visibility group.

Scenario: Patron de visibilidad por tipo de pagina

  • WHEN se implementa visibilidad por tipo de pagina
  • THEN los campos show_on_home, show_on_posts, show_on_pages, show_on_archives, show_on_search
  • AND DEBEN estar en grupo _page_visibility (NO en visibility)
  • AND DEBEN mapearse via FieldMapper del componente
  • AND DEBEN evaluarse via PageVisibilityHelper::shouldShow()

Scenario: Configuracion por defecto de show_on_archives para nuevos componentes

  • WHEN se configura _page_visibility para componentes nuevos
  • THEN archive-header DEBE tener show_on_archives true en _page_visibility
  • AND post-grid DEBE tener show_on_archives true en _page_visibility

Scenario: Componentes existentes en archives

  • WHEN se evalua que componentes mostrar en archives via _page_visibility
  • THEN hero DEBE tener show_on_archives false por defecto (configurable)
  • AND table-of-contents DEBE tener show_on_archives false
  • AND featured-image DEBE tener show_on_archives false
  • AND social-share DEBE tener show_on_archives false
  • AND related-post DEBE tener show_on_archives false
  • AND cta-box-sidebar DEBE tener show_on_archives true
  • AND contact-form DEBE tener show_on_archives configurable

Scenario: Llamada a componente con visibilidad deshabilitada (Patron Template Unificado)

  • GIVEN el template unificado llama a TODOS los componentes para mantener consistencia
  • WHEN un template llama roi_render_component() para un componente
  • AND ese componente tiene show_on_archives false
  • THEN el componente NO DEBE renderizarse (retorna string vacio)
  • AND esto es comportamiento correcto y esperado, NO un error
  • AND permite que el admin habilite/deshabilite componentes sin modificar templates
  • NOTE Por ejemplo: table-of-contents se llama en sidebar pero no renderiza en archives porque show_on_archives=false

Requirement: Templates a Modernizar

These templates MUST be updated to use the unified structure.

Scenario: Modernizar home.php

  • WHEN se actualiza home.php
  • THEN DEBE reemplazar get_template_part() con roi_render_component()
  • AND DEBE eliminar referencia a TemplateParts/content.php
  • AND DEBE usar estructura unificada con hero, archive-header, post-grid

Scenario: Modernizar archive.php

  • WHEN se actualiza archive.php
  • THEN DEBE reemplazar get_template_part() con roi_render_component()
  • AND DEBE eliminar referencia a TemplateParts/content.php
  • AND DEBE usar estructura unificada

Scenario: Modernizar category.php

  • GIVEN category.php existe en roi-theme/ (verificado)
  • WHEN se actualiza category.php
  • THEN DEBE reemplazar get_template_part() con roi_render_component()
  • AND DEBE eliminar referencia a TemplateParts/content.php
  • AND DEBE usar estructura unificada

Scenario: Modernizar tag.php

  • GIVEN tag.php existe en roi-theme/ (verificado)
  • WHEN se actualiza tag.php
  • THEN DEBE reemplazar get_template_part() con roi_render_component()
  • AND DEBE eliminar referencia a TemplateParts/content.php
  • AND DEBE usar estructura unificada

Scenario: Modernizar author.php

  • GIVEN author.php existe en roi-theme/ (verificado)
  • WHEN se actualiza author.php
  • THEN DEBE reemplazar get_template_part() con roi_render_component()
  • AND DEBE eliminar referencia a TemplateParts/content.php
  • AND DEBE usar estructura unificada
  • AND archive-header detectara automaticamente contexto de autor

Scenario: Modernizar date.php

  • GIVEN date.php existe en roi-theme/ (verificado)
  • WHEN se actualiza date.php
  • THEN DEBE reemplazar get_template_part() con roi_render_component()
  • AND DEBE eliminar referencia a TemplateParts/content.php
  • AND DEBE usar estructura unificada
  • AND archive-header detectara automaticamente contexto de fecha

Scenario: Modernizar search.php

  • GIVEN search.php existe en roi-theme/ (verificado)
  • WHEN se actualiza search.php
  • THEN DEBE reemplazar get_template_part() con roi_render_component()
  • AND DEBE eliminar referencia a TemplateParts/content.php
  • AND DEBE usar estructura unificada con post-grid
  • AND archive-header detectara automaticamente contexto de busqueda mostrando "Resultados: [termino]"

Requirement: Orden de Implementacion

Components and templates MUST be implemented in a specific order.

Scenario: Secuencia de implementacion

  • WHEN se implementa esta especificacion
  • THEN Fase 1 es crear componente archive-header (5 pasos del flujo)
  • AND Fase 2 es crear componente post-grid (5 pasos del flujo)
  • AND Fase 3 es modernizar home.php
  • AND Fase 4 es modernizar archive.php
  • AND Fase 5 es modernizar category.php
  • AND Fase 6 es modernizar tag.php
  • AND Fase 7 es modernizar author.php
  • AND Fase 8 es modernizar date.php
  • AND Fase 9 es modernizar search.php
  • AND Fase 10 es configurar visibilidad de componentes existentes

Scenario: Cada componente sigue flujo de 5 fases

  • WHEN se crea archive-header o post-grid
  • THEN DEBE seguir Fase 1: Schema JSON
  • AND DEBE seguir Fase 2: Sincronizacion wp roi-theme sync-component
  • AND DEBE seguir Fase 3: Renderer
  • AND DEBE seguir Fase 4: FormBuilder
  • AND DEBE seguir Fase 5: Validacion validate-architecture.php

Requirement: Dependencias Existentes

The implementation MUST use existing infrastructure.

Scenario: Uso de PageVisibilityHelper

  • WHEN un Renderer verifica visibilidad
  • THEN DEBE usar PageVisibilityHelper::shouldShow(componentName)
  • AND esta en Shared/Infrastructure/Services/PageVisibilityHelper.php

Scenario: Uso de CSSGeneratorInterface

  • WHEN un Renderer genera CSS
  • THEN DEBE inyectar CSSGeneratorInterface via constructor
  • AND DEBE usar $this->cssGenerator->generate()

Scenario: Uso de roi_should_render_any_wrapper

  • WHEN un template determina si mostrar sidebar
  • THEN DEBE usar roi_should_render_any_wrapper()
  • AND esta definida en functions-addon.php linea 423

Scenario: Uso de DIContainer

  • WHEN se instancian servicios
  • THEN DEBE usar DIContainer::getInstance()
  • AND NO DEBE instanciar servicios con new directamente