# 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