Plugins bloques

This commit is contained in:
Jose Ibáñez
2026-02-13 13:13:06 +01:00
commit 26d7f11378
24 changed files with 6324 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
<?php
/**
* Plugin Name: Importar Actuaciones desde CSV
* Description: Importador idempotente de Actuaciones (Bloques en Transición) desde CSV con mapeo interactivo de categorías.
* Version: 1.1.0
* Author: Freepress Coop
* License: GPL-2.0+
* Text Domain: bloques-actuaciones-import
* Requires Plugins: bloques-transicion
* Requires at least: 6.0
* Requires PHP: 8.0
*/
if (!defined('ABSPATH')) {
exit;
}
define('BLOQUES_IMPORT_VERSION', '1.1.0');
define('BLOQUES_IMPORT_DIR', plugin_dir_path(__FILE__));
define('BLOQUES_IMPORT_URL', plugin_dir_url(__FILE__));
require_once BLOQUES_IMPORT_DIR . 'includes/class-bloques-actuaciones-importer.php';
add_action('admin_menu', 'bloques_import_register_menu', 20);
function bloques_import_register_menu() {
if (!defined('BLOQUES_PLUGIN_DIR')) {
return;
}
add_submenu_page(
'bloques-dashboard',
__('Importar Actuaciones CSV', 'bloques-actuaciones-import'),
__('📥 Importar CSV', 'bloques-actuaciones-import'),
'manage_options',
'bloques-import-actuaciones',
'bloques_import_render_page'
);
}
function bloques_import_render_page() {
$step = 'upload'; // upload | mapping | done
$message = '';
$message_type = '';
$import_errors = [];
$analysis = null;
// ── PASO 2: el usuario ha confirmado mapeos → importar ──
if (
isset($_POST['bloques_import_step']) &&
$_POST['bloques_import_step'] === 'import' &&
wp_verify_nonce($_POST['bloques_import_nonce'] ?? '', 'bloques_import_actuaciones')
) {
$csv_path = sanitize_text_field($_POST['csv_path'] ?? '');
$geocode = !empty($_POST['reverse_geocode']);
$ini_map = array_map('sanitize_text_field', (array) ($_POST['ini_map'] ?? []));
$linea_map = array_map('sanitize_text_field', (array) ($_POST['linea_map'] ?? []));
$result = Bloques_Actuaciones_Importer::import($csv_path, $ini_map, $linea_map, $geocode);
$message = $result['message'];
$message_type = $result['success'] ? 'success' : 'error';
$import_errors = $result['errors'] ?? [];
$step = 'done';
}
// ── PASO 1: subir CSV → analizar y mostrar mapeos ──
elseif (
isset($_POST['bloques_import_step']) &&
$_POST['bloques_import_step'] === 'analyze' &&
wp_verify_nonce($_POST['bloques_import_nonce'] ?? '', 'bloques_import_actuaciones')
) {
$analysis = Bloques_Actuaciones_Importer::analyze($_FILES['csv_file'] ?? null);
if (!$analysis['success']) {
$message = $analysis['message'];
$message_type = 'error';
$step = 'upload';
} else {
$step = 'mapping';
}
}
include BLOQUES_IMPORT_DIR . 'templates/admin/import-page.php';
}

View File

@@ -0,0 +1,457 @@
<?php
/**
* Importador de Actuaciones desde CSV — flujo en dos pasos.
*
* Paso 1 (analyze): lee el CSV, extrae valores únicos de Iniciativa y Línea de trabajo,
* los compara con los términos existentes y devuelve los que necesitan mapeo.
* Paso 2 (import): recibe el CSV guardado + los mapeos confirmados por el usuario
* e importa/actualiza las actuaciones de forma idempotente.
*
* @package Bloques_Actuaciones_Import
*/
if (!defined('ABSPATH')) {
exit;
}
class Bloques_Actuaciones_Importer {
private const CSV_DELIMITER = ';';
private const NOMINATIM_DELAY_US = 1100000; // 1.1 s
private const NOMINATIM_USER_AGENT = 'BloquesTransicion/1.0 (WordPress; tangente.coop)';
/**
* Mapeos por defecto del CSV a los slugs de WP.
* clave = nombre tal como aparece en el CSV (mb_strtolower).
*/
private const DEFAULT_INICIATIVA_MAP = [
'bloques' => 'oficina-transicion-justa',
'colegios' => 'coles-transicion',
];
/* ─────────────────────────────────────────────
* PASO 1 — Analizar CSV
* ───────────────────────────────────────────── */
/**
* Analizar el CSV y devolver términos que necesitan mapeo.
*
* @param array $file $_FILES['csv_file']
* @return array{ success:bool, message:string, csv_path:string,
* unmatched_iniciativas:array, unmatched_lineas:array,
* existing_iniciativas:array, existing_lineas:array,
* stats:array }
*/
public static function analyze($file) {
if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
return ['success' => false, 'message' => __('No se ha subido ningún archivo válido.', 'bloques-actuaciones-import')];
}
// Guardar el CSV temporalmente en uploads para el paso 2
$upload_dir = wp_upload_dir();
$tmp_path = trailingslashit($upload_dir['basedir']) . 'bloques-import-' . wp_generate_password(12, false) . '.csv';
if (!move_uploaded_file($file['tmp_name'], $tmp_path)) {
return ['success' => false, 'message' => __('No se pudo guardar el archivo temporal.', 'bloques-actuaciones-import')];
}
$errors = [];
$rows = self::parse_csv($tmp_path, $errors);
if ($rows === null) {
@unlink($tmp_path);
return ['success' => false, 'message' => __('Error al leer el CSV.', 'bloques-actuaciones-import') . ' ' . implode(' ', $errors)];
}
// Recoger valores únicos del CSV
$csv_iniciativas = [];
$csv_lineas = [];
foreach ($rows as $row) {
$ini = trim(self::val($row, ['Iniciativa']));
if ($ini !== '') {
$csv_iniciativas[$ini] = true;
}
$lin = trim(self::val($row, ['Línea de trabajo', 'Líneas de trabajo', 'Linea de trabajo']));
if ($lin !== '') {
foreach (preg_split('/\s*;\s*/', $lin) as $l) {
$l = trim($l);
if ($l !== '') {
$csv_lineas[$l] = true;
}
}
}
}
$csv_iniciativas = array_keys($csv_iniciativas);
$csv_lineas = array_keys($csv_lineas);
// Términos existentes en WP
$existing_iniciativas = self::get_existing_terms('iniciativa');
$existing_lineas = self::get_existing_terms('linea_trabajo');
// Determinar cuáles necesitan mapeo
$unmatched_iniciativas = self::find_unmatched($csv_iniciativas, $existing_iniciativas, self::DEFAULT_INICIATIVA_MAP);
$unmatched_lineas = self::find_unmatched($csv_lineas, $existing_lineas, []);
return [
'success' => true,
'csv_path' => $tmp_path,
'total_rows' => count($rows),
'unmatched_iniciativas' => $unmatched_iniciativas,
'unmatched_lineas' => $unmatched_lineas,
'existing_iniciativas' => $existing_iniciativas,
'existing_lineas' => $existing_lineas,
'default_map' => self::DEFAULT_INICIATIVA_MAP,
];
}
/* ─────────────────────────────────────────────
* PASO 2 — Importar con mapeos confirmados
* ───────────────────────────────────────────── */
/**
* @param string $csv_path Ruta al CSV almacenado en paso 1
* @param array $iniciativa_map csv_value => slug_existente | '__new__'
* @param array $linea_map csv_value => slug_existente | '__new__'
* @param bool $do_reverse_geocode
* @return array{ success:bool, message:string, created:int, updated:int, errors:string[] }
*/
public static function import($csv_path, array $iniciativa_map, array $linea_map, $do_reverse_geocode = true) {
$created = 0;
$updated = 0;
$errors = [];
if (!file_exists($csv_path) || !is_readable($csv_path)) {
return self::result(false, __('No se encontró el archivo CSV temporal.', 'bloques-actuaciones-import'), 0, 0, []);
}
$rows = self::parse_csv($csv_path, $errors);
if ($rows === null) {
return self::result(false, __('Error al leer el CSV.', 'bloques-actuaciones-import'), 0, 0, $errors);
}
$total = count($rows);
foreach ($rows as $idx => $row) {
$title = self::val($row, ['Título/Nombre', 'Título', 'Nombre']);
if (trim((string) $title) === '') {
$errors[] = sprintf(__('Fila %d: título vacío, omitida.', 'bloques-actuaciones-import'), $idx + 2);
continue;
}
$result = self::get_or_create($title);
if (!$result) {
$errors[] = sprintf(__('Fila %d: no se pudo crear/obtener "%s".', 'bloques-actuaciones-import'), $idx + 2, esc_html($title));
continue;
}
$post_id = $result['id'];
$result['created'] ? $created++ : $updated++;
$desc = self::val($row, ['Descripción', 'Descripcion']);
wp_update_post([
'ID' => $post_id,
'post_title' => $title,
'post_content' => $desc,
'post_excerpt' => wp_trim_words(wp_strip_all_tags($desc), 30),
'post_status' => 'publish',
'post_type' => 'actuacion',
]);
// Iniciativa
$csv_ini = trim(self::val($row, ['Iniciativa']));
if ($csv_ini !== '') {
self::assign_term($post_id, 'iniciativa', $csv_ini, $iniciativa_map);
}
// Líneas de trabajo
$csv_lin = trim(self::val($row, ['Línea de trabajo', 'Líneas de trabajo', 'Linea de trabajo']));
if ($csv_lin !== '') {
$lineas_list = array_filter(array_map('trim', preg_split('/\s*;\s*/', $csv_lin)));
$term_ids = [];
foreach ($lineas_list as $l) {
$tid = self::resolve_term('linea_trabajo', $l, $linea_map);
if ($tid) {
$term_ids[] = $tid;
}
}
if (!empty($term_ids)) {
wp_set_object_terms($post_id, $term_ids, 'linea_trabajo');
}
}
// Coordenadas
$lat_long = self::val($row, ['Lat/Long', 'Lat', 'Long']);
self::set_coords($post_id, $lat_long);
// Reverse geocode → dirección
if ($do_reverse_geocode && $lat_long !== '') {
$address = self::reverse_geocode($lat_long);
if ($address !== '') {
update_post_meta($post_id, 'direccion', $address);
if (function_exists('update_field')) {
update_field('direccion', $address, $post_id);
}
}
if ($total > 1) {
usleep(self::NOMINATIM_DELAY_US);
}
}
}
// Limpiar archivo temporal
@unlink($csv_path);
$msg = sprintf(
__('Importación completada: %d creadas, %d actualizadas de %d filas.', 'bloques-actuaciones-import'),
$created, $updated, $total
);
if (!empty($errors)) {
$msg .= ' ' . sprintf(__('%d advertencias.', 'bloques-actuaciones-import'), count($errors));
}
return self::result(true, $msg, $created, $updated, $errors);
}
/* ─────────────────────────────────────────────
* Helpers privados
* ───────────────────────────────────────────── */
private static function result($ok, $msg, $c, $u, $e) {
return ['success' => $ok, 'message' => $msg, 'created' => $c, 'updated' => $u, 'errors' => $e];
}
/**
* Obtener términos existentes de una taxonomía como [ slug => name ].
*/
private static function get_existing_terms($taxonomy) {
$terms = get_terms(['taxonomy' => $taxonomy, 'hide_empty' => false]);
$result = [];
if (!is_wp_error($terms)) {
foreach ($terms as $t) {
$result[$t->slug] = $t->name;
}
}
return $result;
}
/**
* Determinar qué valores del CSV no coinciden con términos existentes.
* Devuelve array de [ csv_value => suggested_slug|'' ].
*/
private static function find_unmatched(array $csv_values, array $existing, array $default_map) {
$unmatched = [];
$existing_lower = [];
foreach ($existing as $slug => $name) {
$existing_lower[mb_strtolower($name)] = $slug;
}
foreach ($csv_values as $csv_val) {
$key = mb_strtolower(trim($csv_val));
// Coincidencia exacta por nombre
if (isset($existing_lower[$key])) {
continue;
}
// Coincidencia exacta por slug
$slug_candidate = sanitize_title($csv_val);
if (isset($existing[$slug_candidate])) {
continue;
}
// Mapeo por defecto
if (isset($default_map[$key]) && isset($existing[$default_map[$key]])) {
// Hay sugerencia pero se mostrará al usuario para que confirme
$unmatched[$csv_val] = $default_map[$key];
continue;
}
// Sin coincidencia
$unmatched[$csv_val] = '';
}
return $unmatched;
}
/**
* Asignar un término de taxonomía a un post, usando el mapa de mapeos.
*/
private static function assign_term($post_id, $taxonomy, $csv_value, array $map) {
$tid = self::resolve_term($taxonomy, $csv_value, $map);
if ($tid) {
wp_set_object_terms($post_id, [$tid], $taxonomy);
}
}
/**
* Resolver un valor CSV a un term_id, creando si procede.
*/
private static function resolve_term($taxonomy, $csv_value, array $map) {
$key = trim($csv_value);
if ($key === '') {
return null;
}
// ¿Hay mapeo del usuario?
if (isset($map[$key])) {
$mapped = $map[$key];
if ($mapped === '__new__') {
// Crear nuevo término
return self::ensure_term($taxonomy, $key);
}
// Usar slug existente
$term = get_term_by('slug', $mapped, $taxonomy);
return $term ? (int) $term->term_id : self::ensure_term($taxonomy, $key);
}
// Intentar match directo por nombre (case-insensitive)
$existing = self::get_existing_terms($taxonomy);
foreach ($existing as $slug => $name) {
if (mb_strtolower($name) === mb_strtolower($key)) {
return (int) get_term_by('slug', $slug, $taxonomy)->term_id;
}
}
// Intentar por slug
$slug_try = sanitize_title($key);
$term = get_term_by('slug', $slug_try, $taxonomy);
if ($term) {
return (int) $term->term_id;
}
// Sin mapeo explícito → crear
return self::ensure_term($taxonomy, $key);
}
private static function ensure_term($taxonomy, $name) {
$slug = sanitize_title($name);
$term = get_term_by('slug', $slug, $taxonomy);
if ($term) {
return (int) $term->term_id;
}
$r = wp_insert_term($name, $taxonomy, ['slug' => $slug]);
return is_wp_error($r) ? null : (int) $r['term_id'];
}
/* ── Post idempotente ── */
private static function get_or_create($title) {
global $wpdb;
$id = $wpdb->get_var($wpdb->prepare(
"SELECT ID FROM {$wpdb->posts} WHERE post_type = 'actuacion' AND post_title = %s AND post_status IN ('publish','draft','pending','private') LIMIT 1",
$title
));
if ($id) {
return ['id' => (int) $id, 'created' => false];
}
$id = wp_insert_post(['post_title' => $title, 'post_type' => 'actuacion', 'post_status' => 'publish']);
if (!$id || is_wp_error($id)) {
return false;
}
return ['id' => (int) $id, 'created' => true];
}
/* ── Coordenadas ── */
private static function set_coords($post_id, $lat_long) {
if (!preg_match('/^(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)$/', trim($lat_long), $m)) {
return;
}
$lat = (float) $m[1];
$lng = (float) $m[2];
update_post_meta($post_id, 'latitud', $lat);
update_post_meta($post_id, 'longitud', $lng);
if (function_exists('update_field')) {
update_field('latitud', $lat, $post_id);
update_field('longitud', $lng, $post_id);
}
}
/* ── Reverse geocode (Nominatim) ── */
private static function reverse_geocode($lat_long) {
if (!preg_match('/^(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)$/', trim($lat_long), $m)) {
return '';
}
$url = add_query_arg([
'lat' => (float) $m[1],
'lon' => (float) $m[2],
'format' => 'json',
'addressdetails' => 1,
], 'https://nominatim.openstreetmap.org/reverse');
$resp = wp_remote_get($url, [
'timeout' => 10,
'user-agent' => self::NOMINATIM_USER_AGENT,
'headers' => ['Accept-Language' => 'es'],
]);
if (is_wp_error($resp) || wp_remote_retrieve_response_code($resp) !== 200) {
return '';
}
$data = json_decode(wp_remote_retrieve_body($resp), true);
if (empty($data['address'])) {
return $data['display_name'] ?? '';
}
$a = $data['address'];
$parts = array_filter([
$a['road'] ?? '',
$a['house_number'] ?? '',
$a['suburb'] ?? $a['neighbourhood'] ?? '',
$a['city'] ?? $a['town'] ?? $a['village'] ?? $a['municipality'] ?? '',
$a['state'] ?? '',
]);
return implode(', ', $parts);
}
/* ── CSV parser ── */
private static function parse_csv($path, &$errors = []) {
$handle = fopen($path, 'r');
if (!$handle) {
$errors[] = __('No se pudo abrir el archivo.', 'bloques-actuaciones-import');
return null;
}
// Saltar BOM
$bom = fread($handle, 3);
if ($bom !== "\xEF\xBB\xBF") {
rewind($handle);
}
$header = fgetcsv($handle, 0, self::CSV_DELIMITER);
if ($header === false || empty($header)) {
fclose($handle);
$errors[] = __('Cabecera CSV no válida.', 'bloques-actuaciones-import');
return null;
}
$header = array_map('trim', $header);
$rows = [];
while (($raw = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
$padded = array_pad($raw, count($header), '');
$row = array_combine($header, array_slice($padded, 0, count($header)));
if (is_array($row)) {
$rows[] = array_map(fn($v) => is_string($v) ? trim($v) : $v, $row);
}
}
fclose($handle);
return $rows;
}
private static function val(array $row, array $keys) {
foreach ($keys as $k) {
if (isset($row[$k]) && $row[$k] !== '') {
return $row[$k];
}
}
return '';
}
}

View File

@@ -0,0 +1,172 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
$step = $step ?? 'upload';
$message = $message ?? '';
$message_type = $message_type ?? '';
$import_errors = $import_errors ?? [];
$analysis = $analysis ?? null;
?>
<div class="wrap">
<h1><?php esc_html_e('Importar Actuaciones desde CSV', 'bloques-actuaciones-import'); ?></h1>
<?php if ($message) : ?>
<div class="notice notice-<?php echo esc_attr($message_type === 'success' ? 'success' : 'error'); ?> is-dismissible">
<p><?php echo esc_html($message); ?></p>
</div>
<?php endif; ?>
<?php if (!empty($import_errors)) : ?>
<div class="notice notice-warning">
<p><strong><?php esc_html_e('Detalle:', 'bloques-actuaciones-import'); ?></strong></p>
<ul style="margin-left:1.5em;list-style:disc;">
<?php foreach (array_slice($import_errors, 0, 30) as $err) : ?>
<li><?php echo esc_html($err); ?></li>
<?php endforeach; ?>
</ul>
<?php if (count($import_errors) > 30) : ?>
<p><?php echo esc_html(sprintf(__('… y %d más.', 'bloques-actuaciones-import'), count($import_errors) - 30)); ?></p>
<?php endif; ?>
</div>
<?php endif; ?>
<?php /* ═══════════════════════════════════════════
* PASO 1 — Subir CSV
* ═══════════════════════════════════════════ */ ?>
<?php if ($step === 'upload' || $step === 'done') : ?>
<div class="card" style="max-width:660px;padding:20px;">
<h2 style="margin-top:0;"><?php esc_html_e('1. Subir archivo CSV', 'bloques-actuaciones-import'); ?></h2>
<p><?php esc_html_e('CSV con delimitador punto y coma (;). Columnas: Título/Nombre, Descripción, Lat/Long, Iniciativa, Línea de trabajo.', 'bloques-actuaciones-import'); ?></p>
<p><?php esc_html_e('La importación es idempotente: si la actuación ya existe (mismo título), se actualiza.', 'bloques-actuaciones-import'); ?></p>
<form method="post" enctype="multipart/form-data">
<?php wp_nonce_field('bloques_import_actuaciones', 'bloques_import_nonce'); ?>
<input type="hidden" name="bloques_import_step" value="analyze">
<p>
<label for="csv_file"><strong><?php esc_html_e('Archivo CSV', 'bloques-actuaciones-import'); ?></strong></label><br>
<input type="file" name="csv_file" id="csv_file" accept=".csv,.txt" required>
</p>
<p>
<label>
<input type="checkbox" name="reverse_geocode" value="1" checked>
<?php esc_html_e('Obtener dirección desde coordenadas (Nominatim/OpenStreetMap, ~1 petición/s).', 'bloques-actuaciones-import'); ?>
</label>
</p>
<p>
<button type="submit" class="button button-primary"><?php esc_html_e('Analizar CSV', 'bloques-actuaciones-import'); ?></button>
</p>
</form>
</div>
<?php endif; ?>
<?php /* ═══════════════════════════════════════════
* PASO 2 — Mapeo de categorías
* ═══════════════════════════════════════════ */ ?>
<?php if ($step === 'mapping' && $analysis) :
$unmatched_ini = $analysis['unmatched_iniciativas'];
$unmatched_lin = $analysis['unmatched_lineas'];
$existing_ini = $analysis['existing_iniciativas'];
$existing_lin = $analysis['existing_lineas'];
$default_map = $analysis['default_map'];
$has_unmatched = !empty($unmatched_ini) || !empty($unmatched_lin);
?>
<div class="card" style="max-width:780px;padding:20px;">
<h2 style="margin-top:0;">
<?php esc_html_e('2. Revisar mapeo de categorías', 'bloques-actuaciones-import'); ?>
</h2>
<p>
<?php echo esc_html(sprintf(
__('Se han encontrado %d filas en el CSV.', 'bloques-actuaciones-import'),
$analysis['total_rows']
)); ?>
</p>
<form method="post">
<?php wp_nonce_field('bloques_import_actuaciones', 'bloques_import_nonce'); ?>
<input type="hidden" name="bloques_import_step" value="import">
<input type="hidden" name="csv_path" value="<?php echo esc_attr($analysis['csv_path']); ?>">
<input type="hidden" name="reverse_geocode" value="<?php echo !empty($_POST['reverse_geocode']) ? '1' : '0'; ?>">
<?php if (!$has_unmatched) : ?>
<div class="notice notice-success inline" style="margin:12px 0;">
<p><?php esc_html_e('Todas las categorías del CSV coinciden con términos existentes en WordPress. Puedes importar directamente.', 'bloques-actuaciones-import'); ?></p>
</div>
<?php endif; ?>
<?php /* ── Iniciativas ── */ ?>
<?php if (!empty($unmatched_ini)) : ?>
<h3><?php esc_html_e('Iniciativas', 'bloques-actuaciones-import'); ?></h3>
<p class="description"><?php esc_html_e('Estos valores de la columna "Iniciativa" no coinciden exactamente con ningún término existente. Elige a qué iniciativa asignarlos o crea uno nuevo.', 'bloques-actuaciones-import'); ?></p>
<table class="widefat striped" style="max-width:720px;">
<thead>
<tr>
<th><?php esc_html_e('Valor en CSV', 'bloques-actuaciones-import'); ?></th>
<th><?php esc_html_e('Asignar a…', 'bloques-actuaciones-import'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($unmatched_ini as $csv_val => $suggested_slug) : ?>
<tr>
<td><strong><?php echo esc_html($csv_val); ?></strong></td>
<td>
<select name="ini_map[<?php echo esc_attr($csv_val); ?>]" style="width:100%;">
<option value="__new__"><?php echo esc_html(sprintf(__('Crear nueva: "%s"', 'bloques-actuaciones-import'), $csv_val)); ?></option>
<?php foreach ($existing_ini as $slug => $name) : ?>
<option value="<?php echo esc_attr($slug); ?>"<?php selected($suggested_slug, $slug); ?>>
<?php echo esc_html($name); ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php /* ── Líneas de trabajo ── */ ?>
<?php if (!empty($unmatched_lin)) : ?>
<h3 style="margin-top:24px;"><?php esc_html_e('Líneas de trabajo', 'bloques-actuaciones-import'); ?></h3>
<p class="description"><?php esc_html_e('Estos valores de "Línea de trabajo" no coinciden con ningún término existente.', 'bloques-actuaciones-import'); ?></p>
<table class="widefat striped" style="max-width:720px;">
<thead>
<tr>
<th><?php esc_html_e('Valor en CSV', 'bloques-actuaciones-import'); ?></th>
<th><?php esc_html_e('Asignar a…', 'bloques-actuaciones-import'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($unmatched_lin as $csv_val => $suggested_slug) : ?>
<tr>
<td><strong><?php echo esc_html($csv_val); ?></strong></td>
<td>
<select name="linea_map[<?php echo esc_attr($csv_val); ?>]" style="width:100%;">
<option value="__new__"><?php echo esc_html(sprintf(__('Crear nueva: "%s"', 'bloques-actuaciones-import'), $csv_val)); ?></option>
<?php foreach ($existing_lin as $slug => $name) : ?>
<option value="<?php echo esc_attr($slug); ?>"<?php selected($suggested_slug, $slug); ?>>
<?php echo esc_html($name); ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<p style="margin-top:20px;">
<button type="submit" class="button button-primary button-hero">
<?php esc_html_e('Confirmar e importar', 'bloques-actuaciones-import'); ?>
</button>
<a href="<?php echo esc_url(admin_url('admin.php?page=bloques-import-actuaciones')); ?>" class="button" style="margin-left:8px;">
<?php esc_html_e('Cancelar', 'bloques-actuaciones-import'); ?>
</a>
</p>
</form>
</div>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,107 @@
# Bloques en Transición
Plugin de WordPress para gestionar el contenido del microsite "Bloques en Transición" de Tangente.coop.
## Descripción
Este plugin crea los tipos de contenido personalizados, taxonomías y campos ACF necesarios para el microsite de Bloques en Transición.
## Características
### Tipos de Contenido (CPTs)
- **Actuaciones** (`actuacion`): Proyectos y acciones del programa
- **Recursos** (`recurso_bloques`): Documentos, guías e informes
- **Eventos** (`evento_bloques`): Actividades y formaciones
### Taxonomías
- **Iniciativas**: Oficina de Transición Justa, Coles en transición, Comunidad energética, Creación de redes
- **Líneas de Trabajo**: Energía, Cuidados, Alimentación, Movilidad, Renaturalización, Reutilización
- **Tipos de Evento**: Online, Presencial
### Campos ACF
El plugin registra automáticamente los campos ACF necesarios para cada tipo de contenido:
- **Actuaciones**: Es piloto, Dirección, Localidad, Latitud, Longitud
- **Recursos**: Archivo, Tipo de recurso, URL externa
- **Eventos**: Fecha inicio/fin, Hora inicio/fin, Lugar, Dirección, URL online, URL inscripción
## Instalación
1. Sube la carpeta `bloques-transicion` a `/wp-content/plugins/`
2. Activa el plugin desde el panel de WordPress
3. Ve a **Bloques > Cargar datos** para crear las categorías iniciales
## Requisitos
- WordPress 6.0+
- PHP 8.0+
- Plugin ACF (Advanced Custom Fields) instalado y activo
## Uso
### Shortcodes disponibles
```
[bloques-listado type="actuaciones" widget="grid" columns="3"]
[bloques-listado type="recursos" filter="true"]
[bloques-eventos limit="5" upcoming="true"]
[bloques-iniciativas columns="4"]
[bloques-lineas columns="6"]
```
### Atributos del shortcode principal
| Atributo | Valores | Por defecto | Descripción |
|----------|---------|-------------|-------------|
| `type` | actuaciones, recursos, eventos | actuaciones | Tipo de contenido |
| `widget` | grid, list | grid | Modo de visualización |
| `columns` | 1, 2, 3, 4 | 3 | Número de columnas |
| `pagination` | 0, 5, 10, 15, 20 | 0 | Items por página (0 = sin paginación) |
| `filter` | true, false | false | Mostrar filtros |
| `iniciativa` | slug | - | Filtrar por iniciativa |
| `linea` | slug | - | Filtrar por línea de trabajo |
| `limit` | número | -1 | Límite de items |
| `orderby` | date, title, menu_order | date | Ordenar por |
| `order` | ASC, DESC | DESC | Dirección del orden |
## Personalización de estilos
El plugin incluye estilos base que puedes personalizar desde tu theme usando las siguientes variables CSS:
```css
:root {
--bloques-primary: #1E6B52;
--bloques-secondary: #2A9D8F;
--bloques-accent: #F4A261;
--bloques-text: #333;
--bloques-text-light: #666;
--bloques-bg: #f8f9fa;
--bloques-radius: 8px;
}
```
### Clases CSS principales
- `.bloques-listado` - Contenedor principal
- `.bloques-items` - Grid de items
- `.bloques-item` - Item individual
- `.bloques-item-thumbnail` - Imagen del item
- `.bloques-item-content` - Contenido del item
- `.bloques-tag` - Etiquetas de taxonomía
- `.bloques-btn` - Botones
- `.bloques-filter-bar` - Barra de filtros
## Integración con Divi
El plugin es compatible con Divi Theme Builder. Puedes usar los shortcodes en cualquier módulo de código o texto.
## Soporte
Plugin desarrollado por [Freepress Coop](https://freepress.coop)
## Licencia
GPL-2.0+

View File

@@ -0,0 +1,277 @@
/**
* Estilos del panel de administración - Bloques en Transición
*/
/* === DASHBOARD === */
.bloques-dashboard {
max-width: 1200px;
}
.bloques-dashboard h1 {
display: flex;
align-items: center;
gap: 10px;
color: #1E6B52;
}
.bloques-dashboard h1 .dashicons {
font-size: 32px;
width: 32px;
height: 32px;
}
.bloques-dashboard-welcome {
background: linear-gradient(135deg, #1E6B52 0%, #2A9D8F 100%);
color: white;
padding: 30px;
border-radius: 8px;
margin: 20px 0;
}
.bloques-dashboard-welcome h2 {
color: white;
margin-top: 0;
}
.bloques-dashboard-welcome p {
font-size: 16px;
opacity: 0.9;
}
/* === STATS CARDS === */
.bloques-dashboard-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 30px 0;
}
.bloques-stat-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.bloques-stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}
.bloques-stat-icon {
width: 60px;
height: 60px;
background: linear-gradient(135deg, #1E6B52 0%, #2A9D8F 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 15px;
}
.bloques-stat-icon .dashicons {
font-size: 28px;
width: 28px;
height: 28px;
color: white;
}
.bloques-stat-content {
margin-bottom: 10px;
}
.bloques-stat-number {
display: block;
font-size: 36px;
font-weight: 700;
color: #1E6B52;
line-height: 1;
}
.bloques-stat-label {
display: block;
font-size: 14px;
color: #666;
margin-top: 5px;
}
.bloques-stat-link {
font-size: 13px;
text-decoration: none;
color: #2A9D8F;
}
.bloques-stat-link:hover {
color: #1E6B52;
}
/* === TAXONOMIES === */
.bloques-dashboard-taxonomies {
margin: 30px 0;
}
.bloques-dashboard-taxonomies h3 {
color: #1E6B52;
border-bottom: 2px solid #1E6B52;
padding-bottom: 10px;
}
.bloques-tax-cards {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.bloques-tax-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
min-width: 200px;
display: flex;
flex-direction: column;
gap: 10px;
}
.bloques-tax-count {
font-size: 24px;
font-weight: 700;
color: #1E6B52;
}
/* === SHORTCUTS === */
.bloques-dashboard-shortcuts {
margin: 30px 0;
}
.bloques-dashboard-shortcuts h3 {
color: #1E6B52;
border-bottom: 2px solid #1E6B52;
padding-bottom: 10px;
}
.bloques-shortcuts {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.bloques-shortcuts .button-primary {
background: #1E6B52;
border-color: #1E6B52;
display: flex;
align-items: center;
gap: 5px;
}
.bloques-shortcuts .button-primary:hover {
background: #2A9D8F;
border-color: #2A9D8F;
}
/* === SHORTCODES TABLE === */
.bloques-dashboard-shortcodes {
margin: 30px 0;
}
.bloques-dashboard-shortcodes h3 {
color: #1E6B52;
border-bottom: 2px solid #1E6B52;
padding-bottom: 10px;
}
.bloques-dashboard-shortcodes code {
background: #f0f0f0;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
/* === SEEDER PAGE === */
.bloques-seeder {
max-width: 800px;
}
.bloques-seeder h1 {
display: flex;
align-items: center;
gap: 10px;
color: #1E6B52;
}
.bloques-seeder-info {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
margin: 20px 0;
}
.bloques-seeder-section {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.bloques-seeder-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.bloques-seeder-section h3 {
color: #1E6B52;
margin-top: 0;
}
.bloques-seeder-section ul {
margin: 0;
padding-left: 20px;
}
.bloques-seeder-section li {
margin-bottom: 8px;
}
.bloques-seeder-actions {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
}
.bloques-seeder-actions .button-hero {
display: flex;
align-items: center;
gap: 10px;
background: #1E6B52;
border-color: #1E6B52;
}
.bloques-seeder-actions .button-hero:hover {
background: #2A9D8F;
border-color: #2A9D8F;
}
.bloques-seeder-status {
margin-top: 30px;
}
.bloques-seeder-status h3 {
color: #1E6B52;
}
/* === ADMIN MENU STYLING === */
#adminmenu .toplevel_page_bloques-dashboard .wp-menu-image:before {
content: "\f512";
}
#adminmenu .toplevel_page_bloques-dashboard.current,
#adminmenu .toplevel_page_bloques-dashboard:hover {
background: #1E6B52;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
/**
* Scripts del panel de administración - Bloques en Transición
*/
(function($) {
'use strict';
$(document).ready(function() {
// Confirmación antes de cargar datos
$('.bloques-seeder form').on('submit', function(e) {
if (!confirm('¿Estás seguro de que quieres cargar los datos iniciales?')) {
e.preventDefault();
}
});
// Copiar shortcode al portapapeles
$('.bloques-dashboard-shortcodes code').on('click', function() {
const text = $(this).text();
navigator.clipboard.writeText(text).then(function() {
// Feedback visual
const $code = $(this);
const originalBg = $code.css('background');
$code.css('background', '#d4edda');
setTimeout(function() {
$code.css('background', originalBg);
}, 500);
}.bind(this));
});
// Añadir cursor pointer a los shortcodes
$('.bloques-dashboard-shortcodes code').css('cursor', 'pointer').attr('title', 'Clic para copiar');
});
})(jQuery);

View File

@@ -0,0 +1,247 @@
/**
* Scripts del frontend - Bloques en Transición
*/
(function($) {
'use strict';
// Inicializar cuando el DOM esté listo
$(document).ready(function() {
initFilters();
initLoadMore();
initSearch();
});
/**
* Inicializar filtros tipo botonera
*/
function initFilters() {
$('.bloques-filters').each(function() {
const $filters = $(this);
const containerId = $filters.data('container');
const $container = $('#' + containerId);
const $itemsContainer = $container.find('.bloques-items');
// Estado de filtros activos (incluye category para noticias)
let activeFilters = {
iniciativa: '',
linea_trabajo: '',
category: ''
};
// Click en botones de filtro
$filters.on('click', '.bloques-filter-btn', function() {
const $btn = $(this);
const taxonomy = $btn.data('taxonomy');
const value = $btn.data('value');
// Actualizar estado activo en botones del mismo grupo
$btn.closest('.bloques-filter-buttons')
.find('.bloques-filter-btn')
.removeClass('active');
$btn.addClass('active');
// Actualizar filtros activos
activeFilters[taxonomy] = value;
// Aplicar filtros
applyFilters($container, $itemsContainer, activeFilters);
});
});
}
/**
* Aplicar filtros via AJAX
*/
function applyFilters($container, $itemsContainer, filters) {
const type = $container.data('type');
const widget = $container.data('widget');
const upcoming = $container.data('upcoming');
const search = $container.find('.bloques-search-input').val() || '';
// Mostrar loading
$itemsContainer.addClass('bloques-loading');
$.ajax({
url: bloquesData.ajaxUrl,
type: 'POST',
data: {
action: 'bloques_filter',
nonce: bloquesData.nonce,
type: type,
iniciativa: filters.iniciativa || '',
linea_trabajo: filters.linea_trabajo || '',
category: filters.category || '',
search: search,
widget: widget,
upcoming: upcoming
},
success: function(response) {
if (response.success) {
$itemsContainer.html(response.data.html);
// Actualizar contador
const $resultsNumber = $container.find('.bloques-results-number');
const $resultsLabel = $container.find('.bloques-results-label');
if ($resultsNumber.length) {
$resultsNumber.text(response.data.count);
}
if ($resultsLabel.length) {
$resultsLabel.text(response.data.label);
}
// Ocultar botón "Ver más" cuando hay filtros activos
const hasActiveFilters = filters.iniciativa || filters.linea_trabajo || filters.category || search;
$container.find('.bloques-load-more-wrapper').toggle(!hasActiveFilters);
// Resetear offset
$container.data('offset', $container.data('limit'));
// Animar entrada
animateItems($itemsContainer);
}
},
error: function() {
console.error('Error al filtrar contenido');
},
complete: function() {
$itemsContainer.removeClass('bloques-loading');
}
});
}
/**
* Inicializar búsqueda con debounce
*/
function initSearch() {
let searchTimeout;
$('.bloques-search-input').on('input', function() {
const $input = $(this);
const $filters = $input.closest('.bloques-filters');
const containerId = $filters.data('container');
const $container = $('#' + containerId);
const $itemsContainer = $container.find('.bloques-items');
// 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') || ''
};
// Debounce
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function() {
applyFilters($container, $itemsContainer, activeFilters);
}, 300);
});
}
/**
* Inicializar botón "Ver más"
*/
function initLoadMore() {
$(document).on('click', '.bloques-btn-load-more', function() {
const $btn = $(this);
const containerId = $btn.data('container');
const $container = $('#' + containerId);
const $itemsContainer = $container.find('.bloques-items');
const type = $container.data('type');
const widget = $container.data('widget');
const limit = parseInt($container.data('limit')) || 9;
const offset = parseInt($container.data('offset')) || 0;
const total = parseInt($container.data('total')) || 0;
const upcoming = $container.data('upcoming');
// Obtener filtros activos si existen
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
$btn.prop('disabled', true).text('Cargando...');
$.ajax({
url: bloquesData.ajaxUrl,
type: 'POST',
data: {
action: 'bloques_load_more',
nonce: bloquesData.nonce,
type: type,
offset: offset,
limit: limit,
widget: widget,
iniciativa: iniciativa,
linea_trabajo: linea,
category: category,
upcoming: upcoming
},
success: function(response) {
if (response.success) {
// Añadir nuevos items
$itemsContainer.append(response.data.html);
// Actualizar offset
$container.data('offset', response.data.new_offset);
// Ocultar botón si no hay más
if (!response.data.has_more) {
$btn.closest('.bloques-load-more-wrapper').fadeOut();
}
// Animar nuevos items
animateItems($itemsContainer.find('.bloques-card:gt(' + (offset - 1) + '), .bloques-item:gt(' + (offset - 1) + ')'));
}
},
error: function() {
console.error('Error al cargar más contenido');
},
complete: function() {
$btn.prop('disabled', false).html('Ver más <span class="bloques-arrow">→</span>');
}
});
});
}
/**
* Animación de entrada para items
*/
function animateItems($container) {
const $items = $container.find('.bloques-card, .bloques-item, .bloques-novedad-item, .bloques-agenda-item');
$items.each(function(index) {
const $item = $(this);
$item.css({
'opacity': 0,
'transform': 'translateY(20px)'
});
setTimeout(function() {
$item.css({
'opacity': 1,
'transform': 'translateY(0)',
'transition': 'opacity 0.3s ease, transform 0.3s ease'
});
}, index * 50);
});
}
// Exponer funciones públicas
window.BloquesTransicion = {
initFilters: initFilters,
initLoadMore: initLoadMore,
applyFilters: applyFilters,
animateItems: animateItems
};
})(jQuery);

View File

@@ -0,0 +1,213 @@
<?php
/**
* Plugin Name: Bloques en Transición
* Plugin URI: https://tangente.coop/bloques
* Description: Gestión de contenidos para el microsite "Bloques en Transición" - Actuaciones, Entidades, Recursos y Eventos con taxonomías personalizadas.
* Version: 1.0.0
* Author: Freepress Coop
* Author URI: https://freepress.coop
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: bloques-transicion
* Domain Path: /languages
* Requires at least: 6.0
* Requires PHP: 8.0
*/
// Si se accede directamente, salir
if (!defined('ABSPATH')) {
exit;
}
// Constantes del plugin
define('BLOQUES_VERSION', '1.0.0');
define('BLOQUES_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('BLOQUES_PLUGIN_URL', plugin_dir_url(__FILE__));
define('BLOQUES_PLUGIN_BASENAME', plugin_basename(__FILE__));
/**
* Clase principal del plugin
*/
final class Bloques_Transicion {
/**
* Instancia única del plugin
*/
private static $instance = null;
/**
* Obtener instancia única (Singleton)
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor privado
*/
private function __construct() {
$this->load_dependencies();
$this->init_hooks();
}
/**
* Cargar dependencias
*/
private function load_dependencies() {
// Core
require_once BLOQUES_PLUGIN_DIR . 'includes/class-post-types.php';
require_once BLOQUES_PLUGIN_DIR . 'includes/class-taxonomies.php';
require_once BLOQUES_PLUGIN_DIR . 'includes/class-acf-fields.php';
require_once BLOQUES_PLUGIN_DIR . 'includes/class-admin-menu.php';
require_once BLOQUES_PLUGIN_DIR . 'includes/class-shortcodes.php';
require_once BLOQUES_PLUGIN_DIR . 'includes/class-data-seeder.php';
}
/**
* Inicializar hooks
*/
private function init_hooks() {
// Activación y desactivación
register_activation_hook(__FILE__, [$this, 'activate']);
register_deactivation_hook(__FILE__, [$this, 'deactivate']);
// Inicialización
add_action('init', [$this, 'init'], 0);
add_action('plugins_loaded', [$this, 'load_textdomain']);
// Admin
if (is_admin()) {
add_action('admin_enqueue_scripts', [$this, 'admin_assets']);
}
// Frontend
add_action('wp_enqueue_scripts', [$this, 'frontend_assets']);
}
/**
* Inicializar componentes
*/
public function init() {
// Registrar CPTs y taxonomías
Bloques_Post_Types::register();
Bloques_Taxonomies::register();
// Registrar campos ACF
if (class_exists('ACF')) {
Bloques_ACF_Fields::register();
}
// Registrar menú admin
Bloques_Admin_Menu::register();
// Registrar shortcodes
Bloques_Shortcodes::register();
}
/**
* Cargar traducciones
*/
public function load_textdomain() {
load_plugin_textdomain(
'bloques-transicion',
false,
dirname(BLOQUES_PLUGIN_BASENAME) . '/languages/'
);
}
/**
* Assets del admin
*/
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'])
)) {
wp_enqueue_style(
'bloques-admin',
BLOQUES_PLUGIN_URL . 'assets/css/admin.css',
[],
BLOQUES_VERSION
);
wp_enqueue_script(
'bloques-admin',
BLOQUES_PLUGIN_URL . 'assets/js/admin.js',
['jquery'],
BLOQUES_VERSION,
true
);
}
}
/**
* Assets del frontend
*/
public function frontend_assets() {
wp_enqueue_style(
'bloques-frontend',
BLOQUES_PLUGIN_URL . 'assets/css/frontend.css',
[],
BLOQUES_VERSION
);
wp_enqueue_script(
'bloques-frontend',
BLOQUES_PLUGIN_URL . 'assets/js/frontend.js',
['jquery'],
BLOQUES_VERSION,
true
);
wp_localize_script('bloques-frontend', 'bloquesData', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bloques_nonce'),
'pluginUrl' => BLOQUES_PLUGIN_URL,
]);
// Añadir CSS personalizado
$custom_css = get_option('bloques_custom_css', '');
if (!empty($custom_css)) {
wp_add_inline_style('bloques-frontend', $custom_css);
}
}
/**
* Activación del plugin
*/
public function activate() {
// Registrar CPTs y taxonomías
Bloques_Post_Types::register();
Bloques_Taxonomies::register();
// Flush rewrite rules
flush_rewrite_rules();
// Marcar como activado
update_option('bloques_transicion_activated', true);
update_option('bloques_transicion_version', BLOQUES_VERSION);
}
/**
* Desactivación del plugin
*/
public function deactivate() {
flush_rewrite_rules();
}
}
/**
* Iniciar el plugin
*/
function bloques_transicion() {
return Bloques_Transicion::get_instance();
}
// Arrancar
bloques_transicion();

View File

@@ -0,0 +1,453 @@
<?php
/**
* Registro de campos ACF
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
class Bloques_ACF_Fields {
/**
* Registrar todos los grupos de campos
*/
public static function register() {
add_action('acf/init', [__CLASS__, 'register_field_groups']);
}
/**
* Registrar grupos de campos ACF
*/
public static function register_field_groups() {
if (!function_exists('acf_add_local_field_group')) {
return;
}
self::register_actuacion_fields();
self::register_recurso_fields();
self::register_evento_fields();
self::register_iniciativa_fields();
self::register_linea_trabajo_fields();
}
/**
* Campos para Actuaciones
*/
private static function register_actuacion_fields() {
acf_add_local_field_group([
'key' => 'group_bloques_actuacion',
'title' => __('Datos de la Actuación', 'bloques-transicion'),
'fields' => [
// Es piloto
[
'key' => 'field_actuacion_es_piloto',
'label' => __('Es actuación piloto', 'bloques-transicion'),
'name' => 'es_piloto',
'type' => 'true_false',
'instructions' => __('Marcar si esta actuación es un proyecto piloto', 'bloques-transicion'),
'ui' => 1,
'ui_on_text' => __('Sí', 'bloques-transicion'),
'ui_off_text' => __('No', 'bloques-transicion'),
'default_value' => 0,
],
// Tab Ubicación
[
'key' => 'field_actuacion_tab_ubicacion',
'label' => __('Ubicación', 'bloques-transicion'),
'name' => '',
'type' => 'tab',
'placement' => 'top',
],
// Dirección
[
'key' => 'field_actuacion_direccion',
'label' => __('Dirección', 'bloques-transicion'),
'name' => 'direccion',
'type' => 'text',
'instructions' => __('Dirección completa', 'bloques-transicion'),
],
// Localidad
[
'key' => 'field_actuacion_localidad',
'label' => __('Localidad', 'bloques-transicion'),
'name' => 'localidad',
'type' => 'text',
'instructions' => __('Ciudad o barrio', 'bloques-transicion'),
],
// Latitud
[
'key' => 'field_actuacion_latitud',
'label' => __('Latitud', 'bloques-transicion'),
'name' => 'latitud',
'type' => 'number',
'instructions' => __('Coordenada de latitud (ej: 40.4168)', 'bloques-transicion'),
'step' => 'any',
'wrapper' => ['width' => '50'],
],
// Longitud
[
'key' => 'field_actuacion_longitud',
'label' => __('Longitud', 'bloques-transicion'),
'name' => 'longitud',
'type' => 'number',
'instructions' => __('Coordenada de longitud (ej: -3.7038)', 'bloques-transicion'),
'step' => 'any',
'wrapper' => ['width' => '50'],
],
// Mapa (Google Maps si está disponible, sino texto)
[
'key' => 'field_actuacion_mapa',
'label' => __('Seleccionar en mapa', 'bloques-transicion'),
'name' => 'mapa',
'type' => 'google_map',
'instructions' => __('Haz clic en el mapa para obtener las coordenadas automáticamente', 'bloques-transicion'),
'center_lat' => '40.4168',
'center_lng' => '-3.7038',
'zoom' => 12,
'conditional_logic' => [
[
[
'field' => 'field_actuacion_latitud',
'operator' => '==empty',
],
],
],
],
],
'location' => [
[
[
'param' => 'post_type',
'operator' => '==',
'value' => 'actuacion',
],
],
],
'menu_order' => 0,
'position' => 'normal',
'style' => 'default',
'label_placement' => 'top',
'instruction_placement' => 'label',
'active' => true,
]);
}
/**
* Campos para Recursos
*/
private static function register_recurso_fields() {
acf_add_local_field_group([
'key' => 'group_bloques_recurso',
'title' => __('Datos del Recurso', 'bloques-transicion'),
'fields' => [
// Archivo
[
'key' => 'field_recurso_archivo',
'label' => __('Archivo', 'bloques-transicion'),
'name' => 'archivo',
'type' => 'file',
'instructions' => __('Sube el archivo del recurso (PDF, DOC, JPG, PNG, etc.)', 'bloques-transicion'),
'required' => 0,
'return_format' => 'array',
'library' => 'all',
'mime_types' => 'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif,zip',
],
// Tipo de recurso (informativo)
[
'key' => 'field_recurso_tipo',
'label' => __('Tipo de Recurso', 'bloques-transicion'),
'name' => 'tipo_recurso',
'type' => 'select',
'instructions' => __('Categoría del tipo de recurso', 'bloques-transicion'),
'choices' => [
'documento' => __('Documento', 'bloques-transicion'),
'guia' => __('Guía', 'bloques-transicion'),
'informe' => __('Informe', 'bloques-transicion'),
'presentacion' => __('Presentación', 'bloques-transicion'),
'video' => __('Vídeo', 'bloques-transicion'),
'imagen' => __('Imagen', 'bloques-transicion'),
'otro' => __('Otro', 'bloques-transicion'),
],
'default_value' => 'documento',
'allow_null' => 0,
'return_format' => 'value',
],
// URL externa (alternativa al archivo)
[
'key' => 'field_recurso_url_externa',
'label' => __('URL Externa', 'bloques-transicion'),
'name' => 'url_externa',
'type' => 'url',
'instructions' => __('Si el recurso está alojado externamente (YouTube, Drive, etc.)', 'bloques-transicion'),
],
],
'location' => [
[
[
'param' => 'post_type',
'operator' => '==',
'value' => 'recurso_bloques',
],
],
],
'menu_order' => 0,
'position' => 'normal',
'style' => 'default',
'label_placement' => 'top',
'instruction_placement' => 'label',
'active' => true,
]);
}
/**
* Campos para Eventos
*/
private static function register_evento_fields() {
acf_add_local_field_group([
'key' => 'group_bloques_evento',
'title' => __('Datos del Evento', 'bloques-transicion'),
'fields' => [
// Tab Fecha y Hora
[
'key' => 'field_evento_tab_fecha',
'label' => __('Fecha y Hora', 'bloques-transicion'),
'name' => '',
'type' => 'tab',
'placement' => 'top',
],
// Fecha inicio
[
'key' => 'field_evento_fecha_inicio',
'label' => __('Fecha de inicio', 'bloques-transicion'),
'name' => 'fecha_inicio',
'type' => 'date_picker',
'instructions' => __('Fecha en que comienza el evento', 'bloques-transicion'),
'required' => 1,
'display_format' => 'd/m/Y',
'return_format' => 'Y-m-d',
'first_day' => 1,
'wrapper' => ['width' => '50'],
],
// Hora inicio
[
'key' => 'field_evento_hora_inicio',
'label' => __('Hora de inicio', 'bloques-transicion'),
'name' => 'hora_inicio',
'type' => 'time_picker',
'instructions' => __('Hora de inicio del evento', 'bloques-transicion'),
'display_format' => 'H:i',
'return_format' => 'H:i',
'wrapper' => ['width' => '50'],
],
// Fecha fin
[
'key' => 'field_evento_fecha_fin',
'label' => __('Fecha de fin', 'bloques-transicion'),
'name' => 'fecha_fin',
'type' => 'date_picker',
'instructions' => __('Fecha en que termina el evento (opcional)', 'bloques-transicion'),
'display_format' => 'd/m/Y',
'return_format' => 'Y-m-d',
'first_day' => 1,
'wrapper' => ['width' => '50'],
],
// Hora fin
[
'key' => 'field_evento_hora_fin',
'label' => __('Hora de fin', 'bloques-transicion'),
'name' => 'hora_fin',
'type' => 'time_picker',
'instructions' => __('Hora de finalización (opcional)', 'bloques-transicion'),
'display_format' => 'H:i',
'return_format' => 'H:i',
'wrapper' => ['width' => '50'],
],
// Tab Ubicación
[
'key' => 'field_evento_tab_ubicacion',
'label' => __('Ubicación', 'bloques-transicion'),
'name' => '',
'type' => 'tab',
'placement' => 'top',
],
// Lugar
[
'key' => 'field_evento_lugar',
'label' => __('Lugar', 'bloques-transicion'),
'name' => 'lugar',
'type' => 'text',
'instructions' => __('Nombre del lugar donde se celebra', 'bloques-transicion'),
],
// Dirección
[
'key' => 'field_evento_direccion',
'label' => __('Dirección', 'bloques-transicion'),
'name' => 'direccion',
'type' => 'text',
],
// URL Online
[
'key' => 'field_evento_url_online',
'label' => __('Enlace para evento online', 'bloques-transicion'),
'name' => 'url_online',
'type' => 'url',
'instructions' => __('URL de Zoom, Meet, YouTube, etc. para eventos online', 'bloques-transicion'),
],
// Tab Inscripción
[
'key' => 'field_evento_tab_inscripcion',
'label' => __('Inscripción', 'bloques-transicion'),
'name' => '',
'type' => 'tab',
'placement' => 'top',
],
// URL inscripción
[
'key' => 'field_evento_url_inscripcion',
'label' => __('URL de inscripción', 'bloques-transicion'),
'name' => 'url_inscripcion',
'type' => 'url',
'instructions' => __('Enlace al formulario de inscripción', 'bloques-transicion'),
],
// Plazas
[
'key' => 'field_evento_plazas',
'label' => __('Número de plazas', 'bloques-transicion'),
'name' => 'plazas',
'type' => 'number',
'instructions' => __('Aforo máximo (dejar vacío si no hay límite)', 'bloques-transicion'),
'min' => 0,
],
],
'location' => [
[
[
'param' => 'post_type',
'operator' => '==',
'value' => 'evento_bloques',
],
],
],
'menu_order' => 0,
'position' => 'normal',
'style' => 'default',
'label_placement' => 'top',
'instruction_placement' => 'label',
'active' => true,
]);
}
/**
* Campos para taxonomía Iniciativas
*/
private static function register_iniciativa_fields() {
acf_add_local_field_group([
'key' => 'group_bloques_iniciativa_tax',
'title' => __('Datos de la Iniciativa', 'bloques-transicion'),
'fields' => [
// Icono
[
'key' => 'field_iniciativa_icono',
'label' => __('Icono', 'bloques-transicion'),
'name' => 'icono',
'type' => 'image',
'instructions' => __('Icono representativo de la iniciativa (SVG o PNG recomendado)', 'bloques-transicion'),
'return_format' => 'url',
'preview_size' => 'thumbnail',
'library' => 'all',
'mime_types' => 'svg,png,jpg,jpeg',
],
// Color
[
'key' => 'field_iniciativa_color',
'label' => __('Color', 'bloques-transicion'),
'name' => 'color',
'type' => 'color_picker',
'instructions' => __('Color identificativo de la iniciativa', 'bloques-transicion'),
'default_value' => '#1E6B52',
],
// Ubicación
[
'key' => 'field_iniciativa_ubicacion',
'label' => __('Ubicación', 'bloques-transicion'),
'name' => 'ubicacion_texto',
'type' => 'text',
'instructions' => __('Texto descriptivo de la ubicación (ej: "Puente y Villa de Vallecas")', 'bloques-transicion'),
],
// Estado
[
'key' => 'field_iniciativa_estado',
'label' => __('Estado', 'bloques-transicion'),
'name' => 'estado',
'type' => 'text',
'instructions' => __('Estado actual de la iniciativa (ej: "En proceso", "Abierta")', 'bloques-transicion'),
],
],
'location' => [
[
[
'param' => 'taxonomy',
'operator' => '==',
'value' => 'iniciativa',
],
],
],
'menu_order' => 0,
'position' => 'normal',
'style' => 'default',
'label_placement' => 'top',
'instruction_placement' => 'label',
'active' => true,
]);
}
/**
* Campos para taxonomía Líneas de Trabajo
*/
private static function register_linea_trabajo_fields() {
acf_add_local_field_group([
'key' => 'group_bloques_linea_trabajo_tax',
'title' => __('Datos de la Línea de Trabajo', 'bloques-transicion'),
'fields' => [
// Icono
[
'key' => 'field_linea_trabajo_icono',
'label' => __('Icono', 'bloques-transicion'),
'name' => 'icono',
'type' => 'image',
'instructions' => __('Icono representativo de la línea de trabajo', 'bloques-transicion'),
'return_format' => 'url',
'preview_size' => 'thumbnail',
'library' => 'all',
'mime_types' => 'svg,png,jpg,jpeg',
],
// Color (opcional para líneas de trabajo)
[
'key' => 'field_linea_trabajo_color',
'label' => __('Color', 'bloques-transicion'),
'name' => 'color',
'type' => 'color_picker',
'instructions' => __('Color identificativo (opcional)', 'bloques-transicion'),
],
],
'location' => [
[
[
'param' => 'taxonomy',
'operator' => '==',
'value' => 'linea_trabajo',
],
],
],
'menu_order' => 0,
'position' => 'normal',
'style' => 'default',
'label_placement' => 'top',
'instruction_placement' => 'label',
'active' => true,
]);
}
}

View File

@@ -0,0 +1,265 @@
<?php
/**
* Menú de administración personalizado
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
class Bloques_Admin_Menu {
/**
* Registrar menú
*/
public static function register() {
add_action('admin_menu', [__CLASS__, 'add_admin_menu']);
add_action('parent_file', [__CLASS__, 'fix_parent_menu']);
add_action('submenu_file', [__CLASS__, 'fix_submenu']);
}
/**
* Añadir menú principal y submenús
*/
public static function add_admin_menu() {
// Menú principal: Bloques
add_menu_page(
__('Bloques en Transición', 'bloques-transicion'),
__('Bloques', 'bloques-transicion'),
'edit_posts',
'bloques-dashboard',
[__CLASS__, 'render_dashboard'],
'dashicons-building',
25
);
// Dashboard
add_submenu_page(
'bloques-dashboard',
__('Panel de Bloques', 'bloques-transicion'),
__('Panel', 'bloques-transicion'),
'edit_posts',
'bloques-dashboard',
[__CLASS__, 'render_dashboard']
);
// === ACTUACIONES ===
add_submenu_page(
'bloques-dashboard',
__('Actuaciones', 'bloques-transicion'),
__('Actuaciones', 'bloques-transicion'),
'edit_posts',
'edit.php?post_type=actuacion'
);
add_submenu_page(
'bloques-dashboard',
__('Añadir Actuación', 'bloques-transicion'),
__('↳ Añadir nueva', 'bloques-transicion'),
'edit_posts',
'post-new.php?post_type=actuacion'
);
// === RECURSOS ===
add_submenu_page(
'bloques-dashboard',
__('Recursos', 'bloques-transicion'),
__('Recursos', 'bloques-transicion'),
'edit_posts',
'edit.php?post_type=recurso_bloques'
);
add_submenu_page(
'bloques-dashboard',
__('Añadir Recurso', 'bloques-transicion'),
__('↳ Añadir nuevo', 'bloques-transicion'),
'edit_posts',
'post-new.php?post_type=recurso_bloques'
);
// === EVENTOS ===
add_submenu_page(
'bloques-dashboard',
__('Eventos', 'bloques-transicion'),
__('Eventos', 'bloques-transicion'),
'edit_posts',
'edit.php?post_type=evento_bloques'
);
add_submenu_page(
'bloques-dashboard',
__('Añadir Evento', 'bloques-transicion'),
__('↳ Añadir nuevo', 'bloques-transicion'),
'edit_posts',
'post-new.php?post_type=evento_bloques'
);
// === SEPARADOR ===
add_submenu_page(
'bloques-dashboard',
'',
'<span style="display:block;border-top:1px solid #555;margin:10px 0;"></span>',
'edit_posts',
'#separator1'
);
// === TAXONOMÍAS ===
add_submenu_page(
'bloques-dashboard',
__('Iniciativas', 'bloques-transicion'),
__('Iniciativas', 'bloques-transicion'),
'manage_categories',
'edit-tags.php?taxonomy=iniciativa'
);
add_submenu_page(
'bloques-dashboard',
__('Líneas de Trabajo', 'bloques-transicion'),
__('Líneas de Trabajo', 'bloques-transicion'),
'manage_categories',
'edit-tags.php?taxonomy=linea_trabajo'
);
add_submenu_page(
'bloques-dashboard',
__('Tipos de Evento', 'bloques-transicion'),
__('Tipos de Evento', 'bloques-transicion'),
'manage_categories',
'edit-tags.php?taxonomy=tipo_evento'
);
// === SEPARADOR ===
add_submenu_page(
'bloques-dashboard',
'',
'<span style="display:block;border-top:1px solid #555;margin:10px 0;"></span>',
'manage_options',
'#separator2'
);
// === HERRAMIENTAS ===
add_submenu_page(
'bloques-dashboard',
__('Cargar datos iniciales', 'bloques-transicion'),
__('⚙️ Cargar datos', 'bloques-transicion'),
'manage_options',
'bloques-seeder',
[__CLASS__, 'render_seeder_page']
);
// === AJUSTES ===
add_submenu_page(
'bloques-dashboard',
__('Ajustes', 'bloques-transicion'),
__('⚙️ Ajustes', 'bloques-transicion'),
'manage_options',
'bloques-settings',
[__CLASS__, 'render_settings_page']
);
}
/**
* Renderizar página de ajustes
*/
public static function render_settings_page() {
// Guardar opciones si se envió el formulario
if (isset($_POST['bloques_settings_nonce']) && wp_verify_nonce($_POST['bloques_settings_nonce'], 'bloques_save_settings')) {
$custom_css = isset($_POST['bloques_custom_css']) ? wp_strip_all_tags($_POST['bloques_custom_css']) : '';
update_option('bloques_custom_css', $custom_css);
$saved = true;
}
$custom_css = get_option('bloques_custom_css', '');
include BLOQUES_PLUGIN_DIR . 'templates/admin/settings.php';
}
/**
* Corregir menú padre activo
*/
public static function fix_parent_menu($parent_file) {
global $current_screen;
if (!$current_screen) {
return $parent_file;
}
$post_types = Bloques_Post_Types::get_post_types();
$taxonomies = Bloques_Taxonomies::get_taxonomies();
// Si estamos en un CPT de Bloques
if (in_array($current_screen->post_type, $post_types)) {
return 'bloques-dashboard';
}
// Si estamos en una taxonomía de Bloques
if (in_array($current_screen->taxonomy, $taxonomies)) {
return 'bloques-dashboard';
}
return $parent_file;
}
/**
* Corregir submenú activo
*/
public static function fix_submenu($submenu_file) {
global $current_screen;
if (!$current_screen) {
return $submenu_file;
}
$post_types = Bloques_Post_Types::get_post_types();
$taxonomies = Bloques_Taxonomies::get_taxonomies();
// CPTs
if (in_array($current_screen->post_type, $post_types)) {
if ($current_screen->base === 'post') {
return 'post-new.php?post_type=' . $current_screen->post_type;
}
return 'edit.php?post_type=' . $current_screen->post_type;
}
// Taxonomías
if (in_array($current_screen->taxonomy, $taxonomies)) {
return 'edit-tags.php?taxonomy=' . $current_screen->taxonomy;
}
return $submenu_file;
}
/**
* Renderizar dashboard
*/
public static function render_dashboard() {
// Obtener conteos
$counts = [
'actuacion' => wp_count_posts('actuacion')->publish ?? 0,
'recurso_bloques' => wp_count_posts('recurso_bloques')->publish ?? 0,
'evento_bloques' => wp_count_posts('evento_bloques')->publish ?? 0,
];
$iniciativas = wp_count_terms(['taxonomy' => 'iniciativa', 'hide_empty' => false]);
$lineas = wp_count_terms(['taxonomy' => 'linea_trabajo', 'hide_empty' => false]);
include BLOQUES_PLUGIN_DIR . 'templates/admin/dashboard.php';
}
/**
* Renderizar página de seeder
*/
public static function render_seeder_page() {
// Procesar acción si se envió el formulario
if (isset($_POST['bloques_seed_action']) && wp_verify_nonce($_POST['bloques_seed_nonce'], 'bloques_seed')) {
$result = Bloques_Data_Seeder::seed_all();
$message = $result ?
__('Datos cargados correctamente.', 'bloques-transicion') :
__('Error al cargar los datos.', 'bloques-transicion');
$message_type = $result ? 'success' : 'error';
}
include BLOQUES_PLUGIN_DIR . 'templates/admin/seeder.php';
}
}

View File

@@ -0,0 +1,233 @@
<?php
/**
* Carga de datos iniciales (Iniciativas y Líneas de Trabajo)
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
class Bloques_Data_Seeder {
/**
* Cargar todos los datos iniciales
*/
public static function seed_all() {
$result_iniciativas = self::seed_iniciativas();
$result_lineas = self::seed_lineas_trabajo();
$result_tipos = self::seed_tipos_evento();
return $result_iniciativas && $result_lineas && $result_tipos;
}
/**
* Cargar Iniciativas
*/
public static function seed_iniciativas() {
$iniciativas = [
[
'name' => 'Oficina de Transición Justa',
'slug' => 'oficina-transicion-justa',
'description' => 'Acompañamiento técnico, asesoramiento y formación para personas y comunidades interesadas en impulsar iniciativas de cambio.',
'meta' => [
'ubicacion_texto' => 'Puente y Villa de Vallecas',
'estado' => 'Abierta - consultas permanentes',
'color' => '#1E6B52',
],
],
[
'name' => 'Coles en transición',
'slug' => 'coles-transicion',
'description' => 'Acompañamiento en la elaboración e implementación de planes de adaptación climática en centros educativos y otras iniciativas de cambio con participación de la comunidad escolar.',
'meta' => [
'ubicacion_texto' => '7 centros en Puente y Villa de Vallecas',
'estado' => 'En marcha',
'color' => '#4A9B7F',
],
],
[
'name' => 'Comunidad energética',
'slug' => 'comunidad-energetica',
'description' => 'Acompañamiento en la creación de la Comunidad Energética Vallekana para producir, compartir y gestionar energía renovable.',
'meta' => [
'ubicacion_texto' => 'Puente de Vallecas',
'estado' => 'En proceso de constitución',
'color' => '#F4A261',
],
],
[
'name' => 'Creación de redes',
'slug' => 'creacion-redes',
'description' => 'Intercambio de conocimientos y participación. Acompañamiento e impulso del funcionamiento y la creación de redes locales y estatales multiactor para el aprendizaje y la puesta en marcha de acciones hacia la transición justa.',
'meta' => [
'ubicacion_texto' => 'Madrid, Cataluña y Valencia',
'estado' => 'En proceso',
'color' => '#E76F51',
],
],
];
return self::insert_terms($iniciativas, 'iniciativa');
}
/**
* Cargar Líneas de Trabajo
*/
public static function seed_lineas_trabajo() {
$lineas = [
[
'name' => 'Energía',
'slug' => 'energia',
'description' => 'Rehabilitación edificios, comunidades energéticas y autoconsumo renovable',
'meta' => [
'color' => '#F4A261',
],
],
[
'name' => 'Cuidados',
'slug' => 'cuidados',
'description' => 'Redes de apoyo mutuo y espacios vecinales',
'meta' => [
'color' => '#E9C46A',
],
],
[
'name' => 'Alimentación',
'slug' => 'alimentacion',
'description' => 'Alimentación sostenible y saludable y redes de consumo',
'meta' => [
'color' => '#2A9D8F',
],
],
[
'name' => 'Movilidad',
'slug' => 'movilidad',
'description' => 'Movilidad sostenible, uso de la bicicleta y espacio público',
'meta' => [
'color' => '#264653',
],
],
[
'name' => 'Renaturalización',
'slug' => 'renaturalizacion',
'description' => 'Renaturalización de comunidades y patios escolares',
'meta' => [
'color' => '#1E6B52',
],
],
[
'name' => 'Reutilización',
'slug' => 'reutilizacion',
'description' => 'Economía circular y consumo colaborativo',
'meta' => [
'color' => '#E76F51',
],
],
];
return self::insert_terms($lineas, 'linea_trabajo');
}
/**
* Cargar Tipos de Evento
*/
public static function seed_tipos_evento() {
$tipos = [
[
'name' => 'Online',
'slug' => 'online',
'description' => 'Evento que se realiza de forma virtual',
],
[
'name' => 'Presencial',
'slug' => 'presencial',
'description' => 'Evento que se realiza de forma presencial',
],
];
return self::insert_terms($tipos, 'tipo_evento');
}
/**
* Insertar términos en una taxonomía
*/
private static function insert_terms($terms, $taxonomy) {
$success = true;
foreach ($terms as $term_data) {
// Verificar si ya existe
$existing = term_exists($term_data['slug'], $taxonomy);
if ($existing) {
$term_id = is_array($existing) ? $existing['term_id'] : $existing;
// Actualizar descripción si existe
wp_update_term($term_id, $taxonomy, [
'description' => $term_data['description'] ?? '',
]);
} else {
// Crear nuevo término
$result = wp_insert_term(
$term_data['name'],
$taxonomy,
[
'slug' => $term_data['slug'],
'description' => $term_data['description'] ?? '',
]
);
if (is_wp_error($result)) {
$success = false;
continue;
}
$term_id = $result['term_id'];
}
// Guardar meta datos con ACF si está disponible
if (!empty($term_data['meta']) && function_exists('update_field')) {
foreach ($term_data['meta'] as $key => $value) {
update_field($key, $value, $taxonomy . '_' . $term_id);
}
}
}
return $success;
}
/**
* Eliminar todos los datos del plugin
*/
public static function clear_all() {
// Eliminar términos de iniciativas
$iniciativas = get_terms([
'taxonomy' => 'iniciativa',
'hide_empty' => false,
]);
foreach ($iniciativas as $term) {
wp_delete_term($term->term_id, 'iniciativa');
}
// Eliminar términos de líneas de trabajo
$lineas = get_terms([
'taxonomy' => 'linea_trabajo',
'hide_empty' => false,
]);
foreach ($lineas as $term) {
wp_delete_term($term->term_id, 'linea_trabajo');
}
// Eliminar tipos de evento
$tipos = get_terms([
'taxonomy' => 'tipo_evento',
'hide_empty' => false,
]);
foreach ($tipos as $term) {
wp_delete_term($term->term_id, 'tipo_evento');
}
return true;
}
}

View File

@@ -0,0 +1,192 @@
<?php
/**
* Registro de Custom Post Types
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
class Bloques_Post_Types
{
/**
* Registrar todos los CPTs
*/
public static function register()
{
self::register_actuacion();
self::register_recurso();
self::register_evento();
}
/**
* CPT: Actuaciones
*/
private static function register_actuacion()
{
$labels = [
'name' => _x('Actuaciones', 'Post type general name', 'bloques-transicion'),
'singular_name' => _x('Actuación', 'Post type singular name', 'bloques-transicion'),
'menu_name' => _x('Actuaciones', 'Admin Menu text', 'bloques-transicion'),
'name_admin_bar' => _x('Actuación', 'Add New on Toolbar', 'bloques-transicion'),
'add_new' => __('Añadir nueva', 'bloques-transicion'),
'add_new_item' => __('Añadir nueva Actuación', 'bloques-transicion'),
'new_item' => __('Nueva Actuación', 'bloques-transicion'),
'edit_item' => __('Editar Actuación', 'bloques-transicion'),
'view_item' => __('Ver Actuación', 'bloques-transicion'),
'all_items' => __('Todas las Actuaciones', 'bloques-transicion'),
'search_items' => __('Buscar Actuaciones', 'bloques-transicion'),
'parent_item_colon' => __('Actuación padre:', 'bloques-transicion'),
'not_found' => __('No se encontraron actuaciones.', 'bloques-transicion'),
'not_found_in_trash' => __('No hay actuaciones en la papelera.', 'bloques-transicion'),
'featured_image' => _x('Imagen destacada', 'Overrides the "Featured Image" phrase', 'bloques-transicion'),
'set_featured_image' => _x('Establecer imagen destacada', 'Overrides the "Set featured image" phrase', 'bloques-transicion'),
'remove_featured_image' => _x('Eliminar imagen destacada', 'Overrides the "Remove featured image" phrase', 'bloques-transicion'),
'use_featured_image' => _x('Usar como imagen destacada', 'Overrides the "Use as featured image" phrase', 'bloques-transicion'),
'archives' => _x('Archivo de Actuaciones', 'The post type archive label', 'bloques-transicion'),
'insert_into_item' => _x('Insertar en actuación', 'Overrides the "Insert into post" phrase', 'bloques-transicion'),
'uploaded_to_this_item' => _x('Subido a esta actuación', 'Overrides the "Uploaded to this post" phrase', 'bloques-transicion'),
'filter_items_list' => _x('Filtrar lista de actuaciones', 'Screen reader text', 'bloques-transicion'),
'items_list_navigation' => _x('Navegación de actuaciones', 'Screen reader text', 'bloques-transicion'),
'items_list' => _x('Lista de actuaciones', 'Screen reader text', 'bloques-transicion'),
];
$args = [
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => false, // Lo añadimos manualmente al menú de Bloques
'query_var' => true,
'rewrite' => ['slug' => 'bloques-en-transicion/actuaciones', 'with_front' => false],
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => null,
'menu_icon' => 'dashicons-location-alt',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'revisions'],
'show_in_rest' => true,
'taxonomies' => ['iniciativa', 'linea_trabajo'],
];
register_post_type('actuacion', $args);
}
/**
* CPT: Recursos
*/
private static function register_recurso()
{
$labels = [
'name' => _x('Recursos', 'Post type general name', 'bloques-transicion'),
'singular_name' => _x('Recurso', 'Post type singular name', 'bloques-transicion'),
'menu_name' => _x('Recursos', 'Admin Menu text', 'bloques-transicion'),
'name_admin_bar' => _x('Recurso', 'Add New on Toolbar', 'bloques-transicion'),
'add_new' => __('Añadir nuevo', 'bloques-transicion'),
'add_new_item' => __('Añadir nuevo Recurso', 'bloques-transicion'),
'new_item' => __('Nuevo Recurso', 'bloques-transicion'),
'edit_item' => __('Editar Recurso', 'bloques-transicion'),
'view_item' => __('Ver Recurso', 'bloques-transicion'),
'all_items' => __('Todos los Recursos', 'bloques-transicion'),
'search_items' => __('Buscar Recursos', 'bloques-transicion'),
'parent_item_colon' => __('Recurso padre:', 'bloques-transicion'),
'not_found' => __('No se encontraron recursos.', 'bloques-transicion'),
'not_found_in_trash' => __('No hay recursos en la papelera.', 'bloques-transicion'),
'featured_image' => _x('Imagen del recurso', 'Overrides the "Featured Image" phrase', 'bloques-transicion'),
'set_featured_image' => _x('Establecer imagen', 'Overrides the "Set featured image" phrase', 'bloques-transicion'),
'remove_featured_image' => _x('Eliminar imagen', 'Overrides the "Remove featured image" phrase', 'bloques-transicion'),
'use_featured_image' => _x('Usar como imagen', 'Overrides the "Use as featured image" phrase', 'bloques-transicion'),
'archives' => _x('Archivo de Recursos', 'The post type archive label', 'bloques-transicion'),
'insert_into_item' => _x('Insertar en recurso', 'Overrides the "Insert into post" phrase', 'bloques-transicion'),
'uploaded_to_this_item' => _x('Subido a este recurso', 'Overrides the "Uploaded to this post" phrase', 'bloques-transicion'),
'filter_items_list' => _x('Filtrar lista de recursos', 'Screen reader text', 'bloques-transicion'),
'items_list_navigation' => _x('Navegación de recursos', 'Screen reader text', 'bloques-transicion'),
'items_list' => _x('Lista de recursos', 'Screen reader text', 'bloques-transicion'),
];
$args = [
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => false,
'query_var' => true,
'rewrite' => ['slug' => 'bloques-en-transicion/recursos', 'with_front' => false],
'capability_type' => 'post',
'has_archive' => false, // Desactivado para usar la página de Divi
'hierarchical' => false,
'menu_position' => null,
'menu_icon' => 'dashicons-media-document',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'revisions'],
'show_in_rest' => true,
'taxonomies' => ['iniciativa', 'linea_trabajo'],
];
register_post_type('recurso_bloques', $args);
}
/**
* CPT: Eventos
*/
private static function register_evento()
{
$labels = [
'name' => _x('Eventos', 'Post type general name', 'bloques-transicion'),
'singular_name' => _x('Evento', 'Post type singular name', 'bloques-transicion'),
'menu_name' => _x('Eventos', 'Admin Menu text', 'bloques-transicion'),
'name_admin_bar' => _x('Evento', 'Add New on Toolbar', 'bloques-transicion'),
'add_new' => __('Añadir nuevo', 'bloques-transicion'),
'add_new_item' => __('Añadir nuevo Evento', 'bloques-transicion'),
'new_item' => __('Nuevo Evento', 'bloques-transicion'),
'edit_item' => __('Editar Evento', 'bloques-transicion'),
'view_item' => __('Ver Evento', 'bloques-transicion'),
'all_items' => __('Todos los Eventos', 'bloques-transicion'),
'search_items' => __('Buscar Eventos', 'bloques-transicion'),
'parent_item_colon' => __('Evento padre:', 'bloques-transicion'),
'not_found' => __('No se encontraron eventos.', 'bloques-transicion'),
'not_found_in_trash' => __('No hay eventos en la papelera.', 'bloques-transicion'),
'featured_image' => _x('Imagen del evento', 'Overrides the "Featured Image" phrase', 'bloques-transicion'),
'set_featured_image' => _x('Establecer imagen', 'Overrides the "Set featured image" phrase', 'bloques-transicion'),
'remove_featured_image' => _x('Eliminar imagen', 'Overrides the "Remove featured image" phrase', 'bloques-transicion'),
'use_featured_image' => _x('Usar como imagen', 'Overrides the "Use as featured image" phrase', 'bloques-transicion'),
'archives' => _x('Archivo de Eventos', 'The post type archive label', 'bloques-transicion'),
'insert_into_item' => _x('Insertar en evento', 'Overrides the "Insert into post" phrase', 'bloques-transicion'),
'uploaded_to_this_item' => _x('Subido a este evento', 'Overrides the "Uploaded to this post" phrase', 'bloques-transicion'),
'filter_items_list' => _x('Filtrar lista de eventos', 'Screen reader text', 'bloques-transicion'),
'items_list_navigation' => _x('Navegación de eventos', 'Screen reader text', 'bloques-transicion'),
'items_list' => _x('Lista de eventos', 'Screen reader text', 'bloques-transicion'),
];
$args = [
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => false,
'query_var' => true,
'rewrite' => ['slug' => 'bloques-en-transicion/eventos', 'with_front' => false],
'capability_type' => 'post',
'has_archive' => false, // Desactivado para usar la página de Divi
'hierarchical' => false,
'menu_position' => null,
'menu_icon' => 'dashicons-calendar-alt',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'revisions'],
'show_in_rest' => true,
'taxonomies' => ['iniciativa', 'linea_trabajo', 'tipo_evento'],
];
register_post_type('evento_bloques', $args);
}
/**
* Obtener todos los CPTs del plugin
*/
public static function get_post_types()
{
return ['actuacion', 'recurso_bloques', 'evento_bloques'];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,164 @@
<?php
/**
* Registro de Taxonomías personalizadas
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
class Bloques_Taxonomies {
/**
* Registrar todas las taxonomías
*/
public static function register() {
self::register_iniciativa();
self::register_linea_trabajo();
self::register_tipo_evento();
}
/**
* Taxonomía: Iniciativas
*/
private static function register_iniciativa() {
$labels = [
'name' => _x('Iniciativas', 'taxonomy general name', 'bloques-transicion'),
'singular_name' => _x('Iniciativa', 'taxonomy singular name', 'bloques-transicion'),
'search_items' => __('Buscar Iniciativas', 'bloques-transicion'),
'popular_items' => __('Iniciativas populares', 'bloques-transicion'),
'all_items' => __('Todas las Iniciativas', 'bloques-transicion'),
'parent_item' => __('Iniciativa padre', 'bloques-transicion'),
'parent_item_colon' => __('Iniciativa padre:', 'bloques-transicion'),
'edit_item' => __('Editar Iniciativa', 'bloques-transicion'),
'view_item' => __('Ver Iniciativa', 'bloques-transicion'),
'update_item' => __('Actualizar Iniciativa', 'bloques-transicion'),
'add_new_item' => __('Añadir nueva Iniciativa', 'bloques-transicion'),
'new_item_name' => __('Nombre de nueva Iniciativa', 'bloques-transicion'),
'separate_items_with_commas' => __('Separar iniciativas con comas', 'bloques-transicion'),
'add_or_remove_items' => __('Añadir o eliminar iniciativas', 'bloques-transicion'),
'choose_from_most_used' => __('Elegir de las más usadas', 'bloques-transicion'),
'not_found' => __('No se encontraron iniciativas.', 'bloques-transicion'),
'no_terms' => __('No hay iniciativas', 'bloques-transicion'),
'menu_name' => __('Iniciativas', 'bloques-transicion'),
'items_list_navigation' => __('Navegación de iniciativas', 'bloques-transicion'),
'items_list' => __('Lista de iniciativas', 'bloques-transicion'),
'back_to_items' => __('← Volver a Iniciativas', 'bloques-transicion'),
];
$args = [
'labels' => $labels,
'hierarchical' => true, // Como categorías
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_nav_menus' => true,
'show_tagcloud' => false,
'show_in_rest' => true,
'rewrite' => ['slug' => 'bloques/iniciativa', 'with_front' => false],
'query_var' => true,
];
// Asociar a todos los CPTs de Bloques
$post_types = ['actuacion', 'recurso_bloques', 'evento_bloques'];
register_taxonomy('iniciativa', $post_types, $args);
}
/**
* Taxonomía: Líneas de Trabajo
*/
private static function register_linea_trabajo() {
$labels = [
'name' => _x('Líneas de Trabajo', 'taxonomy general name', 'bloques-transicion'),
'singular_name' => _x('Línea de Trabajo', 'taxonomy singular name', 'bloques-transicion'),
'search_items' => __('Buscar Líneas de Trabajo', 'bloques-transicion'),
'popular_items' => __('Líneas de Trabajo populares', 'bloques-transicion'),
'all_items' => __('Todas las Líneas de Trabajo', 'bloques-transicion'),
'parent_item' => __('Línea de Trabajo padre', 'bloques-transicion'),
'parent_item_colon' => __('Línea de Trabajo padre:', 'bloques-transicion'),
'edit_item' => __('Editar Línea de Trabajo', 'bloques-transicion'),
'view_item' => __('Ver Línea de Trabajo', 'bloques-transicion'),
'update_item' => __('Actualizar Línea de Trabajo', 'bloques-transicion'),
'add_new_item' => __('Añadir nueva Línea de Trabajo', 'bloques-transicion'),
'new_item_name' => __('Nombre de nueva Línea de Trabajo', 'bloques-transicion'),
'separate_items_with_commas' => __('Separar líneas con comas', 'bloques-transicion'),
'add_or_remove_items' => __('Añadir o eliminar líneas', 'bloques-transicion'),
'choose_from_most_used' => __('Elegir de las más usadas', 'bloques-transicion'),
'not_found' => __('No se encontraron líneas de trabajo.', 'bloques-transicion'),
'no_terms' => __('No hay líneas de trabajo', 'bloques-transicion'),
'menu_name' => __('Líneas de Trabajo', 'bloques-transicion'),
'items_list_navigation' => __('Navegación de líneas', 'bloques-transicion'),
'items_list' => __('Lista de líneas', 'bloques-transicion'),
'back_to_items' => __('← Volver a Líneas de Trabajo', 'bloques-transicion'),
];
$args = [
'labels' => $labels,
'hierarchical' => true,
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_nav_menus' => true,
'show_tagcloud' => false,
'show_in_rest' => true,
'rewrite' => ['slug' => 'bloques/linea-trabajo', 'with_front' => false],
'query_var' => true,
];
$post_types = ['actuacion', 'recurso_bloques', 'evento_bloques'];
register_taxonomy('linea_trabajo', $post_types, $args);
}
/**
* Taxonomía: Tipo de Evento
*/
private static function register_tipo_evento() {
$labels = [
'name' => _x('Tipos de Evento', 'taxonomy general name', 'bloques-transicion'),
'singular_name' => _x('Tipo de Evento', 'taxonomy singular name', 'bloques-transicion'),
'search_items' => __('Buscar Tipos', 'bloques-transicion'),
'popular_items' => __('Tipos populares', 'bloques-transicion'),
'all_items' => __('Todos los Tipos', 'bloques-transicion'),
'parent_item' => null,
'parent_item_colon' => null,
'edit_item' => __('Editar Tipo', 'bloques-transicion'),
'view_item' => __('Ver Tipo', 'bloques-transicion'),
'update_item' => __('Actualizar Tipo', 'bloques-transicion'),
'add_new_item' => __('Añadir nuevo Tipo', 'bloques-transicion'),
'new_item_name' => __('Nombre del nuevo Tipo', 'bloques-transicion'),
'separate_items_with_commas' => __('Separar tipos con comas', 'bloques-transicion'),
'add_or_remove_items' => __('Añadir o eliminar tipos', 'bloques-transicion'),
'choose_from_most_used' => __('Elegir de los más usados', 'bloques-transicion'),
'not_found' => __('No se encontraron tipos.', 'bloques-transicion'),
'no_terms' => __('No hay tipos', 'bloques-transicion'),
'menu_name' => __('Tipos de Evento', 'bloques-transicion'),
'items_list_navigation' => __('Navegación de tipos', 'bloques-transicion'),
'items_list' => __('Lista de tipos', 'bloques-transicion'),
'back_to_items' => __('← Volver a Tipos', 'bloques-transicion'),
];
$args = [
'labels' => $labels,
'hierarchical' => false, // Como etiquetas (permite múltiple selección)
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_nav_menus' => true,
'show_tagcloud' => false,
'show_in_rest' => true,
'rewrite' => ['slug' => 'bloques/tipo-evento', 'with_front' => false],
'query_var' => true,
];
register_taxonomy('tipo_evento', ['evento_bloques'], $args);
}
/**
* Obtener todas las taxonomías del plugin
*/
public static function get_taxonomies() {
return ['iniciativa', 'linea_trabajo', 'tipo_evento'];
}
}

View File

@@ -0,0 +1,493 @@
<?php
/**
* Template: Dashboard de Bloques en Transición
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
?>
<div class="wrap bloques-dashboard">
<h1>
<span class="dashicons dashicons-building"></span>
<?php _e('Bloques en Transición', 'bloques-transicion'); ?>
</h1>
<div class="bloques-dashboard-welcome">
<h2><?php _e('Bienvenido al panel de gestión del microsite', 'bloques-transicion'); ?></h2>
<p><?php _e('Desde aquí puedes gestionar todo el contenido del microsite "Bloques en Transición".', 'bloques-transicion'); ?></p>
</div>
<div class="bloques-dashboard-stats">
<div class="bloques-stat-card">
<div class="bloques-stat-icon">
<span class="dashicons dashicons-location-alt"></span>
</div>
<div class="bloques-stat-content">
<span class="bloques-stat-number"><?php echo esc_html($counts['actuacion']); ?></span>
<span class="bloques-stat-label"><?php _e('Actuaciones', 'bloques-transicion'); ?></span>
</div>
<a href="<?php echo admin_url('edit.php?post_type=actuacion'); ?>" class="bloques-stat-link">
<?php _e('Ver todas', 'bloques-transicion'); ?> →
</a>
</div>
<div class="bloques-stat-card">
<div class="bloques-stat-icon">
<span class="dashicons dashicons-media-document"></span>
</div>
<div class="bloques-stat-content">
<span class="bloques-stat-number"><?php echo esc_html($counts['recurso_bloques']); ?></span>
<span class="bloques-stat-label"><?php _e('Recursos', 'bloques-transicion'); ?></span>
</div>
<a href="<?php echo admin_url('edit.php?post_type=recurso_bloques'); ?>" class="bloques-stat-link">
<?php _e('Ver todos', 'bloques-transicion'); ?> →
</a>
</div>
<div class="bloques-stat-card">
<div class="bloques-stat-icon">
<span class="dashicons dashicons-calendar-alt"></span>
</div>
<div class="bloques-stat-content">
<span class="bloques-stat-number"><?php echo esc_html($counts['evento_bloques']); ?></span>
<span class="bloques-stat-label"><?php _e('Eventos', 'bloques-transicion'); ?></span>
</div>
<a href="<?php echo admin_url('edit.php?post_type=evento_bloques'); ?>" class="bloques-stat-link">
<?php _e('Ver todos', 'bloques-transicion'); ?> →
</a>
</div>
</div>
<div class="bloques-dashboard-taxonomies">
<h3><?php _e('Categorías', 'bloques-transicion'); ?></h3>
<div class="bloques-tax-cards">
<div class="bloques-tax-card">
<strong><?php _e('Iniciativas', 'bloques-transicion'); ?></strong>
<span class="bloques-tax-count"><?php echo esc_html($iniciativas); ?></span>
<a href="<?php echo admin_url('edit-tags.php?taxonomy=iniciativa'); ?>">
<?php _e('Gestionar', 'bloques-transicion'); ?>
</a>
</div>
<div class="bloques-tax-card">
<strong><?php _e('Líneas de Trabajo', 'bloques-transicion'); ?></strong>
<span class="bloques-tax-count"><?php echo esc_html($lineas); ?></span>
<a href="<?php echo admin_url('edit-tags.php?taxonomy=linea_trabajo'); ?>">
<?php _e('Gestionar', 'bloques-transicion'); ?>
</a>
</div>
</div>
</div>
<div class="bloques-dashboard-shortcuts">
<h3><?php _e('Acciones rápidas', 'bloques-transicion'); ?></h3>
<div class="bloques-shortcuts">
<a href="<?php echo admin_url('post-new.php?post_type=actuacion'); ?>" class="button button-primary">
<span class="dashicons dashicons-plus-alt"></span>
<?php _e('Nueva Actuación', 'bloques-transicion'); ?>
</a>
<a href="<?php echo admin_url('post-new.php?post_type=recurso_bloques'); ?>" class="button button-primary">
<span class="dashicons dashicons-plus-alt"></span>
<?php _e('Nuevo Recurso', 'bloques-transicion'); ?>
</a>
<a href="<?php echo admin_url('post-new.php?post_type=evento_bloques'); ?>" class="button button-primary">
<span class="dashicons dashicons-plus-alt"></span>
<?php _e('Nuevo Evento', 'bloques-transicion'); ?>
</a>
<a href="<?php echo admin_url('admin.php?page=bloques-settings'); ?>" class="button">
<span class="dashicons dashicons-admin-customizer"></span>
<?php _e('CSS personalizado', 'bloques-transicion'); ?>
</a>
</div>
</div>
<div class="bloques-dashboard-shortcodes">
<h3><?php _e('Guía de Shortcodes', 'bloques-transicion'); ?></h3>
<div class="bloques-shortcode-section">
<h4><?php _e('Shortcode principal', 'bloques-transicion'); ?></h4>
<p class="description"><?php _e('Usa este shortcode para mostrar cualquier tipo de contenido con filtros y paginación.', 'bloques-transicion'); ?></p>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width: 45%;"><?php _e('Shortcode', 'bloques-transicion'); ?></th>
<th><?php _e('Descripción', 'bloques-transicion'); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[bloques-listado type="recursos" filter="true"]</code></td>
<td><?php _e('Recursos con filtros tipo botonera', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>[bloques-listado type="recursos" limit="9" pagination="true"]</code></td>
<td><?php _e('Recursos con botón "Ver más"', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>[bloques-listado type="noticias" columns="3"]</code></td>
<td><?php _e('Noticias en grid de 3 columnas', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>[bloques-listado type="eventos" widget="list"]</code></td>
<td><?php _e('Eventos en formato lista', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>[bloques-listado type="actuaciones" filter="true" search="true"]</code></td>
<td><?php _e('Actuaciones con filtros y buscador', 'bloques-transicion'); ?></td>
</tr>
</tbody>
</table>
<h5><?php _e('Atributos disponibles', 'bloques-transicion'); ?></h5>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width: 20%;"><?php _e('Atributo', 'bloques-transicion'); ?></th>
<th style="width: 25%;"><?php _e('Valores', 'bloques-transicion'); ?></th>
<th style="width: 15%;"><?php _e('Default', 'bloques-transicion'); ?></th>
<th><?php _e('Descripción', 'bloques-transicion'); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>type</code></td>
<td>recursos | noticias | eventos | actuaciones</td>
<td>recursos</td>
<td><?php _e('Tipo de contenido a mostrar', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>widget</code></td>
<td>grid | list</td>
<td>grid</td>
<td><?php _e('Modo de visualización', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>columns</code></td>
<td>1 | 2 | 3 | 4</td>
<td>3</td>
<td><?php _e('Número de columnas en grid', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>limit</code></td>
<td>número</td>
<td>-1 (todos)</td>
<td><?php _e('Número máximo de items', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>pagination</code></td>
<td>true | false</td>
<td>false</td>
<td><?php _e('Mostrar botón "Ver más"', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>filter</code></td>
<td>true | false</td>
<td>false</td>
<td><?php _e('Mostrar filtros tipo botonera', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>search</code></td>
<td>true | false</td>
<td>false</td>
<td><?php _e('Mostrar campo de búsqueda', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>iniciativa</code></td>
<td>slug</td>
<td>-</td>
<td><?php _e('Filtrar por iniciativa (slug)', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>linea</code></td>
<td>slug</td>
<td>-</td>
<td><?php _e('Filtrar por línea de trabajo (slug)', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>upcoming</code></td>
<td>true | false</td>
<td>true</td>
<td><?php _e('Solo eventos futuros (solo para eventos)', 'bloques-transicion'); ?></td>
</tr>
</tbody>
</table>
</div>
<div class="bloques-shortcode-section">
<h4><?php _e('Shortcodes con header (para Home)', 'bloques-transicion'); ?></h4>
<p class="description"><?php _e('Bloques con cabecera y enlace "Ver más" para usar en la página de inicio.', 'bloques-transicion'); ?></p>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width: 45%;"><?php _e('Shortcode', 'bloques-transicion'); ?></th>
<th><?php _e('Descripción', 'bloques-transicion'); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[bloques-novedades limit="3"]</code></td>
<td><?php _e('Bloque de novedades con icono y enlace', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>[bloques-agenda limit="4"]</code></td>
<td><?php _e('Bloque de agenda con próximos eventos', 'bloques-transicion'); ?></td>
</tr>
</tbody>
</table>
<h5><?php _e('Atributos para bloques con header', 'bloques-transicion'); ?></h5>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width: 20%;"><?php _e('Atributo', 'bloques-transicion'); ?></th>
<th style="width: 35%;"><?php _e('Default', 'bloques-transicion'); ?></th>
<th><?php _e('Descripción', 'bloques-transicion'); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>limit</code></td>
<td>3 (novedades) / 4 (agenda)</td>
<td><?php _e('Número de items', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>title</code></td>
<td>"Novedades" / "Agenda"</td>
<td><?php _e('Título del bloque', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>subtitle</code></td>
<td>"Últimas noticias..." / "Próximas actividades"</td>
<td><?php _e('Subtítulo', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>show_header</code></td>
<td>true</td>
<td><?php _e('Mostrar cabecera con icono', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>link_text</code></td>
<td>"Ver todas las novedades" / "Ver calendario completo"</td>
<td><?php _e('Texto del enlace', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>link_url</code></td>
<td>/bloques-en-transicion/noticias/ | /bloques-en-transicion/eventos/</td>
<td><?php _e('URL del enlace', 'bloques-transicion'); ?></td>
</tr>
</tbody>
</table>
</div>
<div class="bloques-shortcode-section">
<h4><?php _e('Shortcodes de taxonomías', 'bloques-transicion'); ?></h4>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width: 45%;"><?php _e('Shortcode', 'bloques-transicion'); ?></th>
<th><?php _e('Descripción', 'bloques-transicion'); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[bloques-iniciativas columns="4"]</code></td>
<td><?php _e('Cards de iniciativas', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>[bloques-lineas columns="6"]</code></td>
<td><?php _e('Líneas de trabajo con iconos', 'bloques-transicion'); ?></td>
</tr>
</tbody>
</table>
</div>
<div class="bloques-shortcode-section">
<h4><?php _e('Ejemplos de uso', 'bloques-transicion'); ?></h4>
<div class="bloques-examples">
<div class="bloques-example">
<strong><?php _e('Página de Recursos:', 'bloques-transicion'); ?></strong>
<code>[bloques-listado type="recursos" filter="true" search="true" limit="12" pagination="true"]</code>
</div>
<div class="bloques-example">
<strong><?php _e('Home - Novedades y Agenda:', 'bloques-transicion'); ?></strong>
<code>[bloques-novedades limit="3"]</code>
<code>[bloques-agenda limit="4"]</code>
</div>
<div class="bloques-example">
<strong><?php _e('Recursos de una iniciativa:', 'bloques-transicion'); ?></strong>
<code>[bloques-listado type="recursos" iniciativa="coles-en-transicion"]</code>
</div>
</div>
</div>
</div>
</div>
<style>
.bloques-dashboard-welcome {
background: linear-gradient(135deg, #1E6B52 0%, #2A9D8F 100%);
color: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 25px;
}
.bloques-dashboard-welcome h2 {
color: white;
margin-top: 0;
}
.bloques-dashboard-welcome p {
opacity: 0.9;
margin-bottom: 0;
}
.bloques-dashboard-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.bloques-stat-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
gap: 10px;
}
.bloques-stat-icon {
width: 50px;
height: 50px;
border-radius: 10px;
background: #f0f9f7;
display: flex;
align-items: center;
justify-content: center;
}
.bloques-stat-icon .dashicons {
font-size: 24px;
color: #1E6B52;
}
.bloques-stat-number {
font-size: 32px;
font-weight: 700;
color: #1f2937;
}
.bloques-stat-label {
color: #6b7280;
font-size: 14px;
}
.bloques-stat-link {
color: #F97316;
text-decoration: none;
font-weight: 500;
}
.bloques-dashboard-taxonomies,
.bloques-dashboard-shortcuts,
.bloques-dashboard-shortcodes {
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.bloques-dashboard-taxonomies h3,
.bloques-dashboard-shortcuts h3,
.bloques-dashboard-shortcodes h3 {
margin-top: 0;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.bloques-tax-cards {
display: flex;
gap: 20px;
margin-top: 15px;
}
.bloques-tax-card {
background: #f8f9fa;
padding: 15px 20px;
border-radius: 6px;
display: flex;
align-items: center;
gap: 15px;
}
.bloques-tax-count {
background: #1E6B52;
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 13px;
font-weight: 600;
}
.bloques-shortcuts {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
.bloques-shortcuts .button {
display: inline-flex;
align-items: center;
gap: 6px;
}
.bloques-shortcode-section {
margin-top: 25px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.bloques-shortcode-section:first-of-type {
margin-top: 15px;
padding-top: 0;
border-top: none;
}
.bloques-shortcode-section h4 {
margin: 0 0 10px;
color: #1E6B52;
}
.bloques-shortcode-section h5 {
margin: 20px 0 10px;
color: #374151;
}
.bloques-shortcode-section .description {
margin-bottom: 15px;
color: #6b7280;
}
.bloques-examples {
background: #f8f9fa;
padding: 20px;
border-radius: 6px;
display: flex;
flex-direction: column;
gap: 15px;
}
.bloques-example {
display: flex;
flex-direction: column;
gap: 8px;
}
.bloques-example strong {
color: #374151;
}
.bloques-example code {
background: white;
padding: 8px 12px;
border-radius: 4px;
display: block;
font-size: 12px;
}
@media (max-width: 1024px) {
.bloques-dashboard-stats {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.bloques-dashboard-stats {
grid-template-columns: 1fr;
}
.bloques-tax-cards {
flex-direction: column;
}
}
</style>

View File

@@ -0,0 +1,100 @@
<?php
/**
* Template: Página de carga de datos iniciales
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
?>
<div class="wrap bloques-seeder">
<h1>
<span class="dashicons dashicons-database-import"></span>
<?php _e('Cargar datos iniciales', 'bloques-transicion'); ?>
</h1>
<?php if (isset($message)): ?>
<div class="notice notice-<?php echo esc_attr($message_type); ?> is-dismissible">
<p><?php echo esc_html($message); ?></p>
</div>
<?php endif; ?>
<div class="bloques-seeder-info">
<h2><?php _e('¿Qué datos se cargarán?', 'bloques-transicion'); ?></h2>
<div class="bloques-seeder-section">
<h3><?php _e('Iniciativas (4)', 'bloques-transicion'); ?></h3>
<ul>
<li><strong>Oficina de Transición Justa</strong> - Puente y Villa de Vallecas</li>
<li><strong>Coles en transición</strong> - 7 centros en Puente y Villa de Vallecas</li>
<li><strong>Comunidad energética</strong> - Puente de Vallecas</li>
<li><strong>Creación de redes</strong> - Madrid, Cataluña y Valencia</li>
</ul>
</div>
<div class="bloques-seeder-section">
<h3><?php _e('Líneas de Trabajo (6)', 'bloques-transicion'); ?></h3>
<ul>
<li><strong>Energía</strong> - Rehabilitación edificios, comunidades energéticas y autoconsumo renovable</li>
<li><strong>Cuidados</strong> - Redes de apoyo mutuo y espacios vecinales</li>
<li><strong>Alimentación</strong> - Alimentación sostenible y saludable y redes de consumo</li>
<li><strong>Movilidad</strong> - Movilidad sostenible, uso de la bicicleta y espacio público</li>
<li><strong>Renaturalización</strong> - Renaturalización de comunidades y patios escolares</li>
<li><strong>Reutilización</strong> - Economía circular y consumo colaborativo</li>
</ul>
</div>
<div class="bloques-seeder-section">
<h3><?php _e('Tipos de Evento (2)', 'bloques-transicion'); ?></h3>
<ul>
<li><strong>Online</strong></li>
<li><strong>Presencial</strong></li>
</ul>
</div>
</div>
<div class="bloques-seeder-actions">
<form method="post" action="">
<?php wp_nonce_field('bloques_seed', 'bloques_seed_nonce'); ?>
<input type="hidden" name="bloques_seed_action" value="1">
<p class="description">
<?php _e('Esta acción creará o actualizará las categorías con los datos predefinidos. Los datos existentes no se eliminarán.', 'bloques-transicion'); ?>
</p>
<p>
<button type="submit" class="button button-primary button-hero">
<span class="dashicons dashicons-database-import"></span>
<?php _e('Cargar datos iniciales', 'bloques-transicion'); ?>
</button>
</p>
</form>
</div>
<div class="bloques-seeder-status">
<h3><?php _e('Estado actual', 'bloques-transicion'); ?></h3>
<?php
$iniciativas_count = wp_count_terms(['taxonomy' => 'iniciativa', 'hide_empty' => false]);
$lineas_count = wp_count_terms(['taxonomy' => 'linea_trabajo', 'hide_empty' => false]);
$tipos_count = wp_count_terms(['taxonomy' => 'tipo_evento', 'hide_empty' => false]);
?>
<table class="wp-list-table widefat fixed striped">
<tbody>
<tr>
<td><?php _e('Iniciativas', 'bloques-transicion'); ?></td>
<td><strong><?php echo esc_html($iniciativas_count); ?></strong></td>
</tr>
<tr>
<td><?php _e('Líneas de Trabajo', 'bloques-transicion'); ?></td>
<td><strong><?php echo esc_html($lineas_count); ?></strong></td>
</tr>
<tr>
<td><?php _e('Tipos de Evento', 'bloques-transicion'); ?></td>
<td><strong><?php echo esc_html($tipos_count); ?></strong></td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,149 @@
<?php
/**
* Template: Página de Ajustes
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
?>
<div class="wrap bloques-settings">
<h1>
<span class="dashicons dashicons-admin-settings"></span>
<?php _e('Ajustes de Bloques en Transición', 'bloques-transicion'); ?>
</h1>
<?php if (isset($saved) && $saved): ?>
<div class="notice notice-success is-dismissible">
<p><?php _e('Ajustes guardados correctamente.', 'bloques-transicion'); ?></p>
</div>
<?php endif; ?>
<form method="post" action="">
<?php wp_nonce_field('bloques_save_settings', 'bloques_settings_nonce'); ?>
<div class="bloques-settings-section">
<h2><?php _e('CSS Personalizado', 'bloques-transicion'); ?></h2>
<p class="description">
<?php _e('Añade CSS personalizado para los shortcodes de Bloques en Transición. Este CSS se cargará en el frontend.', 'bloques-transicion'); ?>
</p>
<table class="form-table">
<tr>
<th scope="row">
<label for="bloques_custom_css"><?php _e('CSS', 'bloques-transicion'); ?></label>
</th>
<td>
<textarea
name="bloques_custom_css"
id="bloques_custom_css"
rows="20"
class="large-text code"
style="font-family: monospace; font-size: 13px;"
><?php echo esc_textarea($custom_css); ?></textarea>
<p class="description">
<?php _e('Puedes usar las variables CSS definidas:', 'bloques-transicion'); ?>
</p>
<pre style="background: #f6f7f7; padding: 15px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
--bloques-primary: #1E6B52; /* Color principal */
--bloques-secondary: #2A9D8F; /* Color secundario */
--bloques-accent: #F97316; /* Color de acento (naranja) */
--bloques-text: #1f2937; /* Color del texto */
--bloques-text-light: #6b7280; /* Color texto secundario */
--bloques-bg: #f9fafb; /* Color de fondo */
--bloques-border: #e5e7eb; /* Color de bordes */
--bloques-radius: 12px; /* Radio de bordes */</pre>
</td>
</tr>
</table>
<h3><?php _e('Clases CSS disponibles', 'bloques-transicion'); ?></h3>
<table class="wp-list-table widefat fixed striped" style="max-width: 800px;">
<thead>
<tr>
<th><?php _e('Clase', 'bloques-transicion'); ?></th>
<th><?php _e('Descripción', 'bloques-transicion'); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>.bloques-listado</code></td>
<td><?php _e('Contenedor principal del shortcode', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-filters</code></td>
<td><?php _e('Contenedor de filtros', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-filter-btn</code></td>
<td><?php _e('Botones de filtro', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-filter-btn.active</code></td>
<td><?php _e('Botón de filtro activo', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-items</code></td>
<td><?php _e('Grid de items', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-card</code></td>
<td><?php _e('Card de recurso o noticia', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-card-thumbnail</code></td>
<td><?php _e('Imagen de la card', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-card-content</code></td>
<td><?php _e('Contenido de la card', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-card-title</code></td>
<td><?php _e('Título de la card', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-btn-outline</code></td>
<td><?php _e('Botón con borde (descargar)', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-btn-load-more</code></td>
<td><?php _e('Botón "Ver más"', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-agenda-item</code></td>
<td><?php _e('Item de agenda', 'bloques-transicion'); ?></td>
</tr>
<tr>
<td><code>.bloques-novedad-item</code></td>
<td><?php _e('Item de novedad', 'bloques-transicion'); ?></td>
</tr>
</tbody>
</table>
</div>
<p class="submit">
<input type="submit" name="submit" class="button button-primary" value="<?php esc_attr_e('Guardar cambios', 'bloques-transicion'); ?>">
</p>
</form>
</div>
<style>
.bloques-settings-section {
background: white;
padding: 20px 25px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
margin-top: 20px;
}
.bloques-settings-section h2 {
margin-top: 0;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.bloques-settings-section h3 {
margin-top: 30px;
}
</style>

View File

@@ -0,0 +1,102 @@
<?php
/**
* Template: Item de Actuación
*
* Variables disponibles:
* - $id, $title, $excerpt, $content, $permalink, $thumbnail
* - $iniciativas, $lineas, $widget, $type
* - $es_piloto, $direccion, $localidad, $latitud, $longitud
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
$classes = ['bloques-item', 'bloques-item-actuacion'];
if ($widget === 'list') {
$classes[] = 'bloques-item-list';
}
if ($es_piloto) {
$classes[] = 'bloques-item-piloto';
}
?>
<article id="bloques-actuacion-<?php echo esc_attr($id); ?>" class="<?php echo esc_attr(implode(' ', $classes)); ?>">
<?php if ($thumbnail): ?>
<div class="bloques-item-thumbnail">
<a href="<?php echo esc_url($permalink); ?>">
<img src="<?php echo esc_url($thumbnail); ?>" alt="<?php echo esc_attr($thumbnail_alt ?: $title); ?>" loading="lazy">
</a>
<?php if ($es_piloto): ?>
<span class="bloques-badge bloques-badge-piloto">
<?php _e('Piloto', 'bloques-transicion'); ?>
</span>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="bloques-item-content">
<header class="bloques-item-header">
<?php if (!empty($iniciativas)): ?>
<div class="bloques-item-taxonomies">
<?php foreach ($iniciativas as $term):
$color = get_field('color', $term);
$icono = get_field('icono', $term);
?>
<span class="bloques-tag bloques-tag-iniciativa" style="<?php echo $color ? '--tag-color: ' . esc_attr($color) . ';' : ''; ?>">
<?php if ($icono): ?>
<img src="<?php echo esc_url($icono); ?>" alt="" class="bloques-tag-icon">
<?php endif; ?>
<?php echo esc_html($term->name); ?>
</span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<h3 class="bloques-item-title">
<a href="<?php echo esc_url($permalink); ?>">
<?php echo esc_html($title); ?>
</a>
</h3>
</header>
<?php if ($excerpt): ?>
<div class="bloques-item-excerpt">
<?php echo wp_kses_post($excerpt); ?>
</div>
<?php endif; ?>
<?php if ($localidad || $direccion): ?>
<div class="bloques-item-location">
<span class="bloques-icon bloques-icon-location"></span>
<?php
$location_parts = array_filter([$direccion, $localidad]);
echo esc_html(implode(', ', $location_parts));
?>
</div>
<?php endif; ?>
<?php if (!empty($lineas)): ?>
<div class="bloques-item-lineas">
<?php foreach ($lineas as $term):
$icono = get_field('icono', $term);
?>
<span class="bloques-linea-badge" title="<?php echo esc_attr($term->name); ?>">
<?php if ($icono): ?>
<img src="<?php echo esc_url($icono); ?>" alt="<?php echo esc_attr($term->name); ?>">
<?php else: ?>
<?php echo esc_html(mb_substr($term->name, 0, 1)); ?>
<?php endif; ?>
</span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<footer class="bloques-item-footer">
<a href="<?php echo esc_url($permalink); ?>" class="bloques-item-link bloques-btn">
<?php _e('Ver actuación', 'bloques-transicion'); ?>
</a>
</footer>
</div>
</article>

View File

@@ -0,0 +1,72 @@
<?php
/**
* Template: Item por defecto para listados
*
* Variables disponibles:
* - $id, $title, $excerpt, $content, $permalink, $thumbnail
* - $iniciativas, $lineas, $widget, $type
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
$classes = ['bloques-item', 'bloques-item-' . $type];
if ($widget === 'list') {
$classes[] = 'bloques-item-list';
}
?>
<article id="bloques-item-<?php echo esc_attr($id); ?>" class="<?php echo esc_attr(implode(' ', $classes)); ?>">
<?php if ($thumbnail): ?>
<div class="bloques-item-thumbnail">
<a href="<?php echo esc_url($permalink); ?>">
<img src="<?php echo esc_url($thumbnail); ?>" alt="<?php echo esc_attr($thumbnail_alt ?: $title); ?>" loading="lazy">
</a>
</div>
<?php endif; ?>
<div class="bloques-item-content">
<header class="bloques-item-header">
<?php if (!empty($iniciativas) || !empty($lineas)): ?>
<div class="bloques-item-taxonomies">
<?php foreach ($iniciativas as $term):
$color = get_field('color', $term);
?>
<span class="bloques-tag bloques-tag-iniciativa" style="<?php echo $color ? '--tag-color: ' . esc_attr($color) . ';' : ''; ?>">
<?php echo esc_html($term->name); ?>
</span>
<?php endforeach; ?>
<?php foreach ($lineas as $term):
$color = get_field('color', $term);
?>
<span class="bloques-tag bloques-tag-linea" style="<?php echo $color ? '--tag-color: ' . esc_attr($color) . ';' : ''; ?>">
<?php echo esc_html($term->name); ?>
</span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<h3 class="bloques-item-title">
<a href="<?php echo esc_url($permalink); ?>">
<?php echo esc_html($title); ?>
</a>
</h3>
</header>
<?php if ($excerpt): ?>
<div class="bloques-item-excerpt">
<?php echo wp_kses_post($excerpt); ?>
</div>
<?php endif; ?>
<footer class="bloques-item-footer">
<a href="<?php echo esc_url($permalink); ?>" class="bloques-item-link">
<?php _e('Ver más', 'bloques-transicion'); ?>
<span class="bloques-arrow">→</span>
</a>
</footer>
</div>
</article>

View File

@@ -0,0 +1,184 @@
<?php
/**
* Template: Item de Evento
*
* Variables disponibles:
* - $id, $title, $excerpt, $content, $permalink, $thumbnail
* - $iniciativas, $lineas, $widget, $type, $date
* - $fecha_inicio, $hora_inicio, $fecha_fin, $hora_fin
* - $lugar, $direccion, $url_online, $url_inscripcion, $tipos_evento
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
// Determinar si es online, presencial o híbrido
$es_online = false;
$es_presencial = false;
if (!empty($tipos_evento)) {
foreach ($tipos_evento as $tipo) {
if ($tipo->slug === 'online') $es_online = true;
if ($tipo->slug === 'presencial') $es_presencial = true;
}
}
$modalidad = '';
$modalidad_class = '';
if ($es_online && $es_presencial) {
$modalidad = __('Híbrido', 'bloques-transicion');
$modalidad_class = 'bloques-modalidad-hibrido';
} elseif ($es_online) {
$modalidad = __('Online', 'bloques-transicion');
$modalidad_class = 'bloques-modalidad-online';
} elseif ($es_presencial) {
$modalidad = __('Presencial', 'bloques-transicion');
$modalidad_class = 'bloques-modalidad-presencial';
}
// Meses en español abreviados
$meses = [
'01' => 'ENE', '02' => 'FEB', '03' => 'MAR', '04' => 'ABR',
'05' => 'MAY', '06' => 'JUN', '07' => 'JUL', '08' => 'AGO',
'09' => 'SEP', '10' => 'OCT', '11' => 'NOV', '12' => 'DIC',
];
// Parsear fecha
$dia = '';
$mes = '';
if ($fecha_inicio) {
$fecha_clean = str_replace('-', '', $fecha_inicio);
if (strlen($fecha_clean) >= 8) {
$dia = ltrim(substr($fecha_clean, 6, 2), '0');
$mes_num = substr($fecha_clean, 4, 2);
$mes = $meses[$mes_num] ?? $mes_num;
}
}
// Determinar ubicación a mostrar
$ubicacion_texto = $modalidad ?: $lugar;
// Widget "agenda" (para bloques-agenda)
if ($widget === 'agenda'): ?>
<article id="bloques-evento-<?php echo esc_attr($id); ?>" class="bloques-agenda-item">
<div class="bloques-agenda-fecha">
<span class="bloques-agenda-mes"><?php echo esc_html($mes); ?></span>
<span class="bloques-agenda-dia"><?php echo esc_html($dia); ?></span>
</div>
<div class="bloques-agenda-content">
<div class="bloques-agenda-meta">
<?php if ($hora_inicio): ?>
<span class="bloques-agenda-hora"><?php echo esc_html($hora_inicio); ?></span>
<?php endif; ?>
<?php if ($ubicacion_texto): ?>
<span class="bloques-agenda-separator">•</span>
<span class="bloques-agenda-ubicacion <?php echo esc_attr($modalidad_class); ?>">
<?php echo esc_html($ubicacion_texto); ?>
</span>
<?php endif; ?>
</div>
<h4 class="bloques-agenda-title"><?php echo esc_html($title); ?></h4>
</div>
</article>
<?php else:
// Widget por defecto (list o grid)
$classes = ['bloques-item', 'bloques-item-evento'];
if ($widget === 'list') {
$classes[] = 'bloques-item-list';
}
// Formatear fecha completa
$fecha_formateada = '';
if ($fecha_inicio) {
$fecha_obj = DateTime::createFromFormat('Ymd', str_replace('-', '', $fecha_inicio));
if ($fecha_obj) {
$meses_largo = ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
$dia_num = $fecha_obj->format('j');
$mes_nombre = $meses_largo[(int)$fecha_obj->format('n') - 1];
$anio = $fecha_obj->format('Y');
$fecha_formateada = "$dia_num de $mes_nombre, $anio";
}
}
?>
<article id="bloques-evento-<?php echo esc_attr($id); ?>" class="<?php echo esc_attr(implode(' ', $classes)); ?>">
<div class="bloques-evento-fecha">
<span class="bloques-evento-dia"><?php echo esc_html($dia); ?></span>
<span class="bloques-evento-mes"><?php echo esc_html($mes); ?></span>
</div>
<div class="bloques-item-content">
<header class="bloques-item-header">
<?php if ($modalidad): ?>
<span class="bloques-evento-modalidad <?php echo esc_attr($modalidad_class); ?>">
<?php echo esc_html($modalidad); ?>
</span>
<?php endif; ?>
<h3 class="bloques-item-title">
<a href="<?php echo esc_url($permalink); ?>">
<?php echo esc_html($title); ?>
</a>
</h3>
</header>
<div class="bloques-evento-meta">
<?php if ($fecha_formateada): ?>
<div class="bloques-evento-meta-item">
<span class="bloques-icon bloques-icon-calendar"></span>
<span><?php echo esc_html($fecha_formateada); ?></span>
</div>
<?php endif; ?>
<?php if ($hora_inicio): ?>
<div class="bloques-evento-meta-item">
<span class="bloques-icon bloques-icon-clock"></span>
<span>
<?php echo esc_html($hora_inicio); ?>
<?php if ($hora_fin): ?>
- <?php echo esc_html($hora_fin); ?>
<?php endif; ?>
</span>
</div>
<?php endif; ?>
<?php if ($lugar || $direccion): ?>
<div class="bloques-evento-meta-item">
<span class="bloques-icon bloques-icon-location"></span>
<span><?php echo esc_html($lugar ?: $direccion); ?></span>
</div>
<?php endif; ?>
</div>
<?php if ($excerpt): ?>
<div class="bloques-item-excerpt">
<?php echo wp_kses_post($excerpt); ?>
</div>
<?php endif; ?>
<?php if (!empty($iniciativas)): ?>
<div class="bloques-item-taxonomies">
<?php foreach ($iniciativas as $term):
$color = get_field('color', $term);
?>
<span class="bloques-tag bloques-tag-iniciativa" style="<?php echo $color ? '--tag-color: ' . esc_attr($color) . ';' : ''; ?>">
<?php echo esc_html($term->name); ?>
</span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<footer class="bloques-item-footer">
<a href="<?php echo esc_url($permalink); ?>" class="bloques-item-link bloques-btn">
<?php _e('Ver evento', 'bloques-transicion'); ?>
</a>
<?php if ($url_inscripcion): ?>
<a href="<?php echo esc_url($url_inscripcion); ?>" class="bloques-btn bloques-btn-secondary" target="_blank" rel="noopener">
<?php _e('Inscribirse', 'bloques-transicion'); ?>
</a>
<?php endif; ?>
</footer>
</div>
</article>
<?php endif; ?>

View File

@@ -0,0 +1,75 @@
<?php
/**
* Template: Item de Noticia
*
* Variables disponibles:
* - $id, $title, $excerpt, $content, $permalink, $thumbnail
* - $widget, $type, $date
* - $category, $category_color
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
// Widget compact (para bloques-novedades)
if ($widget === 'compact'): ?>
<article id="bloques-noticia-<?php echo esc_attr($id); ?>" class="bloques-novedad-item">
<div class="bloques-novedad-content">
<div class="bloques-novedad-meta">
<?php if ($category): ?>
<span class="bloques-novedad-category" style="background-color: <?php echo esc_attr($category_color); ?>;">
<?php echo esc_html($category->name); ?>
</span>
<?php endif; ?>
<span class="bloques-novedad-date"><?php echo esc_html($date); ?></span>
</div>
<h4 class="bloques-novedad-title">
<a href="<?php echo esc_url($permalink); ?>">
<?php echo esc_html($title); ?>
</a>
</h4>
<p class="bloques-novedad-excerpt">
<?php echo wp_trim_words($excerpt, 15, '...'); ?>
</p>
</div>
</article>
<?php else:
// Widget grid (card completa)
$classes = ['bloques-card', 'bloques-card-noticia'];
if ($widget === 'list') {
$classes[] = 'bloques-card-horizontal';
}
?>
<article id="bloques-noticia-<?php echo esc_attr($id); ?>" class="<?php echo esc_attr(implode(' ', $classes)); ?>">
<a href="<?php echo esc_url($permalink); ?>" class="bloques-card-link">
<div class="bloques-card-thumbnail">
<?php if ($thumbnail): ?>
<img src="<?php echo esc_url($thumbnail); ?>" alt="<?php echo esc_attr($thumbnail_alt ?: $title); ?>" loading="lazy">
<?php else: ?>
<div class="bloques-card-placeholder">
<span class="bloques-card-placeholder-text">BLOQUES</span>
</div>
<?php endif; ?>
<?php if ($category): ?>
<span class="bloques-card-badge" style="background-color: <?php echo esc_attr($category_color); ?>;">
<?php echo esc_html($category->name); ?>
</span>
<?php endif; ?>
</div>
<div class="bloques-card-content">
<h3 class="bloques-card-title"><?php echo esc_html($title); ?></h3>
<?php if ($excerpt): ?>
<p class="bloques-card-excerpt"><?php echo wp_trim_words($excerpt, 20, '...'); ?></p>
<?php endif; ?>
<span class="bloques-card-date"><?php echo esc_html($date); ?></span>
</div>
</a>
</article>
<?php endif; ?>

View File

@@ -0,0 +1,76 @@
<?php
/**
* Template: Item de Recurso (Grid Card)
*
* Variables disponibles:
* - $id, $title, $excerpt, $content, $permalink, $thumbnail
* - $iniciativas, $lineas, $widget, $type, $date
* - $archivo, $tipo_recurso, $url_externa
*
* @package Bloques_Transicion
*/
if (!defined('ABSPATH')) {
exit;
}
// Determinar URL de descarga
$download_url = '';
$file_info = '';
$file_extension = '';
$file_size = '';
if (!empty($archivo) && is_array($archivo)) {
$download_url = $archivo['url'];
$file_extension = strtoupper(pathinfo($archivo['filename'], PATHINFO_EXTENSION));
$file_size = isset($archivo['filesize']) ? size_format($archivo['filesize']) : '';
} elseif ($url_externa) {
$download_url = $url_externa;
$file_extension = 'WEB';
}
// Clases según widget
$classes = ['bloques-card', 'bloques-card-recurso'];
if ($widget === 'list') {
$classes[] = 'bloques-card-horizontal';
}
?>
<article id="bloques-recurso-<?php echo esc_attr($id); ?>" class="<?php echo esc_attr(implode(' ', $classes)); ?>">
<div class="bloques-card-thumbnail">
<?php if ($thumbnail): ?>
<img src="<?php echo esc_url($thumbnail); ?>" alt="<?php echo esc_attr($thumbnail_alt ?: $title); ?>" loading="lazy">
<?php else: ?>
<div class="bloques-card-placeholder">
<span class="bloques-card-placeholder-text">COMMUNITY</span>
</div>
<?php endif; ?>
<?php if ($file_extension): ?>
<span class="bloques-card-badge"><?php echo esc_html($file_extension); ?></span>
<?php endif; ?>
</div>
<div class="bloques-card-content">
<h3 class="bloques-card-title"><?php echo esc_html($title); ?></h3>
<?php if ($file_size): ?>
<span class="bloques-card-meta">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6H6zm7 7V3.5L18.5 9H13z"/>
</svg>
<?php echo esc_html($file_size); ?>
</span>
<?php endif; ?>
<div class="bloques-card-footer">
<?php if ($download_url): ?>
<a href="<?php echo esc_url($download_url); ?>" class="bloques-btn-outline" target="_blank" rel="noopener">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
</svg>
<?php _e('Descargar', 'bloques-transicion'); ?>
</a>
<?php endif; ?>
</div>
</div>
</article>

View File

@@ -0,0 +1,48 @@
<?php
/**
* Desinstalación del plugin
*
* @package Bloques_Transicion
*/
// Si no se llama desde WordPress, salir
if (!defined('WP_UNINSTALL_PLUGIN')) {
exit;
}
// Eliminar opciones del plugin
delete_option('bloques_transicion_activated');
delete_option('bloques_transicion_version');
// Nota: No eliminamos los CPTs ni taxonomías por defecto
// para preservar el contenido del usuario.
// Si se desea eliminar todo el contenido, descomentar las siguientes líneas:
/*
// Eliminar todos los posts de los CPTs
$post_types = ['actuacion', 'recurso_bloques', 'evento_bloques'];
foreach ($post_types as $post_type) {
$posts = get_posts([
'post_type' => $post_type,
'posts_per_page' => -1,
'post_status' => 'any',
]);
foreach ($posts as $post) {
wp_delete_post($post->ID, true);
}
}
// Eliminar taxonomías
$taxonomies = ['iniciativa', 'linea_trabajo', 'tipo_evento'];
foreach ($taxonomies as $taxonomy) {
$terms = get_terms([
'taxonomy' => $taxonomy,
'hide_empty' => false,
]);
foreach ($terms as $term) {
wp_delete_term($term->term_id, $taxonomy);
}
}
*/