PageSourceSearch

https://www.um.es/o/um-lr-74-principal-theme/js/components/um-agen…=js&languageId=es_ES&t=1789636316000

js um.es collected 2026-09-24 08:06:53 UTC 42,517 bytes, 1,125 lines download raw bytes

1/*1789636316000*/
2/**
3 * Definición de los campos de los eventos
4 *
5 * @param data[]
6 * @param data[].fecha_inicio
7 * @param data[].fecha_fin
8 * @param data[].lugar
9 * @param data[].descripcion
10 * @param data[].horario
11 * @param data[].organismo
12 * @param data[].email_contacto
13 * @param data[].persona_contacto
14 * @param data[].telf_contacto
15 * @param data[].direccion_contacto
16 * @param data[].url_imagen
17 * @param data[].url_tv
18 * @param data[].url_pdf_boletin_oficial
19 * @param data[].referencia_publicacion
20 * @param data[].titulo
21 * @param data[].anexo_imagen_carrusel
22 * @param data[].etiqueta_categoria
23 * @param data[].fecha_plazo_inicio
24 * @param data[].fecha_plazo_fin
25 * @param data[].unidad
26 * @param data[].organismo
27 * @param data[].solicitudes
28 * @param data[].destinatarios
29 * @param data[].duracion
30 * @param data[].anexos
31 * @param data[].texto_realizacion
32 * @param data[].texto_plazo
33 * @param data[].resultados
34 * @param data[].url_agenda
35 * @param category[]
36 * @param category[].id_categoria
37 * @param category[].etiqueta_categoria
38 */
39
40(function ($) {
41  const BASE_URL_ATTR = 'data-baseurl';
42  const VIEW_PARAM = 'tipoconsulta';
43  const DATE_URL_PARAM = 'fecha';
44  const SEARCH_URL_PARAM = 'busqueda';
45  const QUERY_URL_PARAM = 'q';
46  const DAY_URL_PARAM = 'dia';
47  const TODAY_URL_PARAM = 'hoy';
48  const WEEK_URL_PARAM = 'proximasemana';
49  const MONTH_URL_PARAM = 'proximomes';
50  const SOON_URL_PARAM = 'proximamente';
51  const PREV_URL_PARAM = 'anteriores';
52  const CATEGORY_URL_PARAM = 'categoria';
53  const PAGE_SEARCH_PARAM = '&page=';
54  const PAGE_LINK_SELECTOR = '.page-link';
55
56  let baseUrl;
57  let getUrlParameter;
58
59  const monthNames = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
60  const listLinksMessages = [];
61
62  listLinksMessages[DAY_URL_PARAM] = 'el día ';
63  listLinksMessages[WEEK_URL_PARAM] = 'durante la próxima semana';
64  listLinksMessages[MONTH_URL_PARAM] = 'durante el próximo mes';
65  listLinksMessages[SEARCH_URL_PARAM] = 'para la búsqueda ';
66  listLinksMessages[SOON_URL_PARAM] = 'próximamente';
67  listLinksMessages[TODAY_URL_PARAM] = 'hoy';
68
69  function _isNaN(o) {
70    // eslint-disable-next-line no-restricted-globals
71    return typeof o === 'number' && isNaN(o);
72  }
73
74  function _isValidServiceDate(value) {
75    let parts;
76    let d;
77    let m;
78    let y;
79    let date;
80
81    if (!value || value === '--') return false;
82    parts = String(value).split('-');
83    if (parts.length !== 3) return false;
84    d = parseInt(parts[0], 10);
85    m = parseInt(parts[1], 10);
86    y = parseInt(parts[2], 10);
87    if (_isNaN(d) || _isNaN(m) || _isNaN(y)) return false;
88    date = new Date(y, m - 1, d);
89    return date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d;
90  }
91
92  function _clean(value) {
93    if (value === undefined || value === null) return '';
94    const text = String(value).trim();
95    return (text === '' || text === '--') ? '' : value;
96  }
97
98  /**
99   * Indica si la fecha introducida es correcta.
100   *
101   * Una fecha correcta se considera aquella que al menos está formada por año y mes, y además los
102   * datos introducidos forman una fecha válida.
103   * @param date in aaaa-dd-mm format
104   * @returns {boolean}
105   * @private
106   */
107  function _isValidDate(date) {
108    let day;
109    let month;
110    let year;
111    let dateArray;
112    let monthNumber;
113    let auxDate;
114
115    if (date) {
116      dateArray = date.split('-');
117      year = dateArray[0];
118      month = dateArray[1];
119      day = dateArray[2];
120
121      if (typeof year !== 'undefined' && typeof month !== 'undefined') {
122        monthNumber = parseInt(month, 10) - 1;
123
124        if (monthNumber >= 0 && monthNumber <= 11) {
125          if (typeof day !== 'undefined') {
126            auxDate = new Date(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10));
127          } else {
128            auxDate = new Date(parseInt(year, 10), parseInt(month, 10) - 1);
129          }
130
131          return Object.prototype.toString.call(auxDate) === '[object Date]' && !_isNaN(auxDate.getTime());
132        }
133      }
134    }
135
136    return false;
137  }
138
139  /**
140   * Comprueba si una categoría forma parte de aquellas para las que debe mostrarle la vista de
141   * eventos de plazo abierto por defecto.
142   */
143  function _isSoonCategory(category) {
144    const soonCategories = ['261', '311'];
145    return soonCategories.indexOf(category) !== -1;
146  }
147
148  // Fuente: https://stackoverflow.com/questions/16941104/remove-a-parameter-to-the-url-with-javascript
149  function _removeURLParameter(key, sourceURL) {
150    let i;
151    let rtn = sourceURL.split('?')[0];
152    let param;
153    let paramsArr = [];
154    const queryString = (sourceURL.indexOf('?') !== -1) ? sourceURL.split('?')[1] : '';
155    if (queryString !== '') {
156      paramsArr = queryString.split('&');
157      for (i = paramsArr.length - 1; i >= 0; i -= 1) {
158        param = paramsArr[i].split('=')[0];
159        if (param === key) {
160          paramsArr.splice(i, 1);
161        }
162      }
163      if (paramsArr.length > 0) {
164        rtn = rtn + '?' + paramsArr.join('&');
165      }
166    }
167    return rtn;
168  }
169
170  function _buildEventListUrl(date) {
171    return baseUrl + '?' + VIEW_PARAM + '=dia&fecha=' + date;
172  }
173
174  function _goToDate(date) {
175    let today;
176    let dateParam;
177
178    // Si faltan datos, se muestran los eventos del día actual (si no hay mes o día).
179    if (!_isValidDate(date)) {
180      today = new Date();
181      dateParam = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
182    } else {
183      dateParam = date;
184    }
185
186    window.location.href = _buildEventListUrl(dateParam);
187  }
188
189  function _getDateString(startDate, endDate) {
190    if (startDate !== '') {
191      if (endDate !== '') {
192        return (endDate === startDate ? startDate : 'Del ' + startDate + ' al ' + endDate);
193      }
194
195      return startDate;
196    }
197
198    return endDate || '';
199  }
200
201  function _getCurrentDate() {
202    // La fecha en la URL viene en formato yyyy-mm-dd
203    const date = getUrlParameter(DATE_URL_PARAM);
204    let today;
205    let day;
206    let month;
207    let year;
208
209    if (!_isValidDate(date)) {
210      today = new Date();
211      year = today.getFullYear();
212      month = ('0' + (today.getMonth() + 1)).slice(-2);
213      day = today.getDate();
214      return [year, month, day].join('-');
215    }
216    return date;
217  }
218
219  /**
220   * Construye la URL de una petición para el servicio de agenda
221   * @param jsonBaseUrl La URL base del servicio
222   * @param view La vista (tipo de consulta) solicitada
223   * @param dateParam La fecha de la consulta, si tiene
224   * @param isCategoriesUrl Indica si es una URL para obtener las categorías de una consulta
225   * @param page La página a obtener en una consulta con paginación. Null si no hay paginación.
226   * @param scope El ámbito de la consulta, si tiene.
227   * @returns {string} La URL completa de la consulta a realizar al servicio.
228   * @private
229   */
230  function _buildRequestUrl(jsonBaseUrl, view, dateParam, isCategoriesUrl, page, scope) {
231    let jsonUrl = jsonBaseUrl;
232    let q;
233    const category = getUrlParameter(CATEGORY_URL_PARAM);
234    const hasPagination = page !== null;
235
236    if (!view || view === DAY_URL_PARAM || view === TODAY_URL_PARAM) {
237      jsonUrl += '?tipoconsulta=dia&fecha_inicio=' + dateParam;
238    } else if (view === WEEK_URL_PARAM || view === MONTH_URL_PARAM || view === SOON_URL_PARAM) {
239      jsonUrl += '?tipoconsulta=' + view;
240    } else if (view === SEARCH_URL_PARAM) {
241      if (!isCategoriesUrl) {
242        jsonUrl = 'https://www.um.es/ws-siu/agenda/busqueda.php';
243      }
244      q = getUrlParameter(QUERY_URL_PARAM);
245      if (q) {
246        jsonUrl += '?q=' + q;
247
248        if (hasPagination && !_isNaN(page)) {
249          jsonUrl += '&paginacion=si' + PAGE_SEARCH_PARAM + page;
250        }
251      }
252    } else if (view === PREV_URL_PARAM) {
253      jsonUrl += '?tipoconsulta=' + view;
254
255      if (hasPagination && !_isNaN(page)) {
256        jsonUrl += '&paginacion=si' + PAGE_SEARCH_PARAM + page;
257      }
258    }
259
260    // Comprueba el filtro de categorías
261    if (category) {
262      jsonUrl += '&categoria=' + category;
263    }
264
265    // Comprueba el ámbito de la búsqueda
266    if (scope) {
267      jsonUrl += '&ambito=' + scope;
268    }
269
270    return jsonUrl;
271  }
272
273  function _printDataColumns(label, content, type, linkText) {
274    let formattedText;
275    const aText = (typeof linkText !== 'undefined' ? linkText : content);
276    const columnLabel = (label !== '' ? '<b>' + label + '</b>:' : '');
277    let dataColumn = '<div class="col-xs-12 as-detalle__info-item">';
278
279    if (type !== 'nolabel') {
280      dataColumn += columnLabel + ' ';
281    }
282
283    if (type === 'link') {
284      formattedText = '<a href="' + content + '">' + aText + '</a>';
285    } else if (type === 'email') {
286      formattedText = '<a href="mailto:' + content + '">' + aText + '</a>';
287    } else {
288      formattedText = content;
289    }
290
291    dataColumn += ' <span class="visible-xs-inline"><br/></span>' + formattedText + '</div>';
292
293    return dataColumn;
294  }
295
296  function _ddmmyyyyToDayStamp(dateString) {
297    let dateArray;
298
299    if (dateString) {
300      dateArray = dateString.split('-');
301      return new Date(parseInt(dateArray[2], 10),
302        parseInt(dateArray[1], 10) - 1,
303        parseInt(dateArray[0], 10)).getTime();
304    }
305
306    return new Date().setHours(0, 0, 0, 0);
307  }
308
309  function _yyyymmddToDayStamp(dateString) {
310    let dateArray;
311
312    if (dateString) {
313      dateArray = dateString.split('-');
314      return new Date(parseInt(dateArray[0], 10),
315        parseInt(dateArray[1], 10) - 1,
316        parseInt(dateArray[2], 10)).getTime();
317    }
318
319    return new Date().setHours(0, 0, 0, 0);
320  }
321
322  /**
323   * Comprueba si un rango de fecha se superpone a otro mayor
324   * @param aStart number fecha de inicio del rango menor
325   * @param aEnd number fecha de fin del rango menor
326   * @param bStart number fecha de inicio del rango mayor
327   * @param bEnd number fecha de fin del rango menor
328   * @returns {boolean}
329   */
330  function _dateRangeOverlaps(aStart, aEnd, bStart, bEnd) {
331    if (aStart <= bStart && bStart <= aEnd) return true; // b starts in a
332    if (aStart <= bEnd && bEnd <= aEnd) return true; // b ends in a
333    if (bStart < aStart && aEnd < bEnd) return true; // a in b
334    return false;
335  }
336
337  function _buildPagination($p, currentPage, lastPage) {
338    const $firstPage = $('.primera');
339    const $prevPage = $('.anterior');
340    const $currentPage = $('.actual');
341    const $nextPage = $('.siguiente');
342    const $lastPage = $('.ultima');
343    const isFirstPage = currentPage === 1;
344    const isLastPage = currentPage === lastPage;
345    const currentPageText = currentPage + ' de ' + lastPage;
346
347    // Resetea la navegación
348    $p.find('.page-item').removeClass('disabled');
349
350    // Añade el número de la página actual
351    $currentPage.find(PAGE_LINK_SELECTOR).text(currentPageText);
352
353    /* Si es la primera página inhabilita los controles para ir a página anterior y primera.
354     * En caso contrario añade el valor al botón de página anterior. */
355    if (isFirstPage) {
356      $firstPage.addClass('disabled').attr('data-page', -1);
357      $prevPage.addClass('disabled').attr('data-page', -1);
358      $firstPage.find(PAGE_LINK_SELECTOR).attr('data-page', -1);
359      $prevPage.find(PAGE_LINK_SELECTOR).attr('data-page', -1);
360    } else {
361      $firstPage.find(PAGE_LINK_SELECTOR).attr('data-page', 1);
362      $prevPage.find(PAGE_LINK_SELECTOR).attr('data-page', parseInt(currentPage, 10) - 1);
363    }
364
365    /* Si es la última página inhabilita los controles para ir a página siguiente y última.
366     * En caso contrario añade los valores de los botones de página siguiente y última. */
367    if (isLastPage) {
368      $nextPage.addClass('disabled');
369      $lastPage.addClass('disabled');
370      $nextPage.find(PAGE_LINK_SELECTOR).attr('data-page', -1);
371      $lastPage.find(PAGE_LINK_SELECTOR).attr('data-page', -1);
372    } else {
373      $nextPage.find(PAGE_LINK_SELECTOR).attr('data-page', parseInt(currentPage, 10) + 1);
374      $lastPage.find(PAGE_LINK_SELECTOR).attr('data-page', lastPage);
375    }
376  }
377
378  function _initListado($listado) {
379    let dateParts;
380    let textoMensajes;
381    const dateParam = _getCurrentDate();
382    const currentDay = _yyyymmddToDayStamp(dateParam);
383    const jsonBaseUrl = $listado.attr('data-jsonurl');
384    const defaultView = $listado.attr('data-defaultview') || 'dia';
385    let detailUrl = $listado.attr('data-detalleurl') || 'https://www.um.es/web/agenda';
386    const view = getUrlParameter(VIEW_PARAM) || defaultView;
387    const category = getUrlParameter('categoria');
388    const $pagination = $listado.find('.as-listado__paginacion');
389    const $prevEventsPagination = $('.as-listado__anteriores-paginacion .pagination');
390    const isSearch = view === SEARCH_URL_PARAM;
391    const hasPagination = isSearch; // De momento solo hay paginación en la búsqueda.
392    const scope = $listado.attr('data-scope');
393    const isSingleView = $listado.attr('data-issingleview') === 'true';
394    const pageParam = getUrlParameter('page') || '1';
395
396    /* Se detecta la vista por defecto si:
397    *  - defaultView y view son iguales
398    *  - no hay parámetro fecha en la URL */
399    const isDefaultView = defaultView === view && !getUrlParameter(DATE_URL_PARAM);
400
401    /* Solo se muestran actividades anteriores si se está viendo la vista por defecto, si están
402       activadas y si el ámbito es distinto de Agenda Universidad de Murcia (ámbito 1) */
403    const displayPreviousActivities = isDefaultView && scope !== '1' && $('.lista-anteriores-container').length > 0;
404
405    // Forma la URL de la petición a partir de los parámetros recibidos
406    const jsonUrl = _buildRequestUrl(jsonBaseUrl, view, dateParam, false, pageParam, scope);
407
408    /* Función local únicamente para el listado. Cambia los eventos visibles (hoy/plazo abierto) */
409    const _toggleVisibles = function () {
410      $listado.find('.as-listado__cabecera--hoy, .as-listado__cabecera--plazo, .actividad--activo, .actividad--en-plazo').toggleClass('d-none');
411    };
412
413    /* Función local para listado; construye la lista de actividades anteriores */
414    const _buildPrevActivitiesList = function (page) {
415      const $list = $listado.find('.lista-anteriores');
416      const jsonPrevUrl = _buildRequestUrl(jsonBaseUrl, PREV_URL_PARAM, null, false, page, scope);
417
418      $.get(jsonPrevUrl, (data) => {
419        const previousActivitiesCount = data['numero-total-items'];
420        const activitiesToDisplay = data.resultados || [];
421        const lastPage = data['numero-paginas'];
422
423        // Muestra la cuenta total de actividades
424        $('.total-registros-anteriores').text(previousActivitiesCount);
425
426        // Construye el listado
427        $list.empty();
428        activitiesToDisplay.forEach((event) => {
429          let textoMostrar;
430          let dateString;
431          let eventDetailUrl = event.url_agenda;
432          const eventTitle = _clean(event.titulo);
433          const $template = $listado.find('.plantilla-actividad .actividad');
434          const $eventNode = $template.clone();
435          const $nodeDate = $eventNode.find('.actividad__fecha');
436          const $nodeTerm = $eventNode.find('.actividad__plazo');
437          const $nodeLocation = $eventNode.find('.actividad__lugar');
438
439          // Imagen
440          if (_clean(event.anexo_imagen_carrusel)) {
441            $eventNode.find('.actividad__imagen img').attr('src', event.anexo_imagen_carrusel).attr('alt', eventTitle);
442          }
443
444          // Categoría
445          if (_clean(event.etiqueta_categoria)) {
446            $eventNode.find('.actividad__categoria').text(event.etiqueta_categoria);
447          } else {
448            $eventNode.find('.actividad__categoria').remove();
449          }
450
451          /* Construye el enlace para eventos sin ámbito Universidad de Murcia */
452          if (!eventDetailUrl || eventDetailUrl === '--') {
453            $eventNode.find('.actividad__titulo').text(eventTitle || '');
454          } else {
455            if (!eventDetailUrl.startsWith('http')) {
456              if (!detailUrl.endsWith('/')) {
457                detailUrl += '/';
458              }
459              eventDetailUrl = detailUrl + eventDetailUrl;
460            }
461            // Título y enlace
462            $eventNode.find('.actividad__titulo a').attr('href', eventDetailUrl).text(eventTitle);
463          }
464
465          /* Fecha de realización: Se muestra el campo texto_realizacion si existe. En caso
466           * contrario se muestra la fecha y el horario. Si no hay fecha_inicio se considera que el
467           * evento no tiene fecha de realización y no se muestra */
468          if (_clean(event.texto_realizacion)) {
469            $nodeDate.html(event.texto_realizacion);
470          } else if (_isValidServiceDate(event.fecha_inicio)) {
471            dateString = _getDateString(event.fecha_inicio, _isValidServiceDate(event.fecha_fin) ? event.fecha_fin : '');
472
473            if (_clean(event.horario)) {
474              dateString += ' - ' + event.horario;
475            }
476
477            $nodeDate.text(dateString);
478          } else {
479            $nodeDate.remove();
480          }
481
482          /* Fecha de plazo: Se muestra el campo texto_plazo si existe. En caso contrario se muestra
483           * la fecha. Si no hay fecha_plazo_fin se considera que el evento no tiene fecha de plazo
484           * y no se muestra este dato */
485          if (_clean(event.texto_plazo)) {
486            $nodeTerm.text(event.texto_plazo);
487          } else if (_isValidServiceDate(event.fecha_plazo_fin)) {
488            textoMostrar = '';
489
490            if (_isValidServiceDate(event.fecha_plazo_inicio)) {
491              textoMostrar += 'Plazo: ';
492            } else {
493              textoMostrar += 'Fin de plazo: ';
494            }
495
496            textoMostrar += _getDateString(
497              _isValidServiceDate(event.fecha_plazo_inicio) ? event.fecha_plazo_inicio : '',
498              event.fecha_plazo_fin
499            );
500            $nodeTerm.text(textoMostrar);
501          } else {
502            $nodeTerm.remove();
503          }
504
505          if (_clean(event.lugar)) {
506            $nodeLocation.append(event.lugar);
507          } else {
508            $nodeLocation.remove();
509          }
510
511          $list.append($eventNode);
512        });
513
514        // Construye la paginación
515        _buildPagination($prevEventsPagination, page, lastPage);
516      });
517    };
518
519    // Completa los mensajes de la parte superior
520    textoMensajes = listLinksMessages[view];
521
522    // En vista de día, construye el mensaje que indica el día del que se muestran los eventos
523    if (view === DAY_URL_PARAM) {
524      dateParts = dateParam.split('-');
525      textoMensajes += dateParts[2] + ' de ' + monthNames[parseInt(dateParts[1], 10) - 1] + ' de ' + dateParts[0];
526    }
527
528    $listado.find('.as-listado__texto--hoy, .as-listado__texto--plazo').text(textoMensajes);
529
530    // Realiza la petición de los datos del listado
531    $.get(jsonUrl)
532      .done((data) => {
533        let pagesText;
534        let $resultInfoText;
535        let searchUrl;
536        let activeEventsCount = 0;
537        let soonEventsCount = 0;
538
539        /* En caso de búsqueda, muestra la cabecera con la información de búsqueda y oculta la
540         * cabecera estándar. También comprueba si debe llevar o no paginación.
541         */
542        if (isSearch) {
543          $resultInfoText = $listado.find('.as-listado__cabecera--info-resultados');
544          $resultInfoText.find('.total-registros').text(data['total-registros']);
545          $resultInfoText.find('.consulta').text(data['consulta-realizada']);
546          $listado.find('.as-listado__cabecera--busqueda').removeClass('d-none');
547          $listado.find('.as-listado__cabecera').remove();
548        } else {
549          $listado.find('.as-listado__cabecera').removeClass('d-none');
550          $listado.find('.as-listado__cabecera--busqueda').remove();
551          $pagination.remove();
552        }
553
554        if (isSingleView) {
555          $listado.find('.as-listado__cabecera--subtitulo-hoy').hide();
556        }
557
558        // Comprueba si hay resultados y los recorre para dibujarlos
559        if (data.resultados && data.resultados.length > 0) {
560          data.resultados.forEach((event) => {
561            let dateString;
562            let textoMostrar;
563            let rangeStartDay;
564            let rangeEndDay;
565            let daysToAdd;
566            let eventDetailUrl = event.url_agenda;
567            const $template = $listado.find('.plantilla-actividad .actividad');
568            const $list = $listado.find('.lista-actividades');
569            const $eventNode = $template.clone();
570            const eventTitle = _clean(event.titulo);
571            const $nodeDate = $eventNode.find('.actividad__fecha');
572            const $nodeTerm = $eventNode.find('.actividad__plazo');
573            const $nodeLocation = $eventNode.find('.actividad__lugar');
574
575            let startDay;
576            let endDay;
577            /* En vista única todos los eventos son activos, si no inicializa a fal
577so */
578            let isActiveEvent = isSingleView;
579
580            /* Comprueba si es un evento activo o está en plazo si no es vista única */
581            // Si el listado es de un día concreto (hoy o por fecha)
582            if (!isSingleView) {
583              if (view === DAY_URL_PARAM) {
584                if (_isValidServiceDate(event.fecha_inicio)) {
585                  startDay = _ddmmyyyyToDayStamp(event.fecha_inicio);
586
587                  if (_isValidServiceDate(event.fecha_fin)) {
588                    endDay = _ddmmyyyyToDayStamp(event.fecha_fin);
589                    isActiveEvent = startDay <= currentDay && currentDay <= endDay;
590                  } else {
591                    isActiveEvent = startDay === currentDay;
592                  }
593                }
594              } else if (view === WEEK_URL_PARAM || view === MONTH_URL_PARAM) {
595                /* Si el listado muestra la vista semana o mes, compara los rangos de fechas
596                 * para saber si es un evento activo o en plazo.
597                 * - Si no hay fecha inicio asume que es un evento en plazo.
598                 * - Si no hay fecha de fin asume que es un evento de un solo día.
599                 */
600                if (_isValidServiceDate(event.fecha_inicio)) {
601                  daysToAdd = (view === WEEK_URL_PARAM ? 7 : 30);
602                  rangeStartDay = currentDay;
603                  rangeEndDay = new Date(dateParam.valueOf());
604                  rangeEndDay.setDate(rangeEndDay.getDate() + daysToAdd);
605                  rangeEndDay = rangeEndDay.setHours(0, 0, 0, 0);
606                  startDay = _ddmmyyyyToDayStamp(event.fecha_inicio);
607
608                  if (_isValidServiceDate(event.fecha_fin)) {
609                    endDay = _ddmmyyyyToDayStamp(event.fecha_fin);
610                  } else {
611                    endDay = startDay;
612                  }
613
614                  isActiveEvent = _dateRangeOverlaps(startDay, endDay, rangeStartDay, rangeEndDay);
615                }
616              } else if (view === SOON_URL_PARAM) {
617                isActiveEvent = _isValidServiceDate(event.fecha_inicio);
618              } else if (view === SEARCH_URL_PARAM) {
619                /* Fuerza que todos los eventos se muestren en la misma sección en caso de búsqueda
620                 * para no diferenciar entre eventos activos y en plazo */
621                isActiveEvent = true;
622              }
623            }
624
625            /* Categoriza el nodo según las fechas e incrementa la cuenta de eventos de hoy o en
626               plazo si no es búsqueda */
627            if (isActiveEvent) {
628              activeEventsCount += 1;
629              $eventNode.addClass('actividad--activo');
630            } else {
631              soonEventsCount += 1;
632              // Los eventos en plazo se ocultan por defecto con la clase .d-none
633              $eventNode.addClass('actividad--en-plazo d-none');
634            }
635
636            // Imagen
637            if (_clean(event.anexo_imagen_carrusel)) {
638              $eventNode.find('.actividad__imagen img').attr('src', event.anexo_imagen_carrusel).attr('alt', eventTitle);
639            }
640
641            // Categoría
642            if (_clean(event.etiqueta_categoria)) {
643              $eventNode.find('.actividad__categoria').text(event.etiqueta_categoria);
644            } else {
645              $eventNode.find('.actividad__categoria').remove();
646            }
647
648            /* Construye el enlace para eventos sin ámbito Universidad de Murcia */
649            if (!eventDetailUrl || eventDetailUrl === '--') {
650              $eventNode.find('.actividad__titulo').text(eventTitle || '');
651            } else {
652              if (!eventDetailUrl.startsWith('http')) {
653                if (!detailUrl.endsWith('/')) {
654                  detailUrl += '/';
655                }
656                eventDetailUrl = detailUrl + eventDetailUrl;
657              }
658              // Título y enlace
659              $eventNode.find('.actividad__titulo a').attr('href', eventDetailUrl).text(eventTitle);
660            }
661
662            /* Fecha de realización: Se muestra el campo texto_realizacion si existe. En caso
663             * contrario se muestra la fecha y el horario. Si no hay fecha_inicio se considera que
664             * el evento no tiene fecha de realización y no se muestra */
665            if (_clean(event.texto_realizacion)) {
666              $nodeDate.html(event.texto_realizacion);
667            } else if (_isValidServiceDate(event.fecha_inicio)) {
668              dateString = _getDateString(event.fecha_inicio, _isValidServiceDate(event.fecha_fin) ? event.fecha_fin : '');
669
670              if (_clean(event.horario)) {
671                dateString += ' - ' + event.horario;
672              }
673
674              $nodeDate.text(dateString);
675            } else {
676              $nodeDate.remove();
677            }
678
679            /* Fecha de plazo: Se muestra el campo texto_plazo si existe. En caso contrario se
680             * muestra la fecha. Si no hay fecha_plazo_fin se considera que el evento no tiene fecha
681             *  de plazo y no se muestra este dato */
682            if (_clean(event.texto_plazo)) {
683              $nodeTerm.text(event.texto_plazo);
684            } else if (_isValidServiceDate(event.fecha_plazo_fin)) {
685              textoMostrar = '';
686
687              if (_isValidServiceDate(event.fecha_plazo_inicio)) {
688                textoMostrar += 'Plazo: ';
689              } else {
690                textoMostrar += 'Fin de plazo: ';
691              }
692
693              textoMostrar += _getDateString(
694                _isValidServiceDate(event.fecha_plazo_inicio) ? event.fecha_plazo_inicio : '',
695                event.fecha_plazo_fin
696              );
697              $nodeTerm.text(textoMostrar);
698            } else {
699              $nodeTerm.remove();
700            }
701
702            if (_clean(event.lugar)) {
703              $nodeLocation.append(event.lugar);
704            } else {
705              $nodeLocation.remove();
706            }
707
708            $list.append($eventNode);
709          });
710
711          // Si es búsqueda, construye la paginación y habilita los textos adecuados
712          if (isSearch) {
713            // Limpia de la URL el término de la búsqueda
714            searchUrl = _removeURLParameter('page', window.location.href) + PAGE_SEARCH_PARAM;
715
716            if (hasPagination) {
717              $pagination.removeClass('d-none');
718              // Mensaje de página actual
719              pagesText = 'Página ' + data['pagina-actual'] + ' de ' + data['numero-paginas'];
720
721              // Asigna valores a los campos de páginas primera y anterior de la paginación
722              if (data['pagina-anterior'] !== '') {
723                $pagination.find('.primera').attr('href', searchUrl + '1');
724                $pagination.find('.anterior').attr('href', searchUrl + data['pagina-anterior']);
725              } else {
726                $pagination.find('.primera').parent('li').remove();
727                $pagination.find('.anterior').parent('li').remove();
728              }
729
730              // Asigna valores a los campos de páginas siguiente y última de la paginación
731              if (data['pagina-siguiente'] !== '') {
732                $pagination.find('.siguiente').attr('href', searchUrl + data['pagina-siguiente']);
733                $pagination.find('.ultima').attr('href', searchUrl + data['numero-paginas']);
734              } else {
735                $pagination.find('.siguiente').parent('li').remove();
736                $pagination.find('.ultima').parent('li').remove();
737              }
738              $pagination.find('.actual').text(pagesText);
739            }
740          } else {
741            // Si no es búsqueda completa los campos de información de número de eventos
742            $listado.find('.as-listado__numero--hoy').text('(' + activeEventsCount + ')');
743
744            $pagination.remove();
745
746            if (!isSingleView) {
747              $listado.find('.as-listado__numero--plazo').text('(' + soonEventsCount + ')');
748            }
749          }
750        } else if (!isSearch) {
751          $listado.find('.sin-resultados').removeClass('d-none');
752        }
753
754        /* Cambia la vista por defecto a Plazo abierto para determinadas categorías */
755        if (!isSingleView && _isSoonCategory(category)) {
756          _toggleVisibles();
757        }
758
759        // Añade eventos de click
760        if (!isSingleView) {
761          $listado.on('click', '.as-listado__cambia-vista', (event) => {
762            _toggleVisibles();
763            event.preventDefault();
764          });
765        }
766      })
767      .always(() => {
768        $listado.find('.lista-actividades .loading-animation').addClass('d-none');
769      });
770
771    // Listado de anteriores
772    if (displayPreviousActivities) {
773      $('.lista-anteriores-container').removeClass('d-none');
774      _buildPrevActivitiesList(1);
775
776      // Evento para la navegación por las páginas
777      $prevEventsPagination.on('click', PAGE_LINK_SELECTOR, (event) => {
778        const targetPage = $(event.currentTarget).attr('data-page');
779        event.preventDefault();
780        if (targetPage !== '-1') {
781          _buildPrevActivitiesList(parseInt(targetPage, 10));
782        }
783      });
784    }
785  }
786
787  function _initCalendario($calendar) {
788    let menuLinksString = '';
789    const menuLinks = {
790      hoy: 'Hoy',
791      proximasemana: 'Pr&oacute;xima Semana',
792      proximomes: 'Pr&oacute;ximo mes'
793    };
794
795    const options = {
796      autosize: true,
797      onSelect: _goToDate,
798      'dateFormat': 'yy-mm-dd'
799    };
800
801    $.datepicker.setDefaults(
802      $.extend(
803        { 'dateFormat': 'yy-mm-dd' }, $.datepicker.regional.es
804      )
805    );
806
807    baseUrl = $calendar.attr(BASE_URL_ATTR);
808    $calendar.removeAttr(BASE_URL_ATTR);
809    $calendar.datepicker(options);
810
811    /* Crea un enlace por cada elemento de menuLinks */
812    Object.keys(menuLinks).forEach((key) => {
813      let menuLinkHref;
814      if (Object.hasOwnProperty.call(menuLinks, key)) {
815        menuLinkHref = baseUrl;
816        if (key !== TODAY_URL_PARAM) {
817          menuLinkHref += '?' + VIEW_PARAM + '=' + key;
818        }
819        menuLinksString += '<a class="as-calendario__date-filter" href="' + menuLinkHref + '">' + menuLinks[key] + '</a>';
820      }
821    });
822
823    $calendar.prepend(menuLinksString);
824
825    $(document).on('click', '.ui-datepicker-next, .ui-datepicker-prev', (event) => {
826      event.stopPropagation();
827    });
828  }
829
830  function _initMenu($menu) {
831    let paramCharacter;
832    let jsonBaseUrl;
833    let currentLocation;
834    const view = getUrlParameter(VIEW_PARAM);
835    const currentDate = _getCurrentDate();
836    const scope = $menu.attr('data-scope');
837    const categoriesUrl = _buildRequestUrl($menu.attr('data-jsoncategorias'), view, currentDate, true, null, scope);
838
839    if (categoriesUrl) {
840      /* Si la página actual es listado, añade categoría a la selección actual.
841         Si no, enlaza el listado con la categoría */
842      jsonBaseUrl = ($('.as-listado__nuevo').length ? window.location.href : $menu.attr('data-baseUrl'));
843      // Obtiene la URL para el enlace quitando la categoría anterior,no debe ser acumulativo
844      currentLocation = _removeURLParameter('categoria', jsonBaseUrl);
845      paramCharacter = (currentLocation.indexOf('?') > -1 ? '&' : '?');
846
847      // Obtiene la lista de categorías y construye el selector
848      $.get(categoriesUrl, (data) => {
849        const $categoryList = $menu.find('.menu__enlace--categoria').next();
850
851        if (Array.isArray(data) && data.length > 0) {
852          data.forEach((category) => {
853            if (!_clean(category.id_categoria)) return;
854            const categoryUrl = currentLocation + paramCharacter + 'categoria=' + category.id_categoria;
855            $categoryList.append('<li><a href="' + categoryUrl + '">' + _clean(category.etiqueta_categoria) + '</a></li>');
856          });
857        } else {
858          $categoryList.append('<li>No hay categorías disponibles</li>');
859        }
860      });
861    }
862  }
863
864  function _initVistaDetalle($detail) {
865    let jsonUrl;
866    const id = getUrlParameter('id');
867    const $loadingSpinner = $('.detalle-loading-animation');
868
869    if (!id) {
870      window.location.href = $detail.attr('data-listadourl');
871    } else {
872      jsonUrl = $detail.attr('data-jsonurl') + '?id=' + id;
873
874      if (jsonUrl !== '') {
875        // Obtiene los datos por Ajax
876        $.get(jsonUrl)
877          .done((data) => {
878            let evento;
879            let $error;
880            let firstFile;
881            let $publicacion;
882            let $contacto;
883            let hayMasInfo;
884            let hayPublicacion;
885            let hayContacto;
886            let fechaMostrar;
887            const $organizacion = $detail.find('.as-detalle__organizacion');
888            let plazoMostrar = '';
889
890            if (data && data.length > 0) {
891              evento = data[0];
892
893              // Título
894              $detail.find('.as-detalle__titulo').text(_clean(evento.titulo));
895              $detail.find('.as-detalle__categoria').text(_clean(evento.etiqueta_categoria));
896
897              // Imagen
898              if (evento.anexo_imagen_carrusel) {
899                $detail.find('.as-detalle__imagen').attr('src', evento.anexo_imagen_carrusel);
900              } else {
901                $detail.find('.as-detalle__imagen').parent().remove();
902              }
903
904              // Fecha de realización
905              if (_clean(evento.texto_realizacion)) {
906                fechaMostrar = evento.texto_realizacion;
907              } else if (_isValidServiceDate(evento.fecha_inicio)) {
908                fechaMostrar = _getDateString(evento.fecha_inicio, _isValidServiceDate(evento.fecha_fin) ? evento.fecha_fin : '');
909              }
910
911              if (fechaMostrar) {
912                $detail.find('.as-detalle__fechas').append(fechaMostrar);
913              } else {
914                $detail.find('.as-detalle__fechas').remove();
915              }
916
917              // Hora
918              if (_clean(evento.horario)) {
919                $detail.find('.as-detalle__horas').append(evento.horario);
920              } else {
921                $detail.find('.as-detalle__horas').remove();
922              }
923
924              // Lugar
925              if (_clean(evento.lugar)) {
926                $detail.find('.as-detalle__lugar').append(evento.lugar);
927              } else {
928                $detail.find('.as-detalle__lugar').remove();
929              }
930
931              // Fecha plazo
932              if (_clean(evento.texto_plazo)) {
933                if (_clean(evento.solicitudes)) {
934                  plazoMostrar += evento.solicitudes + '<ul><li>';
935                }
936
937                plazoMostrar += evento.texto_plazo;
938
939                if (_clean(evento.solicitudes)) {
940                  plazoMostrar += '</li></ul>';
941                }
942              } else {
943                if (_clean(evento.solicitudes)) {
944                  plazoMostrar += '<b>Solicitudes</b>: ' + evento.solicitudes;
945                }
946
947                if (_isValidServiceDate(evento.fecha_plazo_fin)) {
948                  if (_clean(evento.solicitudes)) {
949                    plazoMostrar += '<ul><li>';
950                  }
951
952                  if (!_isValidServiceDate(evento.fecha_plazo_inicio)) {
953                    plazoMostrar += '<b>Fin de plazo</b>: ' + evento.fecha_plazo_fin;
954                  } else {
955                    plazoMostrar += 'Plazo: ' + _getDateString(evento.fecha_plazo_inicio, evento.fecha_plazo_fin);
956                  }
957
958                  if (_clean(evento.solicitudes)) {
959                    plazoMostrar += '</li></ul>';
960                  }
961                }
962              }
963
964              if (plazoMostrar) {
965                $detail.find('.as-detalle__plazo').append(plazoMostrar);
966              } else {
967                $detail.find('.as-detalle__plazo').remove();
968              }
969
970              // Retransmisión en directo
971              if (_clean(evento.url_tv)) {
972                $detail.find('.as-detalle__directo a').attr('href', evento.url_tv);
973              } else {
974                $detail.find('.as-detalle__directo').remove();
975              }
976
977              // Organismo y Unidad (Organización)
978              if (_clean(evento.organismo)) {
979                $organizacion.append(_printDataColumns('Organismo', evento.organismo));
980              }
981
982              if (_clean(evento.unidad)) {
983                $organizacion.append(_printDataColumns('Unidad', evento.unidad));
984              }
985
986              // Destinatarios
987              if (_clean(evento.destinatarios)) {
988                $organizacion.append(_printDataColumns('Destinatarios', evento.destinatarios));
989              }
990
991              // Duración
992              if (_clean(evento.duracion)) {
993                $organizacion.append(_printDataColumns('Duración', evento.duracion));
994              }
995
996              if ($organizacion.is(':empty')) {
997                $organizacion.remove();
998              }
999
1000              // Descripción
1001              if (_clean(evento.descripcion)) {
1002                $detail.find('.as-detalle__descripcion').html(evento.descripcion);
1003              } else {
1004                $detail.find('.as-detalle__descripcion, .descripcion__titulo').remove();
1005              }
1006
1007              // Más información (Datos de contacto, publicación)
1008              hayContacto = _clean(evento.persona_contacto) || _clean(evento.email_contacto)
1009                || _clean(evento.telf_contacto) || _clean(evento.direccion_contacto);
1010              hayPublicacion = evento.url || _clean(evento.anexos);
1011              hayMasInfo = hayContacto || hayPublicacion;
1012
1013              if (hayMasInfo) {
1014                $contacto = $detail.find('#info-seccion-contacto');
1015                $publicacion = $detail.find('#info-seccion-publicacion');
1016
1017                // Datos de contacto
1018                if (hayContacto) {
1019                  if (_clean(evento.persona_contacto)) {
1020                    $contacto.append(_printDataColumns('Persona', evento.persona_contacto, 'nolabel'));
1021                  }
1022
1023                  if (_clean(evento.email_contacto)) {
1024                    $contacto.append(_printDataColumns('Correo', evento.email_contacto, 'email'));
1025                  }
1026
1027                  if (_clean(evento.telf_contacto)) {
1028                    $contacto.append(_printDataColumns('Teléfono', evento.telf_contacto));
1029                  }
1030
1031                  if (_clean(evento.direccion_contacto)) {
1032                    $contacto.append(_printDataColumns('Dirección', evento.direccion_contacto));
1033                  }
1034                } else {
1035                  $detail.find('.mas-info__item--contacto').remove();
1036                  $contacto.remove();
1037                }
1038
1039                // Datos de publicación
1040                if (hayPublicacion) {
1041                  if (evento.url) {
1042                    $publicacion.append(_printDataColumns('Web', evento.url, 'link', 'Enlace página web'));
1043                  }
1044
1045                  if (_clean(evento.anexos)) {
1046                    firstFile = true;
1047                    evento.anexos.split('#').forEach((anexo) => {
1048                      const anexoArray = anexo.split('|');
1049                      if (anexoArray.length < 2 || !_clean(anexoArray[0])) return;
1050                      let label;
1051                      if (firstFile) {
1052                        label = 'Documentos';
1053                        firstFile = false;
1054                      } else {
1055                        label = '';
1056                      }
1057
1058                      $publicacion.append(_printDataColumns(label, anexoArray[0], 'link', anexoArray[1]));
1059                    });
1060                  }
1061                } else {
1062                  $detail.find('#info-seccion-publicacion').remove();
1063                  $publicacion.remove();
1064                }
1065              } else {
1066                $detail.find('.mas-info__titulo, .mas-info, .as-detalle__info-seccion').remove();
1067              }
1068
1069              // Mostramos el contenido una vez se ha terminado de completar
1070              $detail.removeClass('d-none');
1071            } else {
1072              $error = $detail.next();
1073              if ($error.length > 0) {
1074                $error.removeClass('d-none');
1075              }
1076            }
1077          })
1078          .always(() => {
1079            $loadingSpinner.addClass('d-none');
1080          });
1081      }
1082    }
1083  }
1084
1085  // eslint-disable-next-line no-param-reassign
1086  $.fn.agendaSiu = function () {
1087    getUrlParameter = UM.Utils.getUrlParameter;
1088    return this.each(function () {
1089      const $this = $(this);
1090      if ($this.hasClass('as-calendario__nuevo')) {
1091        _initCalendario($this);
1092      } else if ($this.hasClass('as-listado__nuevo')) {
1093        _initListado($this);
1094      } else if ($this.hasClass('as-detalle__nuevo')) {
1095        _initVistaDetalle($this);
1096      } else if ($this.hasClass('as-menu__nuevo')) {
1097        _initMenu($this);
1098      }
1099    });
1100  };
1101}(jQuery));
1102
1103/* Polyfills */
1104
1105if (!String.prototype.startsWith) {
1106  // eslint-disable-next-line no-extend-native
1107  String.prototype.startsWith = function (stringBuscada, posicion = 0) {
1108    return this.indexOf(stringBuscada, posicion) === posicion;
1109  };
1110}
1111
1112if (!String.prototype.endsWith) {
1113  // eslint-disable-next-line no-extend-native
1114  String.prototype.endsWith = function (searchString, positionParam) {
1115    let position = positionParam;
1116    const subjectString = this.toString();
1117    // eslint-disable-next-line no-restricted-globals
1118    if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
1119      position = subjectString.length;
1120    }
1121    position -= searchString.length;
1122    const lastIndex = subjectString.indexOf(searchString, position);
1123    return lastIndex !== -1 && lastIndex === position;
1124  };
1125}

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.