Ejemplos de Integración

Implementaciones completas del widget para diferentes casos de uso.

Implementación básica

El ejemplo más sencillo: un widget inline en tu página de reservas.

HTML
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Reservar Cita | Mi Salon</title>
    <style>
        body {
            font-family: system-ui, sans-serif;
            max-width: 600px;
            margin: 40px auto;
            padding: 0 20px;
        }
        h1 {
            text-align: center;
            margin-bottom: 30px;
        }
    </style>
</head>
<body>

    <h1>Reserva tu cita</h1>

    <div id="salonbookit-widget"></div>

    <script src="https://app.salonbookit.com/static/widget/v1/salonbookit-widget.min.js"></script>
    <script>
        SalonBookIt.init({
            apiKey: 'TU_API_KEY',
            container: '#salonbookit-widget',
            theme: 'light',
            locale: 'es'
        });
    </script>

</body>
</html>

Botón que abre modal

Un botón flotante que abre el widget en un popup. Ideal para landing pages.

HTML

Servicio preseleccionado

Útil para páginas de servicios individuales donde quieres que el widget ya tenga un servicio seleccionado.

HTML
<!-- Página: /servicios/corte-clasico -->

<div class="service-page">
    <h1>Corte Clásico</h1>
    <p>Nuestro corte de cabello más popular para caballeros.</p>
    <p><strong>Precio: 25€</strong> | <strong>Duración: 30 min</strong></p>

    <h2>Reservar este servicio</h2>
    <div id="widget"></div>
</div>

<script src="https://app.salonbookit.com/static/widget/v1/salonbookit-widget.min.js"></script>
<script>
    // El ID del servicio lo obtienes de tu backend o de la URL
    const serviceId = 15; // ID del "Corte Clásico"

    SalonBookIt.init({
        apiKey: 'TU_API_KEY',
        container: '#widget',
        serviceId: serviceId, // Preselecciona este servicio
        allowMultipleServices: false // Solo permite este servicio
    });
</script>

Integración con Google Analytics

Trackea cada paso del embudo de reservas.

JavaScript
SalonBookIt.init({
    apiKey: 'TU_API_KEY',
    container: '#widget',

    onReady: (business) => {
        gtag('event', 'widget_loaded', {
            'business_name': business.nombre
        });
    },

    onStepChange: (step, data) => {
        gtag('event', 'booking_step', {
            'step_name': step,
            'services_count': data.services?.length || 0
        });
    },

    onServiceSelect: (service, selected, allSelected) => {
        if (selected) {
            gtag('event', 'add_to_cart', {
                'items': [{
                    'id': service.id,
                    'name': service.nombre,
                    'price': service.precio
                }]
            });
        }
    },

    onBookingComplete: (booking) => {
        gtag('event', 'purchase', {
            'transaction_id': booking.id,
            'value': booking.total,
            'currency': 'EUR',
            'items': booking.servicios.map(s => ({
                'id': s.id,
                'name': s.nombre,
                'price': s.precio
            }))
        });

        // Facebook Pixel
        fbq('track', 'Purchase', {
            value: booking.total,
            currency: 'EUR'
        });
    },

    onPaymentComplete: (payment) => {
        gtag('event', 'payment_complete', {
            'payment_method': 'stripe',
            'amount': payment.amount
        });
    },

    onError: (error) => {
        gtag('event', 'exception', {
            'description': error.message,
            'fatal': false
        });
    }
});

Estilos personalizados

Personaliza completamente la apariencia del widget para que encaje con tu marca.

HTML + CSS
<style>
/* Tema personalizado */
#mi-widget .sbk-widget {
    /* Colores de marca */
    --sbk-primary: #ff6b6b;
    --sbk-primary-hover: #ee5a5a;
    --sbk-background: #fafafa;
    --sbk-surface: #ffffff;
    --sbk-text: #2d3436;
    --sbk-text-secondary: #636e72;
    --sbk-border: #dfe6e9;

    /* Bordes más redondeados */
    --sbk-radius-sm: 8px;
    --sbk-radius-md: 12px;
    --sbk-radius-lg: 20px;

    /* Fuente personalizada */
    --sbk-font-family: 'Poppins', sans-serif;

    /* Sombras más pronunciadas */
    --sbk-shadow-md: 0 10px 40px rgba(0,0,0,0.15);
}

/* Estilo del botón principal */
#mi-widget .sbk-btn-primary {
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* Cards de servicios */
#mi-widget .sbk-service-card {
    border: 2px solid transparent;
    transition: all 0.3s ease;
}

#mi-widget .sbk-service-card:hover {
    border-color: var(--sbk-primary);
    transform: translateY(-2px);
}

#mi-widget .sbk-service-card.selected {
    border-color: var(--sbk-primary);
    background: rgba(255, 107, 107, 0.05);
}
</style>

<div id="mi-widget"></div>

<script src="https://app.salonbookit.com/static/widget/v1/salonbookit-widget.min.js"></script>
<script>
    SalonBookIt.init({
        apiKey: 'TU_API_KEY',
        container: '#mi-widget',
        primaryColor: '#ff6b6b',
        borderRadius: '20px',
        fontFamily: 'Poppins, sans-serif'
    });
</script>

Componente React completo

React (TypeScript)
import React, { useEffect, useRef, useState } from 'react';

interface BookingWidgetProps {
    apiKey: string;
    serviceId?: number;
    staffId?: number;
    onBookingComplete?: (booking: any) => void;
    onError?: (error: any) => void;
}

declare global {
    interface Window {
        SalonBookIt: any;
    }
}

const BookingWidget: React.FC<BookingWidgetProps> = ({
    apiKey,
    serviceId,
    staffId,
    onBookingComplete,
    onError
}) => {
    const containerRef = useRef<HTMLDivElement>(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<string | null>(null);

    useEffect(() => {
        const loadScript = () => {
            return new Promise<void>((resolve, reject) => {
                if (window.SalonBookIt) {
                    resolve();
                    return;
                }

                const script = document.createElement('script');
                script.src = 'https://app.salonbookit.com/static/widget/v1/salonbookit-widget.min.js';
                script.async = true;
                script.onload = () => resolve();
                script.onerror = () => reject(new Error('Error al cargar widget'));
                document.body.appendChild(script);
            });
        };

        const initWidget = async () => {
            try {
                await loadScript();

                if (!containerRef.current) return;

                window.SalonBookIt.init({
                    apiKey,
                    container: containerRef.current,
                    serviceId,
                    staffId,
                    locale: 'es',
                    onReady: () => setLoading(false),
                    onBookingComplete: (booking: any) => {
                        onBookingComplete?.(booking);
                    },
                    onError: (err: any) => {
                        setError(err.message);
                        onError?.(err);
                    }
                });
            } catch (err) {
                setError('Error al cargar el widget');
            }
        };

        initWidget();

        return () => {
            // Cleanup si es necesario
        };
    }, [apiKey, serviceId, staffId]);

    if (error) {
        return (
            <div className="widget-error">
                <p>Error: {error}</p>
                <button onClick={() => window.location.reload()}>
                    Reintentar
                </button>
            </div>
        );
    }

    return (
        <div className="widget-wrapper">
            {loading && (
                <div className="widget-loading">
                    <div className="spinner" />
                    <p>Cargando...</p>
                </div>
            )}
            <div ref={containerRef} style={{ opacity: loading ? 0 : 1 }} />
        </div>
    );
};

export default BookingWidget;

// Uso:
// <BookingWidget
//     apiKey="hh_pub_live_..."
//     serviceId={15}
//     onBookingComplete={(booking) => navigate(`/confirmacion/${booking.id}`)}
// />

WordPress con Shortcode

Crea un shortcode para insertar el widget fácilmente en posts y páginas.

PHP (functions.php)
<?php
/**
 * Shortcode para widget de SalonBookIt
 * Uso: [salonbookit_widget service="15" staff="3"]
 */
function salonbookit_widget_shortcode($atts) {
    $atts = shortcode_atts([
        'service' => '',
        'staff' => '',
        'mode' => 'inline',
        'button_text' => 'Reservar Cita'
    ], $atts);

    // Generar ID único para el contenedor
    $container_id = 'sbk-widget-' . uniqid();

    // Construir opciones JS
    $options = [
        'apiKey' => get_option('salonbookit_api_key'),
        'container' => '#' . $container_id,
        'mode' => esc_attr($atts['mode']),
        'buttonText' => esc_attr($atts['button_text']),
        'locale' => substr(get_locale(), 0, 2)
    ];

    if (!empty($atts['service'])) {
        $options['serviceId'] = intval($atts['service']);
    }
    if (!empty($atts['staff'])) {
        $options['staffId'] = intval($atts['staff']);
    }

    $options_json = json_encode($options);

    ob_start();
    ?>
    <div id="<?php echo esc_attr($container_id); ?>"></div>
    <script>
    document.addEventListener('DOMContentLoaded', function() {
        if (window.SalonBookIt) {
            SalonBookIt.init(<?php echo $options_json; ?>);
        }
    });
    </script>
    <?php
    return ob_get_clean();
}
add_shortcode('salonbookit_widget', 'salonbookit_widget_shortcode');

// Encolar script
function salonbookit_enqueue_scripts() {
    wp_enqueue_script(
        'salonbookit-widget',
        'https://app.salonbookit.com/static/widget/v1/salonbookit-widget.min.js',
        [],
        '1.0',
        true
    );
}
add_action('wp_enqueue_scripts', 'salonbookit_enqueue_scripts');

Uso en el editor de WordPress:

Shortcode
[salonbookit_widget]

[salonbookit_widget service="15"]

[salonbookit_widget mode="button" button_text="Pedir Cita"]