diff --git a/Admin/ArchiveHeader/Infrastructure/FieldMapping/ArchiveHeaderFieldMapper.php b/Admin/ArchiveHeader/Infrastructure/FieldMapping/ArchiveHeaderFieldMapper.php
new file mode 100644
index 00000000..62d0efe3
--- /dev/null
+++ b/Admin/ArchiveHeader/Infrastructure/FieldMapping/ArchiveHeaderFieldMapper.php
@@ -0,0 +1,81 @@
+ ['group' => 'visibility', 'attribute' => 'is_enabled'],
+ 'archiveHeaderShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
+ 'archiveHeaderShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
+
+ // Page Visibility (grupo especial _page_visibility)
+ 'archiveHeaderVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
+ 'archiveHeaderVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
+ 'archiveHeaderVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
+ 'archiveHeaderVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
+ 'archiveHeaderVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
+
+ // Exclusions (grupo especial _exclusions)
+ 'archiveHeaderExclusionsEnabled' => ['group' => '_exclusions', 'attribute' => 'exclusions_enabled'],
+ 'archiveHeaderExcludeCategories' => ['group' => '_exclusions', 'attribute' => 'exclude_categories', 'type' => 'json_array'],
+ 'archiveHeaderExcludePostIds' => ['group' => '_exclusions', 'attribute' => 'exclude_post_ids', 'type' => 'json_array_int'],
+ 'archiveHeaderExcludeUrlPatterns' => ['group' => '_exclusions', 'attribute' => 'exclude_url_patterns', 'type' => 'json_array_lines'],
+
+ // Content
+ 'archiveHeaderBlogTitle' => ['group' => 'content', 'attribute' => 'blog_title'],
+ 'archiveHeaderShowPostCount' => ['group' => 'content', 'attribute' => 'show_post_count'],
+ 'archiveHeaderShowDescription' => ['group' => 'content', 'attribute' => 'show_description'],
+ 'archiveHeaderCategoryPrefix' => ['group' => 'content', 'attribute' => 'category_prefix'],
+ 'archiveHeaderTagPrefix' => ['group' => 'content', 'attribute' => 'tag_prefix'],
+ 'archiveHeaderAuthorPrefix' => ['group' => 'content', 'attribute' => 'author_prefix'],
+ 'archiveHeaderDatePrefix' => ['group' => 'content', 'attribute' => 'date_prefix'],
+ 'archiveHeaderSearchPrefix' => ['group' => 'content', 'attribute' => 'search_prefix'],
+ 'archiveHeaderCountSingular' => ['group' => 'content', 'attribute' => 'posts_count_singular'],
+ 'archiveHeaderCountPlural' => ['group' => 'content', 'attribute' => 'posts_count_plural'],
+
+ // Typography
+ 'archiveHeaderHeadingLevel' => ['group' => 'typography', 'attribute' => 'heading_level'],
+ 'archiveHeaderTitleSize' => ['group' => 'typography', 'attribute' => 'title_size'],
+ 'archiveHeaderTitleWeight' => ['group' => 'typography', 'attribute' => 'title_weight'],
+ 'archiveHeaderDescriptionSize' => ['group' => 'typography', 'attribute' => 'description_size'],
+ 'archiveHeaderCountSize' => ['group' => 'typography', 'attribute' => 'count_size'],
+
+ // Colors
+ 'archiveHeaderTitleColor' => ['group' => 'colors', 'attribute' => 'title_color'],
+ 'archiveHeaderPrefixColor' => ['group' => 'colors', 'attribute' => 'prefix_color'],
+ 'archiveHeaderDescriptionColor' => ['group' => 'colors', 'attribute' => 'description_color'],
+ 'archiveHeaderCountBgColor' => ['group' => 'colors', 'attribute' => 'count_bg_color'],
+ 'archiveHeaderCountTextColor' => ['group' => 'colors', 'attribute' => 'count_text_color'],
+
+ // Spacing
+ 'archiveHeaderMarginTop' => ['group' => 'spacing', 'attribute' => 'margin_top'],
+ 'archiveHeaderMarginBottom' => ['group' => 'spacing', 'attribute' => 'margin_bottom'],
+ 'archiveHeaderPadding' => ['group' => 'spacing', 'attribute' => 'padding'],
+ 'archiveHeaderTitleMarginBottom' => ['group' => 'spacing', 'attribute' => 'title_margin_bottom'],
+ 'archiveHeaderCountPadding' => ['group' => 'spacing', 'attribute' => 'count_padding'],
+
+ // Behavior
+ 'archiveHeaderIsSticky' => ['group' => 'behavior', 'attribute' => 'is_sticky'],
+ 'archiveHeaderStickyOffset' => ['group' => 'behavior', 'attribute' => 'sticky_offset'],
+ ];
+ }
+}
diff --git a/Admin/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderFormBuilder.php b/Admin/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderFormBuilder.php
new file mode 100644
index 00000000..454a51f4
--- /dev/null
+++ b/Admin/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderFormBuilder.php
@@ -0,0 +1,492 @@
+buildHeader($componentId);
+
+ $html .= '
';
+
+ // Columna izquierda
+ $html .= '
';
+ $html .= $this->buildVisibilityGroup($componentId);
+ $html .= $this->buildContentGroup($componentId);
+ $html .= $this->buildBehaviorGroup($componentId);
+ $html .= '
';
+
+ // Columna derecha
+ $html .= '
';
+ $html .= $this->buildTypographyGroup($componentId);
+ $html .= $this->buildColorsGroup($componentId);
+ $html .= $this->buildSpacingGroup($componentId);
+ $html .= '
';
+
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildHeader(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Visibilidad';
+ $html .= ' ';
+
+ $enabled = $this->renderer->getFieldValue($componentId, 'visibility', 'is_enabled', true);
+ $html .= $this->buildSwitch('archiveHeaderEnabled', 'Activar componente', 'bi-power', $enabled);
+
+ $showOnDesktop = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_desktop', true);
+ $html .= $this->buildSwitch('archiveHeaderShowOnDesktop', 'Mostrar en escritorio', 'bi-display', $showOnDesktop);
+
+ $showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
+ $html .= $this->buildSwitch('archiveHeaderShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
+
+ // Page visibility checkboxes
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Mostrar en tipos de pagina';
+ $html .= '
';
+
+ $showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
+ $showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', false);
+ $showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', false);
+ $showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', true);
+ $showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', true);
+
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('archiveHeaderVisibilityHome', 'Home', 'bi-house', $showOnHome);
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('archiveHeaderVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('archiveHeaderVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('archiveHeaderVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('archiveHeaderVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
+ $html .= '
';
+ $html .= '
';
+
+ // Exclusions
+ $exclusionPartial = new ExclusionFormPartial($this->renderer);
+ $html .= $exclusionPartial->render($componentId, 'archiveHeader');
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildContentGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Contenido';
+ $html .= ' ';
+
+ // Blog Title
+ $blogTitle = $this->renderer->getFieldValue($componentId, 'content', 'blog_title', 'Blog');
+ $html .= '
';
+ $html .= ' Titulo del blog ';
+ $html .= ' ';
+ $html .= ' Mostrado en la pagina principal del blog ';
+ $html .= '
';
+
+ // Switches
+ $showPostCount = $this->renderer->getFieldValue($componentId, 'content', 'show_post_count', true);
+ $html .= $this->buildSwitch('archiveHeaderShowPostCount', 'Mostrar contador de posts', 'bi-hash', $showPostCount);
+
+ $showDescription = $this->renderer->getFieldValue($componentId, 'content', 'show_description', true);
+ $html .= $this->buildSwitch('archiveHeaderShowDescription', 'Mostrar descripcion', 'bi-text-paragraph', $showDescription);
+
+ // Prefixes section
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Prefijos de titulo';
+ $html .= '
';
+
+ $html .= '
';
+
+ $categoryPrefix = $this->renderer->getFieldValue($componentId, 'content', 'category_prefix', 'Categoria:');
+ $html .= '
';
+ $html .= ' Categoria ';
+ $html .= ' ';
+ $html .= '
';
+
+ $tagPrefix = $this->renderer->getFieldValue($componentId, 'content', 'tag_prefix', 'Etiqueta:');
+ $html .= '
';
+ $html .= ' Etiqueta ';
+ $html .= ' ';
+ $html .= '
';
+
+ $authorPrefix = $this->renderer->getFieldValue($componentId, 'content', 'author_prefix', 'Articulos de:');
+ $html .= '
';
+ $html .= ' Autor ';
+ $html .= ' ';
+ $html .= '
';
+
+ $datePrefix = $this->renderer->getFieldValue($componentId, 'content', 'date_prefix', 'Archivo:');
+ $html .= '
';
+ $html .= ' Fecha ';
+ $html .= ' ';
+ $html .= '
';
+
+ $searchPrefix = $this->renderer->getFieldValue($componentId, 'content', 'search_prefix', 'Resultados para:');
+ $html .= '
';
+ $html .= ' Busqueda ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+
+ // Post count texts
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Textos del contador';
+ $html .= '
';
+
+ $html .= '
';
+
+ $countSingular = $this->renderer->getFieldValue($componentId, 'content', 'posts_count_singular', 'publicacion');
+ $html .= '
';
+ $html .= ' Singular ';
+ $html .= ' ';
+ $html .= '
';
+
+ $countPlural = $this->renderer->getFieldValue($componentId, 'content', 'posts_count_plural', 'publicaciones');
+ $html .= '
';
+ $html .= ' Plural ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildBehaviorGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Comportamiento';
+ $html .= ' ';
+
+ $isSticky = $this->renderer->getFieldValue($componentId, 'behavior', 'is_sticky', false);
+ $html .= $this->buildSwitch('archiveHeaderIsSticky', 'Header fijo al hacer scroll', 'bi-pin-angle', $isSticky);
+
+ $stickyOffset = $this->renderer->getFieldValue($componentId, 'behavior', 'sticky_offset', '0');
+ $html .= '
';
+ $html .= ' Offset sticky ';
+ $html .= ' ';
+ $html .= ' Distancia desde el top cuando es sticky (ej: 60px) ';
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildTypographyGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Tipografia';
+ $html .= ' ';
+
+ // Heading level
+ $headingLevel = $this->renderer->getFieldValue($componentId, 'typography', 'heading_level', 'h1');
+ $html .= '
';
+ $html .= ' Nivel de encabezado ';
+ $html .= ' ';
+ $html .= ' Importante para SEO y accesibilidad ';
+ $html .= '
';
+
+ $html .= '
';
+
+ $titleSize = $this->renderer->getFieldValue($componentId, 'typography', 'title_size', '2rem');
+ $html .= '
';
+ $html .= ' Tamano titulo ';
+ $html .= ' ';
+ $html .= '
';
+
+ $titleWeight = $this->renderer->getFieldValue($componentId, 'typography', 'title_weight', '700');
+ $html .= '
';
+ $html .= ' Peso titulo ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $descriptionSize = $this->renderer->getFieldValue($componentId, 'typography', 'description_size', '1rem');
+ $html .= '
';
+ $html .= ' Tamano descripcion ';
+ $html .= ' ';
+ $html .= '
';
+
+ $countSize = $this->renderer->getFieldValue($componentId, 'typography', 'count_size', '0.875rem');
+ $html .= '
';
+ $html .= ' Tamano contador ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildColorsGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Colores';
+ $html .= ' ';
+
+ $html .= '
';
+
+ $titleColor = $this->renderer->getFieldValue($componentId, 'colors', 'title_color', '#0E2337');
+ $html .= $this->buildColorPicker('archiveHeaderTitleColor', 'Titulo', $titleColor);
+
+ $prefixColor = $this->renderer->getFieldValue($componentId, 'colors', 'prefix_color', '#6b7280');
+ $html .= $this->buildColorPicker('archiveHeaderPrefixColor', 'Prefijo', $prefixColor);
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $descriptionColor = $this->renderer->getFieldValue($componentId, 'colors', 'description_color', '#6b7280');
+ $html .= $this->buildColorPicker('archiveHeaderDescriptionColor', 'Descripcion', $descriptionColor);
+
+ $html .= '
';
+
+ $html .= '
Contador de posts
';
+ $html .= '
';
+
+ $countBgColor = $this->renderer->getFieldValue($componentId, 'colors', 'count_bg_color', '#FF8600');
+ $html .= $this->buildColorPicker('archiveHeaderCountBgColor', 'Fondo', $countBgColor);
+
+ $countTextColor = $this->renderer->getFieldValue($componentId, 'colors', 'count_text_color', '#ffffff');
+ $html .= $this->buildColorPicker('archiveHeaderCountTextColor', 'Texto', $countTextColor);
+
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildSpacingGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Espaciado';
+ $html .= ' ';
+
+ $html .= '
';
+
+ $marginTop = $this->renderer->getFieldValue($componentId, 'spacing', 'margin_top', '2rem');
+ $html .= '
';
+ $html .= ' Margen superior ';
+ $html .= ' ';
+ $html .= '
';
+
+ $marginBottom = $this->renderer->getFieldValue($componentId, 'spacing', 'margin_bottom', '2rem');
+ $html .= '
';
+ $html .= ' Margen inferior ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $padding = $this->renderer->getFieldValue($componentId, 'spacing', 'padding', '1.5rem');
+ $html .= '
';
+ $html .= ' Padding ';
+ $html .= ' ';
+ $html .= '
';
+
+ $titleMarginBottom = $this->renderer->getFieldValue($componentId, 'spacing', 'title_margin_bottom', '0.5rem');
+ $html .= '
';
+ $html .= ' Margen titulo ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+ $countPadding = $this->renderer->getFieldValue($componentId, 'spacing', 'count_padding', '0.25rem 0.75rem');
+ $html .= ' Padding contador ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildSwitch(string $id, string $label, string $icon, mixed $checked): string
+ {
+ $checked = $checked === true || $checked === '1' || $checked === 1;
+
+ $html = ' ';
+ $html .= '
';
+ $html .= sprintf(
+ ' ',
+ esc_attr($id),
+ $checked ? 'checked' : ''
+ );
+ $html .= sprintf(
+ ' ',
+ esc_attr($id)
+ );
+ $html .= sprintf(' ', esc_attr($icon));
+ $html .= sprintf(' %s ', esc_html($label));
+ $html .= ' ';
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildColorPicker(string $id, string $label, string $value): string
+ {
+ $html = ' ';
+ $html .= sprintf(
+ '
%s ',
+ esc_html($label)
+ );
+ $html .= '
';
+ $html .= sprintf(
+ ' ',
+ esc_attr($id),
+ esc_attr($value)
+ );
+ $html .= sprintf(
+ ' %s ',
+ esc_attr($id),
+ esc_html(strtoupper($value))
+ );
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
+ {
+ $checked = $checked === true || $checked === '1' || $checked === 1;
+
+ $html = ' ';
+ $html .= sprintf(
+ ' ',
+ esc_attr($id),
+ $checked ? 'checked' : ''
+ );
+ $html .= sprintf(
+ ' ',
+ esc_attr($id)
+ );
+ $html .= sprintf(' ', esc_attr($icon));
+ $html .= sprintf(' %s', esc_html($label));
+ $html .= ' ';
+ $html .= '
';
+
+ return $html;
+ }
+}
diff --git a/Admin/CtaBoxSidebar/Infrastructure/Ui/CtaBoxSidebarFormBuilder.php b/Admin/CtaBoxSidebar/Infrastructure/Ui/CtaBoxSidebarFormBuilder.php
index 781973a8..d5e5def7 100644
--- a/Admin/CtaBoxSidebar/Infrastructure/Ui/CtaBoxSidebarFormBuilder.php
+++ b/Admin/CtaBoxSidebar/Infrastructure/Ui/CtaBoxSidebarFormBuilder.php
@@ -109,7 +109,7 @@ final class CtaBoxSidebarFormBuilder
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
- $showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
+ $showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', true);
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
// Grid 3 columnas según Design System
diff --git a/Admin/Infrastructure/Ui/AdminDashboardRenderer.php b/Admin/Infrastructure/Ui/AdminDashboardRenderer.php
index 7517b079..cb05ddc8 100644
--- a/Admin/Infrastructure/Ui/AdminDashboardRenderer.php
+++ b/Admin/Infrastructure/Ui/AdminDashboardRenderer.php
@@ -98,6 +98,16 @@ final class AdminDashboardRenderer implements DashboardRendererInterface
'label' => 'Related Posts',
'icon' => 'bi-grid-3x3-gap',
],
+ 'archive-header' => [
+ 'id' => 'archive-header',
+ 'label' => 'Archive Header',
+ 'icon' => 'bi-layout-text-window',
+ ],
+ 'post-grid' => [
+ 'id' => 'post-grid',
+ 'label' => 'Post Grid',
+ 'icon' => 'bi-grid-3x3',
+ ],
'contact-form' => [
'id' => 'contact-form',
'label' => 'Contact Form',
diff --git a/Admin/Infrastructure/Ui/ComponentGroupRegistry.php b/Admin/Infrastructure/Ui/ComponentGroupRegistry.php
index 05464063..54eea99f 100644
--- a/Admin/Infrastructure/Ui/ComponentGroupRegistry.php
+++ b/Admin/Infrastructure/Ui/ComponentGroupRegistry.php
@@ -37,7 +37,7 @@ final class ComponentGroupRegistry
'label' => __('Contenido Principal', 'roi-theme'),
'icon' => 'bi-file-richtext',
'description' => __('Secciones principales de páginas y posts', 'roi-theme'),
- 'components' => ['hero', 'featured-image', 'table-of-contents', 'related-post']
+ 'components' => ['hero', 'featured-image', 'table-of-contents', 'related-post', 'archive-header', 'post-grid']
],
'ctas-conversion' => [
'label' => __('CTAs & Conversión', 'roi-theme'),
diff --git a/Admin/PostGrid/Infrastructure/FieldMapping/PostGridFieldMapper.php b/Admin/PostGrid/Infrastructure/FieldMapping/PostGridFieldMapper.php
new file mode 100644
index 00000000..d7508f60
--- /dev/null
+++ b/Admin/PostGrid/Infrastructure/FieldMapping/PostGridFieldMapper.php
@@ -0,0 +1,97 @@
+ ['group' => 'visibility', 'attribute' => 'is_enabled'],
+ 'postGridShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
+ 'postGridShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
+
+ // Page Visibility (grupo especial _page_visibility)
+ 'postGridVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
+ 'postGridVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
+ 'postGridVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
+ 'postGridVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
+ 'postGridVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
+
+ // Exclusions (grupo especial _exclusions)
+ 'postGridExclusionsEnabled' => ['group' => '_exclusions', 'attribute' => 'exclusions_enabled'],
+ 'postGridExcludeCategories' => ['group' => '_exclusions', 'attribute' => 'exclude_categories', 'type' => 'json_array'],
+ 'postGridExcludePostIds' => ['group' => '_exclusions', 'attribute' => 'exclude_post_ids', 'type' => 'json_array_int'],
+ 'postGridExcludeUrlPatterns' => ['group' => '_exclusions', 'attribute' => 'exclude_url_patterns', 'type' => 'json_array_lines'],
+
+ // Content
+ 'postGridShowThumbnail' => ['group' => 'content', 'attribute' => 'show_thumbnail'],
+ 'postGridShowExcerpt' => ['group' => 'content', 'attribute' => 'show_excerpt'],
+ 'postGridShowMeta' => ['group' => 'content', 'attribute' => 'show_meta'],
+ 'postGridShowCategories' => ['group' => 'content', 'attribute' => 'show_categories'],
+ 'postGridExcerptLength' => ['group' => 'content', 'attribute' => 'excerpt_length'],
+ 'postGridReadMoreText' => ['group' => 'content', 'attribute' => 'read_more_text'],
+ 'postGridNoPostsMessage' => ['group' => 'content', 'attribute' => 'no_posts_message'],
+
+ // Layout
+ 'postGridColumnsDesktop' => ['group' => 'layout', 'attribute' => 'columns_desktop'],
+ 'postGridColumnsTablet' => ['group' => 'layout', 'attribute' => 'columns_tablet'],
+ 'postGridColumnsMobile' => ['group' => 'layout', 'attribute' => 'columns_mobile'],
+ 'postGridImagePosition' => ['group' => 'layout', 'attribute' => 'image_position'],
+
+ // Media
+ 'postGridFallbackImage' => ['group' => 'media', 'attribute' => 'fallback_image'],
+ 'postGridFallbackImageAlt' => ['group' => 'media', 'attribute' => 'fallback_image_alt'],
+
+ // Typography
+ 'postGridHeadingLevel' => ['group' => 'typography', 'attribute' => 'heading_level'],
+ 'postGridCardTitleSize' => ['group' => 'typography', 'attribute' => 'card_title_size'],
+ 'postGridCardTitleWeight' => ['group' => 'typography', 'attribute' => 'card_title_weight'],
+ 'postGridExcerptSize' => ['group' => 'typography', 'attribute' => 'excerpt_size'],
+ 'postGridMetaSize' => ['group' => 'typography', 'attribute' => 'meta_size'],
+
+ // Colors
+ 'postGridCardBgColor' => ['group' => 'colors', 'attribute' => 'card_bg_color'],
+ 'postGridCardTitleColor' => ['group' => 'colors', 'attribute' => 'card_title_color'],
+ 'postGridCardHoverBgColor' => ['group' => 'colors', 'attribute' => 'card_hover_bg_color'],
+ 'postGridCardBorderColor' => ['group' => 'colors', 'attribute' => 'card_border_color'],
+ 'postGridCardHoverBorderColor' => ['group' => 'colors', 'attribute' => 'card_hover_border_color'],
+ 'postGridExcerptColor' => ['group' => 'colors', 'attribute' => 'excerpt_color'],
+ 'postGridMetaColor' => ['group' => 'colors', 'attribute' => 'meta_color'],
+ 'postGridCategoryBgColor' => ['group' => 'colors', 'attribute' => 'category_bg_color'],
+ 'postGridCategoryTextColor' => ['group' => 'colors', 'attribute' => 'category_text_color'],
+ 'postGridPaginationColor' => ['group' => 'colors', 'attribute' => 'pagination_color'],
+ 'postGridPaginationActiveBg' => ['group' => 'colors', 'attribute' => 'pagination_active_bg'],
+ 'postGridPaginationActiveColor' => ['group' => 'colors', 'attribute' => 'pagination_active_color'],
+
+ // Spacing
+ 'postGridGridGap' => ['group' => 'spacing', 'attribute' => 'grid_gap'],
+ 'postGridCardPadding' => ['group' => 'spacing', 'attribute' => 'card_padding'],
+ 'postGridSectionMarginTop' => ['group' => 'spacing', 'attribute' => 'section_margin_top'],
+ 'postGridSectionMarginBottom' => ['group' => 'spacing', 'attribute' => 'section_margin_bottom'],
+
+ // Visual Effects
+ 'postGridCardBorderRadius' => ['group' => 'visual_effects', 'attribute' => 'card_border_radius'],
+ 'postGridCardShadow' => ['group' => 'visual_effects', 'attribute' => 'card_shadow'],
+ 'postGridCardHoverShadow' => ['group' => 'visual_effects', 'attribute' => 'card_hover_shadow'],
+ 'postGridCardTransition' => ['group' => 'visual_effects', 'attribute' => 'card_transition'],
+ 'postGridImageBorderRadius' => ['group' => 'visual_effects', 'attribute' => 'image_border_radius'],
+ ];
+ }
+}
diff --git a/Admin/PostGrid/Infrastructure/Ui/PostGridFormBuilder.php b/Admin/PostGrid/Infrastructure/Ui/PostGridFormBuilder.php
new file mode 100644
index 00000000..e99112c1
--- /dev/null
+++ b/Admin/PostGrid/Infrastructure/Ui/PostGridFormBuilder.php
@@ -0,0 +1,619 @@
+buildHeader();
+
+ $html .= '';
+
+ // Columna izquierda
+ $html .= '
';
+ $html .= $this->buildVisibilityGroup($componentId);
+ $html .= $this->buildContentGroup($componentId);
+ $html .= $this->buildLayoutGroup($componentId);
+ $html .= $this->buildMediaGroup($componentId);
+ $html .= '
';
+
+ // Columna derecha
+ $html .= '
';
+ $html .= $this->buildTypographyGroup($componentId);
+ $html .= $this->buildColorsGroup($componentId);
+ $html .= $this->buildSpacingGroup($componentId);
+ $html .= $this->buildEffectsGroup($componentId);
+ $html .= '
';
+
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildHeader(): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Visibilidad';
+ $html .= ' ';
+
+ $enabled = $this->renderer->getFieldValue($componentId, 'visibility', 'is_enabled', true);
+ $html .= $this->buildSwitch('postGridEnabled', 'Activar componente', 'bi-power', $enabled);
+
+ $showOnDesktop = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_desktop', true);
+ $html .= $this->buildSwitch('postGridShowOnDesktop', 'Mostrar en escritorio', 'bi-display', $showOnDesktop);
+
+ $showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
+ $html .= $this->buildSwitch('postGridShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
+
+ // Checkboxes de visibilidad por tipo de página
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Mostrar en tipos de pagina';
+ $html .= '
';
+
+ $showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
+ $showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', false);
+ $showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', false);
+ $showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', true);
+ $showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', true);
+
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('postGridVisibilityHome', 'Home', 'bi-house', $showOnHome);
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('postGridVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('postGridVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('postGridVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
+ $html .= '
';
+ $html .= '
';
+ $html .= $this->buildPageVisibilityCheckbox('postGridVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
+ $html .= '
';
+ $html .= '
';
+
+ // Reglas de exclusion
+ $exclusionPartial = new ExclusionFormPartial($this->renderer);
+ $html .= $exclusionPartial->render($componentId, 'postGrid');
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildContentGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Contenido';
+ $html .= ' ';
+
+ // Switches de contenido
+ $showThumbnail = $this->renderer->getFieldValue($componentId, 'content', 'show_thumbnail', true);
+ $html .= $this->buildSwitch('postGridShowThumbnail', 'Mostrar imagen destacada', 'bi-image', $showThumbnail);
+
+ $showExcerpt = $this->renderer->getFieldValue($componentId, 'content', 'show_excerpt', true);
+ $html .= $this->buildSwitch('postGridShowExcerpt', 'Mostrar extracto', 'bi-text-paragraph', $showExcerpt);
+
+ $showMeta = $this->renderer->getFieldValue($componentId, 'content', 'show_meta', true);
+ $html .= $this->buildSwitch('postGridShowMeta', 'Mostrar metadatos', 'bi-info-circle', $showMeta);
+
+ $showCategories = $this->renderer->getFieldValue($componentId, 'content', 'show_categories', true);
+ $html .= $this->buildSwitch('postGridShowCategories', 'Mostrar categorias', 'bi-folder', $showCategories);
+
+ $html .= '
';
+
+ // Excerpt length
+ $excerptLength = $this->renderer->getFieldValue($componentId, 'content', 'excerpt_length', '20');
+ $html .= '
';
+ $html .= ' Longitud del extracto ';
+ $html .= ' ';
+ $html .= ' 10 palabras ';
+ $html .= ' 15 palabras ';
+ $html .= ' 20 palabras ';
+ $html .= ' 25 palabras ';
+ $html .= ' 30 palabras ';
+ $html .= ' ';
+ $html .= '
';
+
+ // Read more text
+ $readMoreText = $this->renderer->getFieldValue($componentId, 'content', 'read_more_text', 'Leer mas');
+ $html .= '
';
+ $html .= ' Texto de leer mas ';
+ $html .= ' ';
+ $html .= '
';
+
+ // No posts message
+ $noPostsMessage = $this->renderer->getFieldValue($componentId, 'content', 'no_posts_message', 'No se encontraron publicaciones');
+ $html .= '
';
+ $html .= ' Mensaje sin posts ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildLayoutGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Disposicion';
+ $html .= ' ';
+
+ // Columns desktop
+ $colsDesktop = $this->renderer->getFieldValue($componentId, 'layout', 'columns_desktop', '3');
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' ';
+ $html .= ' Columnas escritorio';
+ $html .= ' ';
+ $html .= ' ';
+ $html .= ' 2 columnas ';
+ $html .= ' 3 columnas ';
+ $html .= ' 4 columnas ';
+ $html .= ' ';
+ $html .= '
';
+
+ // Columns tablet
+ $colsTablet = $this->renderer->getFieldValue($componentId, 'layout', 'columns_tablet', '2');
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' ';
+ $html .= ' Columnas tablet';
+ $html .= ' ';
+ $html .= ' ';
+ $html .= ' 1 columna ';
+ $html .= ' 2 columnas ';
+ $html .= ' 3 columnas ';
+ $html .= ' ';
+ $html .= '
';
+
+ // Columns mobile
+ $colsMobile = $this->renderer->getFieldValue($componentId, 'layout', 'columns_mobile', '1');
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' ';
+ $html .= ' Columnas movil';
+ $html .= ' ';
+ $html .= ' ';
+ $html .= ' 1 columna ';
+ $html .= ' 2 columnas ';
+ $html .= ' ';
+ $html .= '
';
+
+ // Image position
+ $imagePosition = $this->renderer->getFieldValue($componentId, 'layout', 'image_position', 'top');
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' ';
+ $html .= ' Posicion de imagen';
+ $html .= ' ';
+ $html .= ' ';
+ $html .= ' Arriba ';
+ $html .= ' Izquierda ';
+ $html .= ' Sin imagen ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildMediaGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Medios';
+ $html .= ' ';
+
+ // Fallback image
+ $fallbackImage = $this->renderer->getFieldValue($componentId, 'media', 'fallback_image', '');
+ $html .= '
';
+ $html .= ' URL imagen por defecto ';
+ $html .= ' ';
+ $html .= '
';
+
+ // Fallback image alt
+ $fallbackImageAlt = $this->renderer->getFieldValue($componentId, 'media', 'fallback_image_alt', 'Imagen por defecto');
+ $html .= '
';
+ $html .= ' Texto alternativo ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildTypographyGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Tipografia';
+ $html .= ' ';
+
+ // Heading level
+ $headingLevel = $this->renderer->getFieldValue($componentId, 'typography', 'heading_level', 'h3');
+ $html .= '
';
+ $html .= ' Nivel de encabezado ';
+ $html .= ' ';
+ $html .= ' H2 ';
+ $html .= ' H3 ';
+ $html .= ' H4 ';
+ $html .= ' H5 ';
+ $html .= ' H6 ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildColorsGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Colores';
+ $html .= ' ';
+
+ // Cards
+ $html .= '
Cards
';
+ $html .= '
';
+
+ $cardBgColor = $this->renderer->getFieldValue($componentId, 'colors', 'card_bg_color', '#ffffff');
+ $html .= $this->buildColorPicker('postGridCardBgColor', 'Fondo', $cardBgColor);
+
+ $cardTitleColor = $this->renderer->getFieldValue($componentId, 'colors', 'card_title_color', '#0E2337');
+ $html .= $this->buildColorPicker('postGridCardTitleColor', 'Titulo', $cardTitleColor);
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $cardHoverBgColor = $this->renderer->getFieldValue($componentId, 'colors', 'card_hover_bg_color', '#f9fafb');
+ $html .= $this->buildColorPicker('postGridCardHoverBgColor', 'Fondo hover', $cardHoverBgColor);
+
+ $cardBorderColor = $this->renderer->getFieldValue($componentId, 'colors', 'card_border_color', '#e5e7eb');
+ $html .= $this->buildColorPicker('postGridCardBorderColor', 'Borde', $cardBorderColor);
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $cardHoverBorderColor = $this->renderer->getFieldValue($componentId, 'colors', 'card_hover_border_color', '#FF8600');
+ $html .= $this->buildColorPicker('postGridCardHoverBorderColor', 'Borde hover', $cardHoverBorderColor);
+
+ $excerptColor = $this->renderer->getFieldValue($componentId, 'colors', 'excerpt_color', '#6b7280');
+ $html .= $this->buildColorPicker('postGridExcerptColor', 'Extracto', $excerptColor);
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $metaColor = $this->renderer->getFieldValue($componentId, 'colors', 'meta_color', '#9ca3af');
+ $html .= $this->buildColorPicker('postGridMetaColor', 'Metadatos', $metaColor);
+
+ $categoryBgColor = $this->renderer->getFieldValue($componentId, 'colors', 'category_bg_color', '#FFF5EB');
+ $html .= $this->buildColorPicker('postGridCategoryBgColor', 'Fondo cat.', $categoryBgColor);
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $categoryTextColor = $this->renderer->getFieldValue($componentId, 'colors', 'category_text_color', '#FF8600');
+ $html .= $this->buildColorPicker('postGridCategoryTextColor', 'Texto cat.', $categoryTextColor);
+
+ $html .= '
';
+
+ // Paginacion
+ $html .= '
Paginacion
';
+ $html .= '
';
+
+ $paginationColor = $this->renderer->getFieldValue($componentId, 'colors', 'pagination_color', '#0E2337');
+ $html .= $this->buildColorPicker('postGridPaginationColor', 'Color', $paginationColor);
+
+ $paginationActiveBg = $this->renderer->getFieldValue($componentId, 'colors', 'pagination_active_bg', '#FF8600');
+ $html .= $this->buildColorPicker('postGridPaginationActiveBg', 'Activo fondo', $paginationActiveBg);
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $paginationActiveColor = $this->renderer->getFieldValue($componentId, 'colors', 'pagination_active_color', '#ffffff');
+ $html .= $this->buildColorPicker('postGridPaginationActiveColor', 'Activo texto', $paginationActiveColor);
+
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildSpacingGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Espaciado';
+ $html .= ' ';
+
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildEffectsGroup(string $componentId): string
+ {
+ $html = '';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Efectos Visuales';
+ $html .= ' ';
+
+ $html .= '
';
+
+ $html .= '
';
+ $cardTransition = $this->renderer->getFieldValue($componentId, 'visual_effects', 'card_transition', 'all 0.3s ease');
+ $html .= ' Transicion ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+ $cardShadow = $this->renderer->getFieldValue($componentId, 'visual_effects', 'card_shadow', '0 1px 3px rgba(0,0,0,0.1)');
+ $html .= ' Sombra normal ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+ $cardHoverShadow = $this->renderer->getFieldValue($componentId, 'visual_effects', 'card_hover_shadow', '0 4px 12px rgba(0,0,0,0.15)');
+ $html .= ' Sombra hover ';
+ $html .= ' ';
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildSwitch(string $id, string $label, string $icon, mixed $checked): string
+ {
+ $checked = $checked === true || $checked === '1' || $checked === 1;
+
+ $html = ' ';
+ $html .= '
';
+ $html .= sprintf(
+ ' ',
+ esc_attr($id),
+ $checked ? 'checked' : ''
+ );
+ $html .= sprintf(
+ ' ',
+ esc_attr($id)
+ );
+ $html .= sprintf(' ', esc_attr($icon));
+ $html .= sprintf(' %s ', esc_html($label));
+ $html .= ' ';
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildColorPicker(string $id, string $label, string $value): string
+ {
+ $html = ' ';
+ $html .= sprintf(
+ '
%s ',
+ esc_html($label)
+ );
+ $html .= '
';
+ $html .= sprintf(
+ ' ',
+ esc_attr($id),
+ esc_attr($value)
+ );
+ $html .= sprintf(
+ ' %s ',
+ esc_attr($id),
+ esc_html(strtoupper($value))
+ );
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
+ {
+ $checked = $checked === true || $checked === '1' || $checked === 1;
+
+ $html = ' ';
+ $html .= sprintf(
+ ' ',
+ esc_attr($id),
+ $checked ? 'checked' : ''
+ );
+ $html .= sprintf(
+ ' ',
+ esc_attr($id)
+ );
+ $html .= sprintf(' ', esc_attr($icon));
+ $html .= sprintf(' %s', esc_html($label));
+ $html .= ' ';
+ $html .= '
';
+
+ return $html;
+ }
+}
diff --git a/Admin/Shared/Infrastructure/FieldMapping/FieldMapperProvider.php b/Admin/Shared/Infrastructure/FieldMapping/FieldMapperProvider.php
index acdc7790..a945af90 100644
--- a/Admin/Shared/Infrastructure/FieldMapping/FieldMapperProvider.php
+++ b/Admin/Shared/Infrastructure/FieldMapping/FieldMapperProvider.php
@@ -33,6 +33,8 @@ final class FieldMapperProvider
'Footer',
'ThemeSettings',
'AdsensePlacement',
+ 'ArchiveHeader',
+ 'PostGrid',
];
public function __construct(
diff --git a/Public/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderRenderer.php b/Public/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderRenderer.php
new file mode 100644
index 00000000..3de8dd15
--- /dev/null
+++ b/Public/ArchiveHeader/Infrastructure/Ui/ArchiveHeaderRenderer.php
@@ -0,0 +1,314 @@
+getData();
+
+ if (!$this->isEnabled($data)) {
+ return '';
+ }
+
+ if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
+ return '';
+ }
+
+ $visibilityClass = $this->getVisibilityClass($data);
+ if ($visibilityClass === null) {
+ return '';
+ }
+
+ $css = $this->generateCSS($data);
+ $html = $this->buildHTML($data, $visibilityClass);
+
+ return sprintf("\n%s", $css, $html);
+ }
+
+ public function supports(string $componentType): bool
+ {
+ return $componentType === self::COMPONENT_NAME;
+ }
+
+ private function isEnabled(array $data): bool
+ {
+ $value = $data['visibility']['is_enabled'] ?? false;
+ return $value === true || $value === '1' || $value === 1;
+ }
+
+ private function getVisibilityClass(array $data): ?string
+ {
+ $showDesktop = $data['visibility']['show_on_desktop'] ?? true;
+ $showDesktop = $showDesktop === true || $showDesktop === '1' || $showDesktop === 1;
+ $showMobile = $data['visibility']['show_on_mobile'] ?? true;
+ $showMobile = $showMobile === true || $showMobile === '1' || $showMobile === 1;
+
+ if (!$showDesktop && !$showMobile) {
+ return null;
+ }
+ if (!$showDesktop && $showMobile) {
+ return 'd-lg-none';
+ }
+ if ($showDesktop && !$showMobile) {
+ return 'd-none d-lg-block';
+ }
+ return '';
+ }
+
+ private function generateCSS(array $data): string
+ {
+ $colors = $data['colors'] ?? [];
+ $spacing = $data['spacing'] ?? [];
+ $typography = $data['typography'] ?? [];
+ $behavior = $data['behavior'] ?? [];
+
+ $cssRules = [];
+
+ // Container
+ $marginTop = $spacing['margin_top'] ?? '2rem';
+ $marginBottom = $spacing['margin_bottom'] ?? '2rem';
+ $padding = $spacing['padding'] ?? '1.5rem';
+
+ $cssRules[] = $this->cssGenerator->generate('.archive-header', [
+ 'margin-top' => $marginTop,
+ 'margin-bottom' => $marginBottom,
+ 'padding' => $padding,
+ ]);
+
+ // Sticky behavior
+ $isSticky = $behavior['is_sticky'] ?? false;
+ $isSticky = $isSticky === true || $isSticky === '1' || $isSticky === 1;
+
+ if ($isSticky) {
+ $stickyOffset = $behavior['sticky_offset'] ?? '0';
+ $cssRules[] = $this->cssGenerator->generate('.archive-header', [
+ 'position' => 'sticky',
+ 'top' => $stickyOffset,
+ 'z-index' => '100',
+ 'background' => '#ffffff',
+ ]);
+ }
+
+ // Title
+ $titleColor = $colors['title_color'] ?? '#0E2337';
+ $titleSize = $typography['title_size'] ?? '2rem';
+ $titleWeight = $typography['title_weight'] ?? '700';
+ $titleMarginBottom = $spacing['title_margin_bottom'] ?? '0.5rem';
+
+ $cssRules[] = $this->cssGenerator->generate('.archive-header__title', [
+ 'color' => $titleColor,
+ 'font-size' => $titleSize,
+ 'font-weight' => $titleWeight,
+ 'margin-bottom' => $titleMarginBottom,
+ 'line-height' => '1.2',
+ ]);
+
+ // Prefix
+ $prefixColor = $colors['prefix_color'] ?? '#6b7280';
+
+ $cssRules[] = $this->cssGenerator->generate('.archive-header__prefix', [
+ 'color' => $prefixColor,
+ 'font-weight' => '400',
+ ]);
+
+ // Description
+ $descColor = $colors['description_color'] ?? '#6b7280';
+ $descSize = $typography['description_size'] ?? '1rem';
+
+ $cssRules[] = $this->cssGenerator->generate('.archive-header__description', [
+ 'color' => $descColor,
+ 'font-size' => $descSize,
+ 'margin-top' => '0.5rem',
+ 'line-height' => '1.6',
+ ]);
+
+ // Post count badge
+ $countBgColor = $colors['count_bg_color'] ?? '#FF8600';
+ $countTextColor = $colors['count_text_color'] ?? '#ffffff';
+ $countSize = $typography['count_size'] ?? '0.875rem';
+ $countPadding = $spacing['count_padding'] ?? '0.25rem 0.75rem';
+
+ $cssRules[] = $this->cssGenerator->generate('.archive-header__count', [
+ 'background-color' => $countBgColor,
+ 'color' => $countTextColor,
+ 'font-size' => $countSize,
+ 'padding' => $countPadding,
+ 'border-radius' => '9999px',
+ 'font-weight' => '500',
+ 'display' => 'inline-block',
+ 'margin-left' => '0.75rem',
+ ]);
+
+ return implode("\n", $cssRules);
+ }
+
+ private function buildHTML(array $data, string $visibilityClass): string
+ {
+ $content = $data['content'] ?? [];
+ $typography = $data['typography'] ?? [];
+
+ $headingLevel = $typography['heading_level'] ?? 'h1';
+ $showPostCount = $content['show_post_count'] ?? true;
+ $showPostCount = $showPostCount === true || $showPostCount === '1' || $showPostCount === 1;
+ $showDescription = $content['show_description'] ?? true;
+ $showDescription = $showDescription === true || $showDescription === '1' || $showDescription === 1;
+
+ // Get context-specific title and description
+ $titleData = $this->getContextualTitle($content);
+ $title = $titleData['title'];
+ $prefix = $titleData['prefix'];
+ $description = $showDescription ? $titleData['description'] : '';
+
+ // Get post count
+ $postCount = $this->getPostCount();
+ $countSingular = $content['posts_count_singular'] ?? 'publicacion';
+ $countPlural = $content['posts_count_plural'] ?? 'publicaciones';
+ $countText = $postCount === 1 ? $countSingular : $countPlural;
+
+ $containerClass = 'archive-header';
+ if (!empty($visibilityClass)) {
+ $containerClass .= ' ' . $visibilityClass;
+ }
+
+ $html = sprintf('', esc_attr($containerClass));
+
+ // Title with optional prefix
+ $html .= sprintf('<%s class="archive-header__title">', esc_attr($headingLevel));
+
+ if (!empty($prefix)) {
+ $html .= sprintf(
+ ' ',
+ esc_html($prefix)
+ );
+ }
+
+ $html .= esc_html($title);
+
+ // Post count badge
+ if ($showPostCount && $postCount > 0) {
+ $html .= sprintf(
+ '',
+ $postCount,
+ esc_html($countText)
+ );
+ }
+
+ $html .= sprintf('%s>', esc_attr($headingLevel));
+
+ // Description
+ if (!empty($description)) {
+ $html .= sprintf(
+ '',
+ esc_html($description)
+ );
+ }
+
+ $html .= '
';
+
+ return $html;
+ }
+
+ /**
+ * Get contextual title based on current page type
+ *
+ * @param array $content Content settings from schema
+ * @return array{title: string, prefix: string, description: string}
+ */
+ private function getContextualTitle(array $content): array
+ {
+ $title = '';
+ $prefix = '';
+ $description = '';
+
+ if (is_category()) {
+ $prefix = $content['category_prefix'] ?? 'Categoria:';
+ $title = single_cat_title('', false) ?: '';
+ $description = category_description() ?: '';
+ } elseif (is_tag()) {
+ $prefix = $content['tag_prefix'] ?? 'Etiqueta:';
+ $title = single_tag_title('', false) ?: '';
+ $description = tag_description() ?: '';
+ } elseif (is_author()) {
+ $prefix = $content['author_prefix'] ?? 'Articulos de:';
+ $title = get_the_author() ?: '';
+ $description = get_the_author_meta('description') ?: '';
+ } elseif (is_date()) {
+ $prefix = $content['date_prefix'] ?? 'Archivo:';
+ $title = $this->getDateArchiveTitle();
+ $description = '';
+ } elseif (is_search()) {
+ $prefix = $content['search_prefix'] ?? 'Resultados para:';
+ $title = get_search_query() ?: '';
+ $description = '';
+ } elseif (is_home()) {
+ $prefix = '';
+ $title = $content['blog_title'] ?? 'Blog';
+ $description = '';
+ } elseif (is_archive()) {
+ $prefix = '';
+ $title = get_the_archive_title() ?: 'Archivo';
+ $description = get_the_archive_description() ?: '';
+ } else {
+ $prefix = '';
+ $title = $content['blog_title'] ?? 'Blog';
+ $description = '';
+ }
+
+ return [
+ 'title' => $title,
+ 'prefix' => $prefix,
+ 'description' => strip_tags($description),
+ ];
+ }
+
+ /**
+ * Get formatted title for date archives
+ */
+ private function getDateArchiveTitle(): string
+ {
+ if (is_day()) {
+ return get_the_date();
+ } elseif (is_month()) {
+ return get_the_date('F Y');
+ } elseif (is_year()) {
+ return get_the_date('Y');
+ }
+ return get_the_archive_title() ?: '';
+ }
+
+ /**
+ * Get total post count for current query
+ */
+ private function getPostCount(): int
+ {
+ global $wp_query;
+ return $wp_query->found_posts ?? 0;
+ }
+}
diff --git a/Public/PostGrid/Infrastructure/Ui/PostGridRenderer.php b/Public/PostGrid/Infrastructure/Ui/PostGridRenderer.php
new file mode 100644
index 00000000..07cf378d
--- /dev/null
+++ b/Public/PostGrid/Infrastructure/Ui/PostGridRenderer.php
@@ -0,0 +1,572 @@
+getData();
+
+ if (!$this->isEnabled($data)) {
+ return '';
+ }
+
+ if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
+ return '';
+ }
+
+ $visibilityClass = $this->getVisibilityClass($data);
+ if ($visibilityClass === null) {
+ return '';
+ }
+
+ global $wp_query;
+
+ // Si no hay posts, mostrar mensaje
+ if (!have_posts()) {
+ $noPostsMessage = $data['content']['no_posts_message'] ?? 'No se encontraron publicaciones';
+ return $this->renderNoPostsMessage($noPostsMessage, $visibilityClass, $data);
+ }
+
+ $css = $this->generateCSS($data);
+ $html = $this->buildHTML($data, $visibilityClass);
+
+ return sprintf("\n%s", $css, $html);
+ }
+
+ public function supports(string $componentType): bool
+ {
+ return $componentType === self::COMPONENT_NAME;
+ }
+
+ private function isEnabled(array $data): bool
+ {
+ $value = $data['visibility']['is_enabled'] ?? false;
+ return $value === true || $value === '1' || $value === 1;
+ }
+
+ private function getVisibilityClass(array $data): ?string
+ {
+ $showDesktop = $data['visibility']['show_on_desktop'] ?? true;
+ $showDesktop = $showDesktop === true || $showDesktop === '1' || $showDesktop === 1;
+ $showMobile = $data['visibility']['show_on_mobile'] ?? true;
+ $showMobile = $showMobile === true || $showMobile === '1' || $showMobile === 1;
+
+ if (!$showDesktop && !$showMobile) {
+ return null;
+ }
+ if (!$showDesktop && $showMobile) {
+ return 'd-lg-none';
+ }
+ if ($showDesktop && !$showMobile) {
+ return 'd-none d-lg-block';
+ }
+ return '';
+ }
+
+ private function renderNoPostsMessage(string $message, string $visibilityClass, array $data): string
+ {
+ $colors = $data['colors'] ?? [];
+ $spacing = $data['spacing'] ?? [];
+
+ $bgColor = $colors['card_bg_color'] ?? '#ffffff';
+ $textColor = $colors['excerpt_color'] ?? '#6b7280';
+ $borderColor = $colors['card_border_color'] ?? '#e5e7eb';
+ $padding = $spacing['card_padding'] ?? '1.25rem';
+
+ $css = $this->cssGenerator->generate('.post-grid-no-posts', [
+ 'background-color' => $bgColor,
+ 'color' => $textColor,
+ 'border' => "1px solid {$borderColor}",
+ 'border-radius' => '0.5rem',
+ 'padding' => '2rem',
+ 'text-align' => 'center',
+ ]);
+
+ $containerClass = 'post-grid-no-posts';
+ if (!empty($visibilityClass)) {
+ $containerClass .= ' ' . $visibilityClass;
+ }
+
+ $html = sprintf(
+ '',
+ esc_attr($containerClass),
+ esc_html($message)
+ );
+
+ return sprintf("\n%s", $css, $html);
+ }
+
+ private function generateCSS(array $data): string
+ {
+ $colors = $data['colors'] ?? [];
+ $spacing = $data['spacing'] ?? [];
+ $effects = $data['visual_effects'] ?? [];
+ $typography = $data['typography'] ?? [];
+ $layout = $data['layout'] ?? [];
+
+ $cssRules = [];
+
+ // Colores
+ $cardBgColor = $colors['card_bg_color'] ?? '#ffffff';
+ $cardTitleColor = $colors['card_title_color'] ?? '#0E2337';
+ $cardHoverBgColor = $colors['card_hover_bg_color'] ?? '#f9fafb';
+ $cardBorderColor = $colors['card_border_color'] ?? '#e5e7eb';
+ $cardHoverBorderColor = $colors['card_hover_border_color'] ?? '#FF8600';
+ $excerptColor = $colors['excerpt_color'] ?? '#6b7280';
+ $metaColor = $colors['meta_color'] ?? '#9ca3af';
+ $categoryBgColor = $colors['category_bg_color'] ?? '#FFF5EB';
+ $categoryTextColor = $colors['category_text_color'] ?? '#FF8600';
+ $paginationColor = $colors['pagination_color'] ?? '#0E2337';
+ $paginationActiveBg = $colors['pagination_active_bg'] ?? '#FF8600';
+ $paginationActiveColor = $colors['pagination_active_color'] ?? '#ffffff';
+
+ // Spacing
+ $gridGap = $spacing['grid_gap'] ?? '1.5rem';
+ $cardPadding = $spacing['card_padding'] ?? '1.25rem';
+ $sectionMarginTop = $spacing['section_margin_top'] ?? '0';
+ $sectionMarginBottom = $spacing['section_margin_bottom'] ?? '2rem';
+
+ // Visual effects
+ $cardBorderRadius = $effects['card_border_radius'] ?? '0.5rem';
+ $cardShadow = $effects['card_shadow'] ?? '0 1px 3px rgba(0,0,0,0.1)';
+ $cardHoverShadow = $effects['card_hover_shadow'] ?? '0 4px 12px rgba(0,0,0,0.15)';
+ $cardTransition = $effects['card_transition'] ?? 'all 0.3s ease';
+ $imageBorderRadius = $effects['image_border_radius'] ?? '0.375rem';
+
+ // Typography
+ $cardTitleSize = $typography['card_title_size'] ?? '1.1rem';
+ $cardTitleWeight = $typography['card_title_weight'] ?? '600';
+ $excerptSize = $typography['excerpt_size'] ?? '0.9rem';
+ $metaSize = $typography['meta_size'] ?? '0.8rem';
+
+ // Container
+ $cssRules[] = $this->cssGenerator->generate('.post-grid', [
+ 'margin-top' => $sectionMarginTop,
+ 'margin-bottom' => $sectionMarginBottom,
+ ]);
+
+ // Row gap
+ $cssRules[] = $this->cssGenerator->generate('.post-grid .row', [
+ 'gap' => $gridGap,
+ 'row-gap' => $gridGap,
+ ]);
+
+ // Card base
+ $cssRules[] = ".post-grid .card {
+ background: {$cardBgColor};
+ border: 1px solid {$cardBorderColor};
+ border-radius: {$cardBorderRadius};
+ box-shadow: {$cardShadow};
+ transition: {$cardTransition};
+ height: 100%;
+ overflow: hidden;
+ }";
+
+ // Card hover
+ $cssRules[] = ".post-grid .card:hover {
+ background: {$cardHoverBgColor};
+ border-color: {$cardHoverBorderColor};
+ box-shadow: {$cardHoverShadow};
+ transform: translateY(-2px);
+ }";
+
+ // Card body
+ $cssRules[] = $this->cssGenerator->generate('.post-grid .card-body', [
+ 'padding' => $cardPadding,
+ ]);
+
+ // Card image
+ $cssRules[] = ".post-grid .card-img-top {
+ border-radius: {$imageBorderRadius} {$imageBorderRadius} 0 0;
+ object-fit: cover;
+ width: 100%;
+ height: 200px;
+ }";
+
+ // Card title
+ $cssRules[] = ".post-grid .card-title {
+ color: {$cardTitleColor};
+ font-size: {$cardTitleSize};
+ font-weight: {$cardTitleWeight};
+ line-height: 1.4;
+ margin-bottom: 0.75rem;
+ }";
+
+ // Card title hover
+ $cssRules[] = ".post-grid a:hover .card-title {
+ color: {$cardHoverBorderColor};
+ }";
+
+ // Excerpt
+ $cssRules[] = ".post-grid .card-text {
+ color: {$excerptColor};
+ font-size: {$excerptSize};
+ line-height: 1.6;
+ }";
+
+ // Meta
+ $cssRules[] = ".post-grid .post-meta {
+ color: {$metaColor};
+ font-size: {$metaSize};
+ }";
+
+ // Categories
+ $cssRules[] = ".post-grid .post-category {
+ background: {$categoryBgColor};
+ color: {$categoryTextColor};
+ font-size: 0.75rem;
+ font-weight: 600;
+ padding: 0.25rem 0.75rem;
+ border-radius: 9999px;
+ text-decoration: none;
+ display: inline-block;
+ margin-right: 0.5rem;
+ margin-bottom: 0.5rem;
+ }";
+
+ $cssRules[] = ".post-grid .post-category:hover {
+ background: {$categoryTextColor};
+ color: #ffffff;
+ }";
+
+ // Pagination
+ $cssRules[] = ".post-grid .pagination {
+ margin-top: 2rem;
+ }";
+
+ $cssRules[] = ".post-grid .page-link {
+ color: {$paginationColor};
+ border: 1px solid {$cardBorderColor};
+ padding: 0.5rem 1rem;
+ margin: 0 0.25rem;
+ border-radius: 4px;
+ font-weight: 500;
+ transition: all 0.3s ease;
+ }";
+
+ $cssRules[] = ".post-grid .page-link:hover {
+ background-color: rgba(255, 133, 0, 0.1);
+ border-color: {$paginationActiveBg};
+ color: {$paginationActiveBg};
+ }";
+
+ $cssRules[] = ".post-grid .page-item.active .page-link,
+ .post-grid .nav-links .current {
+ background-color: {$paginationActiveBg};
+ border-color: {$paginationActiveBg};
+ color: {$paginationActiveColor};
+ }";
+
+ // WordPress pagination classes
+ $cssRules[] = ".post-grid .nav-links {
+ display: flex;
+ justify-content: center;
+ gap: 0.5rem;
+ margin-top: 2rem;
+ }";
+
+ $cssRules[] = ".post-grid .nav-links a,
+ .post-grid .nav-links span {
+ color: {$paginationColor};
+ border: 1px solid {$cardBorderColor};
+ padding: 0.5rem 1rem;
+ border-radius: 4px;
+ font-weight: 500;
+ transition: all 0.3s ease;
+ text-decoration: none;
+ }";
+
+ $cssRules[] = ".post-grid .nav-links a:hover {
+ background-color: rgba(255, 133, 0, 0.1);
+ border-color: {$paginationActiveBg};
+ color: {$paginationActiveBg};
+ }";
+
+ // Layout responsive columns
+ $colsDesktop = $layout['columns_desktop'] ?? '3';
+ $colsTablet = $layout['columns_tablet'] ?? '2';
+ $colsMobile = $layout['columns_mobile'] ?? '1';
+
+ // Mobile
+ $mobileWidth = $this->getColumnWidth($colsMobile);
+ $cssRules[] = "@media (max-width: 575.98px) {
+ .post-grid .post-card-col {
+ flex: 0 0 {$mobileWidth};
+ max-width: {$mobileWidth};
+ }
+ }";
+
+ // Tablet
+ $tabletWidth = $this->getColumnWidth($colsTablet);
+ $cssRules[] = "@media (min-width: 576px) and (max-width: 991.98px) {
+ .post-grid .post-card-col {
+ flex: 0 0 {$tabletWidth};
+ max-width: {$tabletWidth};
+ }
+ }";
+
+ // Desktop
+ $desktopWidth = $this->getColumnWidth($colsDesktop);
+ $cssRules[] = "@media (min-width: 992px) {
+ .post-grid .post-card-col {
+ flex: 0 0 {$desktopWidth};
+ max-width: {$desktopWidth};
+ }
+ }";
+
+ return implode("\n", $cssRules);
+ }
+
+ private function getColumnWidth(string $cols): string
+ {
+ $colCount = (int)$cols;
+ if ($colCount <= 0) {
+ $colCount = 1;
+ }
+ $percentage = 100 / $colCount;
+ return sprintf('%.4f%%', $percentage);
+ }
+
+ private function buildHTML(array $data, string $visibilityClass): string
+ {
+ $content = $data['content'] ?? [];
+ $typography = $data['typography'] ?? [];
+ $media = $data['media'] ?? [];
+ $layout = $data['layout'] ?? [];
+
+ $showThumbnail = $this->toBool($content['show_thumbnail'] ?? true);
+ $showExcerpt = $this->toBool($content['show_excerpt'] ?? true);
+ $showMeta = $this->toBool($content['show_meta'] ?? true);
+ $showCategories = $this->toBool($content['show_categories'] ?? true);
+ $excerptLength = (int)($content['excerpt_length'] ?? 20);
+ $readMoreText = $content['read_more_text'] ?? 'Leer mas';
+ $headingLevel = $typography['heading_level'] ?? 'h3';
+ $fallbackImage = $media['fallback_image'] ?? '';
+ $fallbackImageAlt = $media['fallback_image_alt'] ?? 'Imagen por defecto';
+ $imagePosition = $layout['image_position'] ?? 'top';
+
+ $containerClass = 'post-grid';
+ if (!empty($visibilityClass)) {
+ $containerClass .= ' ' . $visibilityClass;
+ }
+
+ $html = sprintf('', esc_attr($containerClass));
+ $html .= '
';
+
+ while (have_posts()) {
+ the_post();
+ $html .= $this->buildCardHTML(
+ $showThumbnail,
+ $showExcerpt,
+ $showMeta,
+ $showCategories,
+ $excerptLength,
+ $readMoreText,
+ $headingLevel,
+ $fallbackImage,
+ $fallbackImageAlt,
+ $imagePosition
+ );
+ }
+
+ $html .= '
';
+
+ // Paginacion nativa de WordPress
+ $html .= '';
+
+ $html .= '
';
+
+ wp_reset_postdata();
+
+ return $html;
+ }
+
+ private function toBool(mixed $value): bool
+ {
+ return $value === true || $value === '1' || $value === 1;
+ }
+
+ private function buildCardHTML(
+ bool $showThumbnail,
+ bool $showExcerpt,
+ bool $showMeta,
+ bool $showCategories,
+ int $excerptLength,
+ string $readMoreText,
+ string $headingLevel,
+ string $fallbackImage,
+ string $fallbackImageAlt,
+ string $imagePosition
+ ): string {
+ $permalink = get_permalink();
+ $title = get_the_title();
+
+ $html = ''; // col
+
+ return $html;
+ }
+
+ private function buildImageHTML(string $fallbackImage, string $fallbackImageAlt, string $imagePosition): string
+ {
+ if (has_post_thumbnail()) {
+ $imageClass = $imagePosition === 'left' ? 'card-img-left' : 'card-img-top';
+ return get_the_post_thumbnail(
+ null,
+ 'medium_large',
+ ['class' => $imageClass, 'loading' => 'lazy']
+ );
+ }
+
+ if (!empty($fallbackImage)) {
+ $imageClass = $imagePosition === 'left' ? 'card-img-left' : 'card-img-top';
+ return sprintf(
+ ' ',
+ esc_url($fallbackImage),
+ esc_attr($fallbackImageAlt),
+ esc_attr($imageClass)
+ );
+ }
+
+ return '';
+ }
+
+ private function buildCategoriesHTML(): string
+ {
+ $categories = get_the_category();
+ if (empty($categories)) {
+ return '';
+ }
+
+ $html = '';
+ foreach (array_slice($categories, 0, 2) as $category) {
+ $html .= sprintf(
+ '%s ',
+ esc_html($category->name)
+ );
+ }
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildMetaHTML(): string
+ {
+ $date = get_the_date();
+ $author = get_the_author();
+
+ return sprintf(
+ '%s | %s
',
+ esc_html($date),
+ esc_html($author)
+ );
+ }
+
+ private function buildExcerptHTML(int $length): string
+ {
+ $excerpt = get_the_excerpt();
+
+ if (empty($excerpt)) {
+ $excerpt = wp_trim_words(get_the_content(), $length, '...');
+ } else {
+ $excerpt = wp_trim_words($excerpt, $length, '...');
+ }
+
+ return sprintf(
+ '%s
',
+ esc_html($excerpt)
+ );
+ }
+
+ private function buildPaginationHTML(): string
+ {
+ ob_start();
+
+ the_posts_pagination([
+ 'mid_size' => 2,
+ 'prev_text' => __('« Anterior', 'roi-theme'),
+ 'next_text' => __('Siguiente »', 'roi-theme'),
+ 'screen_reader_text' => __('Navegacion de publicaciones', 'roi-theme'),
+ ]);
+
+ return ob_get_clean();
+ }
+}
diff --git a/Schemas/archive-header.json b/Schemas/archive-header.json
new file mode 100644
index 00000000..f940e8fa
--- /dev/null
+++ b/Schemas/archive-header.json
@@ -0,0 +1,233 @@
+{
+ "component_name": "archive-header",
+ "version": "1.0.0",
+ "description": "Cabecera dinamica para paginas de archivo con titulo y descripcion contextual",
+ "groups": {
+ "visibility": {
+ "label": "Visibilidad",
+ "priority": 10,
+ "fields": {
+ "is_enabled": {
+ "type": "boolean",
+ "label": "Activar componente",
+ "default": true,
+ "editable": true,
+ "required": true
+ },
+ "show_on_desktop": {
+ "type": "boolean",
+ "label": "Mostrar en escritorio",
+ "default": true,
+ "editable": true,
+ "description": "Muestra el componente en pantallas >= 992px"
+ },
+ "show_on_mobile": {
+ "type": "boolean",
+ "label": "Mostrar en movil",
+ "default": true,
+ "editable": true,
+ "description": "Muestra el componente en pantallas < 992px"
+ }
+ }
+ },
+ "content": {
+ "label": "Contenido",
+ "priority": 20,
+ "fields": {
+ "blog_title": {
+ "type": "text",
+ "label": "Titulo del blog",
+ "default": "Blog",
+ "editable": true,
+ "description": "Titulo mostrado en la pagina principal del blog"
+ },
+ "show_post_count": {
+ "type": "boolean",
+ "label": "Mostrar contador de posts",
+ "default": true,
+ "editable": true,
+ "description": "Muestra el numero de posts encontrados"
+ },
+ "show_description": {
+ "type": "boolean",
+ "label": "Mostrar descripcion",
+ "default": true,
+ "editable": true,
+ "description": "Muestra la descripcion de categoria/tag si existe"
+ },
+ "category_prefix": {
+ "type": "text",
+ "label": "Prefijo categoria",
+ "default": "Categoria:",
+ "editable": true
+ },
+ "tag_prefix": {
+ "type": "text",
+ "label": "Prefijo etiqueta",
+ "default": "Etiqueta:",
+ "editable": true
+ },
+ "author_prefix": {
+ "type": "text",
+ "label": "Prefijo autor",
+ "default": "Articulos de:",
+ "editable": true
+ },
+ "date_prefix": {
+ "type": "text",
+ "label": "Prefijo fecha",
+ "default": "Archivo:",
+ "editable": true
+ },
+ "search_prefix": {
+ "type": "text",
+ "label": "Prefijo busqueda",
+ "default": "Resultados para:",
+ "editable": true
+ },
+ "posts_count_singular": {
+ "type": "text",
+ "label": "Texto singular posts",
+ "default": "publicacion",
+ "editable": true
+ },
+ "posts_count_plural": {
+ "type": "text",
+ "label": "Texto plural posts",
+ "default": "publicaciones",
+ "editable": true
+ }
+ }
+ },
+ "typography": {
+ "label": "Tipografia",
+ "priority": 30,
+ "fields": {
+ "heading_level": {
+ "type": "select",
+ "label": "Nivel de encabezado",
+ "default": "h1",
+ "editable": true,
+ "options": ["h1", "h2", "h3", "h4", "h5", "h6"],
+ "description": "Nivel semantico del titulo para SEO"
+ },
+ "title_size": {
+ "type": "text",
+ "label": "Tamano titulo",
+ "default": "2rem",
+ "editable": true
+ },
+ "title_weight": {
+ "type": "text",
+ "label": "Peso titulo",
+ "default": "700",
+ "editable": true
+ },
+ "description_size": {
+ "type": "text",
+ "label": "Tamano descripcion",
+ "default": "1rem",
+ "editable": true
+ },
+ "count_size": {
+ "type": "text",
+ "label": "Tamano contador",
+ "default": "0.875rem",
+ "editable": true
+ }
+ }
+ },
+ "colors": {
+ "label": "Colores",
+ "priority": 40,
+ "fields": {
+ "title_color": {
+ "type": "color",
+ "label": "Color titulo",
+ "default": "#0E2337",
+ "editable": true
+ },
+ "description_color": {
+ "type": "color",
+ "label": "Color descripcion",
+ "default": "#6b7280",
+ "editable": true
+ },
+ "count_bg_color": {
+ "type": "color",
+ "label": "Fondo contador",
+ "default": "#FF8600",
+ "editable": true
+ },
+ "count_text_color": {
+ "type": "color",
+ "label": "Texto contador",
+ "default": "#ffffff",
+ "editable": true
+ },
+ "prefix_color": {
+ "type": "color",
+ "label": "Color prefijo",
+ "default": "#6b7280",
+ "editable": true
+ }
+ }
+ },
+ "spacing": {
+ "label": "Espaciado",
+ "priority": 50,
+ "fields": {
+ "margin_top": {
+ "type": "text",
+ "label": "Margen superior",
+ "default": "2rem",
+ "editable": true
+ },
+ "margin_bottom": {
+ "type": "text",
+ "label": "Margen inferior",
+ "default": "2rem",
+ "editable": true
+ },
+ "padding": {
+ "type": "text",
+ "label": "Padding interno",
+ "default": "1.5rem",
+ "editable": true
+ },
+ "title_margin_bottom": {
+ "type": "text",
+ "label": "Margen inferior titulo",
+ "default": "0.5rem",
+ "editable": true
+ },
+ "count_padding": {
+ "type": "text",
+ "label": "Padding contador",
+ "default": "0.25rem 0.75rem",
+ "editable": true
+ }
+ }
+ },
+ "behavior": {
+ "label": "Comportamiento",
+ "priority": 70,
+ "fields": {
+ "is_sticky": {
+ "type": "boolean",
+ "label": "Header fijo",
+ "default": false,
+ "editable": true,
+ "description": "Mantiene el header visible al hacer scroll"
+ },
+ "sticky_offset": {
+ "type": "text",
+ "label": "Offset sticky",
+ "default": "0",
+ "editable": true,
+ "description": "Distancia desde el top cuando es sticky"
+ }
+ }
+ }
+ }
+}
diff --git a/Schemas/post-grid.json b/Schemas/post-grid.json
new file mode 100644
index 00000000..9512e52f
--- /dev/null
+++ b/Schemas/post-grid.json
@@ -0,0 +1,271 @@
+{
+ "component_name": "post-grid",
+ "version": "1.0.0",
+ "description": "Grid de posts para templates de listados usando el loop principal de WordPress",
+ "groups": {
+ "visibility": {
+ "label": "Visibilidad",
+ "priority": 10,
+ "fields": {
+ "is_enabled": {
+ "type": "boolean",
+ "default": true,
+ "label": "Habilitar componente"
+ },
+ "show_on_desktop": {
+ "type": "boolean",
+ "default": true,
+ "label": "Mostrar en desktop"
+ },
+ "show_on_mobile": {
+ "type": "boolean",
+ "default": true,
+ "label": "Mostrar en movil"
+ }
+ }
+ },
+ "content": {
+ "label": "Contenido",
+ "priority": 20,
+ "fields": {
+ "show_thumbnail": {
+ "type": "boolean",
+ "default": true,
+ "label": "Mostrar imagen destacada"
+ },
+ "show_excerpt": {
+ "type": "boolean",
+ "default": true,
+ "label": "Mostrar extracto"
+ },
+ "show_meta": {
+ "type": "boolean",
+ "default": true,
+ "label": "Mostrar metadatos (fecha, autor)"
+ },
+ "show_categories": {
+ "type": "boolean",
+ "default": true,
+ "label": "Mostrar categorias"
+ },
+ "excerpt_length": {
+ "type": "select",
+ "default": "20",
+ "label": "Longitud del extracto (palabras)",
+ "options": ["10", "15", "20", "25", "30"]
+ },
+ "read_more_text": {
+ "type": "text",
+ "default": "Leer mas",
+ "label": "Texto de leer mas"
+ },
+ "no_posts_message": {
+ "type": "text",
+ "default": "No se encontraron publicaciones",
+ "label": "Mensaje cuando no hay posts"
+ }
+ }
+ },
+ "typography": {
+ "label": "Tipografia",
+ "priority": 30,
+ "fields": {
+ "heading_level": {
+ "type": "select",
+ "default": "h3",
+ "label": "Nivel de encabezado de tarjetas",
+ "options": ["h2", "h3", "h4", "h5", "h6"]
+ },
+ "card_title_size": {
+ "type": "text",
+ "default": "1.1rem",
+ "label": "Tamano titulo de tarjeta"
+ },
+ "card_title_weight": {
+ "type": "text",
+ "default": "600",
+ "label": "Peso titulo de tarjeta"
+ },
+ "excerpt_size": {
+ "type": "text",
+ "default": "0.9rem",
+ "label": "Tamano de extracto"
+ },
+ "meta_size": {
+ "type": "text",
+ "default": "0.8rem",
+ "label": "Tamano de metadatos"
+ }
+ }
+ },
+ "colors": {
+ "label": "Colores",
+ "priority": 40,
+ "fields": {
+ "card_bg_color": {
+ "type": "color",
+ "default": "#ffffff",
+ "label": "Fondo de tarjeta"
+ },
+ "card_title_color": {
+ "type": "color",
+ "default": "#0E2337",
+ "label": "Color titulo de tarjeta"
+ },
+ "card_hover_bg_color": {
+ "type": "color",
+ "default": "#f9fafb",
+ "label": "Fondo hover de tarjeta"
+ },
+ "card_border_color": {
+ "type": "color",
+ "default": "#e5e7eb",
+ "label": "Color borde de tarjeta"
+ },
+ "card_hover_border_color": {
+ "type": "color",
+ "default": "#FF8600",
+ "label": "Color borde hover"
+ },
+ "excerpt_color": {
+ "type": "color",
+ "default": "#6b7280",
+ "label": "Color de extracto"
+ },
+ "meta_color": {
+ "type": "color",
+ "default": "#9ca3af",
+ "label": "Color de metadatos"
+ },
+ "category_bg_color": {
+ "type": "color",
+ "default": "#FFF5EB",
+ "label": "Fondo de categoria"
+ },
+ "category_text_color": {
+ "type": "color",
+ "default": "#FF8600",
+ "label": "Color texto categoria"
+ },
+ "pagination_color": {
+ "type": "color",
+ "default": "#0E2337",
+ "label": "Color de paginacion"
+ },
+ "pagination_active_bg": {
+ "type": "color",
+ "default": "#FF8600",
+ "label": "Fondo paginacion activa"
+ },
+ "pagination_active_color": {
+ "type": "color",
+ "default": "#ffffff",
+ "label": "Color texto paginacion activa"
+ }
+ }
+ },
+ "spacing": {
+ "label": "Espaciado",
+ "priority": 50,
+ "fields": {
+ "grid_gap": {
+ "type": "text",
+ "default": "1.5rem",
+ "label": "Espacio entre tarjetas"
+ },
+ "card_padding": {
+ "type": "text",
+ "default": "1.25rem",
+ "label": "Padding interno de tarjeta"
+ },
+ "section_margin_top": {
+ "type": "text",
+ "default": "0",
+ "label": "Margen superior de seccion"
+ },
+ "section_margin_bottom": {
+ "type": "text",
+ "default": "2rem",
+ "label": "Margen inferior de seccion"
+ }
+ }
+ },
+ "visual_effects": {
+ "label": "Efectos Visuales",
+ "priority": 60,
+ "fields": {
+ "card_border_radius": {
+ "type": "text",
+ "default": "0.5rem",
+ "label": "Radio de borde de tarjeta"
+ },
+ "card_shadow": {
+ "type": "text",
+ "default": "0 1px 3px rgba(0,0,0,0.1)",
+ "label": "Sombra de tarjeta"
+ },
+ "card_hover_shadow": {
+ "type": "text",
+ "default": "0 4px 12px rgba(0,0,0,0.15)",
+ "label": "Sombra hover de tarjeta"
+ },
+ "card_transition": {
+ "type": "text",
+ "default": "all 0.3s ease",
+ "label": "Transicion de tarjeta"
+ },
+ "image_border_radius": {
+ "type": "text",
+ "default": "0.375rem",
+ "label": "Radio de borde de imagen"
+ }
+ }
+ },
+ "layout": {
+ "label": "Disposicion",
+ "priority": 80,
+ "fields": {
+ "columns_desktop": {
+ "type": "select",
+ "default": "3",
+ "label": "Columnas en desktop",
+ "options": ["2", "3", "4"]
+ },
+ "columns_tablet": {
+ "type": "select",
+ "default": "2",
+ "label": "Columnas en tablet",
+ "options": ["1", "2", "3"]
+ },
+ "columns_mobile": {
+ "type": "select",
+ "default": "1",
+ "label": "Columnas en movil",
+ "options": ["1", "2"]
+ },
+ "image_position": {
+ "type": "select",
+ "default": "top",
+ "label": "Posicion de imagen",
+ "options": ["top", "left", "none"]
+ }
+ }
+ },
+ "media": {
+ "label": "Medios",
+ "priority": 90,
+ "fields": {
+ "fallback_image": {
+ "type": "url",
+ "default": "",
+ "label": "Imagen por defecto (URL)"
+ },
+ "fallback_image_alt": {
+ "type": "text",
+ "default": "Imagen por defecto",
+ "label": "Texto alternativo imagen por defecto"
+ }
+ }
+ }
+ }
+}
diff --git a/Shared/Application/UseCases/EvaluatePageVisibility/EvaluatePageVisibilityUseCase.php b/Shared/Application/UseCases/EvaluatePageVisibility/EvaluatePageVisibilityUseCase.php
index 57fc8e94..14c54829 100644
--- a/Shared/Application/UseCases/EvaluatePageVisibility/EvaluatePageVisibilityUseCase.php
+++ b/Shared/Application/UseCases/EvaluatePageVisibility/EvaluatePageVisibilityUseCase.php
@@ -22,15 +22,15 @@ final class EvaluatePageVisibilityUseCase
) {}
/**
- * Evalúa si el componente debe mostrarse en la página actual
+ * Evalua si el componente debe mostrarse en la pagina actual
*/
public function execute(string $componentName): bool
{
$config = $this->visibilityRepository->getVisibilityConfig($componentName);
if (empty($config)) {
- // Usar constante compartida (DRY)
- $config = VisibilityDefaults::DEFAULT_VISIBILITY;
+ // Usar defaults especificos por componente si existen
+ $config = VisibilityDefaults::getForComponent($componentName);
}
$pageType = $this->pageTypeDetector->detect();
diff --git a/Shared/Domain/Constants/VisibilityDefaults.php b/Shared/Domain/Constants/VisibilityDefaults.php
index ca810d2b..73901c26 100644
--- a/Shared/Domain/Constants/VisibilityDefaults.php
+++ b/Shared/Domain/Constants/VisibilityDefaults.php
@@ -16,13 +16,13 @@ namespace ROITheme\Shared\Domain\Constants;
final class VisibilityDefaults
{
/**
- * Configuración de visibilidad por defecto para nuevos componentes
+ * Configuracion de visibilidad por defecto para componentes generales
*
- * - Home: SÍ mostrar (página principal)
- * - Posts: SÍ mostrar (artículos del blog)
- * - Pages: SÍ mostrar (páginas estáticas)
- * - Archives: NO mostrar (listados de categorías/tags)
- * - Search: NO mostrar (resultados de búsqueda)
+ * - Home: SI mostrar (pagina principal)
+ * - Posts: SI mostrar (articulos del blog)
+ * - Pages: SI mostrar (paginas estaticas)
+ * - Archives: NO mostrar (listados de categorias/tags)
+ * - Search: NO mostrar (resultados de busqueda)
*/
public const DEFAULT_VISIBILITY = [
'show_on_home' => true,
@@ -33,7 +33,39 @@ final class VisibilityDefaults
];
/**
- * Lista de campos de visibilidad válidos
+ * Defaults especificos por componente (sobrescriben DEFAULT_VISIBILITY)
+ *
+ * Componentes de listados:
+ * - archive-header: Solo en archives (home para blog title)
+ * - post-grid: En home, archives y search
+ * - cta-box-sidebar: Tambien en archives
+ */
+ public const COMPONENT_VISIBILITY = [
+ 'archive-header' => [
+ 'show_on_home' => true, // Para mostrar blog_title
+ 'show_on_posts' => false,
+ 'show_on_pages' => false,
+ 'show_on_archives' => true, // Proposito principal
+ 'show_on_search' => true, // Mostrar "Resultados: X"
+ ],
+ 'post-grid' => [
+ 'show_on_home' => true, // Blog principal
+ 'show_on_posts' => false,
+ 'show_on_pages' => false,
+ 'show_on_archives' => true, // Listados de categoria/tag
+ 'show_on_search' => true, // Resultados de busqueda
+ ],
+ 'cta-box-sidebar' => [
+ 'show_on_home' => true,
+ 'show_on_posts' => true,
+ 'show_on_pages' => true,
+ 'show_on_archives' => true, // Visible en archives
+ 'show_on_search' => false,
+ ],
+ ];
+
+ /**
+ * Lista de campos de visibilidad validos
*/
public const VISIBILITY_FIELDS = [
'show_on_home',
@@ -42,4 +74,15 @@ final class VisibilityDefaults
'show_on_archives',
'show_on_search',
];
+
+ /**
+ * Obtiene los defaults para un componente especifico
+ *
+ * @param string $componentName Nombre del componente (kebab-case)
+ * @return array Configuracion de visibilidad
+ */
+ public static function getForComponent(string $componentName): array
+ {
+ return self::COMPONENT_VISIBILITY[$componentName] ?? self::DEFAULT_VISIBILITY;
+ }
}
diff --git a/Shared/Infrastructure/Services/MigratePageVisibilityService.php b/Shared/Infrastructure/Services/MigratePageVisibilityService.php
index c86db621..9c697849 100644
--- a/Shared/Infrastructure/Services/MigratePageVisibilityService.php
+++ b/Shared/Infrastructure/Services/MigratePageVisibilityService.php
@@ -20,7 +20,7 @@ final class MigratePageVisibilityService
) {}
/**
- * Ejecuta la migración para todos los componentes
+ * Ejecuta la migracion para todos los componentes
*
* @return array{created: int, skipped: int}
*/
@@ -37,10 +37,10 @@ final class MigratePageVisibilityService
continue;
}
- // Usar constante compartida (DRY)
+ // Usar defaults especificos por componente si existen
$this->visibilityRepository->createDefaultVisibility(
$componentName,
- VisibilityDefaults::DEFAULT_VISIBILITY
+ VisibilityDefaults::getForComponent($componentName)
);
$created++;
}
diff --git a/Shared/Infrastructure/Services/WordPressPageTypeDetector.php b/Shared/Infrastructure/Services/WordPressPageTypeDetector.php
index 4d7eeb09..7dcaf162 100644
--- a/Shared/Infrastructure/Services/WordPressPageTypeDetector.php
+++ b/Shared/Infrastructure/Services/WordPressPageTypeDetector.php
@@ -40,7 +40,10 @@ final class WordPressPageTypeDetector implements PageTypeDetectorInterface
public function isHome(): bool
{
- return is_front_page();
+ // is_front_page() = pagina de inicio configurada
+ // is_home() = pagina de posts (blog)
+ // Ambas cuentan como "home" para visibilidad
+ return is_front_page() || is_home();
}
public function isPost(): bool
diff --git a/archive.php b/archive.php
index bd1d8f81..2dee3bf4 100644
--- a/archive.php
+++ b/archive.php
@@ -2,8 +2,9 @@
/**
* The template for displaying archive pages
*
- * This template displays date-based, category, tag, author, and post type
- * archives with a dynamic title, description, and post loop.
+ * Estructura unificada siguiendo el patron de single.php.
+ * Usa roi_render_component() para todos los componentes.
+ * La visibilidad se controla via PageVisibilityHelper::shouldShow().
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#archive
*
@@ -14,213 +15,78 @@
get_header();
?>
-
+
-
+
+
-
-
+
+
-
+
+
+
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2,
- 'prev_text' => sprintf(
- '%s
%s ',
- '
« ',
- esc_html__( 'Previous', 'roi-theme' )
- ),
- 'next_text' => sprintf(
- '
%s %s',
- esc_html__( 'Next', 'roi-theme' ),
- '
» '
- ),
- 'before_page_number' => '
' . esc_html__( 'Page', 'roi-theme' ) . ' ',
- 'aria_label' => esc_attr__( 'Posts navigation', 'roi-theme' ),
- )
- );
-
- else :
-
- /**
- * No posts found
- * Display a message when no content is available.
- */
- ?>
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
-
+
+
-
-
+
+
-
+
+
+
+
-
-
-
- /**
- * Include the Post-Type-specific template for the content.
- * If you want to override this in a child theme, then include a file
- * called content-___.php (where ___ is the Post Type name) and that will be used instead.
- */
- get_template_part( 'template-parts/content', get_post_type() );
-
- endwhile;
- ?>
-
-
-
- 2,
- 'prev_text' => sprintf(
- '%s
%s ',
- '
« ',
- esc_html__( 'Previous', 'roi-theme' )
- ),
- 'next_text' => sprintf(
- '
%s %s',
- esc_html__( 'Next', 'roi-theme' ),
- '
» '
- ),
- 'before_page_number' => '
' . esc_html__( 'Page', 'roi-theme' ) . ' ',
- 'aria_label' => esc_attr__( 'Posts navigation', 'roi-theme' ),
- )
- );
-
- else :
-
- /**
- * No posts found
- * Display a message when no content is available.
- */
- get_template_part( 'template-parts/content', 'none' );
-
- endif;
- ?>
-
-
-
-
-
-
-
-
+
+
-
+
-
+
+
-
-
+
+
-
+
+
+
+
-
-
- endwhile;
- ?>
+
-
-
- 2,
- 'prev_text' => sprintf(
- '%s
%s ',
- '
« ',
- esc_html__( 'Previous', 'roi-theme' )
- ),
- 'next_text' => sprintf(
- '
%s %s',
- esc_html__( 'Next', 'roi-theme' ),
- '
» '
- ),
- 'before_page_number' => '
' . esc_html__( 'Page', 'roi-theme' ) . ' ',
- 'aria_label' => esc_attr__( 'Posts navigation', 'roi-theme' ),
- )
- );
-
- else :
-
- /**
- * No posts found
- * Display a message when no content is available.
- */
- get_template_part( 'template-parts/content', 'none' );
-
- endif;
- ?>
-
-
-
-
-
-
-
-
+
+
-
+
-
+
+
-
-
+
+
-
+
+
+
+
-
-
- endwhile;
- ?>
+
-
-
- 2,
- 'prev_text' => sprintf(
- '%s
%s ',
- '
« ',
- esc_html__( 'Previous', 'roi-theme' )
- ),
- 'next_text' => sprintf(
- '
%s %s',
- esc_html__( 'Next', 'roi-theme' ),
- '
» '
- ),
- 'before_page_number' => '
' . esc_html__( 'Page', 'roi-theme' ) . ' ',
- 'aria_label' => esc_attr__( 'Posts navigation', 'roi-theme' ),
- )
- );
-
- else :
-
- /**
- * No posts found
- * Display a message when no content is available.
- */
- get_template_part( 'template-parts/content', 'none' );
-
- endif;
- ?>
-
-
-
-
-
-
-
-
+
+
attribute_value;
- // Convertir booleanos almacenados como '1' o '0'
- if ($value === '1' || $value === '0') {
+ // Solo convertir a booleano campos que realmente son booleanos
+ // Los grupos 'visibility', '_page_visibility' y campos que empiezan con 'show_', 'is_', 'enable'
+ $isBooleanField = (
+ $row->group_name === 'visibility' ||
+ $row->group_name === '_page_visibility' ||
+ str_starts_with($row->attribute_name, 'show_') ||
+ str_starts_with($row->attribute_name, 'is_') ||
+ str_starts_with($row->attribute_name, 'enable')
+ );
+
+ if ($isBooleanField && ($value === '1' || $value === '0')) {
$value = ($value === '1');
} else {
// Intentar decodificar JSON
@@ -321,6 +330,12 @@ function roi_render_component(string $componentName): string {
case 'footer':
$renderer = new \ROITheme\Public\Footer\Infrastructure\Ui\FooterRenderer($cssGenerator);
break;
+ case 'archive-header':
+ $renderer = new \ROITheme\Public\ArchiveHeader\Infrastructure\Ui\ArchiveHeaderRenderer($cssGenerator);
+ break;
+ case 'post-grid':
+ $renderer = new \ROITheme\Public\PostGrid\Infrastructure\Ui\PostGridRenderer($cssGenerator);
+ break;
}
if (!$renderer) {
diff --git a/home.php b/home.php
index ee8271e6..70fdee2b 100644
--- a/home.php
+++ b/home.php
@@ -2,9 +2,9 @@
/**
* The template for displaying the blog posts index
*
- * This template is used when the blog posts page is different from the front page.
- * It displays a listing of recent blog posts with pagination.
- * Set in WordPress Settings > Reading > "Posts page".
+ * Estructura unificada siguiendo el patron de single.php.
+ * Usa roi_render_component() para todos los componentes.
+ * La visibilidad se controla via PageVisibilityHelper::shouldShow().
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#home
*
@@ -15,109 +15,78 @@
get_header();
?>
-
+
-
+
+
-
-
+
+
-
+
+
+
+
-
-
+
+
-
-
+
+
-
- /**
- * Include the Post-Type-specific template for the content.
- * If you want to override this in a child theme, then include a file
- * called content-___.php (where ___ is the Post Type name) and that will be used instead.
- */
- get_template_part( 'template-parts/content', get_post_type() );
+
+
+
+
+
+
-
+
+
- 2,
- 'prev_text' => sprintf(
- '%s
%s ',
- '
« ',
- esc_html__( 'Previous', 'roi-theme' )
- ),
- 'next_text' => sprintf(
- '
%s %s',
- esc_html__( 'Next', 'roi-theme' ),
- '
» '
- ),
- 'before_page_number' => '
' . esc_html__( 'Page', 'roi-theme' ) . ' ',
- 'aria_label' => esc_attr__( 'Posts navigation', 'roi-theme' ),
- )
- );
+
- else :
-
- /**
- * No posts found
- * Display a message when no content is available.
- */
- get_template_part( 'template-parts/content', 'none' );
-
- endif;
- ?>
-
-
-
-
-
-
-
-
+
+
-
+
-
+
+
-
+
+
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
-
-
+
-
+
+
+
+
+
+
-
- 'count',
- 'order' => 'DESC',
- 'number' => 10,
- 'hide_empty' => true,
- )
- );
+
+
- if ( ! empty( $categories ) ) :
- ?>
-
-
+
-
- 10,
- 'post_status' => 'publish',
- )
- );
-
- if ( ! empty( $recent_posts ) ) :
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
-
+
+
-
-
+
+
-
+
+
+
+
-
-
- endwhile;
- ?>
+
-
-
- 2,
- 'prev_text' => sprintf(
- '%s
%s ',
- '
« ',
- esc_html__( 'Previous', 'roi-theme' )
- ),
- 'next_text' => sprintf(
- '
%s %s',
- esc_html__( 'Next', 'roi-theme' ),
- '
» '
- ),
- 'before_page_number' => '
' . esc_html__( 'Page', 'roi-theme' ) . ' ',
- 'aria_label' => esc_attr__( 'Posts navigation', 'roi-theme' ),
- )
- );
-
- else :
-
- /**
- * No posts found
- * Display a message when no content is available.
- */
- get_template_part( 'template-parts/content', 'none' );
-
- endif;
- ?>
-
-
-
-
-
-
-
-
+
+