post_type !== 'post') { return $permalink; } if (has_category('bloques-en-transicion', $post) || has_category('bloques', $post)) { return home_url('/bloques-en-transicion/noticias/' . $post->post_name . '/'); } return $permalink; } /** * Construir tax_query correcta según el tipo de contenido. * * Para noticias: siempre incluye la condición base (category = bloques-en-transicion) * y la combina con AND con los filtros de iniciativa/linea_trabajo. * Para CPTs: solo filtra por iniciativa/linea_trabajo. * * @param string $type Tipo de contenido (noticias, actuaciones, recursos, eventos) * @param string $iniciativa Slug de iniciativa para filtrar (vacío = sin filtro) * @param string $linea Slug de línea de trabajo para filtrar (vacío = sin filtro) * @return array tax_query lista para WP_Query */ private static function build_tax_query($type, $iniciativa = '', $linea = '') { $tax_query = []; // Para noticias: siempre requerir la categoría base if ($type === 'noticias') { $tax_query[] = [ 'taxonomy' => 'category', 'field' => 'slug', 'terms' => ['bloques-en-transicion', 'bloques'], 'include_children' => true, ]; } // Filtro por iniciativa if (!empty($iniciativa)) { $tax_query[] = [ 'taxonomy' => 'iniciativa', 'field' => 'slug', 'terms' => is_array($iniciativa) ? $iniciativa : explode(',', $iniciativa), ]; } // Filtro por línea de trabajo if (!empty($linea)) { $tax_query[] = [ 'taxonomy' => 'linea_trabajo', 'field' => 'slug', 'terms' => is_array($linea) ? $linea : explode(',', $linea), ]; } // Si hay más de una condición, usar AND if (count($tax_query) > 1) { $tax_query['relation'] = 'AND'; } return $tax_query; } /** * Mapeo de tipos */ private static function get_type_config($type) { $configs = [ 'actuaciones' => [ 'post_type' => 'actuacion', 'template' => 'actuaciones', 'label_singular' => __('actuación', 'bloques-transicion'), 'label_plural' => __('actuaciones', 'bloques-transicion'), ], 'recursos' => [ 'post_type' => 'recurso_bloques', 'template' => 'recursos', 'label_singular' => __('recurso', 'bloques-transicion'), 'label_plural' => __('recursos', 'bloques-transicion'), ], 'eventos' => [ 'post_type' => 'evento_bloques', 'template' => 'eventos', 'label_singular' => __('evento', 'bloques-transicion'), 'label_plural' => __('eventos', 'bloques-transicion'), ], 'noticias' => [ 'post_type' => 'post', 'template' => 'noticias', 'label_singular' => __('noticia', 'bloques-transicion'), 'label_plural' => __('noticias', 'bloques-transicion'), ], ]; return $configs[$type] ?? $configs['actuaciones']; } /** * Shortcode principal: [bloques-listado] * * Atributos: * - type: actuaciones|recursos|eventos|noticias (requerido) * - widget: grid|list (default: grid) * - columns: 1|2|3|4 (default: 3) * - limit: número de items a mostrar (default: -1 = todos) * - pagination: true|false - botón "Ver más" (default: false) * - filter: true|false - filtros tipo botonera (default: false) * - search: true|false - campo de búsqueda (default: false) * - iniciativa: slug de iniciativa para filtrar * - linea: slug de línea de trabajo para filtrar * - orderby: date|title|menu_order (default: date) * - order: ASC|DESC (default: DESC) * - upcoming: solo eventos futuros (default: true para eventos) * - class: clase CSS adicional */ public static function render_listado($atts) { $atts = shortcode_atts([ 'type' => 'recursos', 'widget' => 'grid', 'columns' => 3, 'limit' => -1, 'pagination' => 'false', 'filter' => 'false', 'search' => 'false', 'iniciativa' => '', 'linea' => '', 'orderby' => 'date', 'order' => 'DESC', 'upcoming' => 'true', 'class' => '', ], $atts, 'bloques-listado'); $type_config = self::get_type_config($atts['type']); $post_type = $type_config['post_type']; $show_filter = filter_var($atts['filter'], FILTER_VALIDATE_BOOLEAN); $show_search = filter_var($atts['search'], FILTER_VALIDATE_BOOLEAN); $show_pagination = filter_var($atts['pagination'], FILTER_VALIDATE_BOOLEAN); $upcoming = filter_var($atts['upcoming'], FILTER_VALIDATE_BOOLEAN); $limit = intval($atts['limit']); // Query args $args = [ 'post_type' => $post_type, 'posts_per_page' => $limit > 0 ? $limit : -1, 'orderby' => $atts['orderby'], 'order' => $atts['order'], 'post_status' => 'publish', ]; // Ordenar eventos por fecha de inicio if ($post_type === 'evento_bloques') { $args['meta_key'] = 'fecha_inicio'; $args['orderby'] = 'meta_value'; $args['order'] = 'ASC'; // Solo eventos futuros if ($upcoming) { $args['meta_query'] = [ [ 'key' => 'fecha_inicio', 'value' => date('Ymd'), 'compare' => '>=', 'type' => 'DATE', ], ]; } } // Construir tax_query (para noticias incluye automáticamente la categoría base) $tax_query = self::build_tax_query($atts['type'], $atts['iniciativa'], $atts['linea']); if (!empty($tax_query)) { $args['tax_query'] = $tax_query; } $query = new WP_Query($args); $total_posts = $query->found_posts; // Generar ID único para el contenedor $container_id = 'bloques-listado-' . wp_rand(1000, 9999); ob_start(); ?>
have_posts()) { while ($query->have_posts()) { $query->the_post(); echo self::render_item(get_post(), $atts['type'], $atts['widget']); } wp_reset_postdata(); } else { echo '

' . __('No se encontraron resultados.', 'bloques-transicion') . '

'; } ?>
0 && $total_posts > $limit): ?>
'iniciativa', 'hide_empty' => true, ]); $lineas = get_terms([ 'taxonomy' => 'linea_trabajo', 'hide_empty' => true, ]); ob_start(); ?>
$post->ID, 'title' => get_the_title($post), 'excerpt' => get_the_excerpt($post), 'content' => apply_filters('the_content', $post->post_content), 'permalink' => get_permalink($post), 'thumbnail' => get_the_post_thumbnail_url($post, 'medium_large'), 'thumbnail_alt' => get_post_meta(get_post_thumbnail_id($post), '_wp_attachment_image_alt', true), 'iniciativas' => wp_get_post_terms($post->ID, 'iniciativa'), 'lineas' => wp_get_post_terms($post->ID, 'linea_trabajo'), 'widget' => $widget, 'type' => $type, 'date' => get_the_date('j M Y', $post), ]; // Datos específicos por tipo switch ($type) { case 'actuaciones': $data['es_piloto'] = get_field('es_piloto', $post->ID); $data['direccion'] = get_field('direccion', $post->ID); $data['localidad'] = get_field('localidad', $post->ID); $data['latitud'] = get_field('latitud', $post->ID); $data['longitud'] = get_field('longitud', $post->ID); break; case 'recursos': $data['archivo'] = get_field('archivo', $post->ID); $data['tipo_recurso'] = get_field('tipo_recurso', $post->ID); $data['url_externa'] = get_field('url_externa', $post->ID); break; case 'eventos': $data['fecha_inicio'] = get_field('fecha_inicio', $post->ID); $data['hora_inicio'] = get_field('hora_inicio', $post->ID); $data['fecha_fin'] = get_field('fecha_fin', $post->ID); $data['hora_fin'] = get_field('hora_fin', $post->ID); $data['lugar'] = get_field('lugar', $post->ID); $data['direccion'] = get_field('direccion', $post->ID); $data['url_online'] = get_field('url_online', $post->ID); $data['url_inscripcion'] = get_field('url_inscripcion', $post->ID); $data['tipos_evento'] = wp_get_post_terms($post->ID, 'tipo_evento'); break; case 'noticias': // Obtener categorías del post (excluyendo bloques-en-transicion) $categories = get_the_category($post->ID); $display_category = null; $category_color = '#F97316'; foreach ($categories as $cat) { if (!in_array($cat->slug, ['bloques-en-transicion', 'bloques'])) { $display_category = $cat; // Intentar obtener color de iniciativa si existe con ese slug $iniciativa = get_term_by('slug', $cat->slug, 'iniciativa'); if ($iniciativa) { $color = get_field('color', $iniciativa); if ($color) { $category_color = $color; } } break; } } if (!$display_category && !empty($categories)) { $display_category = $categories[0]; } $data['category'] = $display_category; $data['category_color'] = $category_color; break; } return $data; } /** * Shortcode para grid simple: [bloques-grid] */ public static function render_grid($atts) { $atts['widget'] = 'grid'; return self::render_listado($atts); } /** * Shortcode para recursos: [bloques-recursos] */ public static function render_recursos($atts) { $atts = shortcode_atts([ 'widget' => 'grid', 'columns' => 3, 'limit' => -1, 'pagination' => 'false', 'filter' => 'true', 'search' => 'true', 'class' => '', ], $atts, 'bloques-recursos'); $atts['type'] = 'recursos'; return self::render_listado($atts); } /** * Shortcode para noticias: [bloques-noticias] */ public static function render_noticias($atts) { $atts = shortcode_atts([ 'widget' => 'grid', 'columns' => 3, 'limit' => -1, 'pagination' => 'false', 'filter' => 'false', 'class' => '', ], $atts, 'bloques-noticias'); $atts['type'] = 'noticias'; return self::render_listado($atts); } /** * Shortcode específico para eventos: [bloques-eventos] */ public static function render_eventos($atts) { $atts = shortcode_atts([ 'widget' => 'list', 'columns' => 1, 'limit' => 5, 'upcoming' => 'true', 'pagination' => 'false', 'filter' => 'false', 'class' => '', ], $atts, 'bloques-eventos'); $atts['type'] = 'eventos'; return self::render_listado($atts); } /** * Shortcode para Novedades con header: [bloques-novedades] */ public static function render_novedades($atts) { $atts = shortcode_atts([ 'limit' => 3, 'columns' => 1, 'title' => __('Novedades', 'bloques-transicion'), 'subtitle' => __('Últimas noticias del proyecto', 'bloques-transicion'), 'show_header' => 'true', 'link_text' => __('Ver todas las novedades', 'bloques-transicion'), 'link_url' => '/bloques-en-transicion/noticias/', 'class' => '', ], $atts, 'bloques-novedades'); $show_header = filter_var($atts['show_header'], FILTER_VALIDATE_BOOLEAN); ob_start(); ?>

'noticias', 'widget' => 'compact', 'columns' => $atts['columns'], 'limit' => $atts['limit'], 'class' => 'bloques-novedades-list', ]); ?>
4, 'title' => __('Agenda', 'bloques-transicion'), 'subtitle' => __('Próximas actividades', 'bloques-transicion'), 'show_header' => 'true', 'link_text' => __('Ver calendario completo', 'bloques-transicion'), 'link_url' => '/bloques-en-transicion/noticias/', 'upcoming' => 'true', 'class' => '', ], $atts, 'bloques-agenda'); $show_header = filter_var($atts['show_header'], FILTER_VALIDATE_BOOLEAN); ob_start(); ?>

'eventos', 'widget' => 'agenda', 'columns' => 1, 'limit' => $atts['limit'], 'upcoming' => $atts['upcoming'], 'class' => 'bloques-agenda-list', ]); ?>
'cards', 'columns' => 4, 'show_description' => 'true', 'class' => '', ], $atts, 'bloques-iniciativas'); $iniciativas = get_terms([ 'taxonomy' => 'iniciativa', 'hide_empty' => false, ]); $show_desc = filter_var($atts['show_description'], FILTER_VALIDATE_BOOLEAN); ob_start(); ?>
<?php echo esc_attr($term->name); ?>

name); ?>

description): ?>

description); ?>

'icons', 'columns' => 6, 'show_description' => 'true', 'class' => '', ], $atts, 'bloques-lineas'); $lineas = get_terms([ 'taxonomy' => 'linea_trabajo', 'hide_empty' => false, ]); $show_desc = filter_var($atts['show_description'], FILTER_VALIDATE_BOOLEAN); ob_start(); ?>
<?php echo esc_attr($term->name); ?>

name); ?>

description): ?>

description); ?>

$post_type, 'posts_per_page' => -1, 'post_status' => 'publish', ]; // Búsqueda if (!empty($search)) { $args['s'] = $search; } // Eventos if ($post_type === 'evento_bloques') { $args['meta_key'] = 'fecha_inicio'; $args['orderby'] = 'meta_value'; $args['order'] = 'ASC'; if (filter_var($upcoming, FILTER_VALIDATE_BOOLEAN)) { $args['meta_query'] = [ [ 'key' => 'fecha_inicio', 'value' => date('Ymd'), 'compare' => '>=', 'type' => 'DATE', ], ]; } } // Construir tax_query $args['tax_query'] = self::build_tax_query($type, $iniciativa, $linea); $query = new WP_Query($args); ob_start(); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); echo self::render_item(get_post(), $type, $widget); } wp_reset_postdata(); } else { echo '

' . __('No se encontraron resultados.', 'bloques-transicion') . '

'; } $html = ob_get_clean(); wp_send_json_success([ 'html' => $html, 'count' => $query->found_posts, 'label' => $query->found_posts === 1 ? $type_config['label_singular'] : $type_config['label_plural'], ]); } /** * Handler AJAX para cargar más */ public static function ajax_load_more() { check_ajax_referer('bloques_nonce', 'nonce'); $type = sanitize_text_field($_POST['type'] ?? 'recursos'); $offset = intval($_POST['offset'] ?? 0); $limit = intval($_POST['limit'] ?? 9); $widget = sanitize_text_field($_POST['widget'] ?? 'grid'); $iniciativa = sanitize_text_field($_POST['iniciativa'] ?? ''); $linea = sanitize_text_field($_POST['linea_trabajo'] ?? ''); $upcoming = sanitize_text_field($_POST['upcoming'] ?? 'true'); $type_config = self::get_type_config($type); $post_type = $type_config['post_type']; $args = [ 'post_type' => $post_type, 'posts_per_page' => $limit, 'offset' => $offset, 'post_status' => 'publish', ]; // Eventos if ($post_type === 'evento_bloques') { $args['meta_key'] = 'fecha_inicio'; $args['orderby'] = 'meta_value'; $args['order'] = 'ASC'; if (filter_var($upcoming, FILTER_VALIDATE_BOOLEAN)) { $args['meta_query'] = [ [ 'key' => 'fecha_inicio', 'value' => date('Ymd'), 'compare' => '>=', 'type' => 'DATE', ], ]; } } // Construir tax_query $args['tax_query'] = self::build_tax_query($type, $iniciativa, $linea); $query = new WP_Query($args); ob_start(); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); echo self::render_item(get_post(), $type, $widget); } wp_reset_postdata(); } $html = ob_get_clean(); // Calcular si hay más $total_query = new WP_Query(array_merge($args, ['posts_per_page' => -1, 'offset' => 0, 'fields' => 'ids'])); $has_more = ($offset + $limit) < $total_query->found_posts; wp_send_json_success([ 'html' => $html, 'has_more' => $has_more, 'new_offset' => $offset + $query->post_count, ]); } }