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>
This commit is contained in:
FrankZamora
2025-12-06 19:14:09 -06:00
parent 6be292e085
commit b79569c5e7

View File

@@ -0,0 +1,417 @@
# 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