{"id":14311,"date":"2026-03-24T12:05:56","date_gmt":"2026-03-24T12:05:56","guid":{"rendered":"https:\/\/centremompo.es\/acceder\/?p=14311"},"modified":"2026-03-24T12:21:56","modified_gmt":"2026-03-24T12:21:56","slug":"14311-2","status":"publish","type":"post","link":"https:\/\/centremompo.es\/acceder\/14311-2\/","title":{"rendered":"Temporizador"},"content":{"rendered":"<p><!DOCTYPE html><br \/>\n<html lang=\"es\"><br \/>\n<head><br \/>\n<meta charset=\"UTF-8\"><br \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><br \/>\n<title>Calculadora de Cambios \u2014 Centre Momp\u00f3<\/title>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=DM+Serif+Display&#038;family=DM+Sans:wght@400;500;600;700&#038;display=swap\" rel=\"stylesheet\">\n<style>\n  :root {\n    --bg: #FAF6F1;\n    --card: #FFFFFF;\n    --accent: #B8860B;\n    --accent-light: #D4A843;\n    --accent-bg: #F5EDDF;\n    --text: #2C2418;\n    --text-muted: #8A7B6B;\n    --border: #E8DFD2;\n    --session-a: #E8F0E4;\n    --session-b: #E4E8F0;\n    --pause: #FFF3E0;\n    --danger: #C0392B;\n    --success: #27AE60;\n    --shadow: 0 2px 16px rgba(44,36,24,0.07);\n  }\n  * { margin: 0; padding: 0; box-sizing: border-box; }\n  body {\n    font-family: 'DM Sans', sans-serif;\n    background: var(--bg);\n    color: var(--text);\n    min-height: 100vh;\n    padding: 24px 16px;\n  }\n  .container { max-width: 520px; margin: 0 auto; }\n  .header { text-align: center; margin-bottom: 32px; }\n  .header h1 {\n    font-family: 'DM Serif Display', serif;\n    font-size: 1.8rem;\n    color: var(--text);\n    letter-spacing: -0.02em;\n    margin-bottom: 6px;\n  }\n  .header p { color: var(--text-muted); font-size: 0.92rem; }<\/p>\n<p>  .controls {\n    background: var(--card);\n    border-radius: 16px;\n    padding: 28px 24px;\n    box-shadow: var(--shadow);\n    border: 1px solid var(--border);\n    margin-bottom: 24px;\n  }\n  .control-row {\n    display: flex;\n    gap: 16px;\n    margin-bottom: 16px;\n  }\n  .control-row:last-child { margin-bottom: 0; }\n  .control-group {\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n  }\n  .control-group label {\n    font-size: 0.78rem;\n    font-weight: 600;\n    color: var(--text-muted);\n    text-transform: uppercase;\n    letter-spacing: 0.06em;\n  }\n  .control-group input,\n  .control-group select {\n    font-family: 'DM Sans', sans-serif;\n    font-size: 1.1rem;\n    font-weight: 500;\n    padding: 12px 14px;\n    border: 1.5px solid var(--border);\n    border-radius: 10px;\n    background: var(--bg);\n    color: var(--text);\n    transition: border-color 0.2s, box-shadow 0.2s;\n    outline: none;\n    -webkit-appearance: none;\n    appearance: none;\n  }\n  .control-group input:focus,\n  .control-group select:focus {\n    border-color: var(--accent);\n    box-shadow: 0 0 0 3px rgba(184,134,11,0.12);\n  }\n  .control-group select {\n    background-image: url(\"data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%238A7B6B' stroke-width='1.5' fill='none'\/%3E%3C\/svg%3E\");\n    background-repeat: no-repeat;\n    background-position: right 14px center;\n    padding-right: 36px;\n  }<\/p>\n<p>  \/* Active anchor highlight *\/\n  .control-group input.anchor {\n    border-color: var(--accent);\n    background: var(--accent-bg);\n  }\n  .anchor-hint {\n    font-size: 0.72rem;\n    color: var(--accent);\n    font-weight: 500;\n    margin-top: 2px;\n    min-height: 16px;\n  }<\/p>\n<p>  .computed-result {\n    text-align: center;\n    font-size: 0.9rem;\n    color: var(--accent);\n    font-weight: 600;\n    background: var(--accent-bg);\n    padding: 10px 16px;\n    border-radius: 10px;\n    margin-top: 16px;\n  }<\/p>\n<p>  \/* Toggle *\/\n  .toggle-row {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    padding: 14px 0;\n    border-top: 1px solid var(--border);\n    margin-top: 16px;\n  }\n  .toggle-label {\n    font-size: 0.9rem;\n    font-weight: 500;\n    color: var(--text);\n    display: flex;\n    flex-direction: column;\n    gap: 2px;\n  }\n  .toggle-label small {\n    font-size: 0.78rem;\n    font-weight: 400;\n    color: var(--text-muted);\n  }\n  .toggle-switch {\n    position: relative;\n    width: 48px;\n    height: 28px;\n    flex-shrink: 0;\n  }\n  .toggle-switch input { opacity: 0; width: 0; height: 0; }\n  .toggle-slider {\n    position: absolute;\n    inset: 0;\n    background: var(--border);\n    border-radius: 28px;\n    cursor: pointer;\n    transition: background 0.25s;\n  }\n  .toggle-slider::before {\n    content: '';\n    position: absolute;\n    width: 22px; height: 22px;\n    left: 3px; top: 3px;\n    background: #FFF;\n    border-radius: 50%;\n    transition: transform 0.25s;\n    box-shadow: 0 1px 4px rgba(0,0,0,0.15);\n  }\n  .toggle-switch input:checked + .toggle-slider { background: var(--accent); }\n  .toggle-switch input:checked + .toggle-slider::before { transform: translateX(20px); }<\/p>\n<p>  .duration-pair {\n    display: flex;\n    gap: 16px;\n    margin-top: 12px;\n  }\n  .duration-pair.hidden { display: none; }<\/p>\n<p>  \/* Timeline *\/\n  .timeline {\n    background: var(--card);\n    border-radius: 16px;\n    padding: 24px;\n    box-shadow: var(--shadow);\n    border: 1px solid var(--border);\n    margin-bottom: 24px;\n  }\n  .timeline-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 20px;\n    padding-bottom: 14px;\n    border-bottom: 1px solid var(--border);\n    flex-wrap: wrap;\n    gap: 8px;\n  }\n  .timeline-header h2 {\n    font-family: 'DM Serif Display', serif;\n    font-size: 1.2rem;\n    color: var(--text);\n  }\n  .total-time {\n    font-size: 0.82rem;\n    font-weight: 600;\n    color: var(--accent);\n    background: var(--accent-bg);\n    padding: 5px 12px;\n    border-radius: 20px;\n  }\n  .timeline-item {\n    display: flex;\n    align-items: stretch;\n    gap: 16px;\n    margin-bottom: 4px;\n  }\n  .timeline-time {\n    width: 56px;\n    flex-shrink: 0;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    padding-top: 14px;\n  }\n  .timeline-time span {\n    font-size: 0.92rem;\n    font-weight: 700;\n    color: var(--text);\n    font-variant-numeric: tabular-nums;\n  }\n  .timeline-line {\n    width: 2px;\n    flex-grow: 1;\n    background: var(--border);\n    margin-top: 6px;\n  }\n  .timeline-card {\n    flex: 1;\n    padding: 14px 16px;\n    border-radius: 10px;\n    border-left: 3.5px solid transparent;\n    transition: transform 0.15s ease;\n  }\n  .timeline-card:hover { transform: translateX(3px); }\n  .timeline-card.session-a { background: var(--session-a); border-left-color: #6DAF5E; }\n  .timeline-card.session-b { background: var(--session-b); border-left-color: #5E7FAF; }\n  .timeline-card.pause { background: var(--pause); border-left-color: #E09944; padding: 8px 16px; }\n  .timeline-card.active {\n    animation: pulse-glow 1.8s ease-in-out infinite;\n    box-shadow: 0 0 0 2px var(--accent);\n  }\n  @keyframes pulse-glow {\n    0%, 100% { box-shadow: 0 0 0 2px var(--accent); }\n    50% { box-shadow: 0 0 0 4px rgba(184,134,11,0.25); }\n  }\n  .card-title { font-weight: 600; font-size: 0.95rem; margin-bottom: 2px; }\n  .card-detail { font-size: 0.82rem; color: var(--text-muted); }\n  .pause .card-title { font-size: 0.82rem; font-weight: 500; color: var(--text-muted); }<\/p>\n<p>  \/* Alert *\/\n  .alert-section {\n    background: var(--card);\n    border-radius: 16px;\n    padding: 24px;\n    box-shadow: var(--shadow);\n    border: 1px solid var(--border);\n    text-align: center;\n  }\n  .alert-btn {\n    font-family: 'DM Sans', sans-serif;\n    font-size: 1rem;\n    font-weight: 600;\n    padding: 14px 32px;\n    border: none;\n    border-radius: 12px;\n    cursor: pointer;\n    transition: all 0.2s ease;\n    display: inline-flex;\n    align-items: center;\n    gap: 8px;\n  }\n  .alert-btn.start { background: var(--accent); color: #FFF; }\n  .alert-btn.start:hover {\n    background: var(--accent-light);\n    transform: translateY(-1px);\n    box-shadow: 0 4px 12px rgba(184,134,11,0.25);\n  }\n  .alert-btn.stop { background: var(--danger); color: #FFF; }\n  .alert-btn.stop:hover { background: #E74C3C; }\n  .alert-status { margin-top: 14px; font-size: 0.85rem; color: var(--text-muted); }\n  .alert-status.active { color: var(--success); font-weight: 600; }\n  .current-time {\n    font-variant-numeric: tabular-nums;\n    font-size: 2rem;\n    font-weight: 700;\n    color: var(--text);\n    margin-bottom: 16px;\n    letter-spacing: -0.02em;\n  }<\/p>\n<p>  \/* Notification *\/\n  .notification-overlay {\n    display: none;\n    position: fixed;\n    inset: 0;\n    background: rgba(44,36,24,0.6);\n    backdrop-filter: blur(4px);\n    z-index: 1000;\n    justify-content: center;\n    align-items: center;\n  }\n  .notification-overlay.show { display: flex; }\n  .notification-box {\n    background: var(--card);\n    border-radius: 20px;\n    padding: 40px 36px;\n    text-align: center;\n    box-shadow: 0 20px 60px rgba(0,0,0,0.2);\n    max-width: 380px;\n    width: 90%;\n    animation: popIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);\n  }\n  @keyframes popIn {\n    from { transform: scale(0.8); opacity: 0; }\n    to { transform: scale(1); opacity: 1; }\n  }\n  .notification-icon { font-size: 3rem; margin-bottom: 16px; }\n  .notification-title { font-family: 'DM Serif Display', serif; font-size: 1.4rem; margin-bottom: 8px; }\n  .notification-detail { color: var(--text-muted); margin-bottom: 24px; font-size: 0.95rem; white-space: pre-line; }\n  .notification-dismiss {\n    font-family: 'DM Sans', sans-serif;\n    font-size: 1rem;\n    font-weight: 600;\n    padding: 12px 36px;\n    background: var(--accent);\n    color: #FFF;\n    border: none;\n    border-radius: 10px;\n    cursor: pointer;\n  }\n  .notification-dismiss:hover { background: var(--accent-light); }<\/p>\n<p>  @media (max-width: 480px) {\n    .control-row, .duration-pair { flex-direction: column; gap: 12px; }\n    .header h1 { font-size: 1.5rem; }\n    .controls { padding: 20px 18px; }\n    .timeline { padding: 18px; }\n  }\n<\/style>\n<p><\/head><br \/>\n<body><\/p>\n<div class=\"container\">\n<div class=\"header\">\n<h1>Calculadora de Cambios<\/h1>\n<p>Planifica los turnos de pr\u00e1ctica entre masajistas<\/p>\n<\/p><\/div>\n<div class=\"controls\">\n<div class=\"control-row\">\n<div class=\"control-group\">\n        <label>Hora inicio<\/label><br \/>\n        <input type=\"time\" id=\"startTime\" placeholder=\"--:--\"><\/p>\n<div class=\"anchor-hint\" id=\"startHint\"><\/div>\n<\/p><\/div>\n<div class=\"control-group\">\n        <label>Hora final<\/label><br \/>\n        <input type=\"time\" id=\"endTime\" value=\"13:30\"><\/p>\n<div class=\"anchor-hint\" id=\"endHint\">Referencia \u25b8<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div class=\"control-row\">\n<div class=\"control-group\">\n        <label>N\u00ba de cambios<\/label><br \/>\n        <select id=\"numCambios\"><option value=\"1\">1 cambio<\/option><option value=\"2\" selected>2 cambios<\/option><option value=\"3\">3 cambios<\/option><option value=\"4\">4 cambios<\/option><option value=\"5\">5 cambios<\/option><\/select>\n      <\/div>\n<div class=\"control-group\">\n        <label>Pausa entre turnos<\/label><br \/>\n        <select id=\"pauseDuration\"><option value=\"1\">1 min<\/option><option value=\"2\" selected>2 min<\/option><option value=\"3\">3 min<\/option><option value=\"5\">5 min<\/option><\/select>\n      <\/div>\n<\/p><\/div>\n<p>    <!-- Toggle --><\/p>\n<div class=\"toggle-row\">\n<div class=\"toggle-label\">\n        Primer cambio m\u00e1s breve<br \/>\n        <small>La primera sesi\u00f3n de cada cambio dura menos<\/small>\n      <\/div>\n<p>      <label class=\"toggle-switch\"><br \/>\n        <input type=\"checkbox\" id=\"shortFirstToggle\" checked><br \/>\n        <span class=\"toggle-slider\"><\/span><br \/>\n      <\/label>\n    <\/div>\n<div class=\"duration-pair\" id=\"dualDuration\">\n<div class=\"control-group\">\n        <label>Primer cambio (min)<\/label><br \/>\n        <select id=\"firstDuration\"><option value=\"10\">10 min<\/option><option value=\"15\" selected>15 min<\/option><option value=\"20\">20 min<\/option><option value=\"25\">25 min<\/option><option value=\"30\">30 min<\/option><\/select>\n      <\/div>\n<div class=\"control-group\">\n        <label>Segundo cambio (min)<\/label><br \/>\n        <select id=\"secondDuration\"><option value=\"15\">15 min<\/option><option value=\"20\" selected>20 min<\/option><option value=\"25\">25 min<\/option><option value=\"30\">30 min<\/option><option value=\"40\">40 min<\/option><option value=\"45\">45 min<\/option><option value=\"60\">60 min<\/option><\/select>\n      <\/div>\n<\/p><\/div>\n<div class=\"duration-pair hidden\" id=\"singleDuration\">\n<div class=\"control-group\">\n        <label>Minutos por sesi\u00f3n<\/label><br \/>\n        <select id=\"sessionDuration\"><option value=\"10\">10 min<\/option><option value=\"15\">15 min<\/option><option value=\"20\" selected>20 min<\/option><option value=\"25\">25 min<\/option><option value=\"30\">30 min<\/option><option value=\"40\">40 min<\/option><option value=\"45\">45 min<\/option><option value=\"60\">60 min<\/option><\/select>\n      <\/div>\n<\/p><\/div>\n<div class=\"computed-result\" id=\"computedResult\">\u2014<\/div>\n<\/p><\/div>\n<div class=\"timeline\" id=\"timeline\"><\/div>\n<div class=\"alert-section\">\n<div class=\"current-time\" id=\"currentTime\">&#8211;:&#8211;:&#8211;<\/div>\n<p>    <button class=\"alert-btn start\" id=\"alertBtn\" onclick=\"toggleAlerts()\"><br \/>\n      <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"\/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"\/><\/svg><br \/>\n      Activar avisos<br \/>\n    <\/button><\/p>\n<div class=\"alert-status\" id=\"alertStatus\">Los avisos sonar\u00e1n al inicio de cada cambio<\/div>\n<\/p><\/div>\n<\/div>\n<div class=\"notification-overlay\" id=\"notifOverlay\">\n<div class=\"notification-box\">\n<div class=\"notification-icon\" id=\"notifIcon\"><\/div>\n<div class=\"notification-title\" id=\"notifTitle\">\u00a1Cambio!<\/div>\n<div class=\"notification-detail\" id=\"notifDetail\"><\/div>\n<p>    <button class=\"notification-dismiss\" onclick=\"dismissNotification()\">Entendido<\/button>\n  <\/div>\n<\/div>\n<p><script>\n  let alertsActive = false;\n  let alertTimers = [];\n  let audioCtx = null;\n  let anchor = 'end'; \/\/ 'start' or 'end' \u2014 which field is the reference<\/p>\n<p>  const startTimeEl = document.getElementById('startTime');\n  const endTimeEl = document.getElementById('endTime');\n  const startHint = document.getElementById('startHint');\n  const endHint = document.getElementById('endHint');\n  const numCambiosEl = document.getElementById('numCambios');\n  const pauseDurationEl = document.getElementById('pauseDuration');\n  const shortFirstToggle = document.getElementById('shortFirstToggle');\n  const firstDurationEl = document.getElementById('firstDuration');\n  const secondDurationEl = document.getElementById('secondDuration');\n  const sessionDurationEl = document.getElementById('sessionDuration');\n  const dualDurationEl = document.getElementById('dualDuration');\n  const singleDurationEl = document.getElementById('singleDuration');\n  const computedResultEl = document.getElementById('computedResult');\n  const timelineEl = document.getElementById('timeline');\n  const alertBtn = document.getElementById('alertBtn');\n  const alertStatusEl = document.getElementById('alertStatus');\n  const currentTimeEl = document.getElementById('currentTime');<\/p>\n<p>  \/\/ When user edits start \u2192 anchor becomes start\n  startTimeEl.addEventListener('change', () => {\n    anchor = 'start';\n    updateAnchorUI();\n    rebuild();\n  });<\/p>\n<p>  \/\/ When user edits end \u2192 anchor becomes end\n  endTimeEl.addEventListener('change', () => {\n    anchor = 'end';\n    updateAnchorUI();\n    rebuild();\n  });<\/p>\n<p>  [numCambiosEl, pauseDurationEl, shortFirstToggle, firstDurationEl, secondDurationEl, sessionDurationEl].forEach(el => {\n    el.addEventListener('change', rebuild);\n  });<\/p>\n<p>  function updateAnchorUI() {\n    startTimeEl.classList.toggle('anchor', anchor === 'start');\n    endTimeEl.classList.toggle('anchor', anchor === 'end');\n    startHint.textContent = anchor === 'start' ? 'Referencia \u25b8' : '';\n    endHint.textContent = anchor === 'end' ? 'Referencia \u25b8' : '';\n  }<\/p>\n<p>  function rebuild() {\n    updateToggleUI();\n    buildTimeline();\n    if (alertsActive) { stopAlerts(); startAlerts(); }\n  }<\/p>\n<p>  function updateToggleUI() {\n    const isShort = shortFirstToggle.checked;\n    dualDurationEl.classList.toggle('hidden', !isShort);\n    singleDurationEl.classList.toggle('hidden', isShort);\n  }<\/p>\n<p>  function parseTime(timeStr) {\n    if (!timeStr) return null;\n    const [h, m] = timeStr.split(':').map(Number);\n    return h * 60 + m;\n  }<\/p>\n<p>  function formatTime(totalMinutes) {\n    const wrapped = ((totalMinutes % 1440) + 1440) % 1440;\n    const h = Math.floor(wrapped \/ 60);\n    const m = wrapped % 60;\n    return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;\n  }<\/p>\n<p>  function minutesToTimeValue(totalMinutes) {\n    return formatTime(totalMinutes);\n  }<\/p>\n<p>  function getTotalDuration() {\n    const cambios = parseInt(numCambiosEl.value);\n    const pauseMin = parseInt(pauseDurationEl.value);\n    const isShortFirst = shortFirstToggle.checked;<\/p>\n<p>    let dur1, dur2;\n    if (isShortFirst) {\n      dur1 = parseInt(firstDurationEl.value);\n      dur2 = parseInt(secondDurationEl.value);\n    } else {\n      dur1 = parseInt(sessionDurationEl.value);\n      dur2 = dur1;\n    }<\/p>\n<p>    const totalSessions = cambios * 2;\n    const totalPauses = totalSessions - 1;\n    let total = 0;\n    for (let i = 0; i < totalSessions; i++) {\n      total += (i % 2 === 0) ? dur1 : dur2;\n    }\n    total += totalPauses * pauseMin;\n    return { total, dur1, dur2, totalSessions, totalPauses, pauseMin, cambios };\n  }\n\n  function buildTimeline() {\n    const { total, dur1, dur2, totalSessions, pauseMin, cambios } = getTotalDuration();\n\n    let startMin, endMin;\n\n    if (anchor === 'end') {\n      endMin = parseTime(endTimeEl.value);\n      if (endMin === null) { timelineEl.innerHTML = ''; return; }\n      startMin = endMin - total;\n      startTimeEl.value = minutesToTimeValue(startMin);\n    } else {\n      startMin = parseTime(startTimeEl.value);\n      if (startMin === null) { timelineEl.innerHTML = ''; return; }\n      endMin = startMin + total;\n      endTimeEl.value = minutesToTimeValue(endMin);\n    }\n\n    computedResultEl.textContent = `${formatTime(startMin)} \u2192 ${formatTime(endMin)} (${total} min)`;\n\n    \/\/ Build sessions\n    let currentMin = startMin;\n    const sessions = [];\n\n    for (let i = 0; i < totalSessions; i++) {\n      const cambioNum = Math.floor(i \/ 2) + 1;\n      const isFirstInPair = i % 2 === 0;\n      const thisDur = isFirstInPair ? dur1 : dur2;\n      const type = isFirstInPair ? 'a' : 'b';\n      const label = isFirstInPair ? 'A \u2192 B' : 'B \u2192 A';\n      const cambioLabel = isFirstInPair ? 'Primer cambio' : 'Segundo cambio';\n      const subtitle = `Ronda ${cambioNum} \u00b7 ${cambioLabel}`;\n\n      sessions.push({\n        type: 'session', start: currentMin, end: currentMin + thisDur,\n        duration: thisDur, label, subtitle, cssClass: `session-${type}`, index: i\n      });\n      currentMin += thisDur;\n\n      if (i < totalSessions - 1) {\n        sessions.push({ type: 'pause', start: currentMin, end: currentMin + pauseMin });\n        currentMin += pauseMin;\n      }\n    }\n\n    let html = `\n\n<div class=\"timeline-header\">\n<h2>Horario<\/h2>\n<p>      <span class=\"total-time\">${totalSessions} turnos \u00b7 ${total} min<\/span>\n    <\/div>\n<p>`;<\/p>\n<p>    sessions.forEach(item => {\n      if (item.type === 'session') {\n        html += `<\/p>\n<div class=\"timeline-item\">\n<div class=\"timeline-time\">\n              <span>${formatTime(item.start)}<\/span><\/p>\n<div class=\"timeline-line\"><\/div>\n<\/p><\/div>\n<div class=\"timeline-card ${item.cssClass}\" id=\"card-${item.index}\">\n<div class=\"card-title\">${item.label}<\/div>\n<div class=\"card-detail\">${item.subtitle} \u00b7 ${item.duration} min \u00b7 Fin ${formatTime(item.end)}<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>`;\n      } else {\n        html += `<\/p>\n<div class=\"timeline-item\">\n<div class=\"timeline-time\">\n              <span style=\"font-size:0.78rem;color:var(--text-muted)\">${formatTime(item.start)}<\/span><\/p>\n<div class=\"timeline-line\"><\/div>\n<\/p><\/div>\n<div class=\"timeline-card pause\">\n<div class=\"card-title\">\u23f8 Pausa \u00b7 ${pauseMin} min<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>`;\n      }\n    });<\/p>\n<p>    html += `<\/p>\n<div class=\"timeline-item\">\n<div class=\"timeline-time\"><span>${formatTime(endMin)}<\/span><\/div>\n<div class=\"timeline-card\" style=\"background:var(--accent-bg);border-left:3.5px solid var(--accent);padding:10px 16px;\">\n<div class=\"card-title\" style=\"color:var(--accent);\">\u2713 Fin de la sesi\u00f3n<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>`;<\/p>\n<p>    timelineEl.innerHTML = html;\n    window._sessions = sessions.filter(s => s.type === 'session');\n  }<\/p>\n<p>  \/\/ Audio\n  function playBeep(freq = 880, dur = 300, times = 3) {\n    if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n    let t = audioCtx.currentTime;\n    for (let i = 0; i < times; i++) {\n      const osc = audioCtx.createOscillator();\n      const gain = audioCtx.createGain();\n      osc.connect(gain); gain.connect(audioCtx.destination);\n      osc.type = 'sine'; osc.frequency.value = freq;\n      gain.gain.setValueAtTime(0.4, t);\n      gain.gain.exponentialRampToValueAtTime(0.01, t + dur \/ 1000);\n      osc.start(t); osc.stop(t + dur \/ 1000);\n      t += (dur + 150) \/ 1000;\n    }\n  }\n\n  function showNotification(session) {\n    document.getElementById('notifIcon').textContent = session.index === 0 ? '\u25b6\ufe0f' : '';\n    document.getElementById('notifTitle').textContent = session.index === 0 ? '\u00a1Empezamos!' : '\u00a1Cambio!';\n    document.getElementById('notifDetail').textContent = `${session.label} \u2014 ${session.subtitle}\\n${formatTime(session.start)} \u2013 ${formatTime(session.end)}`;\n    document.getElementById('notifOverlay').classList.add('show');\n    playBeep(session.index === 0 ? 660 : 880, 250, session.index === 0 ? 2 : 3);\n  }\n\n  function dismissNotification() {\n    document.getElementById('notifOverlay').classList.remove('show');\n  }\n\n  function toggleAlerts() { alertsActive ? stopAlerts() : startAlerts(); }\n\n  function startAlerts() {\n    if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n    alertsActive = true;\n    alertBtn.className = 'alert-btn stop';\n    alertBtn.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"\/><\/svg> Detener avisos`;\n    alertStatusEl.textContent = 'Avisos activados \u2014 suena al inicio de cada turno';\n    alertStatusEl.className = 'alert-status active';<\/p>\n<p>    const now = new Date();\n    const nowTotalSec = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();<\/p>\n<p>    alertTimers = [];\n    (window._sessions || []).forEach(session => {\n      const delaySec = session.start * 60 - nowTotalSec;\n      if (delaySec > 0) {\n        alertTimers.push(setTimeout(() => {\n          showNotification(session);\n          document.querySelectorAll('.timeline-card.active').forEach(el => el.classList.remove('active'));\n          const card = document.getElementById(`card-${session.index}`);\n          if (card) card.classList.add('active');\n        }, delaySec * 1000));\n      }\n    });<\/p>\n<p>    if (!alertTimers.length) {\n      alertStatusEl.textContent = 'No hay turnos futuros para hoy con esta configuraci\u00f3n';\n      alertStatusEl.className = 'alert-status';\n    }\n  }<\/p>\n<p>  function stopAlerts() {\n    alertsActive = false;\n    alertTimers.forEach(t => clearTimeout(t));\n    alertTimers = [];\n    alertBtn.className = 'alert-btn start';\n    alertBtn.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"\/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"\/><\/svg> Activar avisos`;\n    alertStatusEl.textContent = 'Los avisos sonar\u00e1n al inicio de cada cambio';\n    alertStatusEl.className = 'alert-status';\n    document.querySelectorAll('.timeline-card.active').forEach(el => el.classList.remove('active'));\n  }<\/p>\n<p>  setInterval(() => {\n    const n = new Date();\n    currentTimeEl.textContent = `${String(n.getHours()).padStart(2,'0')}:${String(n.getMinutes()).padStart(2,'0')}:${String(n.getSeconds()).padStart(2,'0')}`;\n  }, 1000);<\/p>\n<p>  updateAnchorUI();\n  updateToggleUI();\n  buildTimeline();\n<\/script><\/p>\n<p><\/body><br \/>\n<\/html><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Calculadora de Cambios \u2014 Centre Momp\u00f3 Calculadora de Cambios Planifica los turnos de pr\u00e1ctica entre masajistas Hora inicio Hora final Referencia \u25b8 N\u00ba de cambios 1 cambio2 cambios3 cambios4 cambios5 cambios Pausa entre turnos 1 min2 min3 min5 min Primer cambio m\u00e1s breve La primera sesi\u00f3n de cada cambio dura menos Primer cambio (min) 10 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-14311","post","type-post","status-publish","format-standard","hentry","category-sin-categoria"],"_links":{"self":[{"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/posts\/14311","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/comments?post=14311"}],"version-history":[{"count":3,"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/posts\/14311\/revisions"}],"predecessor-version":[{"id":14319,"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/posts\/14311\/revisions\/14319"}],"wp:attachment":[{"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/media?parent=14311"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/categories?post=14311"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/centremompo.es\/acceder\/wp-json\/wp\/v2\/tags?post=14311"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}