PageSourceSearch

https://www.clubhipico.cl/carreras/wp-content/plugins/chs-pollon-widget/assets/js/pollon-widget.js?ver=3.3.0

js clubhipico.cl collected 2026-09-24 09:49:18 UTC 21,468 bytes, 562 lines download raw bytes

1/**
2 * CHS Pollón de Oro — Widget JS v4.1
3 *
4 * Datos en chspwData.jornadas = [{f, g, c:[{g,h,h2,n,p}]}]
5 *   f  = fecha (YYYY-MM-DD)
6 *   g  = grupo ganador de la jornada (G1/G2/G3)
7 *   c  = array de carreras individuales [{g: grupo, h: caballo_1, h2: caballo_2 (empate), n: NUMERO_CARRERA, p: nombre_premio}]
8 *
9 * Grilla = celdas por carrera (no por etapa).
10 * Sin llamada AJAX adicional por jornada — todos los datos vienen en chspwData.
11 *
12 * v4.1 — Eliminado el auto-refresco por intervalo (generaba carga sostenida sobre
13 *        admin-ajax.php con cada visitante). Los datos se pintan desde chspwData al
14 *        cargar; el refresco solo ocurre si el usuario pulsa "Actualizar".
15 */
16(function ($) {
17  'use strict';
18
19  /* ── State ─────────────────────────────────────────────── */
20  var state = {
21    open:         false,
22    loading:      false,
23    initialized:  false,
24    jornadas:     [],   // [{f, g, c:[{g,h,h2,n,p}]}] — solo jornadas con resultados reales
25    jornadaIdx:   -1,
26    lastFetch:    null,
27  };
28
29  /* ── DOM refs ───────────────────────────────────────────── */
30  var $widget, $panel, $toggle, $grid, $subtitle,
31      $badge, $refreshBtn, $lastUpdate, $closeBtn, $progressFill,
32      $podium, $selPrev, $selNext, $selValue, $selLatest,
33      $tip;
34
35  /* ── Grupos ─────────────────────────────────────────────── */
36  var GRUPOS = {
37    G1: { label: 'Grupo 1', cls: 'chspw-g1' },
38    G2: { label: 'Grupo 2', cls: 'chspw-g2' },
39    G3: { label: 'Grupo 3', cls: 'chspw-g3' },
40  };
41
42  /* ── Boot ───────────────────────────────────────────────── */
43  $(function () {
44    $widget = $('#chspw-widget');
45    if (!$widget.length) return;
46
47    $panel        = $('#chspw-panel');
48    $toggle       = $('#chspw-toggle');
49    $grid         = $('#chspw-grid');
50    $subtitle     = $('#chspw-subtitle');
51    $badge        = $('#chspw-badge');
52    $refreshBtn   = $('#chspw-refresh');
53    $lastUpdate   = $('#chspw-last-update');
54    $closeBtn     = $('#chspw-close');
55    $progressFill = $('#chspw-progress-fill');
56    $podium       = $('#chspw-podium');
57    $selPrev      = $('#chspw-sel-prev');
58    $selNext      = $('#chspw-sel-next');
59    $selValue     = $('#chspw-sel-value');
60    $selLatest    = $('#chspw-sel-latest');
61
62    if (chspwData.sinEndpoint === '1') {
63      setGridMsg('Configurar endpoint en: Pollón de Oro → Configuración', 'chspw-no-endpoint');
64      setSubtitle('Sin endpoint configurado', 'idle');
65      return;
66    }
67
68    /* Leer jornadas pre-cargadas por PHP en chspwData */
69    state.jornadas = Array.isArray(chspwData.jornadas) ? chspwData.jornadas : [];
70
71    bindEvents();
72
73    if (state.jornadas.length > 0) {
74      state.jornadaIdx  = lastPlayedIdx();
75      state.initialized = true;
76      updatePodium();
77      updateProgress();
78      updateBadge();
79      updateSelector();
80      updateSubtitle();
81      renderJornadaGrid(state.jornadaIdx);
82    } else {
83      fetchData();
84    }
85    /* Auto-refresco eliminado en v4.1: sin polling por intervalo. */
86  });
87
88  /* ── Events ─────────────────────────────────────────────── */
89  function bindEvents() {
90    $toggle.on('click', togglePanel);
91    $closeBtn.on('click', closePanel);
92    $refreshBtn.on('click', function () { fetchData(); });
93    $selPrev.on('click', function () { navigateJornada(-1); });
94    $selNext.on('click', function () { navigateJornada(+1); });
95
96    $(document).on('click.chspw', function (e) {
97      if (state.open && !$(e.target).closest('#chspw-widget').length) {
98        closePanel();
99      }
100    });
101
102    $(document).on('keydown.chspw', function (e) {
103      if (e.key === 'Escape' && state.open) closePanel();
104    });
105
106    /* ── Tooltip: position:fixed appended to <body> para escapar overflow:hidden del panel ── */
107    $grid.on('mouseenter', '.chspw-cell.chspw-played', function (e) {
108      var $c       = $(this);
109      var nro      = $c.data('carrera') || '';
110      var etapaN   = $c.data('etapa')   || 0;
111      var grupo    = String($c.data('grupo')    || '').toUpperCase().replace(/\s+/g, '');
112      var caballo  = String($c.data('caballo')  || '').trim();
113      var caballo2 = String($c.data('caballo2') || '').trim();
114      var premio   = String($c.data('premio')   || '').trim();
115      var fecha    = String($c.data('fecha')    || '').trim();
116      var label    = grupoLabel(grupo);
117      var gc       = grupoClass(grupo);
118      var isEmpate = grupo.indexOf('-') !== -1;
119
120      /* Barra de color superior */
121      var accent = '<div class="chspw-tip-accent' + (gc ? ' ' + gc : '') + '"></div>';
122
123      /* Fila: número de carrera + fecha */
124      var nroLabel = (etapaN > 0 ? 'E' + etapaN + ' · ' : '') + nro + 'ª carrera';
125      var topRow = '<div class="chspw-tip-row-top">'
126                 + '<span class="chspw-tip-nro">' + escHtml(nroLabel) + '</span>'
127                 + (fecha ? '<span class="chspw-tip-fecha">' + escHtml(tipFechaDisplay(fecha)) + '</span>' : '')
128                 + '</div>';
129
130      /* Grupo ganador (con badge de empate si aplica) */
131      var grupoLine = label
132        ? '<span class="chspw-tip-grupo' + (gc ? ' ' + gc : '') + '">'
133          + escHtml(label)
134          + (isEmpate ? '<span class="chspw-tip-empate">Empate</span>' : '')
135          + '</span>'
136        : '';
137
138      /* Caballos ganadores (uno o dos en caso de empate, separados por /) */
139      var horsesHtml = '';
140      if (caballo) {
141        var horseText = escHtml(caballo);
142        if (caballo2 && caballo2 !== caballo) {
143          horseText += ' <span class="chspw-tip-sep">/</span> ' + escHtml(caballo2);
144        }
145        horsesHtml = '<span class="chspw-tip-horse">' + horseText + '</span>';
146      }
147
148      /* Cuerpo */
149      var body = '<div class="chspw-tip-body">'
150               + topRow
151               + grupoLine
152               + horsesHtml
153               + (premio ? '<span class="chspw-tip-prize"><span class="chspw-tip-prize-lbl">Premio:</span> ' + escHtml(premio) + '</span>' : '')
154               + '</div>';
155
156      if (!$tip) { $tip = $('<div class="chspw-tip" aria-hidden="true"></div>').appendTo('body'); }
157      $tip.html(accent + body);
158      tipPosition(e);
159      $tip.addClass('chspw-tip--visible');
160    }).on('mousemove', '.chspw-cell.chspw-played', function (e) {
161      if ($tip) tipPosition(e);
162    }).on('mouseleave', '.chspw-cell.chspw-played', function () {
163      if ($tip) $tip.removeClass('chspw-tip--visible');
164    });
165  }
166
167  /* ── Toggle ─────────────────────────────────────────────── */
168  function togglePanel() { state.open ? closePanel() : openPanel(); }
169
170  function openPanel() {
171    state.open = true;
172    $panel.addClass('chspw-open');
173    $toggle.attr('aria-expanded', 'true');
174    $badge.hide();
175  }
176
177  function closePanel() {
178    state.open = false;
179    $panel.removeClass('chspw-open');
180    $toggle.attr('aria-expanded', 'false');
181    updateBadge();
182  }
183
184  /* ── Navegación ─────────────────────────────────────────── */
185  function navigateJornada(dir) {
186    var newIdx = state.jornadaIdx + dir;
187    if (newIdx < 0 || newIdx >= state.jornadas.length) return;
188    state.jornadaIdx = newIdx;
189    updateSelector();
190    updateSubtitle();
191    renderJornadaGrid(state.jornadaIdx);
192  }
193
194  /* ── Render grilla de la jornada seleccionada ───────────── */
195  function renderJornadaGrid(idx) {
196    var cols = parseInt($widget.data('columnas') || chspwData.columnas || 5, 10);
197    $grid.css('--pw-cols', cols);
198
199    if (!state.jornadas.length) {
200      $grid.html('<div class="chspw-grid-message">Sin datos de temporada</div>');
201      return;
202    }
203
204    var j = (idx >= 0 && idx < state.jornadas.length) ? state.jornadas[idx] : null;
205    if (!j) {
206      $grid.html('<div class="chspw-grid-message">Sin datos</div>');
207      return;
208    }
209
210    var carreras = j.c || [];
211    var html     = '';
212
213    if (carreras.length > 0) {
214      /* Jornada jugada: una celda por carrera, coloreada por grupo */
215      for (var ci = 0; ci < carreras.length; ci++) {
216        var cr   = carreras[ci];
217        var nro  = (cr.n && cr.n > 0) ? cr.n : (ci + 1);  // NUMERO_CARRERA (número físico del programa)
218        var etapa = cr.e || 0;
219        var gc   = grupoClass(cr.g || '');
220        var lbl  = cr.g ? escHtml(cr.g) : '';
221        var cellLbl = (etapa > 0 ? 'E' + etapa + ' · ' : '') + nro + 'ª';
222        var h1   = String(cr.h  || '').trim();
223        var h2   = String(cr.h2 || '').trim();
224        var horseHtml = '';
225        html +=
226          '<div class="chspw-cell chspw-played' + (gc ? ' ' + gc : '') + '"'
227          + ' data-carrera="' + nro + '"'
228          + ' data-etapa="' + etapa + '"'
229          + ' data-grupo="' + escAttr(cr.g || '') + '"'
230          + ' data-caballo="' + escAttr(h1) + '"'
231          + ' data-caballo2="' + escAttr(h2) + '"'
232          + ' data-premio="' + escAttr(cr.p || '') + '"'
233          + ' data-fecha="' + escAttr(j.f || '') + '"'
234          + ' role="gridcell">'
235          + '<span class="chspw-cell-label">' + cellLbl + '</span>'
236          + '<span class="chspw-cell-sub">' + lbl + '</span>'
237          + horseHtml
238          + '</div>';
239      }
240    } else {
241      /* Jornada pendiente: celdas grises con conteo estimado */
242      var pendingCount = getLastKnownCarreraCount();
243      for (var i = 1; i <= pendingCount; i++) {
244        html +=
245          '<div class="chspw-cell chspw-pending"'
246          + ' data-carrera="' + i + '"'
247          + ' role="gridcell"'
248          + ' title="Carrera ' + i + ' — pendiente">'
249          + '<span class="chspw-cell-label">C' + i + '</span>'
250          + '</div>';
251      }
252    }
253
254    $grid.html(html || '<div class="chspw-grid-message">Sin datos</div>');
255  }
256
257  /* Devuelve el número de carreras de la última jornada jugada */
258  function getLastKnownCarreraCount() {
259    for (var k = state.jornadas.length - 2; k >= 0; k--) {
260      if (state.jornadas[k].c && state.jornadas[k].c.length > 0) {
261        return state.jornadas[k].c.length;
262      }
263    }
264    return 14;
265  }
266
267  /* ── Fetch: recargar desde endpoint y actualizar ────────── */
268  function fetchData() {
269    if (state.loading) return;
270    state.loading = true;
271    setSubtitle('Actualizando…', 'loading');
272    $refreshBtn.addClass('chspw-loading');
273
274    $.ajax({
275      url:      chspwData.ajaxUrl,
276      type:     'POST',
277      dataType: 'json',
278      timeout:  14000,
279      data: {
280        action: 'chspw_get_ganadores',
281        nonce:  chspwData.nonce,
282      },
283      success: function (resp) {
284        if (resp && resp.success && Array.isArray(resp.data)) {
285          var prevLen = state.jornadas.length;
286
287          /* resp.data ya viene en formato [{f, g, c:[{g,h,n,p}]}] */
288          state.jornadas = resp.data.map(function (j) {
289            return {
290              f: j.f || j.fecha || '',
291              g: j.g || j.grupo || '',
292              c: Array.isArray(j.c) ? j.c : (Array.isArray(j.carreras) ? j.carreras : []),
293            };
294          });
295
296          if (state.jornadaIdx < 0 || state.jornadaIdx >= state.jornadas.length
297              || state.jornadas.length > prevLen) {
298            state.jornadaIdx = lastPlayedIdx();
299          }
300
301          state.initialized = true;
302          state.lastFetch   = new Date();
303
304          renderJornadaGrid(state.jornadaIdx);
305          updatePodium();
306          updateProgress();
307          updateBadge();
308          updateSelector();
309          updateSubtitle();
310          updateLastUpdate();
311
312          if (state.jornadas.length > prevLen && !state.open) {
313            $toggle.addClass('chspw-pulse');
314            setTimeout(function () { $toggle.removeClass('chspw-pulse'); }, 5000);
315          }
316        } else {
317          var msg = (resp && resp.data && resp.data.message) ? resp.data.message : 'Error al cargar';
318          setSubtitle(msg, 'error');
319        }
320      },
321      error: function (xhr, status) {
322        setSubtitle(status === 'timeout' ? 'Tiempo agotado' : 'Error de conexión', 'error');
323      },
324      complete: function () {
325        state.loading = false;
326        $refreshBtn.removeClass('chspw-loading');
327      },
328    });
329  }
330
331  /* ── Pódium — victorias por carrera en toda la temporada ── */
332  function updatePodium() {
333    if (!$podium || !$podium.length) return;
334
335    /* Contar cada carrera individual, no solo jornadas */
336    var wins = { G1: 0, G2: 0, G3: 0 };
337    for (var i = 0; i < state.jornadas.length; i++) {
338      var carreras = state.jornadas[i].c || [];
339      for (var ci = 0; ci < carreras.length; ci++) {
340        var g = String(carreras[ci].g || '').toUpperCase().replace(/\s+/g, '');
341        if (g in wins) wins[g]++;
342      }
343    }
344
345    if (wins.G1 + wins.G2 + wins.G3 === 0) {
346      $podium.html('<div class="chspw-podium-title">Pódium — sin datos aún</div>');
347      return;
348    }
349
350    var ranked = Object.keys(wins)
351      .map(function (g) { return { g: g, v: wins[g] }; })
352      .sort(function (a, b) { return b.v - a.v; });
353
354    var leader      = ranked[0] ? ranked[0].g : null;
355    var podiumOrder = [ranked[1], ranked[0], ranked[2]];
356    var positions   = ['2°', '1°', '3°'];
357    var posCls      = ['chspw-p2', 'chspw-p1', 'chspw-p3'];
358
359    var html = '<div class="chspw-podium-title">Pódium — victorias por grupo</div>'
360             + '<div class="chspw-podium-stage">';
361
362    for (var p = 0; p < 3; p++) {
363      var item = podiumOrder[p];
364      if (!item) {
365        html += '<div class="chspw-podium-col chspw-p-empty ' + posCls[p] + '"></div>';
366        continue;
367      }
368      var gcfg = GRUPOS[item.g] || { label: item.g, cls: '' };
369      html +=
370        '<div class="chspw-podium-col ' + posCls[p] + ' ' + gcfg.cls + '">'
371        +   '<div class="chspw-podium-wins">' + item.v + '</div>'
372        +   '<div class="chspw-podium-bar"></div>'
373        +   '<div class="chspw-podium-medal">' + positions[p] + '</div>'
374        +   '<div class="chspw-podium-group">' + escHtml(gcfg.label) + '</div>'
375        + '</div>';
376    }
377    html += '</div>';
378
379    html += '<div class="chspw-legend">';
380    var grupos = ['G1', 'G2', 'G3'];
381    for (var gi = 0; gi < grupos.length; gi++) {
382      var gk  = grupos[gi];
383      var gcf = GRUPOS[gk];
384      var ld  = (gk === leader && wins[gk] > 0) ? ' chspw-legend-leader' : '';
385      html +=
386        '<span class="chspw-legend-item ' + gcf.cls + ld + '">'
387        + '<span class="chspw-legend-dot"></span>'
388        + escHtml(gcf.label) + ': ' + wins[gk]
389        + '</span>';
390    }
391    html += '</div>';
392
393    $podium.html(html);
394  }
395
396  /* ── Progress bar ───────────────────────────────────────── */
397  function updateProgress() {
398    if (!$progressFill.length) return;
399    var jugadas = state.jornadas.length;
400    var total   = parseInt(chspwData.etapas, 10) || 15;
401    var pct     = total > 0 ? Math.min(100, Math.round(jugadas / total * 100)) : 0;
402    $progressFill.css('width', pct + '%');
403  }
404
405  /* ── Selector ───────────────────────────────────────────── */
406  function updateSelector() {
407    var n   = state.jornadas.length;
408    var idx = state.jornadaIdx;
409    var j   = n > 0 && idx >= 0 ? state.jornadas[idx] : null;
410
411    if ($selValue.length) {
412      var label = j && j.f ? fechaDisplay(j.f) : (j ? 'Jornada ' + (idx + 1) : '—');
413      $selValue.text(label);
414    }
415    if ($selLatest.length) {
416      var jorN      = idx >= 0 ? idx + 1 : n;
417      var jorEtapas = j && j.c ? j.c.length : 0;
418      $selLatest.text('Jor. N°' + jorN + ' / ' + jorEtapas + ' Etp.');
419    }
420    if ($selPrev.length) $selPrev.prop('disabled', idx <= 0);
421    if ($selNext.length) $selNext.prop('disabled', idx >= n - 1);
422  }
423
424  /* ── Badge ──────────────────────────────────────────────── */
425  function updateBadge() {
426    var count = state.jornadas.length;
427    if (count > 0 && !state.open) {
428      var prev = parseInt($badge.text(), 10) || 0;
429      $badge.text(count).show();
430      if (prev !== count && state.initialized && prev > 0) {
431        $badge.removeClass('chspw-badge-bump');
432        void $badge[0].offsetWidth;
433        $badge.addClass('chspw-badge-bump');
434        setTimeout(function () { $badge.removeClass('chspw-badge-bump'); }, 400);
435      }
436    } else {
437      $badge.hide();
438    }
439  }
440
441  /* ── Subtitle ───────────────────────────────────────────── */
442  function updateSubtitle() {
443    var n     = state.jornadas.length;
444    var total = parseInt(chspwData.etapas, 10) || 15;
445    var j     = (n > 0 && state.jornadaIdx >= 0) ? state.jornadas[state.jornadaIdx] : null;
446    var isLast = (state.jornadaIdx === n - 1);
447    var text;
448    if (j) {
449      var dateStr = j.f ? fechaDisplay(j.f) : 'Jornada ' + (state.jornadaIdx + 1);
450      var pending = !j.g;
451      var carreraCount = j.c ? j.c.length : 0;
452      text = dateStr
453           + (pending ? ' — pendiente' : (' — ' + escHtml(j.g || '')))
454           + (carreraCount > 0 ? ' · ' + carreraCount + ' carreras' : '')
455           + (isLast ? ' (última)' : '');
456    } else {
457      text = n > 0 ? n + ' jornadas' : 'Sin datos de temporada';
458    }
459    setSubtitle(text, n > 0 ? 'live' : 'idle');
460  }
461
462  function setSubtitle(text, type) {
463    var dotCls = 'chspw-dot';
464    if (type === 'live')  dotCls += ' chspw-dot-live';
465    if (type === 'error') dotCls += ' chspw-dot-error';
466    $subtitle.html(
467      '<span class="' + dotCls + '" aria-hidden="true"></span>'
468      + '<span>' + escHtml(text) + '</span>'
469    );
470  }
471
472  function setGridMsg(text, extraCls) {
473    $grid.html(
474      '<div class="chspw-grid-message' + (extraCls ? ' ' + extraCls : '') + '">'
475      + escHtml(text) + '</div>'
476    );
477  }
478
479  /* ── Timestamp ──────────────────────────────────────────── */
480  function updateLastUpdate() {
481    if (!state.lastFetch) return;
482    var d = state.lastFetch;
483    $lastUpdate.text(
484      pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
485    );
486  }
487
488  /* ── Índice de la última jornada con resultados reales ───── */
489  /* Solo jornadas con carreras (c.length > 0) son navegables */
490  function lastPlayedIdx() {
491    for (var k = state.jornadas.length - 1; k >= 0; k--) {
492      if (state.jornadas[k].c && state.jornadas[k].c.length > 0) {
493        return k;
494      }
495    }
496    return state.jornadas.length - 1;  // fallback si no hay ninguna (no debería ocurrir)
497  }
498
499  /* ── Helpers ────────────────────────────────────────────── */
500  function tipPosition(e) {
501    var x = e.clientX + 14, y = e.clientY + 14;
502    var tw = $tip.outerWidth(), th = $tip.outerHeight();
503    if (x + tw > window.innerWidth  - 8) x = e.clientX - tw - 10;
504    if (y + th > window.innerHeight - 8) y = e.clientY - th - 10;
505    $tip.css({ left: x, top: y });
506  }
507
508  function grupoClass(grupo) {
509    var g = String(grupo || '').toUpperCase().replace(/\s+/g, '');
510    if (g === 'G1' || g === '1') return 'chspw-g1';
511    if (g === 'G2' || g === '2') return 'chspw-g2';
512    if (g === 'G3' || g === '3') return 'chspw-g3';
513    if (g.indexOf('-') !== -1)   return 'chspw-gmix';
514    return '';
515  }
516
517  function grupoLabel(grupo) {
518    var g = String(grupo || '').toUpperCase().replace(/\s+/g, '');
519    if (GRUPOS[g]) return GRUPOS[g].label;
520    if (g.indexOf('-') !== -1) return 'Grupo ' + g.replace(/^G/, '').replace(/-G/g, '-');
521    return grupo;
522  }
523
524  function fechaDisplay(iso) {
525    var p = String(iso || '').split('-');
526    return p.length === 3 ? p[2] + ' / ' + p[1] + ' / ' + p[0] : (iso || '—');
527  }
528
529  /* Formatea fecha para tooltip: "2026-05-25" → "25 may. 2026" */
530  function tipFechaDisplay(iso) {
531    var months = ['ene','feb','mar','abr','may','jun','jul','ago','sep','oct','nov','dic'];
532    var p = String(iso || '').split('-');
533    if (p.length !== 3) return iso || '';
534    var d = parseInt(p[2], 10);
535    var m = parseInt(p[1], 10) - 1;
536    var y = parseInt(p[0], 10);
537    return d + ' ' + (months[m] || p[1]) + '. ' + y;
538  }
539
540  function pad(n) { return String(n).padStart(2, '0'); }
541
542  function escHtml(s) {
543    if (typeof s !== 'string') return '';
544    return s
545      .replace(/&/g, '&amp;')
546      .replace(/</g, '&lt;')
547      .replace(/>/g, '&gt;')
548      .replace(/"/g, '&quot;')
549      .replace(/'/g, '&#39;');
550  }
551
552  function escAttr(s) {
553    if (typeof s !== 'string') return '';
554    return s
555      .replace(/&/g, '&amp;')
556      .replace(/"/g, '&quot;')
557      .replace(/'/g, '&#39;')
558      .replace(/</g, '&lt;')
559      .replace(/>/g, '&gt;');
560  }
561
562})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.