ODSLocal Municipios
PHP ≥ 8.1
WordPress ≥ 6.0
GPL-2.0+
Baqueiro · odslocal.es
1. Introducción y arquitectura
ODSLocal Municipios es el plugin núcleo de la plataforma odslocal.es, desarrollada en colaboración con la Universidade de Vigo. Gestiona la jerarquía territorial española, los 178 indicadores oficiales ODS (Objetivos de Desarrollo Sostenible) definidos por UVigo, las mediciones y los permisos finos de usuarios.
Todos los demás plugins odslocal-* son extensiones que dependen de este.
Componentes principales
| Módulo | Responsabilidad |
|---|---|
class-territories.php |
Jerarquía territorial (país → CCAA → diputación → provincia → municipio), CRUD, búsqueda, breadcrumbs. |
class-indicators.php |
Catálogo inmutable de 178 indicadores ODS (generado desde XLS de UVigo). |
class-measurements.php |
Upsert, agregación, comparativa, rollback de mediciones. |
class-scopes.php |
Permisos finos: usuario × territorio × grupo ODS. |
class-import.php |
Pipeline de importación: ingest → staging → preview → publish → rollback. |
class-roles.php |
Registra los 6 roles específicos de ODSLocal. |
class-rest.php |
Endpoints REST públicos en /wp-json/odslocal/v1/. |
class-audit.php |
Registro auditoría (INSERT, UPDATE, DELETE, VALIDATE, …). |
Mejoras respecto a v1.0
| Componente | v1.0 | v1.1 – v1.2.9 |
|---|---|---|
| Jerarquía territorial | Taxonomías WP | Tabla relacional odslocal_territories con parent_id |
| Datos | odslocal_datos (key-value) |
odslocal_measurements con validación, origen, auditoría |
| Permisos | user_meta serializado |
odslocal_user_scope relacional |
| Importación | Directo a tabla final | Pipeline staging + rollback |
2. Requisitos e instalación
2.1 Requisitos
- WordPress 6.0 o superior.
- PHP 8.1 o superior.
- MySQL 5.7+ o MariaDB 10.3+ (necesario
utf8mb4). - PhpSpreadsheet (opcional): sólo si se necesita importar
.xlsx. Instalar víacomposer require phpoffice/phpspreadsheet. Sin él, la importación funciona con CSV.
2.2 Instalación paso a paso
- Subir la carpeta
odslocal-municipios/awp-content/plugins/(o instalar el ZIP desde Plugins → Añadir nuevo). - Activar el plugin en Plugins → Instalados. En la activación se crean automáticamente las 5 tablas, los 6 roles ODSLocal y se añade la opción
odslocal_db_version. - Colocar el fichero
municipios-es.geojsonenassets/geojson/(ver sección 12). - Si se desea cargar los 8 100+ territorios españoles oficiales, instalar además el plugin ODSLocal INE Importer.
- Asignar roles a los usuarios en Usuarios → Perfil.
- Configurar los ámbitos (scopes) de cada colaborador en Municipios → Gestión de accesos.
Si tienes otros plugins
odslocal-* instalados, activa siempre este primero. Los demás dependen de sus clases (ODSLocal_DB, ODSLocal_Territories, ODSLocal_Roles…) y mostrarán un aviso si no está presente.
3. Modelo de datos (tablas)
El plugin crea cinco tablas con el prefijo wp_ (o el configurado en wp-config.php):
odslocal_territories
Jerarquía territorial completa. Un registro por territorio. Relación parent-child a través de parent_id.
| Campo | Tipo | Descripción |
|---|---|---|
id |
BIGINT PK | Identificador interno. |
code_ine |
VARCHAR(10) | Código INE oficial (5 dígitos municipio, 2 provincia, 2 CCAA). |
name |
VARCHAR(255) | Nombre oficial. |
type |
ENUM | country, ccaa, deputation, province, municipality. |
parent_id |
BIGINT FK | Territorio padre (NULL en country). |
status |
ENUM | active o inactive. |
lat, lng |
DECIMAL | Coordenadas (opcional). |
wp_post_id |
BIGINT | ID del CPT municipio asociado (fachada editorial). |
odslocal_measurements
Corazón analítico: un registro por combinación territorio × indicador × año.
| Campo | Tipo | Descripción |
|---|---|---|
territory_id |
BIGINT FK | Referencia a territorios. |
indicator_key |
VARCHAR(255) | Slug del indicador (p. ej. ods_ods_1_1_1__tasa_de_pobreza). |
year |
INT | Año de la medición. |
value |
DOUBLE | Valor numérico. |
source |
VARCHAR(255) | Fuente (INE, Ministerio, elaboración propia…). |
evidence_url |
VARCHAR(1000) | URL a documento de evidencia. |
notes |
TEXT | Notas libres. |
rejection_reason |
TEXT | Motivo de rechazo (si aplica). |
validation_status |
ENUM | draft, pending, approved, rejected. |
validated_by, validated_at |
BIGINT, DATETIME | Quién y cuándo validó. |
updated_by, updated_at |
BIGINT, DATETIME | Autoría y última edición. |
import_run_id |
BIGINT FK | Origen de importación (NULL si entrada manual). |
Otras tablas
| Tabla | Propósito |
|---|---|
odslocal_user_scope |
Permisos: user_id × territory_id × ods_group. NULL = global. |
odslocal_import_runs |
Registro de cada importación (filename, status, ok_rows, error_rows, user, fecha). |
odslocal_import_rows |
Staging de filas brutas antes de publicar. |
odslocal_audit |
Trazabilidad: acción, recurso, user_id, IP, timestamp. |
4. Roles y capacidades
El plugin crea seis roles específicos además de los de WordPress. Un administrador de WordPress conserva todos los permisos.
| Rol | Slug | Uso típico |
|---|---|---|
| Gestor de la plataforma | ods_platform_manager |
Gestión completa de la plataforma sin tocar WordPress core. |
| Gestor municipal (Concello) | ods_municipal_manager |
Edita los datos del o de los municipios que le han sido asignados. |
| Consejo ciudadano | ods_citizen_council |
Igual que el anterior, pensado para grupos de veeduría. |
| Investigador/a | ods_researcher |
Edita únicamente los territorios y grupos ODS asignados. |
| Validador/a | ods_validator |
Cambia el validation_status de las mediciones. |
| Visitante | ods_viewer |
Sólo lectura del panel. |
Capacidades clave
| Capability | Significado |
|---|---|
ods_edit_own |
Editar mediciones propias (sin aprobación de terceros si son draft). |
ods_edit_assigned |
Editar territorios asignados vía scopes. |
ods_edit_all |
Editar cualquier territorio. |
ods_validate |
Aprobar / rechazar mediciones pendientes. |
ods_upload_data |
Usar las importaciones (CSV / XLSX / INE). |
ods_manage_settings |
Tocar opciones globales de la plataforma. |
5. Ámbitos (scopes) de permisos
Un mismo usuario puede tener múltiples scopes que delimitan qué territorios y qué grupos ODS puede editar. La tabla es odslocal_user_scope.
{ territory_id: 42, ods_group: null } → municipio 42, todos los ODS
{ territory_id: 42, ods_group: 'ODS3' } → municipio 42, solo ODS3
{ territory_id: null, ods_group: null } → acceso global (equivale a admin)
{ territory_id: null, ods_group: 'ODS11'} → ODS11 en cualquier territorio
Si un usuario tiene ámbito sobre una provincia, hereda acceso a todos los municipios dentro de ella. Lo mismo aplica a CCAA y diputación.
Configuración
En Municipios → Gestión de accesos:
- Selecciona el usuario (autocompletado).
- Añade filas de ámbito: territorio (con buscador) + grupo ODS (
ODS1..ODS17o Todos). - Pulsa Guardar. Los cambios se aplican inmediatamente: no necesita cerrar sesión.
6. Guía del administrador
Todas las páginas están bajo el menú Municipios en el panel de WordPress.
| Submenú | Contenido | Capability |
|---|---|---|
| Todos los municipios | Lista de CPT municipio (fachada editorial de los territorios tipo municipality). |
edit_municipios |
| Territorios | CRUD territorial completo: alta manual, búsqueda, estadísticas por tipo. | manage_options o ods_manage_settings |
| Gestión de accesos | Asignación de ámbitos user × territorio × ODS. | manage_options |
| Importar datos | Subida CSV/XLSX, staging, preview, publicación, rollback. | ods_upload_data |
| Compara | Panel analítico con comparativa entre municipios. | edit_municipios |
| Auditoría | Histórico de acciones (inserción, edición, validación, rollback). | manage_options |
| Cobertura | Matriz de cobertura territorio × indicador × año. | ods_manage_settings |
7. Gestión de territorios
7.1 Alta manual
- En Municipios → Territorios, rellena el formulario: tipo, nombre, código INE, padre (autocompletado), coordenadas.
- Al guardar, el plugin crea un registro en
odslocal_territoriesy (para municipios) también un CPTmunicipioasociado.
7.2 Alta masiva vía INE Importer
Instalar el plugin ODSLocal INE Importer y ejecutar Poblar municipios → crea de una sola vez CCAA, provincias y los ~8 100 municipios oficiales.
7.3 Búsqueda y breadcrumb
El buscador tolera acentos y minúsculas. Los resultados muestran el breadcrumb completo (p. ej. España › Galicia › A Coruña › Ferrol).
8. Importación de datos (pipeline)
La importación sigue 5 fases para garantizar que nunca se corrompan los datos en producción:
- Ingest — Se sube un CSV/XLSX. Cada fila se graba cruda en
odslocal_import_rowscon suimport_run_id. Si se cae el navegador a mitad, el run quedaprocessingy puede reintentarse. - Normalización — El plugin resuelve cada
codigo_inea suterritory_id, validaindicator_keycontra el catálogo de 178 indicadores y marca cada fila como OK o error con el motivo. - Preview — Se muestra una tabla con el total de filas OK y errores. El usuario puede descargar el CSV de errores para corregir fuera.
- Publicar — Copia sólo las filas OK a
odslocal_measurements. Cada medición guarda suimport_run_id. - Rollback — Mientras un run no esté rechazado ni reemplazado, puede eliminarse por completo: un único click borra todas las mediciones asociadas.
8.1 Formato CSV
codigo_ine,indicator_key,anio,valor,fuente
36038,ods_ods_1_1_1__tasa_de_pobreza_respecto_a_los_valores_i,2023,3.5,INE
36057,ods_ods_3_1_1__esperanza_de_vida_al_nacer,2023,83.2,INE
Separador: coma. Codificación: UTF-8 (con o sin BOM). Decimales: punto o coma (el plugin normaliza).
8.2 Descargar plantilla
Desde Importar datos hay un enlace Descargar plantilla CSV que produce un CSV vacío con los 178 indicator_key de referencia.
Si no está instalado, el plugin rechaza
.xlsx con un mensaje claro. Instálalo vía Composer o convierte el fichero a CSV.
9. Mediciones y validación
9.1 Estados
| Estado | Significado |
|---|---|
draft |
Borrador del colaborador, no visible para terceros. |
pending |
Enviado para revisión. |
approved |
Publicado: aparece en mapa, comparativa, API, exportaciones. |
rejected |
Devuelto al colaborador con motivo. Editable y re-submetible. |
9.2 Flujo habitual
- Colaborador edita en el frontend (plugin ODSLocal Data Editor) o en el admin.
- Guarda como borrador o envía a revisión.
- Usuario con
ods_validateentra en Revisión de datos, aprueba o rechaza. - Colaborador recibe email con la decisión.
10. Shortcodes frontend
Se pegan en cualquier página o post. La página en la que estén cargará automáticamente el CSS/JS necesario.
| Shortcode | Atributos | Descripción |
|---|---|---|
|
height, center_lat, center_lng, zoom |
Mapa coroplético interactivo de España con selector de indicador y año. |
|
— | Comparativa multi-municipio para un indicador y año concretos. |
|
code_ine |
Ficha completa del municipio: últimos valores por ODS. |
Ejemplos
Selecciona primero un grupo ODS para habilitar esta lista.
Cargando…
Comparativa de municipios
11. REST API
Namespace público: /wp-json/odslocal/v1/. Todas las rutas devuelven JSON y sólo exponen mediciones approved.
| Método | Ruta | Descripción |
|---|---|---|
| GET | /territories?search=vigo |
Búsqueda de territorios. |
| GET | /territories/{id}?year=2023 |
Ficha del territorio. |
| GET | /indicators?group=ODS1 |
Lista de indicadores por grupo ODS. |
| GET | /measurements?territory_id=42&year=2023 |
Mediciones con filtros. |
| GET | /map/{indicator_key}/{year} |
Valores para pintar un choropleth. |
| GET | /compare?ids=42,55,81&indicator=...&year=2023 |
Comparativa entre municipios. |
Ejemplo
curl "https://odslocal.es/wp-json/odslocal/v1/territories?search=vigo"
12. GeoJSON del mapa
El fichero assets/geojson/municipios-es.geojson debe añadirse manualmente por motivos de peso y licencia.
Fuentes recomendadas
- IGN (Instituto Geográfico Nacional): cartografía oficial, ~60 MB simplificado.
- OpenDataSoft: versión más ligera (~4 MB), ideal para producción.
Campo de cruce
El frontend identifica cada feature por una de estas propiedades:
properties.codigo_ineproperties.CMUNproperties.cusec
Si tu GeoJSON usa otra propiedad, edita assets/js/frontend.js (línea ~50) añadiéndola al array.
Si falta el GeoJSON o falla la descarga, el mapa muestra un aviso elegante con instrucciones en lugar de romper la página.
13. Auditoría y trazabilidad
Cada acción relevante queda registrada en odslocal_audit:
- Tipos de acción:
insert,update,delete,validate,reject,import,rollback,news_submit,event_checkin(registros de plugins adjacentes). - Campos capturados: usuario, IP (ver sección 15), recurso, payload JSON, timestamp.
- Paginación: 50 registros por página, ordenados por fecha descendente.
Acceso: Municipios → Auditoría (sólo manage_options).
14. Integración con otros plugins
ODSLocal Municipios es la base sobre la que corren:
| Plugin | Qué aporta |
|---|---|
| ODSLocal Contributor Profiles | Perfiles públicos, hub, QR, cartas académicas. |
| ODSLocal Data Editor | Editor frontend con flujo de aprobación. |
| ODSLocal Data Export | Exportación CSV/XLSX/JSON, API pública. |
| ODSLocal INE Importer | Importación automática desde la API oficial del INE. |
| ODSLocal News | Artículos y noticias vinculadas a ODS y territorios. |
| ODSLocal Events | Calendario, reservas, tickets QR, certificados. |
| ODSLocal Geo Display | Visualización cartográfica avanzada. |
| ODSLocal RGPD | Cumplimiento legal RGPD/LSSI/LOPDGDD. |
| b-ods-display | Componentes visuales premium (dashboards, tarjetas ODS). |
Cada extensión verifica class_exists('ODSLocal_DB') antes de ejecutarse y degrada con un aviso si el plugin núcleo está desactivado.
15. Seguridad y privacidad
- Nonces en todos los endpoints AJAX y acciones sensibles.
- Capabilities verificadas en servidor (nunca solo en la UI).
- Prepared statements (
$wpdb->prepare) en todas las consultas con valores dinámicos. - IP del cliente resuelta priorizando
REMOTE_ADDR; sólo se consultanX-Forwarded-For/CF-Connecting-IPcuando hay un proxy reverso real o vía filtro opt-inodslocal_audit_trust_proxy_headers. - Sanitización exhaustiva:
sanitize_text_field,sanitize_key,esc_url_raw,absint. - Rollback transaccional: cualquier import puede revertirse sin pérdida de datos.
- Auditoría inmutable: la tabla
odslocal_auditno se actualiza, sólo se inserta (append-only).
16. Solución de problemas (FAQ)
El mapa no carga
- ¿Está el GeoJSON en
assets/geojson/municipios-es.geojson? - Comprueba la consola del navegador (F12): ¿error 404 o de CORS?
- ¿El campo
properties.codigo_ineexiste? Si no, editafrontend.js.
“Permisos insuficientes” al importar
Asegúrate de que el rol del usuario tiene ods_upload_data. Administrador lo tiene por defecto.
La importación se queda en processing
Probablemente se cortó la conexión. En Importar datos → Historial, cancela el run y vuelve a intentarlo. El rollback no deja datos colgados.
XLSX no funciona
Requiere PhpSpreadsheet. Alternativa: exporta a CSV desde tu hoja de cálculo.
Un usuario ve territorios que no debería
Revisa sus scopes. Recuerda la herencia: si tiene provincia, hereda sus municipios.
Quiero activar el trust de proxies (Cloudflare / reverse proxy real)
add_filter('odslocal_audit_trust_proxy_headers', '__return_true');
17. Mantenimiento
17.1 Actualización
Al subir una versión nueva, el plugin ejecuta migraciones automáticas en el hook plugins_loaded. La versión se guarda en odslocal_db_version y las migraciones se saltan si ya se aplicaron.
17.2 Backup recomendado
Hacer dump de:
- Las 5 tablas
odslocal_* - La tabla
wp_options(al menos filasodslocal_*) - La tabla
wp_usermetapara preservar perfiles y scopes
17.3 Desinstalación
Al desactivar no se borra nada. Al eliminar desde el panel de plugins, las tablas quedan (por seguridad). Si se desea limpieza total, hacer DROP manual tras exportar los datos.
17.4 Traducciones
Text domain: odslocal. Los ficheros .mo se buscan en languages/ del plugin. Se aceptan PRs de nuevos idiomas en el repo oficial.