v5 ya live

HASTA

60% DTO
Oferta Caliente de Diciembre
Launch Sale
0 DÍAS
:
0 HORAS
:
0 MINUTOS
:
0 SEGUNDOS
OBTENER AHORA percent icon

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.

Versión:
Categorías

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.

Stack tecnológico

El frontend de Booknetic está construido con una pila ligera y familiar:

TecnologíaUso
jQuery 3.3.1+Framework JavaScript principal
Vanilla JavaScript (ES6)Lógica complementaria
Bootstrap 4.xFramework CSS
Select2Selectores mejorados
FlatpickrSelector de fechas
intlTelInputCampo de teléfono internacional
NiceScrollScrollbar 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.

Sistema de hooks JavaScript

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.

El objeto bookneticHooks

El 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:

  • Actions ejecutan tu callback para efectos secundarios, como actualizar el DOM, adjuntar eventos o lanzar solicitudes AJAX. Su valor de retorno se ignora.
  • Filters pasan un valor a través de tu callback. Debes devolver el valor modificado; de lo contrario, la siguiente parte del código recibirá undefined.

El argumento opcional uniqueId permite más adelante reemplazar o eliminar un hook específico llamando a addFilter(key, null, uniqueId).

Uso de mayúsculas y minúsculas en las claves de hook

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.

Referencia de acciones

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.

Ciclo de vida del panel de reservas

booking_panel_loaded

Se 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ámetroTipoDescripción
bookneticobjectLa 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_loading

Se dispara justo antes de que comience una transición de paso, antes de que se envíe la solicitud AJAX.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas
next_stepstringEl ID del paso que está a punto de cargarse
current_stepstringEl 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_loading

Se dispara cuando comienza la solicitud AJAX para el siguiente paso.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas
next_stepstringEl paso que se está cargando
current_stepstringEl paso anterior

loaded_step

Se 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ámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas
step_idstringEl ID del paso cargado
current_stepstringEl ID del paso anterior
resultobjectLos 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ámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas
resultobjectLos 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_step

Se dispara cuando un paso visitado previamente se carga desde caché.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas
step_idstringEl 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ámetroTipoDescripción
bookneticobjectLa 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_error

Se dispara cuando un paso no puede cargarse porque el servidor devolvió un error.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas
next_stepstringEl paso que no pudo cargarse
current_stepstringEl paso al que se está regresando
resultobjectLa respuesta de error

Ciclo de vida de AJAX

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ámetroTipoDescripción
paramsobjectLos parámetros de la solicitud
bookneticobjectLa 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}_success

Se dispara después de una respuesta AJAX exitosa.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas
paramsobjectLos parámetros originales de la solicitud
resultobjectLa respuesta del servidor
bookneticHooks.addAction('ajax_after_get_data_success', function (booknetic, params, result) {
    // Los datos del paso se cargaron correctamente
});

ajax_after_{action}_error

Se dispara después de una respuesta AJAX fallida.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas
paramsobjectLos parámetros originales de la solicitud
resultobjectLa respuesta de error, que puede ser undefined en errores de red

Ciclo de vida del pago

before_processing_payment

Se 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ámetroTipoDescripción
payment_methodstringEl método de pago seleccionado, por ejemplo 'local', 'stripe' o 'paypal'
dataobjectDatos 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_payment

Se 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ámetroTipoDescripción
payment_methodstringEl método de pago
statusbooleanSi la iniciación del pago tuvo éxito
dataobjectLos 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_completed

Se dispara cuando el proceso de pago finaliza correctamente.

Parámetros:

ParámetroTipoDescripción
statusbooleanEstado de éxito del pago
paymentDataobjectDatos del resultado del pago

payment_completed_deprecated

Hook heredado mantenido por compatibilidad hacia atrás. Usa payment_completed en su lugar.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas

payment_error

Se dispara cuando falla un intento de pago.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa instancia del panel de reservas

booking_finished_successfully

Se dispara cuando todo el flujo de reserva se completa correctamente y se muestra la pantalla final.

Parámetros:

ParámetroTipoDescripción
bookneticobjectLa 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
        });
    }
});

Panel del cliente

customer_panel_loaded

Se 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);
        });
    });
});

Referencia de filtros

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.

Filtros de AJAX

ajax

Filtra 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ámetroTipoDescripción
paramsobjectLos parámetros de la solicitud
bookneticobjectLa 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ámetroTipoDescripción
paramsobjectLos parámetros de la solicitud
bookneticobjectLa 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_data

Filtra 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ámetroTipoDescripción
dataFormDataLos datos de envío de la cita
bookneticobjectLa 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;
});

Filtros de pasos

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ámetroTipoDescripción
resultobject{ status: true, errorMsg: '' }
bookneticobjectLa 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ámetroTipoDescripción
bookneticobjectLa 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)
});

Filtro del carrito

bkntc_cart

Filtra los datos del elemento del carrito antes de guardarlos en el array del carrito.

Parámetros:

ParámetroTipoDescripción
cart_itemobjectEl objeto de datos del elemento del carrito
bookneticobjectLa 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;
});

Filtro de franjas horarias

bkntc_date_time_load

Filtra el HTML de cada franja horaria antes de que se renderice en el paso Fecha y Hora.

Parámetros:

ParámetroTipoDescripción
htmlstringEl elemento HTML de la franja horaria
time_objobjectLos datos de la franja horaria, como start_time, end_time y otros campos relacionados
bookneticobjectLa 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;
});

El objeto booknetic

Cada instancia del panel de reservas crea su propio objeto booknetic. Este actúa como la API principal y el contenedor de estado del panel.

Propiedades

PropiedadTipoDescripción
panel_jsjQueryReferencia jQuery al elemento raíz de este panel
cartArrarrayArray de elementos del carrito
cartCurrentIndexnumberÍndice del elemento del carrito que se está editando actualmente
calendarDateTimesobjectFechas y franjas horarias disponibles
calendarYearnumberAño del calendario actualmente mostrado
calendarMonthnumberMes del calendario actualmente mostrado
paymentWindowWindowReferencia a la ventana emergente de pago
paymentStatusbooleanEstado de éxito o fallo del pago
appointmentIdnumberID de la cita creada después de la confirmación
paymentIdnumberID del registro de pago

Métodos

booknetic.ajax(action, params, successCallback, loading, errorCallback, async)

Envía una solicitud AJAX al backend de Booknetic.

ParámetroTipoPredeterminadoDescripción
actionstring|objectNombre 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}
paramsobject|FormDataParámetros de la solicitud
successCallbackfunctionCallback ejecutado en caso de éxito con (result)
loadingbooleantrueSi se debe mostrar el spinner de carga
errorCallbackfunctionCallback ejecutado en caso de error con (result)
asyncbooleantrueSi 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ámetroTipoDescripción
messagestringTexto del mensaje
typestring'success', 'error' o 'warning'
durationnumberTiempo de autocierre en milisegundos

booknetic.__(key)

Devuelve una cadena localizada por clave.

booknetic.__('select_service');  // → "Selecciona un servicio" (o su equivalente traducido)

booknetic.getSelected

Un 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.stepManager

El 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 carrito

Métodos utilitarios

booknetic.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 carga

Arquitectura del panel de reservas

Comprender 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.

Flujo de reserva

El panel de reservas sigue un patrón de asistente por pasos:

Location → Staff → Service → Service Extras → Date/Time → [Recurring Info] → Information → Cart → Confirmation → Finish

Los 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.

Inicialización

Cuando se carga una página que contiene el shortcode [booknetic], ocurre lo siguiente:

  1. La vista PHP renderiza el esqueleto HTML del panel de reservas.
  2. Un script inline llama a bookneticInitBookingPage() para cada panel de la página.
  3. El primer paso se carga mediante AJAX usando bkntc_get_data.
  4. Se dispara la acción booking_panel_loaded.
// Flujo de inicialización simplificado
jQuery(".booknetic_appointment").each((i, element) => {
    window.bookneticInitBookingPage(element);
});

Estructura HTML

<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>

Carga del contenido de pasos

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.

Flujo de solicitud AJAX

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', ...)

Formato de respuesta AJAX

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
}

Integración de gateways de pago

Un addon de gateway de pago debe implementar tres hooks clave para integrarse correctamente en el flujo de reservas.

Patrón completo del gateway

(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 fallido

El 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.

Resumen del flujo de pago

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 éxito

JavaScript del panel de administración

La parte administrativa de Booknetic tiene su propia arquitectura JavaScript y su propio objeto booknetic.

Objeto principal del admin

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
};

Patrón JavaScript de módulos

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);
                });
            }
        );
    };
});

Patrón de envío de formularios modales

$(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();
    });
});

Desarrollo de addons

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.

Cargar assets del frontend

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);
}

Añadir cadenas localizadas

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"

Patrón completo de addon para frontend

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;
});

Guías CSS

Los estilos del frontend de Booknetic están acotados para reducir conflictos con temas de WordPress y estilos de terceros.

Aislamiento

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;
}

Nomenclatura de clases CSS

PrefijoAlcanceEjemplo
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.

Clases CSS clave

ClaseDescripción
.booknetic_appointmentContenedor raíz del panel de reservas completo
.booknetic_appointment_stepsBarra lateral izquierda con indicadores de pasos
.booknetic_appointment_containerContenedor principal de contenido
.booknetic_appointment_container_bodyÁrea de contenido dinámico del paso
.booknetic_cardElemento seleccionable tipo tarjeta para ubicación, personal y similares
.booknetic_card_selectedTarjeta actualmente seleccionada
.booknetic_service_cardTarjeta de selección de servicio
.booknetic_service_card_selectedServicio actualmente seleccionado
.booknetic_calendar_rowsRejilla de días del calendario
.booknetic_calendar_daysDía individual del calendario
.booknetic_time_elementElemento de franja horaria
.booknetic_selected_timeFranja horaria actualmente seleccionada
.booknetic_appointment_finishedPantalla de éxito o finalización

Comportamiento responsive

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 ≤ 1000px

En vista móvil:

  • La barra lateral de pasos se oculta
  • El área de contenido ocupa todo el ancho
  • El comportamiento de scroll cambia

Soporte RTL

RTL se detecta automáticamente:

if ($('html').attr('dir') === 'rtl') {
    $('body').addClass('rtl');
}

Referencia de estructura de archivos

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/

Resumen

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 hookCantidadPropósito
Acciones JS18+Reaccionar a eventos del ciclo de vida como carga de pasos, finalización de pagos y éxito de reservas
Filtros JS7+Modificar datos de solicitudes, resultados de validación, elementos del carrito y salida renderizada del frontend

Referencia rápida: hooks más utilizados

HookTipoCaso de uso común
booking_panel_loadedAcciónInicializar addon y vincular listeners de eventos
loaded_step / loaded_step_{id}AcciónInyectar interfaz personalizada en un paso específico
step_end_{step_id}AcciónReaccionar cuando se completa un paso
before_processing_paymentAcciónAbrir una ventana emergente del gateway de pago
after_processing_paymentAcciónRedirigir el popup a la página de checkout del gateway
booking_finished_successfullyAcciónSeguimiento de conversión y analítica
step_validation_{step_id}FiltroAñadir validación personalizada de pasos
appointment_ajax_dataFiltroAñadir datos personalizados al envío de la reserva
ajax / ajax_{action}FiltroModificar parámetros de solicitudes AJAX
bkntc_date_time_loadFiltroPersonalizar el renderizado de franjas horarias
load_step_{step_id}FiltroBloquear condicionalmente la carga de pasos
bkntc_cartFiltroModificar datos de elementos del carrito