Guía de Front-end
Ampliar los flujos de reserva y pago de Booknetic utilizando hooks de JavaScript, la API de objetos y la arquitectura de addons.
Ampliar los flujos de reserva y pago de Booknetic utilizando hooks de JavaScript, la API de objetos y la arquitectura de addons.
La capa frontend de Booknetic ofrece a los desarrolladores una forma flexible de extender la experiencia de reserva, integrar interfaz personalizada, modificar datos de solicitudes y crear addons avanzados sin cambiar directamente el plugin principal.
Esta guía cubre toda la capa de extensión del frontend, incluido el panel de reservas que usan tus clientes, la estructura JavaScript del panel de administración, el sistema de hooks del lado del cliente, la arquitectura del frontend, la integración del flujo de pagos, las reglas de estilo y las referencias de estructura de archivos.
El frontend de Booknetic está construido con una pila ligera y familiar:
| Tecnología | Uso |
|---|---|
| jQuery 3.3.1+ | Framework JavaScript principal |
| Vanilla JavaScript (ES6) | Lógica complementaria |
| Bootstrap 4.x | Framework CSS |
| Select2 | Selectores mejorados |
| Flatpickr | Selector de fechas |
| intlTelInput | Campo de teléfono internacional |
| NiceScroll | Scrollbar personalizado |
Booknetic no utiliza una herramienta de compilación. No hay webpack, no hay gulp y no existe un paso de compilación. Los archivos JavaScript y CSS se cargan directamente tal como están, lo que significa que puedes añadir o editar archivos sin ningún proceso de build de assets.
Booknetic incluye un sistema de hooks del lado del cliente inspirado en los hooks de WordPress. Este es el mecanismo principal de extensión para addons del frontend y comportamientos personalizados del panel de reservas.
bookneticHooksEl objeto global window.bookneticHooks proporciona cuatro métodos principales:
// Registrar un filtro (modifica y devuelve un valor)
bookneticHooks.addFilter(key, callback, uniqueId);
// Ejecutar un filtro (pasa un valor por todos los callbacks registrados)
let result = bookneticHooks.doFilter(key, initialValue, ...extraArgs);
// Registrar una acción (efecto secundario, no necesita valor de retorno)
bookneticHooks.addAction(key, callback, uniqueId);
// Ejecutar una acción
bookneticHooks.doAction(key, ...params);Las acciones y los filtros tienen propósitos diferentes:
undefined.El argumento opcional uniqueId permite más adelante reemplazar o eliminar un hook específico llamando a addFilter(key, null, uniqueId).
Todas las claves de hook se convierten automáticamente a minúsculas internamente. Eso significa que booking_panel_loaded, Booking_Panel_Loaded y BOOKING_PANEL_LOADED apuntan al mismo hook.
Por consistencia, usa nombres en formato snake_case.
Las acciones te permiten reaccionar a eventos importantes dentro del panel de reservas, el sistema AJAX, el flujo de pagos y el panel del cliente.
booking_panel_loadedSe dispara una vez después de que el panel de reservas se haya inicializado completamente. Este es el punto de entrada principal para el JavaScript de tu addon. Úsalo para vincular listeners de eventos y preparar interfaz personalizada.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
bookneticHooks.addAction('booking_panel_loaded', function (booknetic) {
let panel = booknetic.panel_js; // Elemento jQuery de este panel
// Vincular listeners de eventos usando delegación
panel.on('click', '.my_addon_button', function () {
// Manejar clic
});
});Una misma página puede contener múltiples paneles de reservas. Cada panel dispara su propio evento booking_panel_loaded y tiene su propia instancia booknetic. Usa siempre booknetic.panel_js para consultas al DOM en lugar de selectores globales.
before_step_loadingSe dispara justo antes de que comience una transición de paso, antes de que se envíe la solicitud AJAX.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
next_step | string | El ID del paso que está a punto de cargarse |
current_step | string | El ID del paso que se está dejando |
bookneticHooks.addAction('before_step_loading', function (booknetic, nextStep, currentStep) {
console.log(`Navegando de ${currentStep} a ${nextStep}`);
});start_step_loadingSe dispara cuando comienza la solicitud AJAX para el siguiente paso.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
next_step | string | El paso que se está cargando |
current_step | string | El paso anterior |
loaded_stepSe dispara después de que el contenido de un paso se haya cargado y renderizado. Usa esto para mejorar o extender la interfaz de un paso específico.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
step_id | string | El ID del paso cargado |
current_step | string | El ID del paso anterior |
result | object | Los datos de respuesta AJAX |
bookneticHooks.addAction('loaded_step', function (booknetic, stepId, currentStep, result) {
if (stepId !== 'confirm_details') return;
// Inyectar un campo de cupón en el paso de confirmación
let html = '<div class="my_coupon_section">'
+ '<input type="text" placeholder="' + booknetic.__('enter_coupon') + '">'
+ '<button class="my_apply_btn">' + booknetic.__('apply') + '</button>'
+ '</div>';
booknetic.panel_js.find('.booknetic_confirm_details').append(html);
});loaded_step_{step_id}Versión dinámica de loaded_step. Este hook solo se dispara para el paso especificado, por lo que no necesitas una comprobación manual con if.
IDs de paso disponibles:
location, staff, service, service_extras, date_time, recurring_info, information, cart, confirm_details
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
result | object | Los datos de respuesta AJAX |
bookneticHooks.addAction('loaded_step_date_time', function (booknetic, result) {
// Se ejecuta solo cuando se carga el paso Fecha y Hora
// Personaliza aquí el comportamiento del calendario
});loaded_cached_stepSe dispara cuando un paso visitado previamente se carga desde caché.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
step_id | string | El paso cargado desde caché |
step_end_{step_id}Se dispara después de que un paso pase la validación y el usuario esté a punto de avanzar. Esto es útil cuando quieres reaccionar a un paso completado antes de que se cargue el siguiente.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
bookneticHooks.addAction('step_end_service', function (booknetic) {
// El usuario ha seleccionado un servicio y está avanzando
let serviceId = booknetic.getSelected.service();
console.log('Servicio seleccionado:', serviceId);
});step_loaded_with_errorSe dispara cuando un paso no puede cargarse porque el servidor devolvió un error.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
next_step | string | El paso que no pudo cargarse |
current_step | string | El paso al que se está regresando |
result | object | La respuesta de error |
ajax_before_{action}Se dispara antes de que se envíe una solicitud AJAX. {action} es el nombre de la acción AJAX, como get_data, confirm o check_customer_exist.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
params | object | Los parámetros de la solicitud |
booknetic | object | La instancia del panel de reservas |
bookneticHooks.addAction('ajax_before_confirm', function (params, booknetic) {
// Añadir un indicador de carga, deshabilitar botones, etc.
console.log('A punto de confirmar la cita...');
});ajax_after_{action}_successSe dispara después de una respuesta AJAX exitosa.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
params | object | Los parámetros originales de la solicitud |
result | object | La respuesta del servidor |
bookneticHooks.addAction('ajax_after_get_data_success', function (booknetic, params, result) {
// Los datos del paso se cargaron correctamente
});ajax_after_{action}_errorSe dispara después de una respuesta AJAX fallida.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
params | object | Los parámetros originales de la solicitud |
result | object | La respuesta de error, que puede ser undefined en errores de red |
before_processing_paymentSe dispara antes de que comience el proceso de pago. Los addons de gateways de pago usan este hook para inicializar su flujo, como abrir una ventana emergente.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
payment_method | string | El método de pago seleccionado, por ejemplo 'local', 'stripe' o 'paypal' |
data | object | Datos de pago devueltos por el servidor, incluidos tokens, URLs y valores relacionados |
bookneticHooks.addAction('before_processing_payment', function (paymentMethod, data) {
if (paymentMethod !== 'my_gateway') return;
// Abrir una ventana emergente para el gateway de pago
myPaymentWindow = window.open('', 'booknetic_payment_window', 'width=1000,height=700');
});after_processing_paymentSe dispara después de que se haya iniciado el proceso de pago. Esto se usa normalmente para redirigir la ventana emergente a la página del gateway de pago.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
payment_method | string | El método de pago |
status | boolean | Si la iniciación del pago tuvo éxito |
data | object | Los datos de respuesta del pago |
bookneticHooks.addAction('after_processing_payment', function (paymentMethod, status, data) {
if (paymentMethod !== 'my_gateway') return;
if (!status) {
myPaymentWindow.close();
return;
}
// Redirigir la ventana emergente a la página de checkout del gateway
myPaymentWindow.location.href = data['url'];
});payment_completedSe dispara cuando el proceso de pago finaliza correctamente.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
status | boolean | Estado de éxito del pago |
paymentData | object | Datos del resultado del pago |
payment_completed_deprecatedHook heredado mantenido por compatibilidad hacia atrás. Usa payment_completed en su lugar.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
payment_errorSe dispara cuando falla un intento de pago.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
booking_finished_successfullySe dispara cuando todo el flujo de reserva se completa correctamente y se muestra la pantalla final.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
bookneticHooks.addAction('booking_finished_successfully', function (booknetic) {
// Activar seguimiento de conversión, evento analítico, etc.
if (typeof gtag !== 'undefined') {
gtag('event', 'booking_completed', {
appointment_id: booknetic.appointmentId
});
}
});customer_panel_loadedSe dispara cuando se carga el panel del cliente. Esta es la interfaz donde los clientes existentes gestionan sus reservas.
Parámetros: Ninguno
bookneticHooks.addAction('customer_panel_loaded', function () {
// Añadir una pestaña personalizada al panel del cliente
let tabHtml = '<li class="my_addon_tab" data-tab="my_tab">'
+ '<span>Mi pestaña personalizada</span></li>';
$('.booknetic_cp_tabs').append(tabHtml);
$(document).on('click', '.my_addon_tab', function () {
booknetic.ajax('my_addon_get_tab_content', {}, function (result) {
$('.booknetic_cp_content').html(result.html);
});
});
});Los filtros permiten modificar datos de solicitud salientes, resultados de validación de pasos, elementos del carrito y HTML renderizado del frontend antes de que Booknetic los utilice.
ajaxFiltra todos los parámetros de solicitudes AJAX salientes. Este hook se ejecuta para cada solicitud AJAX realizada por el panel de reservas.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
params | object | Los parámetros de la solicitud |
booknetic | object | La instancia del panel de reservas |
Devuelve: object — Los parámetros modificados
bookneticHooks.addFilter('ajax', function (params, booknetic) {
// Añadir un parámetro personalizado a cada solicitud AJAX
params.my_tracking_id = 'abc123';
return params;
});ajax_{action}Filtra parámetros solo para una acción AJAX específica. Por ejemplo, ajax_confirm afecta únicamente a la solicitud de confirmación.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
params | object | Los parámetros de la solicitud |
booknetic | object | La instancia del panel de reservas |
Devuelve: object
bookneticHooks.addFilter('ajax_confirm', function (params, booknetic) {
// Añadir datos extra solo a la solicitud de confirmación
params.special_note = document.getElementById('my_note_field').value;
return params;
});appointment_ajax_dataFiltra el FormData final enviado cuando se confirma la cita. Este es el último punto donde puedes modificar los datos antes de que lleguen al backend.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
data | FormData | Los datos de envío de la cita |
booknetic | object | La instancia del panel de reservas |
Devuelve: FormData
bookneticHooks.addFilter('appointment_ajax_data', function (formData, booknetic) {
// Añadir datos de campos personalizados del formulario
formData.append('coupon_code', document.getElementById('my_coupon_input').value);
formData.append('gift_card_code', document.getElementById('my_gift_card_input').value);
return formData;
});step_validation_{step_id}Valida un paso antes de que el usuario pueda continuar. Devuelve { status: false, errorMsg: '...' } para bloquear la navegación.
IDs de paso disponibles:
location, staff, service, service_extras, date_time, recurring_info, information, cart, confirm_details
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
result | object | { status: true, errorMsg: '' } |
booknetic | object | La instancia del panel de reservas |
Devuelve: object — { status: boolean, errorMsg: string }
bookneticHooks.addFilter('step_validation_information', function (result, booknetic) {
let customField = booknetic.panel_js.find('#my_required_field').val();
if (!customField) {
return {
status: false,
errorMsg: booknetic.__('my_field_required')
};
}
return result;
});load_step_{step_id}Controla si un paso debe cargarse. Devuelve false para evitar que el paso se cargue. Esto es útil cuando necesitas mostrar un modal o recopilar información extra primero.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
booknetic | object | La instancia del panel de reservas |
Devuelve: boolean
bookneticHooks.addFilter('load_step_date_time', function (booknetic) {
// Mostrar un modal personalizado de duración antes de continuar a fecha/hora
if (!durationSelected) {
showDurationModal(booknetic);
return false; // Bloquear carga del paso
}
return true; // Permitir carga del paso (predeterminado)
});bkntc_cartFiltra los datos del elemento del carrito antes de guardarlos en el array del carrito.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
cart_item | object | El objeto de datos del elemento del carrito |
booknetic | object | La instancia del panel de reservas |
Devuelve: object
bookneticHooks.addFilter('bkntc_cart', function (cartItem, booknetic) {
// Añadir datos personalizados al elemento del carrito
cartItem.my_addon_data = {
coupon_code: mySelectedCoupon,
discount: myCalculatedDiscount
};
return cartItem;
});bkntc_date_time_loadFiltra el HTML de cada franja horaria antes de que se renderice en el paso Fecha y Hora.
Parámetros:
| Parámetro | Tipo | Descripción |
|---|---|---|
html | string | El elemento HTML de la franja horaria |
time_obj | object | Los datos de la franja horaria, como start_time, end_time y otros campos relacionados |
booknetic | object | La instancia del panel de reservas |
Devuelve: string
bookneticHooks.addFilter('bkntc_date_time_load', function (html, timeObj, booknetic) {
// Añadir una insignia "Popular" a franjas con alta demanda
if (timeObj.booking_count > 5) {
html = html.replace('</div>', '<span class="popular-badge">Popular</span></div>');
}
return html;
});bookneticCada instancia del panel de reservas crea su propio objeto booknetic. Este actúa como la API principal y el contenedor de estado del panel.
| Propiedad | Tipo | Descripción |
|---|---|---|
panel_js | jQuery | Referencia jQuery al elemento raíz de este panel |
cartArr | array | Array de elementos del carrito |
cartCurrentIndex | number | Índice del elemento del carrito que se está editando actualmente |
calendarDateTimes | object | Fechas y franjas horarias disponibles |
calendarYear | number | Año del calendario actualmente mostrado |
calendarMonth | number | Mes del calendario actualmente mostrado |
paymentWindow | Window | Referencia a la ventana emergente de pago |
paymentStatus | boolean | Estado de éxito o fallo del pago |
appointmentId | number | ID de la cita creada después de la confirmación |
paymentId | number | ID del registro de pago |
booknetic.ajax(action, params, successCallback, loading, errorCallback, async)Envía una solicitud AJAX al backend de Booknetic.
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
action | string|object | — | Nombre de la acción. Si es string, Booknetic le añade el prefijo bkntc_. Si es objeto, debe estar en formato {backend_action, frontend_action} |
params | object|FormData | — | Parámetros de la solicitud |
successCallback | function | — | Callback ejecutado en caso de éxito con (result) |
loading | boolean | true | Si se debe mostrar el spinner de carga |
errorCallback | function | — | Callback ejecutado en caso de error con (result) |
async | boolean | true | Si la solicitud es asíncrona |
booknetic.ajax('my_addon_action', {
param1: 'value1',
param2: 'value2'
}, function (result) {
// Éxito
console.log(result.data);
}, true, function (error) {
// Error
booknetic.toast(error.error_msg, 'error');
});El nombre de la acción se prefija automáticamente con bkntc_. Por ejemplo, 'my_addon_action' se convierte en bkntc_my_addon_action en el servidor.
booknetic.ajaxAsync(action, params, loading, async)Versión basada en promesas de ajax(). Se resuelve con el resultado o se rechaza con el error.
try {
let result = await booknetic.ajaxAsync('my_action', { id: 123 }, true);
console.log(result.data);
} catch (error) {
booknetic.toast(error.error_msg, 'error');
}booknetic.toast(message, type, duration)Muestra una notificación toast.
| Parámetro | Tipo | Descripción |
|---|---|---|
message | string | Texto del mensaje |
type | string | 'success', 'error' o 'warning' |
duration | number | Tiempo de autocierre en milisegundos |
booknetic.__(key)Devuelve una cadena localizada por clave.
booknetic.__('select_service'); // → "Selecciona un servicio" (o su equivalente traducido)booknetic.getSelectedUn objeto que contiene métodos getter para las selecciones actuales de la reserva:
booknetic.getSelected.location(); // ID de la ubicación seleccionada
booknetic.getSelected.service(); // ID del servicio seleccionado
booknetic.getSelected.staff(); // ID del personal seleccionado
booknetic.getSelected.serviceExtras(); // [{extra, quantity}, ...]
booknetic.getSelected.date(); // 'YYYY-MM-DD'
booknetic.getSelected.time(); // 'HH:MM'
booknetic.getSelected.formData(); // {email, name, surname, phone}
booknetic.getSelected.customerId(); // ID del cliente (si es cliente existente)
booknetic.getSelected.paymentMethod(); // Clave del método de pago
booknetic.getSelected.paymentDepositFullAmount(); // 'deposit' o 'full_amount'
// Citas recurrentes
booknetic.getSelected.recurringStartDate();
booknetic.getSelected.recurringEndDate();
booknetic.getSelected.recurringTimesArr();booknetic.stepManagerEl controlador de navegación entre pasos:
booknetic.stepManager.goForward(); // Navegar al siguiente paso
booknetic.stepManager.goBack(); // Navegar al paso anterior
booknetic.stepManager.loadStep(stepId); // Cargar un paso específico
booknetic.stepManager.getNextStep(); // Obtener el ID del siguiente paso
booknetic.stepManager.getPrevStep(); // Obtener el ID del paso anterior
booknetic.stepManager.saveData(); // Guardar selecciones actuales en el carritobooknetic.throttle(fn, delay); // Devuelve una función throttled (predeterminado 500ms)
booknetic.debounce(fn, delay); // Devuelve una función debounced (predeterminado 300ms)
booknetic.htmlspecialchars(string); // Codifica una cadena en HTML
booknetic.htmlspecialchars_decode(string); // Decodifica una cadena HTML
booknetic.sanitizeHTML(node); // Elimina etiquetas <script> de un nodo DOM
booknetic.isMobileView(); // true si el viewport ≤ 1000px
booknetic.zeroPad(number, length); // Añade ceros a la izquierda (ej. zeroPad(42, 4) → "0042")
booknetic.loading(state); // Muestra (1) u oculta (0) el spinner de cargaComprender la arquitectura del panel de reservas hace que el desarrollo de addons sea mucho más sencillo, especialmente cuando necesitas integrarte con pasos, comportamiento de caché o flujo AJAX.
El panel de reservas sigue un patrón de asistente por pasos:
Location → Staff → Service → Service Extras → Date/Time → [Recurring Info] → Information → Cart → Confirmation → FinishLos pasos pueden ocultarse o reordenarse desde la configuración del admin. La navegación, caché y validación son gestionadas automáticamente por el step manager.
Cuando se carga una página que contiene el shortcode [booknetic], ocurre lo siguiente:
bookneticInitBookingPage() para cada panel de la página.bkntc_get_data.booking_panel_loaded.// Flujo de inicialización simplificado
jQuery(".booknetic_appointment").each((i, element) => {
window.bookneticInitBookingPage(element);
});<div class="booknetic_appointment" id="booknetic_theme_{themeId}">
<div class="booknetic_appointment_steps">
<!-- Barra lateral de pasos / indicador de progreso -->
</div>
<div class="booknetic_appointment_container">
<div class="booknetic_appointment_container_header">
<!-- Título del paso actual + icono del carrito -->
</div>
<div class="booknetic_appointment_container_body">
<!-- Contenido dinámico del paso (cargado vía AJAX) -->
</div>
<div class="booknetic_appointment_container_footer">
<!-- Botones Atrás, Siguiente, Confirmar -->
</div>
</div>
<div class="booknetic_appointment_finished">
<!-- Pantalla de éxito / error -->
</div>
</div>Cada paso se carga mediante AJAX a través del endpoint bkntc_get_data. El backend devuelve HTML, que se inserta en .booknetic_appointment_container_body.
Para mejorar el rendimiento, el contenido del paso se almacena en caché en booknetic.cartHTMLBody, por lo que volver a un paso anterior no dispara otra solicitud.
Cada solicitud AJAX sigue este pipeline:
1. La acción se prefija: 'bkntc_' + action
2. Se añade tenant_id
3. Hook: bookneticHooks.doAction('ajax_before_' + action, params, booknetic)
4. Hook: params = bookneticHooks.doFilter('ajax', params, booknetic)
5. Hook: params = bookneticHooks.doFilter('ajax_' + action, params, booknetic)
6. $.ajax() POST → admin-ajax.php
7. En éxito: callback(result) + doAction('ajax_after_{action}_success', ...)
8. En error: callback(result) + doAction('ajax_after_{action}_error', ...)Todas las respuestas AJAX de Booknetic usan la siguiente estructura:
{
status: 'ok' | 'error',
error_msg: 'Mensaje de error', // Presente en errores
errors: [{ field, message }], // Errores de validación por campo
html: '<div>...</div>', // Contenido HTML del paso
data: { ... } // Datos adicionales
}Un addon de gateway de pago debe implementar tres hooks clave para integrarse correctamente en el flujo de reservas.
(function () {
let paymentWindow;
let paymentData;
// Paso 1: Abrir la ventana de pago antes de iniciar el procesamiento
bookneticHooks.addAction('before_processing_payment', function (paymentMethod, data) {
if (paymentMethod !== 'my_gateway') return;
paymentData = data;
paymentWindow = window.open('', 'booknetic_payment_window', 'width=1000,height=700');
});
// Paso 2: Redirigir el popup al gateway después de que el servidor cree el pago
bookneticHooks.addAction('after_processing_payment', function (paymentMethod, status, data) {
if (paymentMethod !== 'my_gateway') return;
if (!status) {
paymentWindow.close();
return;
}
// Redirigir a la página de checkout del gateway
paymentWindow.location.href = data['url'];
});
})();En la página callback del gateway, después de que el cliente complete el pago, la ventana emergente debe llamar a una de las siguientes opciones:
// En la vista callback cargada dentro del popup:
window.opener.bookneticPaymentStatus(true); // Pago exitoso
// o
window.opener.bookneticPaymentStatus(false); // Pago fallidoEl panel principal de reservas observa la ventana emergente. Cuando esta se cierra, comprueba el resultado del pago y luego muestra la pantalla de éxito o un estado de error.
1. El usuario hace clic en "Confirmar"
2. confirmAppointment() → AJAX: bkntc_confirm
3. El servidor crea la cita y devuelve la URL de pago
4. doAction('before_processing_payment') → El gateway abre el popup
5. doAction('after_processing_payment') → El popup se redirige a la URL del gateway
6. El cliente completa el pago en el popup
7. El callback del gateway llama a window.opener.bookneticPaymentStatus(true)
8. El popup se cierra y la ventana principal lo detecta
9. doAction('payment_completed')
10. doAction('booking_finished_successfully') → Se muestra la pantalla de éxitoLa parte administrativa de Booknetic tiene su propia arquitectura JavaScript y su propio objeto booknetic.
El objeto principal del admin está definido en app/Backend/Base/assets/js/booknetic.js:
var booknetic = {
// Localización
__(key) — Obtener una cadena traducida
// UI
loadModal(modal, parameters, options) — Cargar un modal vía AJAX
modalHide($modal) — Cerrar un modal
toast(title, type, duration) — Mostrar una notificación toast
confirm(text, bg, icon, okCallback, okText, cancelText) — Mostrar diálogo de confirmación
// AJAX
ajax(action, params, callback) — Hacer una solicitud AJAX
// DataTable
dataTable.init(container) — Inicializar una tabla
dataTable.reload() — Recargar datos de la tabla
dataTable.doAction(action, ids, params, callback) — Ejecutar una acción de tabla
};Cada módulo del admin suele tener su propio archivo JavaScript y sigue un patrón de interacción coherente:
$(document).ready(function () {
// Botón añadir
$(document).on('click', '#addBtn', function () {
booknetic.loadModal('add_new', {});
});
// Acción editar de DataTable
booknetic.dataTable.actionCallbacks['edit'] = function (ids) {
booknetic.loadModal('add_new', { id: ids[0] });
};
// Acción eliminar de DataTable
booknetic.dataTable.actionCallbacks['delete'] = function (ids) {
booknetic.confirm(
booknetic.__('are_you_sure_want_to_delete'),
'danger', 'trash',
function () {
booknetic.dataTable.doAction('delete', ids, {}, function () {
booknetic.toast(booknetic.__('deleted'), 'success', 2000);
});
}
);
};
});$(document).on('click', '#saveBtnModal', function () {
let name = $('#input_name').val();
if (!name) {
booknetic.toast(booknetic.__('name_required'), 'error');
return;
}
booknetic.ajax('save', {
id: $('#input_id').val(),
name: name
}, function (result) {
booknetic.toast(booknetic.__('saved'), 'success', 2000);
booknetic.modalHide($('.modal'));
booknetic.dataTable.reload();
});
});Un addon de frontend normalmente necesita cargar assets, registrar cadenas localizadas, engancharse a eventos del ciclo de vida, inyectar interfaz, enviar datos personalizados y, en algunos casos, validar pasos.
Registra los scripts y estilos de tu addon mediante hooks PHP:
// En el método initFrontend() de tu AddonLoader:
public function initFrontend()
{
// Cargar assets en el panel de reservas
add_filter('bkntc_booking_panel_assets', function ($assets) {
$assets[] = ['type' => 'js', 'url' => self::loadAsset('assets/js/init.js')];
$assets[] = ['type' => 'css', 'url' => self::loadAsset('assets/css/style.css')];
return $assets;
});
}
// En el método initBackend() de tu AddonLoader:
public function initBackend()
{
// Cargar assets en páginas del admin
add_action('bkntc_enqueue_assets', function ($module, $action, $viewPath) {
if ($module === 'appointments') {
wp_enqueue_script('my-addon-admin', self::loadAsset('assets/js/admin.js'), ['jquery']);
}
}, 10, 3);
}Puedes pasar cadenas traducibles desde PHP a JavaScript:
// PHP: Registrar cadenas
add_filter('bkntc_frontend_localization', function ($strings) {
$strings['my_addon_label'] = bkntc__('My Label');
$strings['my_addon_error'] = bkntc__('Something went wrong');
return $strings;
});
// JavaScript: Usar las cadenas
booknetic.__('my_addon_label'); // → "My Label"
booknetic.__('my_addon_error'); // → "Something went wrong"Un archivo init.js típico de un addon puede seguir esta estructura:
// Panel cargado — vincular eventos
bookneticHooks.addAction('booking_panel_loaded', function (booknetic) {
let panel = booknetic.panel_js;
// Vincular eventos DOM usando delegación
panel.on('click', '.my_addon_apply_btn', function () {
let code = panel.find('#my_addon_input').val();
booknetic.ajax('my_addon_validate', { code: code }, function (result) {
// Actualizar la interfaz con el resultado
panel.find('.my_addon_status').text(result.data.message);
}, true, function (error) {
booknetic.toast(error.error_msg, 'error');
});
});
});
// Inyectar interfaz en un paso específico
bookneticHooks.addAction('loaded_step_confirm_details', function (booknetic, result) {
let html = '<div class="my_addon_section">'
+ '<input type="text" id="my_addon_input" placeholder="' + booknetic.__('my_placeholder') + '">'
+ '<button class="my_addon_apply_btn">' + booknetic.__('apply') + '</button>'
+ '</div>';
booknetic.panel_js.find('.booknetic_confirm_details').append(html);
});
// Añadir datos personalizados al envío de la cita
bookneticHooks.addFilter('appointment_ajax_data', function (formData, booknetic) {
let myValue = booknetic.panel_js.find('#my_addon_input').val();
if (myValue) {
formData.append('my_addon_code', myValue);
}
return formData;
});
// Validación personalizada de paso
bookneticHooks.addFilter('step_validation_information', function (result, booknetic) {
let field = booknetic.panel_js.find('#my_required_field').val();
if (!field) {
return { status: false, errorMsg: booknetic.__('field_required') };
}
return result;
});Los estilos del frontend de Booknetic están acotados para reducir conflictos con temas de WordPress y estilos de terceros.
Los estilos del panel de reservas están aislados bajo .booknetic_appointment:
.booknetic_appointment * {
all: unset;
/* Las propiedades se vuelven a definir explícitamente después */
}Al escribir estilos personalizados, mantenlos siempre dentro del scope del panel de reservas:
.booknetic_appointment .my_addon_section {
padding: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-top: 10px;
}| Prefijo | Alcance | Ejemplo |
|---|---|---|
booknetic_ | Elementos principales del panel de reservas | .booknetic_service_card |
bkntc_ | Clases internas abreviadas | .bkntc_service_list |
bkntcsaas_ | Elementos del módulo SaaS | .bkntcsaas_signup_btn |
Los modificadores de estado usan el sufijo _{state}, como .booknetic_card_selected y .booknetic_category_accordion_hidden.
| Clase | Descripción |
|---|---|
.booknetic_appointment | Contenedor raíz del panel de reservas completo |
.booknetic_appointment_steps | Barra lateral izquierda con indicadores de pasos |
.booknetic_appointment_container | Contenedor principal de contenido |
.booknetic_appointment_container_body | Área de contenido dinámico del paso |
.booknetic_card | Elemento seleccionable tipo tarjeta para ubicación, personal y similares |
.booknetic_card_selected | Tarjeta actualmente seleccionada |
.booknetic_service_card | Tarjeta de selección de servicio |
.booknetic_service_card_selected | Servicio actualmente seleccionado |
.booknetic_calendar_rows | Rejilla de días del calendario |
.booknetic_calendar_days | Día individual del calendario |
.booknetic_time_element | Elemento de franja horaria |
.booknetic_selected_time | Franja horaria actualmente seleccionada |
.booknetic_appointment_finished | Pantalla de éxito o finalización |
El panel de reservas cambia a diseño móvil a partir de 1000px de ancho de viewport.
booknetic.isMobileView(); // devuelve true cuando el viewport ≤ 1000pxEn vista móvil:
RTL se detecta automáticamente:
if ($('html').attr('dir') === 'rtl') {
$('body').addClass('rtl');
}app/Frontend/assets/
├── js/
│ ├── booknetic.js # Lógica principal del panel de reservas (~2400 líneas)
│ ├── booknetic-popup.js # Gestor de modal/popup
│ ├── booknetic-signin.js # Inicio de sesión del cliente
│ ├── booknetic-signup.js # Registro del cliente
│ ├── booknetic-forgot-password.js
│ └── steps/ # Archivos JS específicos de cada paso
│ ├── step_locations.js
│ ├── step_services.js
│ ├── step_staff.js
│ ├── step_service_extras.js
│ ├── step_date_time.js
│ ├── step_information.js
│ ├── step_cart.js
│ ├── step_confirm_details.js
│ └── step_recurring_info.js
├── css/
│ ├── booknetic.css # Estilos principales del panel de reservas
│ └── bootstrap-booknetic.css # Personalización de Bootstrap
└── icons/ # Iconos SVG (40+)
app/Frontend/view/booking_panel/ # Plantillas PHP de pasos
├── locations.php
├── services.php
├── staff.php
├── service_extras.php
├── date_time.php
├── information.php
├── confirm.php
├── cart.php
└── booknetic.php # Plantilla principal del panel
app/Backend/Base/assets/js/
├── booknetic.js # JS principal del panel de administración
└── generic-table.js # Clase GenericTable
app/Backend/[Module]/assets/ # Assets específicos de módulos
├── js/
└── css/La capa de desarrollo frontend de Booknetic te ofrece un sistema práctico de extensión para el panel de reservas, el panel del cliente, el JavaScript del admin, los gateways de pago y el comportamiento de renderizado de la interfaz.
| Tipo de hook | Cantidad | Propósito |
|---|---|---|
| Acciones JS | 18+ | Reaccionar a eventos del ciclo de vida como carga de pasos, finalización de pagos y éxito de reservas |
| Filtros JS | 7+ | Modificar datos de solicitudes, resultados de validación, elementos del carrito y salida renderizada del frontend |
| Hook | Tipo | Caso de uso común |
|---|---|---|
booking_panel_loaded | Acción | Inicializar addon y vincular listeners de eventos |
loaded_step / loaded_step_{id} | Acción | Inyectar interfaz personalizada en un paso específico |
step_end_{step_id} | Acción | Reaccionar cuando se completa un paso |
before_processing_payment | Acción | Abrir una ventana emergente del gateway de pago |
after_processing_payment | Acción | Redirigir el popup a la página de checkout del gateway |
booking_finished_successfully | Acción | Seguimiento de conversión y analítica |
step_validation_{step_id} | Filtro | Añadir validación personalizada de pasos |
appointment_ajax_data | Filtro | Añadir datos personalizados al envío de la reserva |
ajax / ajax_{action} | Filtro | Modificar parámetros de solicitudes AJAX |
bkntc_date_time_load | Filtro | Personalizar el renderizado de franjas horarias |
load_step_{step_id} | Filtro | Bloquear condicionalmente la carga de pasos |
bkntc_cart | Filtro | Modificar datos de elementos del carrito |