cambios profundos

This commit is contained in:
2026-06-20 20:01:40 +02:00
parent 2f5f37c7b4
commit 6f7a9b2b60
21 changed files with 158 additions and 118 deletions
+125 -111
View File
@@ -3,8 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>May HairCare — Peluquería en Barcelona</title>
<meta name="description" content="May HairCare, peluquería en Horta, Barcelona. Cortes, color, peinados y tratamientos para señoras, caballeros y niños.">
<title>MAY Studio — Peluquería en Barcelona</title>
<meta name="description" content="MAY Studio, peluquería en Horta, Barcelona. Cortes, color, peinados y tratamientos para señoras, caballeros y niños.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/style.css">
@@ -14,12 +14,10 @@
<!-- NAV -->
<nav class="nav">
<div class="container nav-inner">
<a href="#" class="logo">May <span>HairCare</span></a>
<a href="#" class="logo">MAY <span>Studio</span></a>
<ul class="nav-links">
<li><a href="#servicios">Servicios</a></li>
<li><a href="#galeria">Galería</a></li>
<li><a href="#profesional">La profesional</a></li>
<li><a href="#horario">Horario</a></li>
<li><a href="#galeria">Nuestro trabajo</a></li>
<li><a href="#reservar">Reservar</a></li>
<li><a href="#contacto">Contacto</a></li>
</ul>
@@ -30,34 +28,42 @@
<!-- HERO -->
<header class="hero">
<div class="hero-bg">
<img src="/assets/hero.jpg" alt="Interior del salón May HairCare">
<img src="/assets/hero.jpg" alt="Interior del salón MAY Studio">
</div>
<div class="container hero-content">
<h1>May HairCare<br>Tu peluquería en Horta</h1>
<h1>MAY Studio<br>Tu peluquería en Horta</h1>
<p class="hero-sub">Cortes, color, peinados y tratamientos capilares para toda la familia. Un espacio pensado para ti.</p>
<div class="hero-actions">
<a href="#reservar" class="btn btn-primary">Reservar cita</a>
<a href="#servicios" class="btn btn-secondary">Ver servicios</a>
<a href="#galeria" class="btn btn-secondary">Ver nuestro trabajo</a>
</div>
</div>
</header>
<!-- SERVICIOS -->
<section id="servicios" class="section">
<!-- PROFESIONAL (Mayra) -->
<section id="profesional" class="section">
<div class="container">
<h2 class="section-title">Nuestros servicios</h2>
<p class="section-subtitle">Cuidamos de tu pelo con los mejores productos y técnicas</p>
<div class="services-grid" id="services-grid">
<p class="services-loading">Cargando servicios…</p>
<div class="profesional-grid">
<div class="profesional-img">
<img src="/assets/may.jpg" alt="Mayra, peluquera profesional en MAY Studio">
</div>
<div class="profesional-info">
<h3>Mayra</h3>
<p class="profesional-role">Peluquera &amp; fundadora</p>
<p>Soy Mayra, fundadora de MAY Studio, un espacio dedicado al cuidado del cabello con un enfoque personalizado.</p>
<p>Estoy especializada en color, mechas y cortes, buscando siempre resultados naturales, equilibrados y elegantes.</p>
<p>Cada cliente recibe una atención individualizada, analizando su cabello y su estilo para crear un resultado que sea bonito, favorecedor y fácil de mantener día a día.</p>
<p>MAY Studio nace con la idea de ofrecer un ambiente tranquilo donde el cuidado del cabello y la atención al detalle son lo más importante.</p>
</div>
</div>
</div>
</section>
<!-- GALERÍA -->
<!-- GALERÍA / Nuestro trabajo -->
<section id="galeria" class="section section-alt">
<div class="container">
<h2 class="section-title">Nuestro trabajo</h2>
<p class="section-subtitle">Algunos de los looks que hemos creado para nuestras clientas</p>
<p class="section-subtitle">Explora algunos de los estilos y transformaciones que creamos en MAY Studio. Haz clic en cualquier imagen para ampliar.</p>
<div class="gallery-grid">
<div class="gallery-item"><img src="/assets/galeria-1.jpg" alt="Trabajo de peluquería"></div>
<div class="gallery-item"><img src="/assets/galeria-2.jpg" alt="Trabajo de peluquería"></div>
@@ -69,50 +75,32 @@
</div>
</section>
<!-- PROFESIONAL -->
<section id="profesional" class="section">
<div class="container">
<div class="profesional-grid">
<div class="profesional-img">
<img src="/assets/may.jpg" alt="May, peluquera profesional en May HairCare">
</div>
<div class="profesional-info">
<h3>May</h3>
<p class="profesional-role">Peluquera &amp; fundadora</p>
<p>Con más de 15 años de experiencia en peluquería profesional, May ha hecho del cuidado del cabello su vocación. Especializada en coloración, corte y tratamientos capilares, trabaja con técnicas actuales y productos de alta calidad para ofrecer resultados duraderos. Su trato cercano y la atención personalizada a cada cliente son la base de May HairCare.</p>
</div>
</div>
</div>
</section>
<!-- HORARIO -->
<section id="horario" class="section section-alt">
<div class="container">
<h2 class="section-title">Horario</h2>
<div class="schedule">
<div class="schedule-row">
<span>Lunes a Viernes</span>
<span>9:30 — 13:30 / 16:30 — 20:30</span>
</div>
<div class="schedule-row">
<span>Sábados</span>
<span>9:00 — 14:00</span>
</div>
<div class="schedule-row closed">
<span>Domingos y festivos</span>
<span>Cerrado</span>
</div>
</div>
<p class="schedule-note">Llámanos o reserva online. Te atendemos con o sin cita.</p>
</div>
</section>
<!-- RESERVAR -->
<section id="reservar" class="section">
<div class="container">
<h2 class="section-title">Reservar cita</h2>
<p class="section-subtitle">Elige uno o varios servicios, día y hora para tu cita</p>
<!-- Horario integrado -->
<div style="max-width: 500px; margin: 0 auto 32px;">
<h3 style="font-family: 'DM Serif Display', serif; font-size: 1.1rem; text-align: center; margin-bottom: 12px;">Horario</h3>
<div class="schedule">
<div class="schedule-row">
<span>Lunes a Viernes</span>
<span>9:30 — 13:30 / 16:30 — 20:30</span>
</div>
<div class="schedule-row">
<span>Sábados</span>
<span>9:00 — 14:00</span>
</div>
<div class="schedule-row closed">
<span>Domingos y festivos</span>
<span>Cerrado</span>
</div>
</div>
<p class="schedule-note">Llámanos o reserva online. Te atendemos con o sin cita.</p>
</div>
<!-- Service selector -->
<div class="service-selector" id="service-selector">
<p class="services-loading">Cargando servicios…</p>
@@ -138,7 +126,7 @@
</div>
<!-- Right: slots + contact form -->
<div class="cal-right">
<div id="slots-placeholder" class="slots-placeholder">Selecciona primero el servicio que deseas</div>
<div id="slots-placeholder" class="slots-placeholder">Selecciona primero el/los servicio(s) que deseas</div>
<div id="slots-container" style="display:none">
<h3 class="slots-title" id="slots-title"></h3>
<div class="slots-grid" id="slots-grid"></div>
@@ -195,7 +183,7 @@
<iframe
src="https://www.openstreetmap.org/export/embed.html?bbox=2.1550%2C41.4380%2C2.1650%2C41.4430&layer=mapnik&marker=41.4405%2C2.1600"
width="100%" height="300" style="border:0; border-radius: 12px;"
loading="lazy" title="Mapa May HairCare">
loading="lazy" title="Mapa MAY Studio">
</iframe>
</div>
</div>
@@ -205,8 +193,8 @@
<!-- FOOTER -->
<footer class="footer">
<div class="container footer-inner">
<span class="logo">May <span>HairCare</span></span>
<p>&copy; 2026 May HairCare. Tu peluquería en Horta.</p>
<span class="logo">MAY <span>Studio</span></span>
<p>&copy; 2026 MAY Studio. Tu peluquería en Horta.</p>
</div>
</footer>
@@ -221,59 +209,94 @@
const PROVIDER_ID = 1;
// ---- Service selector state ----
let selectedCategory = null;
let selectedServices = []; // [{id, nombre, duracion}]
let totalDuration = 0;
let allServices = []; // cache de servicios EA
// ---- Services (ahora desde EasyAppointments) ----
async function loadServices() {
const grid = document.getElementById('services-grid');
// ---- Categories and Services from EasyAppointments ----
async function loadCategories() {
try {
const res = await fetch('/ea-api/v1/services');
if (!res.ok) throw new Error('HTTP ' + res.status);
const services = await res.json();
let res = await fetch('/ea-api/v1/categories');
if (!res.ok) {
res = await fetch('/ea-api/v1/service_categories');
}
if (!res.ok) return [];
return await res.json();
} catch (e) {
console.warn('No se pudieron cargar las categorías de EA');
return [];
}
}
async function loadServices() {
const selectorContainer = document.getElementById('service-selector');
try {
const [servicesRes, categories] = await Promise.all([
fetch('/ea-api/v1/services'),
loadCategories()
]);
if (!servicesRes.ok) throw new Error('HTTP ' + servicesRes.status);
const services = await servicesRes.json();
allServices = services;
// Agrupamos en una categoría simple (puedes crear categorías en EA y extender esto)
const fakeCategorias = [{
nombre: "Servicios",
imagen: "",
servicios: services.map(s => ({
// Filter services to those assigned to this provider
let displayServices = services;
try {
const provRes = await fetch(`/ea-api/v1/providers/${PROVIDER_ID}`);
if (provRes.ok) {
const prov = await provRes.json();
const allowed = (prov.services || prov.service_ids || []).map(id => parseInt(id));
if (allowed.length > 0) {
displayServices = services.filter(s => allowed.includes(parseInt(s.id)));
}
}
} catch(e) {}
const catMap = {};
(categories || []).forEach(cat => {
catMap[cat.id] = cat;
});
const grouped = {};
displayServices.forEach(s => {
const catId = s.categoryId ?? s.serviceCategoryId ?? 'uncat';
if (!grouped[catId]) {
const catInfo = catMap[catId] || {};
grouped[catId] = {
nombre: catInfo.name || catInfo.title || 'Otros servicios',
imagen: '',
servicios: []
};
}
grouped[catId].servicios.push({
id: s.id,
nombre: s.name,
precio: (parseFloat(s.price) || 0) + " €",
duracion: s.duration || 30
}))
}];
});
});
renderServices(fakeCategorias, grid);
let categorias = Object.values(grouped);
// Sort categories alphabetically for consistent order
categorias.sort((a, b) => a.nombre.localeCompare(b.nombre, 'es'));
// Sort services inside each category
categorias.forEach(cat => {
cat.servicios.sort((a, b) => a.nombre.localeCompare(b.nombre, 'es'));
});
if (categorias.length === 0 || displayServices.length === 0) {
selectorContainer.innerHTML = '<p class="services-loading">No hay servicios asignados para este proveedor o categorías.</p>';
return;
}
renderServiceSelector(categorias);
} catch (e) {
console.error(e);
grid.innerHTML = '<p class="services-loading">No se pudieron cargar los servicios desde EasyAppointments.</p>';
if (selectorContainer) selectorContainer.innerHTML = '<p class="services-loading">No se pudieron cargar los servicios desde EasyAppointments.</p>';
}
}
function renderServices(categorias, grid) {
grid.innerHTML = categorias.map(cat => `
<div class="service-card">
<div class="service-img">
<img src="${esc(cat.imagen)}" alt="${esc(cat.nombre)}">
</div>
<div class="service-body">
<h3>${esc(cat.nombre)}</h3>
<ul class="service-list">
${cat.servicios.map(s => `
<li><span>${esc(s.nombre)}</span><span class="price">${esc(s.precio)}</span></li>
`).join('')}
</ul>
</div>
</div>
`).join('');
renderServiceSelector(categorias);
}
function renderServiceSelector(categorias) {
const container = document.getElementById('service-selector');
container.innerHTML = categorias.map(cat => `
@@ -296,7 +319,10 @@
`).join('');
// Listener se añade una vez desde loadServices
container.addEventListener('change', handleServiceChange);
if (!container.dataset.listenerAttached) {
container.addEventListener('change', handleServiceChange);
container.dataset.listenerAttached = 'true';
}
}
function handleServiceChange(e) {
@@ -315,11 +341,7 @@
totalDuration += parseInt(cb.dataset.duracion) || 30;
});
// Lock to one category
const first = document.querySelector('#service-selector input[type=checkbox]:checked');
selectedCategory = first ? first.dataset.cat : null;
updateServiceDisabledState();
// No lock to category anymore - services from different categories can be selected
updateServiceSummary();
if (selectedDate) {
@@ -331,19 +353,11 @@
} else {
const ph = document.getElementById('slots-placeholder');
ph.textContent = selectedServices.length === 0
? 'Selecciona primero el servicio que deseas'
? 'Selecciona primero el/los servicio(s) que deseas'
: 'Selecciona un día en el calendario';
}
}
function updateServiceDisabledState() {
document.querySelectorAll('#service-selector .svc-cat-card').forEach(card => {
const locked = selectedCategory !== null && card.dataset.cat !== selectedCategory;
card.classList.toggle('svc-cat-locked', locked);
card.querySelectorAll('input[type=checkbox]').forEach(cb => { cb.disabled = locked; });
});
}
function updateServiceSummary() {
const bar = document.getElementById('service-summary');
if (selectedServices.length === 0) { bar.style.display = 'none'; return; }
@@ -582,7 +596,7 @@
lastName: lastName,
email: email,
phone: telefono || '',
notes: 'Creado desde web May HairCare'
notes: 'Creado desde web MAY Studio'
})
});