// ============================================================
// OSCILADOR — Servicios + Membresías (3 variantes c/u)
// ============================================================
// ── Opciones del selector de reservas (BookingOverlay) ──
const BOOKING_OPTIONS = [
{ tag: "PRÁCTICA", title: "Práctica DJ · 1h", price: "Bs 100", unit: "/hora", detail: "1 hora · CDJ-3000 × 2 + DJM-A9", cta: "Reservar 1 hora", href: CAL_LINKS.practicaDj1h },
{ tag: "PRÁCTICA", title: "Práctica DJ · 2h", price: "Bs 200", unit: "/sesión", detail: "2 horas continuas · ideal para sets largos", cta: "Reservar 2 horas", href: CAL_LINKS.practicaDj2h },
{ tag: "PRÁCTICA", title: "Práctica DJ · 2 personas · 1h", price: "Bs 150", unit: "/hora", detail: "1 hora · 2 personas juntas · CDJ-3000 × 2 + DJM-A9", cta: "Reservar para 2", href: CAL_LINKS.practicaDj2p },
{ tag: "CLASE", title: "Clase / Sesión guiada", price: "Bs 250", unit: "/hora", detail: "1 hora con instructor, técnica y feedback", cta: "Reservar clase", href: CAL_LINKS.claseSesionGuiada },
{ tag: "GRABACIÓN", title: "Grabación de set + máster", price: "Bs 200", unit: "/set", detail: "Grabación audio profesional + mastering · máx 2h", cta: "Reservar grabación", href: CAL_LINKS.grabacionSet },
{ tag: "MEMBRESÍA", title: "Membresía / Plan mensual", price: "Desde Bs 800", unit: "/mes", detail: "4–20 horas por mes · reserva con prioridad", cta: "Consultar por WhatsApp", href: "https://wa.me/59167165251?text=Hola%2C%20quiero%20consultar%20sobre%20membresías" },
];
const SERVICIOS_DATA = [
{
tag: "PRÁCTICA",
bookingTag: "PRÁCTICA",
price: "Bs 100",
unit: "/hora",
title: "Sala de práctica",
body: "CDJ-3000 × 2 + DJM-A9. 1 persona Bs 100/h — 2 personas juntas Bs 150/h.",
schedule: "LUN–VIE · 11:00–19:00",
cta: "Reservar sala",
},
{
tag: "CLASES",
bookingTag: "CLASE",
price: "Bs 250",
unit: "/hora",
title: "Clase personal",
body: "Una hora con el instructor. Trabajás tu set en vivo, identificás lo que suena mal y lo corregís en el momento.",
schedule: "LUN–VIE · 11:00–19:00",
cta: "Reservar clase",
},
{
tag: "GRUPO",
bookingTag: null,
price: "Bs 1.200",
unit: "/mes",
title: "Curso del Mes",
body: "Cada mes, un instructor distinto. Grupos reducidos, cupos limitados. Precio de lanzamiento · 20% off.",
schedule: "LUN · MIÉ · VIE — POR CONVOCATORIA",
cta: "Consultar disponibilidad",
},
{
tag: "GRABACIÓN",
bookingTag: "GRABACIÓN",
price: "Bs 200",
unit: "/set",
title: "Grabación de set",
body: "Grabación de audio profesional de tu set + mastering. Máximo 2 horas por sesión.",
schedule: "LUN–VIE · CON RESERVA PREVIA",
cta: "Reservar grabación",
},
{
tag: "ALQUILER",
bookingTag: null,
price: "Bs 2.100",
unit: "/noche",
title: "Equipos para evento",
body: "Todo el equipo para tu evento. Entrega y retirada coordinada.",
schedule: "JUE / VIE / SÁB · 20:00–03:00",
cta: "Cotizar alquiler",
},
];
function SvcV1Cards() {
return (
);
}
function SvcV2Rows() {
// Rider-style horizontal rows — denser, more technical
return (
{SERVICIOS_DATA.map((s, i) => (
0{i + 1} · {s.tag}
{s.title}
{s.body}
{s.schedule}
))}
);
}
function SvcV3Numbered() {
// Numbered minimal: huge prices, sparse type
return (
{SERVICIOS_DATA.map((s, i) => (
0{i + 1}
{s.title}
{s.body}
))}
);
}
function Servicios({ variant }) {
if (variant === "rows") return ;
if (variant === "numbered") return ;
return ;
}
// ============================================================
// MEMBRESÍAS
// ============================================================
const MEMBERSHIP_DATA = [
{
tag: "STARTER",
price: "Bs 800",
unit: "/mes",
desc: "4 clases personales por mes.",
bullets: [
"4× clases 1 a 1 (1h c/u)",
"Acceso a sala con instructor",
"Cancela cuando quieras",
],
},
{
tag: "BUILDER",
price: "Bs 1.000",
unit: "/mes",
desc: "2 clases + 8 horas de sala por mes.",
featured: true,
bullets: [
"2× clases personales (1h c/u)",
"8h de sala libre por mes",
"Reserva con prioridad",
"20% off el primer mes en sala",
],
},
{
tag: "RESIDENTE",
price: "Bs 1.500",
unit: "/mes",
desc: "20 horas de sala por mes.",
bullets: [
"20h de sala libre por mes",
"Reserva con prioridad",
"Acceso a horarios extendidos",
],
},
];
function MemCard({ plan, i }) {
return (
{plan.featured && (
MÁS ELEGIDO
)}
{plan.tag}
0{i + 1}/03
{plan.price}
{plan.unit}
{plan.desc}
{plan.bullets.map((b) => (
—
{b}
))}
Consultar {plan.tag}
);
}
function MemV1Grid() {
return (
{MEMBERSHIP_DATA.map((plan, i) => (
))}
);
}
function MemV2Stage() {
// BUILDER center-stage bigger
return (
{MEMBERSHIP_DATA.map((plan, i) => (
))}
);
}
function MemV3Table() {
// Comparison-table rider
return (
{/* spec col */}
{MEMBERSHIP_DATA.map((p) => (
{p.tag}
{p.featured &&
MÁS ELEGIDO }
{p.price}{p.unit}
{p.desc}
))}
{["Clases personales", "Horas de sala", "Reserva prioritaria", "Cupo"].map((row, ri) => (
{row}
{MEMBERSHIP_DATA.map((p) => {
let v = "—";
if (row === "Clases personales") v = p.tag === "STARTER" ? "4 / mes" : p.tag === "BUILDER" ? "2 / mes" : "—";
if (row === "Horas de sala") v = p.tag === "STARTER" ? "—" : p.tag === "BUILDER" ? "8h / mes" : "20h / mes";
if (row === "Reserva prioritaria") v = p.tag === "STARTER" ? "—" : "Sí";
if (row === "Cupo") v = "Limitado";
return (
{v}
);
})}
))}
{MEMBERSHIP_DATA.map((p) => (
))}
);
}
function Membresias({ variant }) {
if (variant === "stage") return ;
if (variant === "table") return ;
return ;
}
// ============================================================
// BOOKING OVERLAY — Selector de reservas (CLA-37 + CLA-38)
// ============================================================
function CalBookingPanel({ opt, onClose, onBack }) {
const [bookingData, setBookingData] = React.useState(null);
React.useEffect(() => {
function handleMessage(e) {
try {
if (!e.origin.includes("cal.com")) return;
const msg = typeof e.data === "string" ? JSON.parse(e.data) : e.data;
if (!msg) return;
console.log("[Cal.com postMessage]", msg.type || msg.action || "(sin type)", msg);
const msgType = (msg.type || msg.action || "").toLowerCase();
const isBooking = msgType === "bookingsuccessful" || msgType === "bookingsuccessfulv2";
if (!isBooking) return;
const booking = msg?.data?.booking || msg?.data || {};
console.log("[Cal booking object COMPLETO]", JSON.stringify(msg.data));
const startTime =
booking.startTime || booking.start_time || booking.startAt ||
booking.start || msg?.data?.startTime || msg?.data?.start_time || "";
let formattedTime = "";
if (startTime) {
try {
formattedTime = new Date(startTime).toLocaleString("es-419", {
weekday: "long", day: "numeric", month: "long",
hour: "2-digit", minute: "2-digit",
});
} catch (_) { formattedTime = startTime; }
}
const waText = formattedTime
? `Hola, acabo de reservar ${opt.title} en Oscilador Academy para el ${formattedTime}. ¿Me enviás el QR de pago?`
: `Hola, acabo de reservar ${opt.title} en Oscilador Academy. ¿Me enviás el QR de pago?`;
setBookingData({ formattedTime, waText });
} catch (_) {}
}
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
const embedUrl = `${opt.href}?embed=true&layout=month_view`;
const fallbackWaText = `Hola, acabo de reservar ${opt.title} en Oscilador. ¿Me enviás el QR de pago?`;
if (bookingData) {
return (
e.stopPropagation()}>
RESERVA
CONFIRMADA ✓
{opt.title}
{bookingData.formattedTime && (
{bookingData.formattedTime}
)}
✕
Tu hora quedó en el calendario. Ahora confirmá el cupo enviando este mensaje — te mandamos el QR de pago.
QR O TRANSFERENCIA · CONFIRMACIÓN EN MENOS DE 24H
);
}
return (
e.stopPropagation()}>
RESERVAR
{opt.tag}
{opt.title}
{opt.price}
✕
);
}
function BookingOverlay() {
const [open, setOpen] = React.useState(false);
const [selectedOpt, setSelectedOpt] = React.useState(null);
const [filteredOptions, setFilteredOptions] = React.useState(null);
React.useEffect(() => {
const handler = (e) => {
setOpen(true);
const tag = e.detail?.tag;
if (tag) {
const filtered = BOOKING_OPTIONS.filter(o => o.tag === tag);
if (filtered.length === 1) {
setSelectedOpt(filtered[0]);
} else if (filtered.length > 1) {
setFilteredOptions(filtered);
}
}
};
window.addEventListener("openBooking", handler);
return () => window.removeEventListener("openBooking", handler);
}, []);
React.useEffect(() => {
document.body.style.overflow = open ? "hidden" : "";
return () => { document.body.style.overflow = ""; };
}, [open]);
function handleClose() { setOpen(false); setSelectedOpt(null); setFilteredOptions(null); }
if (!open) return null;
if (selectedOpt) {
return (
setSelectedOpt(null)}
/>
);
}
return (
e.stopPropagation()}>
RESERVAS
ELIGE TU SERVICIO
¿Qué quieres reservar?
✕
IMPORTANTE · LEER ANTES DE CONTINUAR
La solicitud no confirma el cupo .
El cupo queda confirmado solo después de validar el pago por QR o transferencia.
Oscilador te enviará los datos por WhatsApp. No hay pago por tarjeta en la web.
CONFIRMACIÓN EN MENOS DE 24H · SIN PAGO POR TARJETA · QR VÍA WHATSAPP
);
}
// ============================================================
// BOOKING CONFIRMED — Modal post-cal.com (detecta ?booked=1 en URL)
// ============================================================
const BOOKING_SERVICE_NAMES = {
'practica-1h': 'Práctica DJ · 1h',
'practica-2h': 'Práctica DJ · 2h',
'practica-2p': 'Práctica DJ · 2 personas',
'clase': 'Clase / Sesión guiada',
'grabacion': 'Grabación de set',
};
function BookingConfirmed() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
const params = new URLSearchParams(window.location.search);
if (params.get('booked') !== '1') return;
const service = params.get('service') || '';
const timeRaw = params.get('time') || '';
const name = params.get('name') || '';
let formattedTime = '';
if (timeRaw) {
try {
formattedTime = new Date(timeRaw).toLocaleString('es-419', {
weekday: 'long', day: 'numeric', month: 'long',
hour: '2-digit', minute: '2-digit',
});
} catch (_) {
formattedTime = timeRaw;
}
}
setData({ service, formattedTime, name });
window.history.replaceState({}, '', window.location.pathname);
}, []);
if (!data) return null;
const serviceName = BOOKING_SERVICE_NAMES[data.service] || 'tu reserva';
const waText = data.formattedTime
? `Hola, reservé ${serviceName} para el ${data.formattedTime}. ¿Me enviás el QR de pago?`
: `Hola, reservé ${serviceName} en Oscilador. ¿Me enviás el QR de pago?`;
return (
setData(null)}>
e.stopPropagation()}>
RESERVA
HORA REGISTRADA
{serviceName}
{data.formattedTime && (
{data.formattedTime}
)}
setData(null)} aria-label="Cerrar">✕
Tu hora quedó en el calendario. Escríbenos por WhatsApp y te mandamos el QR.
QR O TRANSFERENCIA · CONFIRMACIÓN EN MENOS DE 24H
);
}
Object.assign(window, { Servicios, Membresias, BookingOverlay, BookingConfirmed });