📁 Carpeta: /mi-plugin/
/mi-plugin/
├── mi-plugin.php
├── includes/
│ ├── custom-post-types.php
│ └── functions.php
└── assets/
├── style.css
└── script.js
📄 mi-plugin.php (archivo principal)
📄 includes/custom-post-types.php
[
'name' => 'Proyectos',
'singular_name' => 'Proyecto',
],
'public' => true,
'has_archive' => true,
'supports' => ['title', 'editor', 'thumbnail'],
'menu_icon' => 'dashicons-portfolio',
]);
});
📄 includes/functions.php
Plugin Completo (Modular y Escalable)
Uso de
register_post_typeadd_meta_boxo tabla personalizada para relación CPT <-> userAPI con
register_rest_routeOrganización del código en carpetas (
/includes,/admin,/api)Seguridad con
nonces,sanitize,current_user_canAutoload con PSR-4 (ideal, si sabes Composer)
'Cursos',
'public' => true,
'has_archive' => true,
'rewrite' => ['slug' => 'cursos'],
'supports' => ['title', 'editor', 'thumbnail'],
'show_in_rest' => true,
]);
});
// 2. Metabox para asignar instructor (usuario)
add_action('add_meta_boxes', function () {
add_meta_box('instructor_curso', 'Instructor asignado', function ($post) {
wp_nonce_field('guardar_instructor', 'instructor_nonce');
$user_id = get_post_meta($post->ID, '_instructor_id', true);
$usuarios = get_users(['role__in' => ['author', 'editor', 'administrator']]);
echo '';
}, 'curso', 'side');
});
add_action('save_post_curso', function ($post_id) {
if (!isset($_POST['instructor_nonce']) || !wp_verify_nonce($_POST['instructor_nonce'], 'guardar_instructor')) return;
update_post_meta($post_id, '_instructor_id', intval($_POST['instructor_id']));
});
// 3. Shortcode con filtro por instructor
add_shortcode('cursos_filtrados', function () {
$instructor = isset($_GET['instructor']) ? intval($_GET['instructor']) : 0;
$args = ['post_type' => 'curso', 'posts_per_page' => -1];
if ($instructor) {
$args['meta_query'] = [[
'key' => '_instructor_id',
'value' => $instructor,
'compare' => '='
]];
}
$query = new WP_Query($args);
ob_start();
echo ' ';
while ($query->have_posts()) {
$query->the_post();
echo '' . get_the_title() . '
' . get_the_excerpt() . '
';
}
wp_reset_postdata();
return ob_get_clean();
});
// 4. REST API: obtener cursos por instructor (GET)
add_action('rest_api_init', function () {
register_rest_route('cursospro/v1', '/instructor/(?P\d+)', [
'methods' => 'GET',
'callback' => 'api_get_cursos_by_instructor',
'permission_callback' => '__return_true',
]);
register_rest_route('cursospro/v1', '/curso', [
'methods' => 'POST',
'callback' => 'api_crear_curso',
'permission_callback' => function () {
return current_user_can('edit_posts');
}
]);
});
function api_get_cursos_by_instructor($req) {
$id = intval($req['id']);
$q = new WP_Query([
'post_type' => 'curso',
'meta_key' => '_instructor_id',
'meta_value' => $id
]);
$result = [];
foreach ($q->posts as $post) {
$result[] = [
'id' => $post->ID,
'titulo' => get_the_title($post),
'link' => get_permalink($post)
];
}
return rest_ensure_response($result);
}
function api_crear_curso($req) {
$titulo = sanitize_text_field($req['titulo']);
$contenido = wp_kses_post($req['contenido']);
$instructor = intval($req['instructor_id']);
$post_id = wp_insert_post([
'post_type' => 'curso',
'post_title' => $titulo,
'post_content' => $contenido,
'post_status' => 'publish'
]);
if ($post_id && $instructor) {
update_post_meta($post_id, '_instructor_id', $instructor);
}
return rest_ensure_response(['id' => $post_id]);
}
// 5. Hook complejo: campo adicional en perfil de usuario
add_action('show_user_profile', 'campo_especialidad_usuario');
add_action('edit_user_profile', 'campo_especialidad_usuario');
function campo_especialidad_usuario($user) {
?>
Especialidad del instructor