
Cómo Crear un Sistema de Reservas para Eventos en WordPress Usando PHP Paso a Paso
WordPress es una plataforma muy flexible y potente que permite extender sus funcionalidades mediante PHP. En este tutorial intermedio, te guiaremos paso a paso para construir un sistema de reservas para eventos dentro de WordPress, usando código PHP personalizado. Este proyecto te mostrará cómo crear un plugin ligero, que añadirá formularios de reserva, gestión y visualización de eventos y reservas en el panel administrativo.
Este tutorial es ideal para desarrolladores PHP con conocimientos básicos en WordPress que quieren aprender a integrar funcionalidades personalizadas sin depender exclusivamente de plugins externos. Al finalizar, tendrás un sistema funcional listo para adaptarse a tus necesidades o las de tus clientes.
Proyecto completo que vas a crear
En este proyecto vamos a crear un plugin personalizado para WordPress que añade un sistema de reservas para eventos. Este sistema permitirá a los visitantes reservar plazas para distintos eventos publicados en el sitio web, y a los administradores gestionar dichas reservas desde el panel de control de WordPress.
Las funcionalidades principales incluyen:
- Creación de un tipo personalizado de contenido ‘Evento’ para publicar eventos.
- Formulario frontend para que los usuarios realicen reservas asociadas a cada evento.
- Almacenamiento y gestión de reservas mediante una tabla personalizada en la base de datos.
- Interfaz administrativa para visualizar, confirmar o cancelar reservas.
- Validaciones básicas para evitar reservas duplicadas o datos erróneos.
Con este proyecto aprenderás a trabajar con Custom Post Types, formularios en frontend, manejo de base de datos personalizada con $wpdb, seguridad en formularios y administración personalizada dentro de WordPress usando PHP.
Estructura de archivos
event-booking-plugin/
event-booking-plugin/event-booking-plugin.php
event-booking-plugin/includes/class-ebp-cpt.php
event-booking-plugin/includes/class-ebp-booking-handler.php
event-booking-plugin/admin/class-ebp-admin.php
event-booking-plugin/templates/booking-form.php
Cómo crear y probar el proyecto
- Crear una carpeta llamada 'event-booking-plugin' en la carpeta wp-content/plugins/ de tu instalación de WordPress.
- Copiar los 5 archivos PHP proporcionados en la estructura dentro de las carpetas correspondientes: el archivo principal en la raíz del plugin, las clases dentro de la carpeta includes, los archivos administrativos en la carpeta admin y la plantilla en templates.
- Activar el plugin 'Event Booking Plugin' desde el panel de administración de WordPress en la sección Plugins.
- Crear algunos eventos a través del tipo de contenido personalizado que el plugin añade.
- Visitar cualquiera de las páginas o entradas donde estén los eventos para ver el formulario de reserva funcionando.
- Probar hacer reservas y revisar el panel administrativo para gestionar las reservas recibidas.
Introducción al proyecto y requisitos previos
Este tutorial asume conocimientos intermedios de PHP y experiencia básica en desarrollo para WordPress, incluyendo cómo crear plugins y trabajar con Custom Post Types. Para seguir el proyecto, debes contar con un entorno WordPress instalado y acceso FTP o gestor de archivos para subir el plugin.
Creando el plugin y su estructura básica
Lo primero es crear la carpeta event-booking-plugin dentro de wp-content/plugins. Allí situaremos cinco archivos PHP que organizaremos para mantener el código modular y limpio.
El archivo principal event-booking-plugin.php será el punto de entrada que carga todas las clases y funciones necesarias. También declararemos el Custom Post Type (CPT) para los eventos usando una clase especializada.
Definiendo el Custom Post Type ‘Evento’
Este CPT permitirá crear los eventos desde el panel de WordPress como entradas personalizadas, con título, contenido, fecha y demás. Usaremos una clase para registrar el CPT en el hook init. Esto facilitará la gestión y permitirá en el futuro extender los eventos con campos personalizados si se desea.
Creando la tabla personalizada para reservas
Las reservas no se almacenan como posts sino en una tabla propia en la base de datos para optimizar consultas y manejo. Para ello, usaremos el objeto $wpdb y crearemos la tabla en la activación del plugin. También incluimos funciones para insertar, obtener y actualizar reservas.
Formulario frontend para realizar reservas
El formulario se mostrará en las páginas de eventos mediante un shortcode. El formulario captura información básica del usuario y el evento asociado. Implementamos validaciones para evitar duplicados y errores comunes, como campos vacíos o formatos incorrectos.
Gestión administrativa de reservas
Desde el panel administrativo del plugin, el administrador podrá ver todas las reservas realizadas, cambiar su estado (confirmado, cancelado, pendiente) y eliminar entradas si es necesario. Esto se realiza creando una página en el menú con tablas personalizadas.
Buenas prácticas y seguridad
- Usar
noncepara proteger los formularios de ataques CSRF. - Validar y sanitizar toda entrada de datos.
- Utilizar funciones de WordPress propias para consultas y creación de tablas.
- Separar la lógica en clases para facilitar mantenimiento y escalabilidad.
Errores comunes y cómo evitarlos
- No activar el plugin después de subirlo al servidor.
- Olvidar hacer
flush_rewrite_rules()tras registrar un CPT, lo que puede generar URLs 404. - No validar datos del formulario, causando entradas erróneas en la base de datos.
- No proteger los formularios con nonce, lo que deja vulnerabilidad CSRF.
- Intentar guardar datos sin comprobar permisos de usuario y roles.
Cuándo y cómo usar este sistema
Este plugin es ideal para sitios que quieran ofrecer un sistema simple y personalizado de reservas para eventos propios, sin depender de plugins genéricos más complejos. También sirve como base para proyectos donde se necesite una solución ligera y adaptada.
Para probarlo, crea eventos desde el admin de WordPress, luego accede a la URL del evento y realiza una reserva. Revisa el menú administrativo para ver cómo se refleja la reserva y cambia su estado para confirmar o cancelar.
Posibles ampliaciones futuras
- Agregar campos personalizados para las reservas, como preferencias o notas.
- Incluir notificaciones por correo electrónico para confirmaciones.
- Integrar pago online para reservas.
- Mejorar la interfaz administrativa con filtros y exportación CSV.
Código completo para copiar y pegar
Archivo Principal del Plugin
Archivo principal que inicializa el plugin, carga las clases necesarias y registra hooks de activación y desactivación.
<?php
/**
* Plugin Name: Event Booking Plugin
* Description: Plugin para crear y gestionar reservas de eventos en WordPress.
* Version: 1.0
* Author: BorisTech
*/
if (!defined('ABSPATH')) {
exit; // Evita acceso directo
}
// Definición de constantes
define('EBP_PLUGIN_PATH', plugin_dir_path(__FILE__));
define('EBP_PLUGIN_URL', plugin_dir_url(__FILE__));
// Carga automática simple de clases
function ebp_autoload($class_name) {
if (strpos($class_name, 'EBP_') === 0) {
$file = EBP_PLUGIN_PATH . 'includes/class-' . strtolower(str_replace('_', '-', $class_name)) . '.php';
if (file_exists($file)) {
require_once $file;
}
}
}
spl_autoload_register('ebp_autoload');
// Carga de archivos administrativos
function ebp_load_admin_files() {
if (is_admin()) {
require_once EBP_PLUGIN_PATH . 'admin/class-ebp-admin.php';
}
}
add_action('plugins_loaded', 'ebp_load_admin_files');
// Inicialización del plugin
function ebp_init() {
// Registrar el Custom Post Type
$cpt = new EBP_CPT();
$cpt->register_post_type();
// Inicializar el manejador de reservas
$booking_handler = new EBP_Booking_Handler();
$booking_handler->init();
}
add_action('init', 'ebp_init');
// Activación del plugin
function ebp_activate() {
// Registrar CPT antes para evitar error de rewrite
$cpt = new EBP_CPT();
$cpt->register_post_type();
// Crear tabla en base de datos
$booking_handler = new EBP_Booking_Handler();
$booking_handler->create_table();
flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'ebp_activate');
// Desactivación
function ebp_deactivate() {
flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'ebp_deactivate');
Clase para Registrar el Custom Post Type "Evento"
Clase que registra el Custom Post Type ‘Evento’ para crear eventos desde el panel de WordPress.
<?php
if (!defined('ABSPATH')) {
exit; // Evita acceso directo
}
class EBP_CPT {
public function register_post_type() {
$labels = [
'name' => __('Eventos', 'ebp'),
'singular_name' => __('Evento', 'ebp'),
'add_new' => __('Añadir Nuevo', 'ebp'),
'add_new_item' => __('Añadir Nuevo Evento', 'ebp'),
'edit_item' => __('Editar Evento', 'ebp'),
'new_item' => __('Nuevo Evento', 'ebp'),
'view_item' => __('Ver Evento', 'ebp'),
'search_items' => __('Buscar Eventos', 'ebp'),
'not_found' => __('No se encontraron eventos', 'ebp'),
'not_found_in_trash' => __('No hay eventos en la papelera', 'ebp'),
'all_items' => __('Todos los Eventos', 'ebp'),
'menu_name' => __('Eventos', 'ebp'),
'name_admin_bar' => __('Evento', 'ebp'),
];
$args = [
'labels' => $labels,
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-calendar-alt',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
'rewrite' => ['slug' => 'eventos'],
'show_in_rest' => true,
];
register_post_type('ebp_event', $args);
}
}
Clase para Manejar Reservas
Clase que crea la tabla personalizada, inserta, valida y obtiene reservas desde la base de datos.
<?php
if (!defined('ABSPATH')) {
exit;
}
class EBP_Booking_Handler {
private $table_name;
private $charset_collate;
public function __construct() {
global $wpdb;
$this->table_name = $wpdb->prefix . 'ebp_bookings';
$this->charset_collate = $wpdb->get_charset_collate();
}
// Crear tabla personalizada para reservas
public function create_table() {
global $wpdb;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$sql = "CREATE TABLE {$this->table_name} (
id mediumint(9) NOT NULL AUTO_INCREMENT,
event_id bigint(20) NOT NULL,
user_name varchar(100) NOT NULL,
user_email varchar(100) NOT NULL,
seats int(3) NOT NULL DEFAULT 1,
status varchar(20) NOT NULL DEFAULT 'pending',
created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (id),
KEY event_id (event_id),
KEY user_email (user_email)
) {$this->charset_collate};";
dbDelta($sql);
}
// Insertar reserva en la tabla
public function insert_booking($data) {
global $wpdb;
// Sanitizar datos
$event_id = intval($data['event_id']);
$user_name = sanitize_text_field($data['user_name']);
$user_email = sanitize_email($data['user_email']);
$seats = intval($data['seats']);
// Evitar duplicados (mismo email y evento)
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$this->table_name} WHERE event_id = %d AND user_email = %s",
$event_id,
$user_email
));
if ($exists > 0) {
return new WP_Error('duplicate_booking', __('Ya tienes una reserva para este evento.', 'ebp'));
}
$result = $wpdb->insert(
$this->table_name,
[
'event_id' => $event_id,
'user_name' => $user_name,
'user_email' => $user_email,
'seats' => $seats,
'status' => 'pending',
'created_at' => current_time('mysql'),
],
['%d', '%s', '%s', '%d', '%s', '%s']
);
if ($result === false) {
return new WP_Error('db_error', __('Error al guardar la reserva.', 'ebp'));
}
return true;
}
// Obtener reservas para administración
public function get_bookings($limit = 20, $offset = 0) {
global $wpdb;
$results = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$this->table_name} ORDER BY created_at DESC LIMIT %d OFFSET %d",
$limit,
$offset
), ARRAY_A);
return $results;
}
// Actualizar estado de reserva
public function update_booking_status($id, $status) {
global $wpdb;
$allowed_status = ['pending', 'confirmed', 'cancelled'];
if (!in_array($status, $allowed_status)) {
return false;
}
$res = $wpdb->update(
$this->table_name,
['status' => $status],
['id' => intval($id)],
['%s'],
['%d']
);
return $res !== false;
}
// Obtener reserva por ID
public function get_booking($id) {
global $wpdb;
return $wpdb->get_row($wpdb->prepare("SELECT * FROM {$this->table_name} WHERE id = %d", intval($id)), ARRAY_A);
}
// Eliminar reserva
public function delete_booking($id) {
global $wpdb;
return $wpdb->delete($this->table_name, ['id' => intval($id)], ['%d']);
}
// Inicializar shortcode y hooks para formulario
public function init() {
add_shortcode('ebp_booking_form', [$this, 'render_booking_form']);
add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
add_action('wp_ajax_ebp_submit_booking', [$this, 'handle_booking_submission']);
add_action('wp_ajax_nopriv_ebp_submit_booking', [$this, 'handle_booking_submission']);
}
// Encolar scripts y estilos para el formulario
public function enqueue_scripts() {
if (is_singular('ebp_event')) {
wp_enqueue_style('ebp-style', EBP_PLUGIN_URL . 'assets/css/ebp-style.css');
wp_enqueue_script('ebp-script', EBP_PLUGIN_URL . 'assets/js/ebp-script.js', ['jquery'], null, true);
wp_localize_script('ebp-script', 'ebp_ajax', ['ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ebp_nonce')]);
}
}
// Renderizar formulario con shortcode
public function render_booking_form($atts) {
if (!is_singular('ebp_event')) {
return '';
}
ob_start();
$event_id = get_the_ID();
include EBP_PLUGIN_PATH . 'templates/booking-form.php';
return ob_get_clean();
}
// Manejar envío AJAX del formulario
public function handle_booking_submission() {
check_ajax_referer('ebp_nonce', 'nonce');
$event_id = isset($_POST['event_id']) ? intval($_POST['event_id']) : 0;
$user_name = isset($_POST['user_name']) ? sanitize_text_field($_POST['user_name']) : '';
$user_email = isset($_POST['user_email']) ? sanitize_email($_POST['user_email']) : '';
$seats = isset($_POST['seats']) ? intval($_POST['seats']) : 1;
// Validaciones básicas
if ($event_id <= 0 || empty($user_name) || empty($user_email) || !is_email($user_email) || $seats <= 0) {
wp_send_json_error(['message' => __('Datos inválidos o incompletos.', 'ebp')]);
}
// Verificar que el evento existe
if (get_post_type($event_id) !== 'ebp_event') {
wp_send_json_error(['message' => __('Evento no válido.', 'ebp')]);
}
$result = $this->insert_booking([
'event_id' => $event_id,
'user_name' => $user_name,
'user_email' => $user_email,
'seats' => $seats,
]);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message()]);
}
wp_send_json_success(['message' => __('Reserva realizada con éxito. Gracias.', 'ebp')]);
}
}
Clase para Gestión Administrativa de Reservas
Clase que crea el menú administrativo para ver, confirmar y cancelar reservas desde el backend de WordPress.
<?php
if (!defined('ABSPATH')) {
exit;
}
class EBP_Admin {
private $booking_handler;
public function __construct() {
$this->booking_handler = new EBP_Booking_Handler();
add_action('admin_menu', [$this, 'add_admin_menu']);
add_action('admin_post_ebp_update_booking', [$this, 'handle_booking_update']);
}
public function add_admin_menu() {
add_menu_page(
__('Reservas Eventos', 'ebp'),
__('Reservas Eventos', 'ebp'),
'manage_options',
'ebp_bookings',
[$this, 'render_admin_page'],
'dashicons-tickets-alt',
26
);
}
public function render_admin_page() {
if (!current_user_can('manage_options')) {
wp_die(__('No tienes permisos suficientes.', 'ebp'));
}
// Obtener reservas
$bookings = $this->booking_handler->get_bookings(50, 0);
?>
<div class="wrap">
<h1><?php _e('Gestión de Reservas de Eventos', 'ebp'); ?></h1>
<table class="widefat fixed" cellspacing="0">
<thead>
<tr>
<th><?php _e('ID', 'ebp'); ?></th>
<th><?php _e('Evento', 'ebp'); ?></th>
<th><?php _e('Nombre', 'ebp'); ?></th>
<th><?php _e('Email', 'ebp'); ?></th>
<th><?php _e('Plazas', 'ebp'); ?></th>
<th><?php _e('Estado', 'ebp'); ?></th>
<th><?php _e('Fecha', 'ebp'); ?></th>
<th><?php _e('Acciones', 'ebp'); ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($bookings)) : ?>
<tr><td colspan="8"><?php _e('No hay reservas aún.', 'ebp'); ?></td></tr>
<?php else :
foreach ($bookings as $booking) :
$event_post = get_post($booking['event_id']);
$event_title = $event_post ? esc_html($event_post->post_title) : __('Evento eliminado', 'ebp');
?>
<tr>
<td><?php echo intval($booking['id']); ?></td>
<td><?php echo $event_title; ?></td>
<td><?php echo esc_html($booking['user_name']); ?></td>
<td><?php echo esc_html($booking['user_email']); ?></td>
<td><?php echo intval($booking['seats']); ?></td>
<td><?php echo esc_html(ucfirst($booking['status'])); ?></td>
<td><?php echo esc_html($booking['created_at']); ?></td>
<td>
<form method="post" style="display:inline-block;">
<?php wp_nonce_field('ebp_update_booking_' . $booking['id']); ?>
<input type="hidden" name="booking_id" value="<?php echo intval($booking['id']); ?>">
<select name="new_status">
<option value="pending" <?php selected('pending', $booking['status']); ?>><?php _e('Pendiente', 'ebp'); ?></option>
<option value="confirmed" <?php selected('confirmed', $booking['status']); ?>><?php _e('Confirmado', 'ebp'); ?></option>
<option value="cancelled" <?php selected('cancelled', $booking['status']); ?>><?php _e('Cancelado', 'ebp'); ?></option>
</select>
<input type="submit" class="button button-primary" value="<?php _e('Actualizar', 'ebp'); ?>">
</form>
</td>
</tr>
<?php
endforeach;
endif;
?>
</tbody>
</table>
</div>
<?php
}
public function handle_booking_update() {
if (!current_user_can('manage_options')) {
wp_die(__('No tienes permisos para realizar esta acción.', 'ebp'));
}
$booking_id = isset($_POST['booking_id']) ? intval($_POST['booking_id']) : 0;
$new_status = isset($_POST['new_status']) ? sanitize_text_field($_POST['new_status']) : '';
if (!$booking_id || !$new_status || !wp_verify_nonce($_POST['_wpnonce'], 'ebp_update_booking_' . $booking_id)) {
wp_die(__('Datos inválidos o falta nonce.', 'ebp'));
}
$updated = $this->booking_handler->update_booking_status($booking_id, $new_status);
if ($updated) {
wp_redirect(admin_url('admin.php?page=ebp_bookings&updated=1'));
exit;
} else {
wp_die(__('Error al actualizar el estado.', 'ebp'));
}
}
}
// Instanciar la clase administrativa solo en admin
if (is_admin()) {
add_action('plugins_loaded', function () {
new EBP_Admin();
});
}
Plantilla del Formulario de Reserva
Formulario que se muestra en frontend para que los usuarios hagan reservas para un evento específico.
<?php
/*
* Plantilla para formulario de reserva
* Variables disponibles: $event_id
*/
if (!defined('ABSPATH')) {
exit;
}
?>
<form id="ebp-booking-form" method="post" action="">
<input type="hidden" name="event_id" value="<?php echo esc_attr($event_id); ?>">
<p>
<label for="ebp_user_name"><?php _e('Nombre completo:', 'ebp'); ?></label><br>
<input type="text" id="ebp_user_name" name="user_name" required maxlength="100">
</p>
<p>
<label for="ebp_user_email"><?php _e('Correo electrónico:', 'ebp'); ?></label><br>
<input type="email" id="ebp_user_email" name="user_email" required maxlength="100">
</p>
<p>
<label for="ebp_seats"><?php _e('Número de plazas:', 'ebp'); ?></label><br>
<input type="number" id="ebp_seats" name="seats" min="1" max="10" value="1" required>
</p>
<p>
<button type="submit"><?php _e('Reservar plaza', 'ebp'); ?></button>
</p>
<div id="ebp-form-message" style="color: green;"></div>
</form>
<script>
(function($){
$('#ebp-booking-form').on('submit', function(e) {
e.preventDefault();
var data = {
action: 'ebp_submit_booking',
nonce: ebp_ajax.nonce,
event_id: $('input[name="event_id"]').val(),
user_name: $('input[name="user_name"]').val(),
user_email: $('input[name="user_email"]').val(),
seats: $('input[name="seats"]').val()
};
$.post(ebp_ajax.ajax_url, data, function(response) {
if(response.success) {
$('#ebp-form-message').css('color', 'green').text(response.data.message);
$('#ebp-booking-form')[0].reset();
} else {
$('#ebp-form-message').css('color', 'red').text(response.data.message);
}
});
});
})(jQuery);
</script>
Has completado con éxito la creación de un sistema de reservas para eventos en WordPress usando PHP. Ahora cuentas con un plugin funcional que puedes personalizar y extender según tus necesidades.
Como próximos pasos, te recomiendo explorar agregar notificaciones vía email, mejorar la interfaz de usuario y optimizar la gestión de reservas para grandes volúmenes de datos. ¡Sigue practicando y perfeccionando tu desarrollo en WordPress!