# Especificacion de Shortcode Post Grid ## Purpose Crear un shortcode `[roi_post_grid]` que permita insertar grids de posts en cualquier pagina o entrada de WordPress, con filtros configurables por categoria, tag, autor y otras opciones. Sigue Clean Architecture delegando logica a Use Cases. ## Context ### Problema Actual El componente `post-grid` implementado en `templates-unificados` solo funciona en templates de archivo (home.php, archive.php, etc.) porque depende del loop principal de WordPress (`global $wp_query`). ### Solucion Crear un shortcode que: 1. Siga Clean Architecture (ShortcodeRegistrar → UseCase → Repository) 2. Reutilice estilos del componente `post-grid` existente 3. Permita filtrar posts por categoria, tag, autor 4. No dependa del loop principal de WordPress ### Relacion con templates-unificados - Este shortcode **complementa** (no reemplaza) el componente post-grid - El componente post-grid sigue funcionando en templates de archivo - El shortcode permite insertar grids en contenido arbitrario ### Nota sobre shortcodes legacy Los shortcodes existentes (`apu_table`, `apu_row`) estan en `Inc/apu-tables.php` (patron legacy). Este nuevo shortcode sigue el patron moderno de Clean Architecture. --- ## Requirements ### Requirement: Arquitectura del Shortcode The shortcode MUST follow Clean Architecture patterns. #### Scenario: Ubicacion del ShortcodeRegistrar - **WHEN** se implementa el shortcode - **THEN** DEBE estar en `Shared/Infrastructure/Wordpress/PostGridShortcodeRegistrar.php` - **AND** DEBE usar namespace `ROITheme\Shared\Infrastructure\Wordpress` - **AND** DEBE registrar el shortcode via add_shortcode #### Scenario: Delegacion a Use Case - **WHEN** el shortcode se ejecuta - **THEN** PostGridShortcodeRegistrar DEBE delegar a RenderPostGridUseCase - **AND** NO DEBE contener logica de negocio - **AND** solo sanitiza atributos y pasa al Use Case #### Scenario: Ubicacion del Use Case - **WHEN** se implementa la logica del shortcode - **THEN** DEBE estar en `Shared/Application/UseCases/RenderPostGrid/RenderPostGridUseCase.php` - **AND** DEBE usar namespace `ROITheme\Shared\Application\UseCases\RenderPostGrid` - **AND** DEBE orquestar Query, Renderer y Settings #### Scenario: Ubicacion del QueryBuilder - **WHEN** se construye el WP_Query - **THEN** DEBE estar en `Shared/Infrastructure/Query/PostGridQueryBuilder.php` - **AND** DEBE usar namespace `ROITheme\Shared\Infrastructure\Query` - **AND** DEBE recibir parametros y retornar WP_Query #### Scenario: Ubicacion del ShortcodeRenderer - **WHEN** se genera el HTML del grid - **THEN** DEBE estar en `Shared/Infrastructure/Ui/PostGridShortcodeRenderer.php` - **AND** DEBE usar namespace `ROITheme\Shared\Infrastructure\Ui` - **AND** DEBE inyectar CSSGeneratorInterface --- ### Requirement: Estructura de Clases Each class MUST have single responsibility following SRP. #### Scenario: Responsabilidad de PostGridShortcodeRegistrar - **WHEN** se define PostGridShortcodeRegistrar - **THEN** DEBE tener metodo estatico `register()` para add_shortcode - **AND** DEBE tener metodo `handleShortcode(array $atts): string` - **AND** handleShortcode DEBE sanitizar atributos - **AND** handleShortcode DEBE obtener UseCase del DIContainer - **AND** handleShortcode DEBE retornar resultado del UseCase - **AND** NO DEBE tener mas de 50 lineas #### Scenario: Responsabilidad de RenderPostGridUseCase - **WHEN** se define RenderPostGridUseCase - **THEN** DEBE recibir RenderPostGridRequest como input - **AND** DEBE inyectar PostGridQueryBuilderInterface via constructor - **AND** DEBE inyectar PostGridShortcodeRendererInterface via constructor - **AND** DEBE inyectar ComponentSettingsRepositoryInterface via constructor - **AND** DEBE orquestar: obtener settings, construir query, renderizar - **AND** DEBE retornar string HTML - **AND** NO DEBE tener mas de 80 lineas #### Scenario: Responsabilidad de PostGridQueryBuilder - **WHEN** se define PostGridQueryBuilder - **THEN** DEBE recibir parametros de filtro (category, tag, etc.) - **AND** DEBE construir array de argumentos WP_Query - **AND** DEBE retornar WP_Query configurado - **AND** NO DEBE generar HTML - **AND** NO DEBE tener mas de 100 lineas #### Scenario: Responsabilidad de PostGridShortcodeRenderer - **WHEN** se define PostGridShortcodeRenderer - **THEN** DEBE recibir WP_Query y configuracion - **AND** DEBE inyectar CSSGeneratorInterface - **AND** DEBE generar HTML del grid - **AND** DEBE generar CSS inline - **AND** NO DEBE construir queries - **AND** NO DEBE tener mas de 150 lineas --- ### Requirement: Interfaces en Domain/Application Interfaces MUST be defined for dependency injection. #### Scenario: Interface del QueryBuilder - **WHEN** se define la interface - **THEN** DEBE estar en `Shared/Domain/Contracts/PostGridQueryBuilderInterface.php` - **AND** DEBE tener metodo `build(array $params): \WP_Query` #### Scenario: Interface del Renderer - **WHEN** se define la interface - **THEN** DEBE estar en `Shared/Domain/Contracts/PostGridShortcodeRendererInterface.php` - **AND** DEBE tener metodo `render(\WP_Query $query, array $settings, array $options): string` #### Scenario: Request DTO - **WHEN** se define el DTO de entrada - **THEN** DEBE estar en `Shared/Application/UseCases/RenderPostGrid/RenderPostGridRequest.php` - **AND** DEBE ser readonly class con propiedades tipadas - **AND** DEBE contener todos los atributos del shortcode --- ### Requirement: Atributos del Shortcode The shortcode MUST accept configurable attributes. #### Scenario: Atributo category - **WHEN** se usa `[roi_post_grid category="precios-unitarios"]` - **THEN** DEBE filtrar posts por slug de categoria - **AND** DEBE aceptar multiples categorias separadas por coma - **AND** ejemplo: `category="cat1,cat2,cat3"` #### Scenario: Atributo exclude_category - **WHEN** se usa `[roi_post_grid exclude_category="noticias"]` - **THEN** DEBE excluir posts de esas categorias - **AND** DEBE aceptar multiples categorias separadas por coma #### Scenario: Atributo tag - **WHEN** se usa `[roi_post_grid tag="concreto"]` - **THEN** DEBE filtrar posts por slug de tag - **AND** DEBE aceptar multiples tags separados por coma #### Scenario: Atributo author - **WHEN** se usa `[roi_post_grid author="admin"]` - **THEN** DEBE filtrar posts por login de autor - **AND** DEBE aceptar ID numerico o login string #### Scenario: Atributo posts_per_page - **WHEN** se usa `[roi_post_grid posts_per_page="6"]` - **THEN** DEBE limitar cantidad de posts mostrados - **AND** default DEBE ser 9 - **AND** DEBE aceptar valores entre 1 y 50 #### Scenario: Atributo columns - **WHEN** se usa `[roi_post_grid columns="4"]` - **THEN** DEBE definir columnas en desktop - **AND** default DEBE ser 3 - **AND** DEBE aceptar valores 1, 2, 3 o 4 #### Scenario: Atributo orderby - **WHEN** se usa `[roi_post_grid orderby="title"]` - **THEN** DEBE ordenar posts por ese campo - **AND** default DEBE ser "date" - **AND** opciones validas: date, title, modified, rand, comment_count #### Scenario: Atributo order - **WHEN** se usa `[roi_post_grid order="ASC"]` - **THEN** DEBE definir direccion del orden - **AND** default DEBE ser "DESC" - **AND** opciones validas: ASC, DESC #### Scenario: Atributo show_pagination - **WHEN** se usa `[roi_post_grid show_pagination="true"]` - **THEN** DEBE mostrar paginacion si hay mas posts - **AND** default DEBE ser false #### Scenario: Atributo offset - **WHEN** se usa `[roi_post_grid offset="3"]` - **THEN** DEBE saltar los primeros N posts - **AND** default DEBE ser 0 #### Scenario: Atributo exclude_posts - **WHEN** se usa `[roi_post_grid exclude_posts="123,456"]` - **THEN** DEBE excluir posts por ID - **AND** DEBE aceptar IDs separados por coma #### Scenario: Atributos de visualizacion - **WHEN** se usan atributos de visualizacion - **THEN** show_thumbnail default true - **AND** show_excerpt default true - **AND** show_meta default true - **AND** show_categories default true - **AND** excerpt_length default 20 #### Scenario: Atributo class - **WHEN** se usa `[roi_post_grid class="my-custom-grid"]` - **THEN** DEBE agregar clase CSS adicional al contenedor #### Scenario: Atributo id para paginacion multiple - **WHEN** se usa `[roi_post_grid id="grid-1" show_pagination="true"]` - **THEN** DEBE usar query var unico `paged_grid-1` - **AND** permite multiples shortcodes paginados en misma pagina --- ### Requirement: Obtencion de Settings The shortcode MUST obtain styles from post-grid component settings. #### Scenario: Lectura de configuracion - **WHEN** RenderPostGridUseCase se ejecuta - **THEN** DEBE usar ComponentSettingsRepositoryInterface - **AND** DEBE obtener settings de componente 'post-grid' - **AND** DEBE aplicar colores, spacing, visual_effects del componente #### Scenario: Settings no encontrados - **WHEN** no existen settings de post-grid en BD - **THEN** DEBE usar valores default definidos en VisibilityDefaults - **AND** NO DEBE fallar con error --- ### Requirement: Renderizado HTML The shortcode MUST generate valid HTML with proper escaping. #### Scenario: Estructura HTML del grid - **WHEN** el shortcode renderiza - **THEN** DEBE generar contenedor con clase `roi-post-grid-shortcode` - **AND** DEBE generar row con clase Bootstrap `row` - **AND** cada card DEBE tener columna responsive #### Scenario: Clases responsive de columnas - **WHEN** columns es 3 - **THEN** cada card DEBE tener `col-12 col-md-6 col-lg-4` - **AND** para columns=4: `col-12 col-md-6 col-lg-3` - **AND** para columns=2: `col-12 col-md-6` - **AND** para columns=1: `col-12` #### Scenario: Sin resultados - **WHEN** el query no encuentra posts - **THEN** DEBE mostrar mensaje "No se encontraron publicaciones" - **AND** NO DEBE romper layout #### Scenario: Escaping obligatorio - **WHEN** se genera HTML - **THEN** DEBE usar esc_html() para textos - **AND** DEBE usar esc_attr() para atributos - **AND** DEBE usar esc_url() para URLs - **AND** DEBE usar wp_kses_post() para excerpts --- ### Requirement: CSS via CSSGenerator The shortcode MUST use CSSGeneratorInterface for styles. #### Scenario: Generacion de CSS - **WHEN** PostGridShortcodeRenderer genera CSS - **THEN** DEBE inyectar CSSGeneratorInterface - **AND** DEBE usar settings de post-grid desde BD - **AND** DEBE generar CSS inline en el shortcode #### Scenario: Selector unico - **WHEN** se genera CSS - **THEN** DEBE usar selector `.roi-post-grid-shortcode` - **AND** si se especifica id, usar `.roi-post-grid-shortcode-{id}` - **AND** NO DEBE conflictuar con post-grid del template --- ### Requirement: Registro del Shortcode The shortcode MUST be registered in WordPress. #### Scenario: Registro en bootstrap - **WHEN** WordPress carga el tema - **THEN** functions-addon.php DEBE llamar PostGridShortcodeRegistrar::register() - **AND** DEBE estar disponible en editor clasico y Gutenberg #### Scenario: Metodo register estatico - **WHEN** se llama PostGridShortcodeRegistrar::register() - **THEN** DEBE ejecutar add_shortcode('roi_post_grid', ...) - **AND** DEBE usar DIContainer para obtener dependencias --- ## Implementation Order ### Fase 1: Interfaces y DTOs 1. Crear `Shared/Domain/Contracts/PostGridQueryBuilderInterface.php` 2. Crear `Shared/Domain/Contracts/PostGridShortcodeRendererInterface.php` 3. Crear `Shared/Application/UseCases/RenderPostGrid/RenderPostGridRequest.php` ### Fase 2: Use Case 1. Crear `Shared/Application/UseCases/RenderPostGrid/RenderPostGridUseCase.php` 2. Implementar orquestacion de query, settings, render ### Fase 3: Infrastructure - Query 1. Crear `Shared/Infrastructure/Query/PostGridQueryBuilder.php` 2. Implementar construccion de WP_Query con todos los filtros ### Fase 4: Infrastructure - Renderer 1. Crear `Shared/Infrastructure/Ui/PostGridShortcodeRenderer.php` 2. Implementar generacion HTML y CSS ### Fase 5: Infrastructure - Registrar 1. Crear `Shared/Infrastructure/Wordpress/PostGridShortcodeRegistrar.php` 2. Implementar registro y sanitizacion de atributos ### Fase 6: Registro y DI 1. Registrar en DIContainer las implementaciones 2. Llamar register() en functions-addon.php ### Fase 7: Testing 1. Probar en pagina estatica 2. Verificar filtros por categoria, tag 3. Verificar paginacion con id unico --- ## File Structure ``` Shared/ ├── Domain/ │ └── Contracts/ │ ├── PostGridQueryBuilderInterface.php │ └── PostGridShortcodeRendererInterface.php ├── Application/ │ └── UseCases/ │ └── RenderPostGrid/ │ ├── RenderPostGridRequest.php │ └── RenderPostGridUseCase.php └── Infrastructure/ ├── Query/ │ └── PostGridQueryBuilder.php ├── Ui/ │ └── PostGridShortcodeRenderer.php └── Wordpress/ └── PostGridShortcodeRegistrar.php ``` --- ## Examples ### Ejemplo: Grid basico ``` [roi_post_grid] ``` ### Ejemplo: Posts de una categoria ``` [roi_post_grid category="precios-unitarios" posts_per_page="6"] ``` ### Ejemplo: Multiples shortcodes con paginacion ``` [roi_post_grid id="grid-cursos" category="cursos" show_pagination="true"] [roi_post_grid id="grid-tutoriales" category="tutoriales" show_pagination="true"] ``` --- ## Dependencies ### Existentes (reutilizar) - `CSSGeneratorInterface` - Para generar CSS - `ComponentSettingsRepositoryInterface` - Para leer config de post-grid - `DIContainer` - Para inyeccion de dependencias - Bootstrap 5 grid system - Para layout responsive ### Nuevas (crear) - `PostGridQueryBuilderInterface` - Contrato para query builder - `PostGridShortcodeRendererInterface` - Contrato para renderer - `RenderPostGridRequest` - DTO de entrada - `RenderPostGridUseCase` - Orquestador - `PostGridQueryBuilder` - Implementacion query - `PostGridShortcodeRenderer` - Implementacion render - `PostGridShortcodeRegistrar` - Registro WordPress --- ## Testing Checklist - [ ] Shortcode renderiza sin atributos - [ ] Filtro por categoria funciona - [ ] Filtro por multiples categorias funciona - [ ] Exclusion de categoria funciona - [ ] Filtro por tag funciona - [ ] Columnas 1, 2, 3, 4 funcionan - [ ] Paginacion con id unico funciona - [ ] Multiples shortcodes paginados funcionan - [ ] CSS se aplica desde settings de post-grid - [ ] Mensaje "sin posts" aparece cuando corresponde - [ ] Escaping correcto en todo el HTML - [ ] Funciona en editor clasico - [ ] Funciona en Gutenberg - [ ] No hay errores PHP - [ ] Clases tienen menos de 150 lineas