---
title: "Cuaderno Técnico: Plugin «Agenda Indaga» (v1.3.0), parte 2"
description: "Entrega 2: El Panel de Administración y la Gestión Multinstancia. En la primera entrega vimos los cimientos del plugin: la estructura principal con la clase IndagaAgenda , la seguridad inicial para bl..."
url: https://indaga.net/cuaderno-tecnico-plugin-agenda-indaga-v1-3-0-parte-2/
date: 2026-10-23
modified: 2026-10-21
author: "Directorio Indaga"
categories: ["Recursos y Utilidades", "WordPress"]
tags: ["agenda indaga", "blog", "cuadernos técnicos", "plugins indaga", "plugins wordpress", "wordpress"]
type: post
lang: es
---

# Cuaderno Técnico: Plugin «Agenda Indaga» (v1.3.0), parte 2

## Entrega 2: El Panel de Administración y la Gestión Multinstancia

En la [primera entrega](https://indaga.net/cuaderno-tecnico-plugin-agenda-indaga-v1-3-0-parte-1) vimos los cimientos del plugin: la estructura principal con la clase `IndagaAgenda`, la seguridad inicial para bloquear accesos directos y la creación nativa de la tabla en la base de datos.

En este segundo bloque me centro en cómo está construido el **panel de administración** en el backend de WordPress (ubicado en `Ajustes > Agenda Indaga`), explicando el menú nativo, la estructura modular por pestañas y el control de las distintas instancias o grupos de eventos.

### 1. Registro del Menú en el Panel de Administración

Para integrar la interfaz dentro del escritorio de WordPress sin cargar librerías adicionales, utilizo la API nativa de menús a través del gancho `admin_menu`:

```
public function registrar_menu_admin() {
    add_menu_page(
        'Agenda Indaga',              // Título de la página
        'Agenda Indaga',              // Título del menú
        'manage_options',             // Capacidad / Permiso requerido
        'indaga-agenda',              // Slug único del menú
        array( $this, 'renderizar_panel_admin' ), // Función de callback
        'dashicons-calendar-alt',     // Icono nativo de Dashicons
        6                             // Posición en la barra lateral
    );
}
```

#### Puntos clave:

- **`manage_options`**: Garantizo que solo las cuentas con perfil de administración puedan acceder a este panel de control.
- **`dashicons-calendar-alt`**: Aprovecho la librería de iconos nativa de WordPress (`Dashicons`), manteniendo el plugin sin dependencias externas.

### 2. Estructura Modular por Pestañas

Para que la experiencia de uso sea limpia e intuitiva, organizo el panel de administración en **tres pestañas bien diferenciadas** usando la navegación nativa de WordPress (`nav-tab-wrapper`):

1. **Gestión e Instancias**: Control global de los eventos y de las instancias creadas.
2. **Instrucciones y Shortcodes**: Guía rápida para saber cómo insertar los shortcodes.
3. **Ficha Técnica**: Resumen de especificaciones del desarrollo.

El cambio entre pestañas se gestiona capturando el parámetro `tab` mediante la URL (p. ej., `?page=indaga-agenda&tab=instrucciones`), sanitizándolo adecuadamente:

```
$tab = isset( $_GET['tab'] ) ? sanitize_text_field( $_GET['tab'] ) : 'gestion';
?>
<h2 class="nav-tab-wrapper">
    <a href="?page=indaga-agenda&amp;tab=gestion" class="nav-tab &lt;?php echo $tab === &#039;gestion&#039; ? &#039;nav-tab-active&#039; : &#039;&#039;; ?" data-wpel-link="internal">">Gestión e Instancias</a>
    <a href="?page=indaga-agenda&amp;tab=instrucciones" class="nav-tab &lt;?php echo $tab === &#039;instrucciones&#039; ? &#039;nav-tab-active&#039; : &#039;&#039;; ?" data-wpel-link="internal">">Instrucciones y Shortcodes</a>
    <a href="?page=indaga-agenda&amp;tab=tecnica" class="nav-tab &lt;?php echo $tab === &#039;tecnica&#039; ? &#039;nav-tab-active&#039; : &#039;&#039;; ?" data-wpel-link="internal">">Ficha Técnica</a>
</h2>
```

### 3. Procesamiento Seguro de Acciones (Eliminación con Nonces)

Antes de pintar la vista o consultar los datos, verifico si se ha enviado alguna petición desde los formularios de administración (por ejemplo, para eliminar un evento suelto o una instancia completa).

La seguridad aquí descansa en dos pilares: **comprobación de permisos** y **validación de Nonce** para evitar ataques CSRF:

```
if ( isset( $_POST['indaga_agenda_nonce'] ) && wp_verify_nonce( $_POST['indaga_agenda_nonce'], 'indaga_agenda_admin_action' ) ) {
    
    // Eliminación de un evento individual
    if ( isset( $_POST['accion'] ) && $_POST['accion'] === 'eliminar_evento' && !empty( $_POST['evento_id'] ) ) {
        $evento_id = absint( $_POST['evento_id'] );
        $wpdb->delete( $tabla, array( 'id' => $evento_id ), array( '%d' ) );
        $mensaje = '<div class="updated notice"><p>Evento eliminado correctamente.</p></div>';
    
    // Eliminación de una instancia completa (y todos sus eventos asociados)
    } elseif ( isset( $_POST['accion'] ) && $_POST['accion'] === 'eliminar_instancia' && !empty( $_POST['grupo_id'] ) ) {
        $grupo_id = sanitize_text_field( $_POST['grupo_id'] );
        $wpdb->delete( $tabla, array( 'grupo_id' => $grupo_id ), array( '%s' ) );
        $mensaje = '<div class="updated notice"><p>Instancia completa y todos sus eventos asociados eliminados correctamente.</p></div>';
    }
}
```

- **`wp_verify_nonce()`**: Garantiza que la petición se ha originado legítimamente dentro del panel y por un usuario autorizado.
- **`absint()` y `sanitize_text_field()`**: Aseguran que los datos recibidos sean enteros positivos o cadenas limpias sin código malicioso.

### 4. Consulta Agrupada para la Gestión Multinstancia

El valor diferencial de la versión 1.3.0 es el soporte multinstancia. Mediante una consulta SQL optimizada con agrupamiento (`GROUP BY`), obtengo la lista de todas las agendas o grupos existentes en la base de datos sin necesidad de tablas auxiliares:

```
$instancias = $wpdb->get_results( 
    "SELECT grupo_id, COUNT(*) as total_eventos, MIN(fecha_inicio) as primer_evento 
     FROM $tabla 
     GROUP BY grupo_id 
     ORDER BY grupo_id ASC" 
);
```

Con esto puedo renderizar una tabla clara que indica el nombre del grupo, cuántos eventos almacena y qué shortcode exacto debe copiarse para mostrar esa agenda concreta en el frontend:

```
<?php foreach ( $instancias as $inst ): ?>
    <tr>
        <td><strong><?php echo esc_html( $inst->grupo_id ); ?></strong></td>
        <td><?php echo esc_html( $inst->total_eventos ); ?> evento(s)</td>
        <td><code>Acceso restringido: no tienes permisos para gestionar los eventos de esta agenda. [Iniciar sesión](https://indaga.net/wp-login.php?redirect_to=https%3A%2F%2Findaga.net%2Fcuaderno-tecnico-plugin-agenda-indaga-v1-3-0-parte-2%2F)</code></td>
        <td><code>            
                
                    
                        « Mes Anterior
                        Mes Siguiente »
                    
                    
                    [+ Nuevo Evento](#form-evento-section)
                
                
            

            
            
                
                    ×
                    Ficha del Evento
                    
                        Categoría:
                        Inicio:
                        Fin:
                        Descripción:
                    
                
            

            
            </code></td>
        <td>
            <form method="POST" style="display:inline;" onsubmit="return confirm('¿Estás seguro de eliminar esta instancia completa y todos sus eventos?');">
                <?php wp_nonce_field( 'indaga_agenda_admin_action', 'indaga_agenda_nonce' ); ?>
                <input type="hidden" name="accion" value="eliminar_instancia">
                <input type="hidden" name="grupo_id" value="<?php echo esc_attr( $inst->grupo_id ); ?>">
                <button type="submit" class="button button-small button-link-delete">Eliminar Instancia</button>
            </form>
        </td>
    </tr>
<?php endforeach; ?>
```

Con esto queda cubierto todo el funcionamiento del panel de administración y el control multinstancia desde el backend.

En la siguiente entrega abordaremos la tercera parte del cuaderno técnico: **los Shortcodes de Frontend (`indaga_agenda` y `calendario_agenda`) **(entre corchetes), analizando el formulario de alta de eventos y el calendario mensual interactivo programado en JavaScript nativo.

Hasta pronto!
