Cuaderno de Ideas: un plugin ligero para tus anotaciones privadas en WordPress

Tiempo de lectura: 13 min

Valencia, 28/09/2026, G.B.
A la hora de gestionar publicaciones y proyectos de contenidos, la dispersión de notas es un dolor de cabeza. Solemos acumular anotaciones en aplicaciones externas, documentos sueltos o notas rápidas que terminan perdiéndose. Para resolver esto, decidí crear un cuaderno de ideas privado integrado dentro del propio panel de administración de WordPress, permitiéndome preparar borradores, visualizarlos de forma privada y exportarlos a distintos formatos (HTML, Markdown o TPL/BBCode para foros como phpBB) de manera 100% nativa.

¿Por qué desarrollar este plugin?

Aunque en el repositorio de WordPress existen bastantes extensiones para tomar notas, la gran mayoría sufren de dos grandes inconvenientes: o cargan librerías pesadas en JavaScript que ralentizan el panel, o guardan la información en servidores de terceros con modelos de suscripción.

Con este plugin conseguimos un entorno de trabajo rápido, privado e integrado en la base de datos de WordPress. Nos permite trabajar sin dependencias externas y transformar cualquier borrador en el código necesario para publicar en blogs, repositorios como GitHub o comunidades en phpBB.

Requisitos y arquitectura técnica

Para mantener el desarrollo ligero y libre de conflictos de compatibilidad, la arquitectura del plugin se apoya en tres pilares básicos:

  • Cero dependencias externas: Funciona única y exclusivamente con la API nativa de WordPress (Custom Post Types, Custom Taxonomies y Meta Boxes). No requiere frameworks ni scripts externos.
  • Seguridad por diseño: Implementa tokens de seguridad (nonces) para validar formularios, comprobación estricta de permisos de usuario (edit_posts), restricción de vista previa pública y sanitización de campos de texto antes de guardarlos en la base de datos.
  • Exportación y vista previa en el servidor: Genera vistas dinámicas para copiar el código transformado al portapapeles y permite la previsualización del post en el frontend de forma estrictamente privada (devolviendo un error 403 de acceso denegado si intenta acceder un usuario no autenticado).

El código completo del plugin

Crea la carpeta /wp-content/plugins/cuaderno-ideas/ y guarda dentro el archivo cuaderno-ideas.php con el siguiente código:

<?php
/**
 * Plugin Name: Cuaderno de Ideas Privado
 * Description: Diario de ideas privado para gestión de publicaciones con vista previa privada y exportación a HTML, Markdown y TPL (BBCode).
 * Version:     1.2.0
 * Author:      INDAGA.net
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Evitar acceso directo
}

/**
 * 1. REGISTRO DEL CUSTOM POST TYPE Y TAXONOMÍAS NATIVAS
 */
function cip_registrar_post_type() {
    $labels = array(
        'name'               => 'Ideas',
        'singular_name'      => 'Idea',
        'menu_name'          => 'Cuaderno Ideas',
        'add_new'            => 'Añadir Idea',
        'add_new_item'       => 'Añadir Nueva Idea',
        'edit_item'          => 'Editar Idea',
        'new_item'           => 'Nueva Idea',
        'view_item'          => 'Ver Idea Privada',
        'search_items'       => 'Buscar Ideas',
        'not_found'          => 'No se encontraron ideas',
    );

    $args = array(
        'labels'             => $labels,
        'public'             => false, // No se indexa en listados públicos
        'publicly_queryable' => true,  // Permite renderizar la vista individual
        'show_ui'            => true,  // Visible en el panel de administración
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array( 'slug' => 'idea-privada' ),
        'capability_type'    => 'post',
        'has_archive'        => false,
        'hierarchical'       => false,
        'menu_position'      => 20,
        'menu_icon'          => 'dashicons-lightbulb',
        'supports'           => array( 'title', 'editor' ),
    );

    register_post_type( 'cip_idea', $args );

    // Categorías de Ideas
    register_taxonomy( 'cip_categoria', 'cip_idea', array(
        'label'        => 'Categorías de Ideas',
        'rewrite'      => false,
        'hierarchical' => true,
        'show_ui'      => true,
    ) );

    // Etiquetas de Ideas
    register_taxonomy( 'cip_etiqueta', 'cip_idea', array(
        'label'        => 'Etiquetas de Ideas',
        'rewrite'      => false,
        'hierarchical' => false,
        'show_ui'      => true,
    ) );
}
add_action( 'init', 'cip_registrar_post_type' );

/**
 * 2. SEGURIDAD: BLOQUEAR VISTA PÚBLICA A USUARIOS NO AUTENTICADOS
 */
function cip_proteger_vista_privada() {
    if ( is_singular( 'cip_idea' ) ) {
        if ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) {
            wp_die(
                'Acceso restringido: Esta idea pertenece a un cuaderno de notas privado.',
                'Acceso Denegado',
                array( 'response' => 403 )
            );
        }
    }
}
add_action( 'template_redirect', 'cip_proteger_vista_privada' );

/**
 * 3. SUBMENÚ Y PÁGINA DE INSTRUCCIONES / CONFIGURACIÓN EN EL PANEL
 */
function cip_agregar_menu_instrucciones() {
    add_submenu_page(
        'edit.php?post_type=cip_idea',
        'Instrucciones y Uso',
        'Instrucciones',
        'manage_options',
        'cip-instrucciones',
        'cip_render_pagina_instrucciones'
    );
}
add_action( 'admin_menu', 'cip_agregar_menu_instrucciones' );

function cip_render_pagina_instrucciones() {
    $active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( $_GET['tab'] ) : 'instrucciones';
    ?>
    <div class="wrap">
        <h1>Cuaderno de Ideas Privado</h1>

        <h2 class="nav-tab-wrapper">
            <a href="?post_type=cip_idea&page=cip-instrucciones&tab=instrucciones" class="nav-tab <?php echo $active_tab === 'instrucciones' ? 'nav-tab-active' : ''; ?>">
                Instrucciones
            </a>
            <a href="?post_type=cip_idea&page=cip-instrucciones&tab=ficha" class="nav-tab <?php echo $active_tab === 'ficha' ? 'nav-tab-active' : ''; ?>">
                Ficha Técnica
            </a>
        </h2>

        <div class="tab-content" style="margin-top:20px; background:#fff; padding:20px; border:1px solid #ccc; max-width:900px;">
            <?php if ( $active_tab === 'instrucciones' ) : ?>
                <h2>Guía de Uso del Cuaderno de Ideas</h2>
                <hr>
                <ol>
                    <li><strong>Crear una Idea:</strong> Dirígete a <code>Cuaderno Ideas &gt; Añadir Idea</code>. Escribe el título y el cuerpo del borrador utilizando el editor nativo.</li>
                    <li><strong>Ver Idea como Post Privado:</strong> Haz clic en el botón «Ver Idea Privada» en el panel lateral para abrir la vista individual de la entrada en el frontend (solo accesible si has iniciado sesión).</li>
                    <li><strong>Categorización:</strong> Asigna categorías y etiquetas en la barra lateral para organizar tus temas.</li>
                    <li><strong>Gestión de Estado:</strong> En el panel lateral «Detalles y Estado de la Idea», define la prioridad (Baja, Media, Alta), marca la casilla si ya ha sido publicada, e indica el lugar y fecha de publicación.</li>
                    <li><strong>Exportar Contenido:</strong> En el panel inferior del editor («Exportar Contenido»), selecciona entre las versiones <strong>HTML</strong>, <strong>Markdown</strong> o <strong>TPL (BBCode para phpBB)</strong> y utiliza los botones para copiar directamente el código al portapapeles.</li>
                </ol>
            <?php else : ?>
                <h2>Ficha del Plugin</h2>
                <hr>
                <p>Diario de ideas privado para gestión interna de publicaciones, vista previa privada e exportación a múltiples formatos de marcado.</p>
                
                <p><strong>Acceso:</strong> Cuaderno Ideas &gt; Instrucciones (<code>wp-admin/edit.php?post_type=cip_idea&amp;page=cip-instrucciones</code>)</p>
                
                <p><strong>Alias / Shortcodes:</strong> No aplica (es un plugin exclusivamente de administración interna privada).</p>
                
                <p><strong>Dependencias:</strong> Ninguna (100% nativo mediante Custom Post Types y API de WordPress).</p>
                
                <p><strong>Seguridad:</strong> Validación mediante nonces (<code>wp_verify_nonce</code>), restricción de vista previa en frontend (<code>template_redirect</code> con respuesta 403 para usuarios no autorizados), verificación de capacidades (<code>edit_posts</code>), sanitización al guardar (<code>sanitize_text_field</code>) y escapado estricto en la salida (<code>esc_textarea</code>, <code>esc_attr</code>, <code>esc_url</code>).</p>
            <?php endif; ?>
        </div>
    </div>
    <?php
}

/**
 * 4. CREACIÓN DE CAMPOS PERSONALIZADOS (META BOXES)
 */
function cip_agregar_meta_boxes() {
    add_meta_box(
        'cip_detalles_idea',
        'Detalles y Estado de la Idea',
        'cip_render_meta_box',
        'cip_idea',
        'side',
        'high'
    );

    add_meta_box(
        'cip_exportar_idea',
        'Exportar Contenido',
        'cip_render_export_box',
        'cip_idea',
        'normal',
        'high'
    );
}
add_action( 'add_meta_boxes', 'cip_agregar_meta_boxes' );

function cip_render_meta_box( $post ) {
    wp_nonce_field( 'cip_guardar_metas', 'cip_nonce' );

    $prioridad   = get_post_meta( $post->ID, '_cip_prioridad', true ) ?: 'media';
    $publicado   = get_post_meta( $post->ID, '_cip_publicado', true );
    $donde       = get_post_meta( $post->ID, '_cip_donde_publicado', true );
    $fecha_pub   = get_post_meta( $post->ID, '_cip_fecha_publicado', true );
    $permalink   = get_permalink( $post->ID );

    ?>
    <?php if ( 'publish' === $post->post_status ) : ?>
        <p>
            <a href="<?php echo esc_url( $permalink ); ?>" target="_blank" class="button button-secondary" style="width:100%; text-align:center;">
                <span class="dashicons dashicons-visibility" style="vertical-align:middle; margin-top:-2px;"></span> Ver Idea Privada
            </a>
        </p>
        <hr>
    <?php endif; ?>

    <p>
        <label for="cip_prioridad"><strong>Prioridad:</strong></label><br>
        <select name="cip_prioridad" id="cip_prioridad" style="width:100%;">
            <option value="baja" <?php selected( $prioridad, 'baja' ); ?>>Baja</option>
            <option value="media" <?php selected( $prioridad, 'media' ); ?>>Media</option>
            <option value="alta" <?php selected( $prioridad, 'alta' ); ?>>Alta</option>
        </select>
    </p>
    <hr>
    <p>
        <label for="cip_publicado">
            <input type="checkbox" name="cip_publicado" id="cip_publicado" value="1" <?php checked( $publicado, '1' ); ?>>
            <strong>¿Publicado fuera?</strong>
        </label>
    </p>
    <p>
        <label for="cip_donde_publicado"><strong>¿Dónde se ha publicado?</strong></label><br>
        <input type="text" name="cip_donde_publicado" id="cip_donde_publicado" value="<?php echo esc_attr( $donde ); ?>" style="width:100%;" placeholder="Ej: Blog principal, Medium...">
    </p>
    <p>
        <label for="cip_fecha_publicado"><strong>Fecha de publicación:</strong></label><br>
        <input type="date" name="cip_fecha_publicado" id="cip_fecha_publicado" value="<?php echo esc_attr( $fecha_pub ); ?>" style="width:100%;">
    </p>
    <?php
}

/**
 * 5. GUARDADO SEGURO DE DATOS (SANITIZACIÓN)
 */
function cip_guardar_metas( $post_id ) {
    if ( ! isset( $_POST['cip_nonce'] ) || ! wp_verify_nonce( $_POST['cip_nonce'], 'cip_guardar_metas' ) ) {
        return;
    }
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    if ( isset( $_POST['cip_prioridad'] ) ) {
        update_post_meta( $post_id, '_cip_prioridad', sanitize_text_field( $_POST['cip_prioridad'] ) );
    }

    $publicado = isset( $_POST['cip_publicado'] ) ? '1' : '0';
    update_post_meta( $post_id, '_cip_publicado', $publicado );

    if ( isset( $_POST['cip_donde_publicado'] ) ) {
        update_post_meta( $post_id, '_cip_donde_publicado', sanitize_text_field( $_POST['cip_donde_publicado'] ) );
    }

    if ( isset( $_POST['cip_fecha_publicado'] ) ) {
        update_post_meta( $post_id, '_cip_fecha_publicado', sanitize_text_field( $_POST['cip_fecha_publicado'] ) );
    }
}
add_action( 'save_post', 'cip_guardar_metas' );

/**
 * 6. MOTOR DE EXPORTACIÓN (HTML, MARKDOWN, TPL/BBCODE)
 */
function cip_render_export_box( $post ) {
    $content = $post->post_content;
    $title   = $post->post_title;

    $markdown = "# " . $title . "\n\n" . strip_tags( $content );

    $bbcode = "[b]" . $title . "[/b]\n\n";
    $bbcode .= wp_strip_all_tags( $content );

    ?>
    <p>Copia directamente la idea convertida al formato de destino:</p>
    <div style="margin-bottom:15px;">
        <button type="button" class="button" onclick="document.getElementById('cip_exp_html').select(); document.execCommand('copy');">Copiar HTML</button>
        <button type="button" class="button" onclick="document.getElementById('cip_exp_md').select(); document.execCommand('copy');">Copiar Markdown</button>
        <button type="button" class="button" onclick="document.getElementById('cip_exp_tpl').select(); document.execCommand('copy');">Copiar TPL / BBCode</button>
    </div>

    <h4>HTML Limpio:</h4>
    <textarea id="cip_exp_html" style="width:100%; height:80px;" readonly><?php echo esc_textarea( wpautop( $content ) ); ?></textarea>

    <h4>Markdown (.md):</h4>
    <textarea id="cip_exp_md" style="width:100%; height:80px;" readonly><?php echo esc_textarea( $markdown ); ?></textarea>

    <h4>TPL / phpBB (BBCode):</h4>
    <textarea id="cip_exp_tpl" style="width:100%; height:80px;" readonly><?php echo esc_textarea( $bbcode ); ?></textarea>
    <?php
}

Explicación del código de forma sencilla

Para entender la lógica tras el plugin, podemos resumir su funcionamiento en cinco partes clave:

Custom Post Type privado pero consultable

Utilizamos register_post_type configurando 'public' => false pero activando 'publicly_queryable' => true. Esto nos permite generar un enlace individual para visualizar la entrada como si fuera un post publicado estándar dentro de nuestro tema gráfico, pero sin aparecer en buscadores ni en los listados generales del sitio.

Protección de la vista privada en el servidor

Para asegurar que nadie ajeno lea nuestras anotaciones, la función cip_proteger_vista_privada() se ejecuta en el hook template_redirect. Si un visitante no autenticado (o sin permisos de edición) intenta abrir la URL directa de una idea, el servidor interrumpe la carga y devuelve una pantalla de error 403 (Acceso Denegado).

Panel de Instrucciones integrado

Siguiendo la estructura limpia que usamos en nuestros desarrollos, se añade una página dentro del menú del plugin (Cuaderno Ideas > Instrucciones) con pestañas dinámicas que recogen la guía de uso y la ficha técnica con los detalles de seguridad y accesos.

Metadatos y control de estado

Mediante add_meta_box creamos un panel lateral en el editor para fijar la prioridad de la idea, indicar si ya ha sido publicada fuera, la fecha y el medio de destino. Al guardar, el proceso valida un código nonce anti-CSRF y limpia los datos recibidos mediante sanitize_text_field.

Motor de exportación al vuelo

El panel inferior convierte el título y el cuerpo del borrador a tres formatos limpios. Para el HTML aplica la función wpautop() para estructurar los párrafos; para Markdown añade la sintaxis básica de encabezados; y para TPL/phpBB traduce el formato a etiquetas BBCode tipo [b]. Incluye además botones en JavaScript nativo para copiar el resultado con un solo clic.

Si quieres probarlo, puedes descargarlo desde Control de plugins.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *