diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..7a1fdb3 Binary files /dev/null and b/.DS_Store differ diff --git a/bloques-actuaciones-import/includes/class-bloques-actuaciones-importer.php b/bloques-actuaciones-import/includes/class-bloques-actuaciones-importer.php index c00407b..5e7614e 100644 --- a/bloques-actuaciones-import/includes/class-bloques-actuaciones-importer.php +++ b/bloques-actuaciones-import/includes/class-bloques-actuaciones-importer.php @@ -16,7 +16,7 @@ if (!defined('ABSPATH')) { class Bloques_Actuaciones_Importer { - private const CSV_DELIMITER = ';'; + private const CSV_DELIMITER_FALLBACK = ';'; private const NOMINATIM_DELAY_US = 1100000; // 1.1 s private const NOMINATIM_USER_AGENT = 'BloquesTransicion/1.0 (WordPress; tangente.coop)'; @@ -411,6 +411,44 @@ class Bloques_Actuaciones_Importer { /* ── CSV parser ── */ + /** + * Detectar el delimitador del CSV analizando la primera línea. + * Prueba con ';' y ',' y elige el que produce más columnas. + */ + private static function detect_delimiter($path) { + $handle = fopen($path, 'r'); + if (!$handle) { + return self::CSV_DELIMITER_FALLBACK; + } + + // Saltar BOM + $bom = fread($handle, 3); + if ($bom !== "\xEF\xBB\xBF") { + rewind($handle); + } + + $first_line = fgets($handle); + fclose($handle); + + if ($first_line === false) { + return self::CSV_DELIMITER_FALLBACK; + } + + $candidates = [',', ';', "\t"]; + $best = self::CSV_DELIMITER_FALLBACK; + $best_count = 0; + + foreach ($candidates as $delim) { + $count = count(str_getcsv($first_line, $delim)); + if ($count > $best_count) { + $best_count = $count; + $best = $delim; + } + } + + return $best; + } + private static function parse_csv($path, &$errors = []) { $handle = fopen($path, 'r'); if (!$handle) { @@ -424,7 +462,10 @@ class Bloques_Actuaciones_Importer { rewind($handle); } - $header = fgetcsv($handle, 0, self::CSV_DELIMITER); + // Auto-detectar delimitador + $delimiter = self::detect_delimiter($path); + + $header = fgetcsv($handle, 0, $delimiter); if ($header === false || empty($header)) { fclose($handle); $errors[] = __('Cabecera CSV no válida.', 'bloques-actuaciones-import'); @@ -434,7 +475,7 @@ class Bloques_Actuaciones_Importer { $header = array_map('trim', $header); $rows = []; - while (($raw = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) { + while (($raw = fgetcsv($handle, 0, $delimiter)) !== false) { $padded = array_pad($raw, count($header), ''); $row = array_combine($header, array_slice($padded, 0, count($header))); if (is_array($row)) { diff --git a/bloques-transicion/assets/js/admin.js b/bloques-transicion/assets/js/admin.js index 3be86d9..48a4464 100644 --- a/bloques-transicion/assets/js/admin.js +++ b/bloques-transicion/assets/js/admin.js @@ -29,6 +29,128 @@ // Añadir cursor pointer a los shortcodes $('.bloques-dashboard-shortcodes code').css('cursor', 'pointer').attr('title', 'Clic para copiar'); + + // Mostrar/ocultar metaboxes de Iniciativa y Línea de Trabajo en el editor de entradas + if (typeof bloquesAdminData !== 'undefined' && bloquesAdminData.isPostEditor) { + initConditionalTaxonomyBoxes(); + } }); + /** + * Mostrar metaboxes de Iniciativa y Línea de Trabajo solo cuando + * la entrada tiene marcada la categoría "Bloques en Transición". + * Compatible con el editor clásico y Gutenberg. + */ + function initConditionalTaxonomyBoxes() { + const catIds = bloquesAdminData.bloquesCatIds || []; + + if (!catIds.length) { + return; + } + + // ── Editor clásico (metaboxes) ── + const $iniciativaBox = $('#iniciativadiv, #taxonomy-iniciativa').closest('.postbox'); + const $lineaBox = $('#linea_trabajodiv, #taxonomy-linea_trabajo').closest('.postbox'); + + if ($iniciativaBox.length || $lineaBox.length) { + // Comprobar estado inicial y escuchar cambios en checkboxes de categorías + function checkClassicCategories() { + let isBloquesChecked = false; + + catIds.forEach(function(catId) { + if ($('#in-category-' + catId).is(':checked')) { + isBloquesChecked = true; + } + }); + + if (isBloquesChecked) { + $iniciativaBox.slideDown(200); + $lineaBox.slideDown(200); + } else { + $iniciativaBox.slideUp(200); + $lineaBox.slideUp(200); + } + } + + // Estado inicial (sin animación) + (function() { + let isBloquesChecked = false; + catIds.forEach(function(catId) { + if ($('#in-category-' + catId).is(':checked')) { + isBloquesChecked = true; + } + }); + if (isBloquesChecked) { + $iniciativaBox.show(); + $lineaBox.show(); + } else { + $iniciativaBox.hide(); + $lineaBox.hide(); + } + })(); + + // Escuchar cambios en categorías + $('#categorychecklist').on('change', 'input[type="checkbox"]', checkClassicCategories); + } + + // ── Gutenberg (editor de bloques) ── + if (typeof wp !== 'undefined' && wp.data && wp.data.select && wp.data.subscribe) { + let previousCategories = []; + + // Selectores de los paneles de taxonomías en el sidebar de Gutenberg + function toggleGutenbergPanels(show) { + // Los metaboxes de taxonomías custom en Gutenberg tienen paneles con + // data-taxonomy o clases como .editor-post-taxonomies__hierarchical-terms-list + // Usamos un enfoque basado en MutationObserver + selectores + const selectors = [ + '.editor-post-taxonomies__hierarchical-terms-list[data-taxonomy="iniciativa"]', + '.editor-post-taxonomies__hierarchical-terms-list[data-taxonomy="linea_trabajo"]', + // Paneles en el sidebar del editor + '[class*="iniciativa"]', + '[class*="linea_trabajo"]', + ]; + + // Buscar los paneles padre (.components-panel__body) que contienen estas taxonomías + const panels = document.querySelectorAll('.components-panel__body'); + panels.forEach(function(panel) { + const heading = panel.querySelector('.components-panel__body-title button'); + if (heading) { + const text = heading.textContent.toLowerCase(); + if (text.includes('iniciativa') || text.includes('línea') || text.includes('linea')) { + panel.style.display = show ? '' : 'none'; + } + } + }); + + // También ocultar los metaboxes clásicos que pueda insertar WP en Gutenberg + ['iniciativadiv', 'linea_trabajodiv'].forEach(function(id) { + const el = document.getElementById(id); + if (el) { + el.style.display = show ? '' : 'none'; + } + }); + } + + wp.data.subscribe(function() { + const editor = wp.data.select('core/editor'); + if (!editor) return; + + const currentCategories = editor.getEditedPostAttribute('categories') || []; + + // Solo actuar si las categorías cambiaron + if (JSON.stringify(currentCategories) === JSON.stringify(previousCategories)) { + return; + } + previousCategories = currentCategories.slice(); + + // Comprobar si alguna de las categorías de Bloques está seleccionada + const isBloquesChecked = catIds.some(function(catId) { + return currentCategories.includes(catId); + }); + + toggleGutenbergPanels(isBloquesChecked); + }); + } + } + })(jQuery); diff --git a/bloques-transicion/assets/js/frontend.js b/bloques-transicion/assets/js/frontend.js index 1ec9b4f..5c80005 100644 --- a/bloques-transicion/assets/js/frontend.js +++ b/bloques-transicion/assets/js/frontend.js @@ -22,11 +22,10 @@ const $container = $('#' + containerId); const $itemsContainer = $container.find('.bloques-items'); - // Estado de filtros activos (incluye category para noticias) + // Estado de filtros activos let activeFilters = { iniciativa: '', - linea_trabajo: '', - category: '' + linea_trabajo: '' }; // Click en botones de filtro @@ -71,7 +70,6 @@ type: type, iniciativa: filters.iniciativa || '', linea_trabajo: filters.linea_trabajo || '', - category: filters.category || '', search: search, widget: widget, upcoming: upcoming @@ -92,7 +90,7 @@ } // Ocultar botón "Ver más" cuando hay filtros activos - const hasActiveFilters = filters.iniciativa || filters.linea_trabajo || filters.category || search; + const hasActiveFilters = filters.iniciativa || filters.linea_trabajo || search; $container.find('.bloques-load-more-wrapper').toggle(!hasActiveFilters); // Resetear offset @@ -127,8 +125,7 @@ // Obtener filtros activos const activeFilters = { iniciativa: $filters.find('.bloques-filter-btn[data-taxonomy="iniciativa"].active').data('value') || '', - linea_trabajo: $filters.find('.bloques-filter-btn[data-taxonomy="linea_trabajo"].active').data('value') || '', - category: $filters.find('.bloques-filter-btn[data-taxonomy="category"].active').data('value') || '' + linea_trabajo: $filters.find('.bloques-filter-btn[data-taxonomy="linea_trabajo"].active').data('value') || '' }; // Debounce @@ -160,12 +157,10 @@ const $filters = $container.find('.bloques-filters'); let iniciativa = ''; let linea = ''; - let category = ''; if ($filters.length) { iniciativa = $filters.find('.bloques-filter-btn[data-taxonomy="iniciativa"].active').data('value') || ''; linea = $filters.find('.bloques-filter-btn[data-taxonomy="linea_trabajo"].active').data('value') || ''; - category = $filters.find('.bloques-filter-btn[data-taxonomy="category"].active').data('value') || ''; } // Mostrar loading @@ -183,7 +178,6 @@ widget: widget, iniciativa: iniciativa, linea_trabajo: linea, - category: category, upcoming: upcoming }, success: function(response) { diff --git a/bloques-transicion/bloques-transicion.php b/bloques-transicion/bloques-transicion.php index 6863b49..bf56b1c 100644 --- a/bloques-transicion/bloques-transicion.php +++ b/bloques-transicion/bloques-transicion.php @@ -124,11 +124,16 @@ final class Bloques_Transicion { public function admin_assets($hook) { $screen = get_current_screen(); - // Solo cargar en páginas del plugin - if ($screen && ( - strpos($screen->id, 'bloques') !== false || - in_array($screen->post_type, ['actuacion', 'recurso_bloques', 'evento_bloques']) - )) { + if (!$screen) { + return; + } + + // Cargar en páginas del plugin y también en editor de entradas (post) + $is_bloques_page = strpos($screen->id, 'bloques') !== false || + in_array($screen->post_type, ['actuacion', 'recurso_bloques', 'evento_bloques']); + $is_post_editor = $screen->post_type === 'post' && in_array($hook, ['post.php', 'post-new.php']); + + if ($is_bloques_page || $is_post_editor) { wp_enqueue_style( 'bloques-admin', BLOQUES_PLUGIN_URL . 'assets/css/admin.css', @@ -143,6 +148,25 @@ final class Bloques_Transicion { BLOQUES_VERSION, true ); + + // Datos para el JS del admin + if ($is_post_editor) { + // Obtener los IDs de las categorías "bloques-en-transicion" y "bloques" + $bloques_cat_ids = []; + $bloques_cat = get_term_by('slug', 'bloques-en-transicion', 'category'); + if ($bloques_cat) { + $bloques_cat_ids[] = $bloques_cat->term_id; + } + $bloques_cat2 = get_term_by('slug', 'bloques', 'category'); + if ($bloques_cat2) { + $bloques_cat_ids[] = $bloques_cat2->term_id; + } + + wp_localize_script('bloques-admin', 'bloquesAdminData', [ + 'isPostEditor' => true, + 'bloquesCatIds' => $bloques_cat_ids, + ]); + } } } diff --git a/bloques-transicion/includes/class-shortcodes.php b/bloques-transicion/includes/class-shortcodes.php index 30b05d8..e2bb321 100644 --- a/bloques-transicion/includes/class-shortcodes.php +++ b/bloques-transicion/includes/class-shortcodes.php @@ -78,6 +78,57 @@ class Bloques_Shortcodes { 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 */ @@ -106,14 +157,6 @@ class Bloques_Shortcodes { 'template' => 'noticias', 'label_singular' => __('noticia', 'bloques-transicion'), 'label_plural' => __('noticias', 'bloques-transicion'), - 'tax_query' => [ - 'relation' => 'OR', - [ - 'taxonomy' => 'category', - 'field' => 'slug', - 'terms' => ['bloques-en-transicion', 'bloques'], - ], - ], ], ]; @@ -191,44 +234,10 @@ class Bloques_Shortcodes { } } - // Construir tax_query según el tipo - if ($atts['type'] === 'noticias') { - // Para noticias: siempre filtrar por categoría bloques-en-transicion - $args['tax_query'] = [ - [ - 'taxonomy' => 'category', - 'field' => 'slug', - 'terms' => ['bloques-en-transicion', 'bloques'], - 'include_children' => true, - ], - ]; - } else { - // Para CPTs: filtros por taxonomía custom - $tax_query = []; - - if (!empty($atts['iniciativa'])) { - $tax_query[] = [ - 'taxonomy' => 'iniciativa', - 'field' => 'slug', - 'terms' => explode(',', $atts['iniciativa']), - ]; - } - - if (!empty($atts['linea'])) { - $tax_query[] = [ - 'taxonomy' => 'linea_trabajo', - 'field' => 'slug', - 'terms' => explode(',', $atts['linea']), - ]; - } - - if (count($tax_query) > 1) { - $tax_query['relation'] = 'AND'; - } - - if (!empty($tax_query)) { - $args['tax_query'] = $tax_query; - } + // 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); @@ -286,6 +295,16 @@ class Bloques_Shortcodes { * Renderizar barra de filtros estilo botonera */ private static function render_filter_bar($type, $container_id, $show_search, $total_posts, $type_config) { + $iniciativas = get_terms([ + 'taxonomy' => 'iniciativa', + 'hide_empty' => true, + ]); + + $lineas = get_terms([ + 'taxonomy' => 'linea_trabajo', + 'hide_empty' => true, + ]); + ob_start(); ?>
@@ -295,117 +314,54 @@ class Bloques_Shortcodes {
- - 'category', - 'child_of' => $bloques_cat->term_id, - 'hide_empty' => true, - ]); - } - - if (!empty($child_cats) && !is_wp_error($child_cats)): ?> -
- -
- + + - slug, 'iniciativa'); - if ($matched_iniciativa) { - $color = get_field('color', $matched_iniciativa); - $icono = get_field('icono', $matched_iniciativa); - } - ?> - - -
+
- + + - - 'iniciativa', - 'hide_empty' => true, - ]); - - $lineas = get_terms([ - 'taxonomy' => 'linea_trabajo', - 'hide_empty' => true, - ]); - ?> - - -
- -
- + + - - - -
+
- - - -
- -
- - - - -
-
- +