PageSourceSearch

https://itb.edu.ec/statics/inicio/inicio.js

js itb.edu.ec collected 2026-09-24 10:27:28 UTC 34,289 bytes, 825 lines download raw bytes

1gsap.registerPlugin(ScrollTrigger);
2let _sliders=[];
3let carreras = [];
4let modalidads = [];
5let horarios = [];
6let especializacions = [];
7let cantones = [];
8let loadContent = !0;
9let sliderLoad= false;
10const formValidators = new Map();
11const animacion = "animate__pulse";
12const mediaElements = document.querySelectorAll("#listaItemsFlecha .itemflecha");
13const exampleModal = document.getElementById("formInscripcionModal");
14const forms = document.querySelectorAll(".needs-validation");
15let splitTextInstances = [],swiperInstance = null;
16const homeCarousel = document.getElementById("homeCarousel");
17let videoBg = document.getElementById("bgvideo");
18let audioBg = null;
19let hideVideo = getCookie("hideVideo") || !1;
20window.addEventListener("load", function () {
21    if (window.innerWidth < 768) { hideVideo = !0;}
22    if (!hideVideo) { LoadVideo();}
23    InitMask();
24});
25window.addEventListener("scroll", (e) => {
26    if (window.scrollY > 200 && loadContent) {
27        loadContent = !1;
28        InitSwipper();
29        StartAnimScroll();
30        LoadBgIamges();
31        LazyLoadIframes();
32    }
33});
34window.onbeforeunload = function (){if(prelodmask == 1){window.scrollTo(0,0);}};
35function InitMask(){
36    const overlay = document.getElementById("overlay");
37    const preloader = document.getElementById("preloader");
38    const body = document.getElementById("body");
39    const tl = gsap.timeline({ ease: "power2.out" });
40    if (hideVideo){
41        tl.add(()=>{RenderCarrousel();},0.3);
42    }
43    if (prelodmask == 1 && hideVideo) {
44        tl.add(() => {
45            if (preloader) preloader.style.display = "none";
46        }, 0.3).add(() => {
47            if (overlay) {
48                overlay.style.display = "none";
49                body.style.overflow = "auto";
50            }
51        }, 3);
52    } else if (prelodmask == 1 && !hideVideo) {
53        tl.add(() => {
54            if (overlay) {
55                overlay.style.display = "none";
56                body.style.overflow = "auto";
57            }
58        }, 3);
59    }
60}
61function LoadVideo() {
62    if (!videoBg) return;
63    document.getElementById("preloader-bg").classList.remove("d-none");
64    const sourceMp4 = document.createElement("source");
65    sourceMp4.src = _root + "/statics/video/bgvideo.mp4";
66    sourceMp4.type = "video/mp4";
67    const sourceWebm = document.createElement("source");
68    sourceWebm.src = _root + "/statics/video/bgvideo.mp4";
69    sourceWebm.type = "video/webm";
70    videoBg.appendChild(sourceMp4);
71    videoBg.appendChild(sourceWebm);
72    videoBg.load();
73    
74    // Configurar y reproducir audio de fondo
75    if (!audioBg) {
76        audioBg = new Audio(_root + "/statics/video/cancion.mp3");
77        audioBg.volume = 0.5; // Volumen al 50%
78        audioBg.loop = false;
79    }
80    
81    // Intentar reproducir ambos
82    videoBg.play().catch((error) => {
83        EndVideo();
84    });
85    
86    audioBg.play().catch((error) => {
87        // console.log("Autoplay de audio bloqueado por el navegador:", error);
88        // No detenemos el video si solo falla el audio
89    });
90
91    StartVideo();
92}
93function StartVideo() {
94    if (videoBg) {
95        videoBg.addEventListener("ended", function () {
96            EndVideo();
97        });
98    }
99}
100function EndVideo() {
101    if (audioBg) {
102        audioBg.pause();
103        audioBg.currentTime = 0;
104        audioBg = null;
105    }
106
107    if (videoBg) {
108        videoBg.pause();
109        videoBg.removeAttribute("src");
110        videoBg.load();
111        document.getElementById("body").style.overflow = "auto";
112        if (videoBg.parentNode) {
113            videoBg.parentNode.removeChild(videoBg);
114        }
115        document.getElementById("preloader-bg").classList.add("d-none");
116    }
117    EndLoadEffect();
118}
119
120function ToggleAudio() {
121    const btn = document.getElementById("btnToggleAudio");
122    if (!audioBg) return;
123
124    if (audioBg.paused) {
125        // Sincronizar audio con el video actual
126        if (videoBg) {
127            audioBg.currentTime = videoBg.currentTime;
128        }
129        audioBg.play().then(() => {
130            if (btn) btn.innerText = "Silenciar";
131        }).catch(err => console.log(err));
132    } else {
133        audioBg.pause();
134        if (btn) btn.innerText = "Activar Sonido";
135    }
136}
137
138// Intentar activar audio con el primer clic en el documento si no se ha activado aun
139document.addEventListener('click', function EnableAudioOnInteraction() {
140    if (audioBg && audioBg.paused) {
141        const btn = document.getElementById("btnToggleAudio");
142        audioBg.play().then(() => {
143            if (btn) btn.innerText = "Silenciar";
144        }).catch(() => {});
145    }
146    document.removeEventListener('click', EnableAudioOnInteraction);
147}, { once: true });
148
149function SkipVideo() {
150    setCookie("hideVideo", !0, 2);
151    EndVideo();
152}
153function CloseVideo() { EndVideo();}
154function RenderCarrousel() {
155    if (homeCarousel.classList.contains("swiper-initialized")) return;
156    const limitCarga = 1;
157    document.fonts.ready.then(() => {
158        const homeCarouselElement = document.getElementById("homeCarousel");
159        if (homeCarouselElement) { homeCarouselElement.classList.add("animate__animated","animate__fadeInUp"); }
160        document.body.style.overflow = "auto";
161        if (homeCarousel) homeCarousel.classList.remove("d-none");
162        const swEffect1 = {prev:{shadow:true,translate:[0,0,-400]},next:{translate:["100%",0,0]}};
163        const swEffect2 = {prev:{shadow:true,translate:["-120%",0,-500]},next:{shadow:true,translate:["120%",0,-500]}};
164        const swEffect3 = {prev:{shadow:true,translate:["-20%",0,-1]},next:{translate:["100%",0,0]}};
165        const allEffects = [swEffect1,swEffect2,swEffect3];
166        let nrndEffect =Math.floor(Math.random() * allEffects.length);
167        const randomEffect = allEffects[nrndEffect];
168        const swiperHome = new Swiper(".home-swiper",
169            {   grabCursor: true,
170                effect: "creative",
171                autoplay: {
172                  delay: 5000,// 5 segundos entre slides
173                  disableOnInteraction: false,// Continúa después de que el usuario interactúa
174                  pauseOnMouseEnter: true,// Pausa al pasar el mouse (opcional)
175                },
176                creativeEffect : randomEffect,
177                slidesPerView : 1,
178                loop: true,  
179                lazy : true,
180                a11y: {
181                    enabled: true,
182                    prevSlideMessage: 'Slide anterior',
183                    nextSlideMessage: 'Siguiente slide',
184                },
185            }
186        );
187        const slides = document.querySelectorAll(".swiper-slide");
188        const outerWrappers = gsap.utils.toArray(".outer");
189        const innerWrappers = gsap.utils.toArray(".inner");
190        gsap.set([outerWrappers,innerWrappers],{yPercent:(i)=>i<outerWrappers.length?100:-100});
191        let currentIndex = 0;
192        let shouldLoadMore = false;
193        AnimateSlide(currentIndex,1);
194        swiperHome.on("slideChangeTransitionStart",()=> {
195            const loadMore = homeCarouselElement.getAttribute("data-load-slider");
196            const nextIndex = swiperHome.realIndex;
197            if (limitCarga === nextIndex && loadMore === 'true' && _sliders.length > 0) {
198                shouldLoadMore = true;
199                homeCarouselElement.setAttribute("data-load-slider", "false");
200            }
201            const direction = nextIndex > currentIndex || (nextIndex === 0 && currentIndex === slides.length - 1) ? 1 : -1;
202            AnimateSlide(nextIndex, direction);
203            currentIndex = nextIndex;
204        });
205        swiperHome.on("slideChangeTransitionEnd",()=>{
206            if (shouldLoadMore) {
207                shouldLoadMore = false;
208                const wrapper = homeCarousel.querySelector('.swiper-wrapper');
209                let slidesHtml = '';
210                _sliders.forEach((slide,index) => {
211                    if (index > 1 && slide.rutalg && slide.rutalg.length > 0){
212                        const desktopImage= slide.rutalg?`https://www.itb.edu.ec/${slide.rutalg}`:`${_root}statics/imagenes/plantilla/portada.jpg`;
213                        const mobileImage= slide.ruta?`https://www.itb.edu.ec/${slide.ruta}`:`${_root}statics/imagenes/main/arte4.jpg`;
214                        if(slide.btnlabel && slide.btnlabel.length > 0){
215                            console.log('URL',slide.url);
216                            slidesHtml += `<div class="swiper-slide section">
217                            <img class="d-none d-sm-none d-md-block bg lazyload" data-src="${desktopImage}" alt="Imagen 1">
218                            <img class="d-block d-sm-block d-md-none bg lazyload" data-src="${mobileImage}" alt="Imagen 1, Mobile">
219                            <div class="caption">
220                                <div class="container">
221                                    <div class="row">
222                                        <div class="col-12 col-md-7 col-lg-6 pe-5 d-flex flex-column justify-content-center align-items-start">
223                                            <div class="outer"><div class="inner">
224                                            <h1 class="section-heading text-start p-0 heading-style"> <span class="text-naranja">${slide.titulo||'Imagen'}</span><br> </h1></div></div>                                       
225                                            <div id="botton-container" class="botton-container mt-5">
226                                            <div class="row"><div class="col-12 col-md-6"> 
227                                            <a class="btn btn-naranja px-5 mx-1" target="_blank" href="${slide.url||'#'}">${slide.btnlabel||'clic aqui'}</a>
228                                            </div></div></div>
229                                        </div>                                  
230                                    </div>
231                                </div>
232                            </div>
233                        </div>`;
234                        }else{
235                        slidesHtml += `<div class="swiper-slide only-image">
236                            <img class="d-none d-sm-none d-md-block bg lazyload" data-src="${desktopImage}" alt="Desktop ${slide.titulo||'Imagen'}">
237                            <img class="d-block d-sm-block d-md-none bg lazyload" data-src="${mobileImage}" alt="Mobil ${slide.titulo||'Imagen'}"></div>`;
238                        }
239                    }
240                });
241                wrapper.insertAdjacentHTML('beforeend', slidesHtml);
242                swiperHome.update();
243            }
244        });
245    });
246}
247function AnimateSlide(index, direction = 1) {
248    const prelodmask = index === 0;
249    const dFactor = direction === 1 ? 1 : -1;
250    const slide = document.querySelectorAll(".swiper-slide.section")[index];
251    if (!slide) return;
252    const [outer,inner,heading,subtitle,btns,image] = [
253        slide.querySelector(".outer"),
254        slide.querySelector(".inner"),
255        slide.querySelector(".section-heading"),
256        slide.querySelector(".hero-subtitle"),
257        slide.querySelector(".botton-container"),
258        slide.querySelector(".bg")
259    ];
260    const elementsToAnimate = [outer,inner,heading,subtitle,btns,image].filter(
260Boolean);
261    gsap.killTweensOf(elementsToAnimate);
262    const tl = gsap.timeline({defaults:{duration:prelodmask?1:0.7,ease:"power1.inOut"}});
263    gsap.set([outer,inner,heading,subtitle,btns,image],{ clearProps: "all" });
264    if (image){
265        tl.fromTo(image,
266            {scale:1.1,xPercent:20 * dFactor,filter:"blur(10px)",opacity:0},
267            {scale:1,xPercent:0,filter:"blur(0px)",opacity:1,duration:prelodmask?1:0.5,ease:"power3.out" });
268    }
269    if (outer && inner) {
270        tl.fromTo([outer, inner], { yPercent: (i) => (i ? -100*dFactor:100*dFactor)},{yPercent:0},">");
271    }
272    if (heading) {
273        tl.fromTo(heading, {y:50,opacity:0},{y:0,opacity:1,duration:prelodmask?1.2:0.6,ease:"power2.out" },">");
274    }
275    if (subtitle) {
276        tl.fromTo(subtitle, {y:50,opacity:0},{y:0,opacity:1,duration:prelodmask?1.2:0.6,ease:"power2.out" },">");
277    }
278    if (btns) {
279        tl.fromTo(btns, {scale:0.8,opacity:0,y:60},{scale:1,opacity:1,y:0,duration:prelodmask?1.3:0.8,ease:"back.out(1.7)"},">");
280    }
281}
282function EndLoadEffect() {
283    RenderCarrousel();
284}
285function StartAnimScroll() {
286    document.querySelectorAll(".anim").forEach((el) => {
287        if (el.hasAttribute("data-animate")) {
288            const animClass = "animate__" + el.getAttribute("data-animate");
289            ScrollTrigger.create({ trigger: el, start: "top 98%", onEnter: () => el.classList.add("animate__animated", animClass), onLeaveBack: () => el.classList.remove("animate__animated", animClass), once: !1 });
290        }
291    });
292}
293function LoadBgIamges() {
294    const lazyBackgrounds = document.querySelectorAll("[data-bg-image]");
295    if ("IntersectionObserver" in window) {
296        let lazyObserver = new IntersectionObserver((entries,observer) => {
297            entries.forEach((entry) => {
298                if (entry.isIntersecting) {
299                    const element = entry.target;
300                    const imageUrl = element.getAttribute("data-bg-image");
301                    const claseImg = element.hasAttribute("data-repeat")?'lazy-background-loaded-repeat':'lazy-background-loaded';
302                    element.style.backgroundImage = `url(${imageUrl})`;
303                    element.classList.add(claseImg);
304                    observer.unobserve(element);
305                }
306            });
307        });
308        lazyBackgrounds.forEach((element) => {
309            lazyObserver.observe(element);
310        });
311    }
312}
313function LazyLoadIframes() {
314    const iframeContainers = document.querySelectorAll(".lazy-iframe");
315    iframeContainers.forEach(container => {
316        const dataSrc = container.getAttribute("data-src");
317        // Skip if no data-src attribute or if it's empty
318        if (!dataSrc || dataSrc.trim() === '') { return; }
319        if ("IntersectionObserver" in window) {
320            const observer = new IntersectionObserver((entries, obs) => {
321                entries.forEach((entry) => {
322                    if (entry.isIntersecting) {
323                        container.innerHTML = `<iframe 
324                            data-testid="embed-iframe"
325                            style="border-radius:12px"
326                            src="${dataSrc}"
327                            width="100%"
328                            height="300"
329                            frameborder="0"
330                            allowfullscreen
331                            allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
332                            loading="lazy"></iframe>`;
333                        container.removeAttribute("data-src");
334                        obs.unobserve(container);
335                    }
336                });
337            });
338            observer.observe(container);
339        } else {
340            // Fallback for browsers without IntersectionObserver
341            container.innerHTML = `<iframe 
342                data-testid="embed-iframe"
343                style="border-radius:12px"
344                src="${dataSrc}"
345                width="100%"
346                height="300"
347                frameborder="0"
348                allowfullscreen
349                allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
350                loading="lazy"></iframe>`;
351            container.removeAttribute("data-src");
352        }
353    });
354}
355function InitSwipper() {
356    new Swiper("#ofertasGrupo", { slidesPerView: 1, spaceBetween: 30, pagination: { clickable: !0, hide: !0 }, breakpoints: {
357        1100:{slidesPerView: 5, spaceBetween: 30},
358        768:{slidesPerView: 4, spaceBetween: 30}
359    } });
360    new Swiper(".swiper-artec", {
361        effect: "cards",
362        grabCursor: !0,
363        on: {
364            slideChange: () => {
365                document.querySelectorAll(".card-img-overlay.active").forEach((o) => o.classList.remove("active"));
366            },
367        },
368    });
369    new Swiper("#avalesContent",{ slidesPerView: 1, spaceBetween: 30, pagination: { clickable: !0, hide: !0 }, breakpoints: { 768: { slidesPerView: 4, spaceBetween: 30 } } });
370    new Swiper(".swiper-artec",{
371        effect: "cards",
372        grabCursor: !0,
373        on: {
374            slideChange: () => {
375                document.querySelectorAll(".card-img-overlay.active").forEach((o) => o.classList.remove("active"));
376            },
377        },
378    });
379}
380if (exampleModal) {
381    exampleModal.addEventListener("show.bs.modal", () => {
382        document.querySelector(".caption .outer").classList.add("d-none");
383        document.querySelector(".hero-subtitle").classList.add("d-none");
384        document.getElementById("botton-container").classList.add("d-none");
385        loadInscriptForm();
386        ResetModalFrm();
387    });
388    exampleModal.addEventListener("hide.bs.modal", () => {
389        document.getElementById("botton-container").classList.remove("d-none");
390        document.querySelector(".caption .outer").classList.remove("d-none");
391        document.querySelector(".hero-subtitle").classList.remove("d-none");
392    });
393}
394
395function loadInscriptForm() {
396    if (carreras.length > 0) return;
397    $.ajax({
398        type: "POST",
399        url: _root + "ajaxsapp/ajax",
400        headers: { "X-Requested-With": "XMLHttpRequest" },
401        data: { action: "formdatainscrip" },
402        success: function (data) {
403            // ESPECIALIDADES
404            especializacions = data.especializaciones || [];
405            let espeOpt = "<option value=''>Seleccione una especialidad</option>";
406            $.each(especializacions, function(i, fila){
407                espeOpt += `<option value="${fila.id}">${fila.descripcion}</option>`;
408            });
409            $("#especialidad_id").html(espeOpt);
410            safeSelectpickerUpdate("#especialidad_id");
411
412            // CARRERAS
413            carreras = data.carreras || [];
414            let carOpt = "<option value=''>Seleccione una carrera</option>";
415            $.each(carreras, function(i, fila){
416                carOpt += `<option value="${fila.id}">${fila.descripcion}</option>`;
417            });
418            $("#carrera_id").html(carOpt);
419            safeSelectpickerUpdate("#carrera_id");
420
421            // COLEGIOS
422            let colOpt = "<option value=''>Seleccione un colegio</option>";
423            $.each(data.colegios || [], function(i, fila){
424                const id = fila.id ?? fila.iD ?? fila.iD;
425                const label = fila.descripcion ?? fila.Name ?? fila.nombre ?? "";
426                colOpt += `<option value="${id}">${label}</option>`;
427            });
428            $("#colegio_id").html(colOpt);
429            safeSelectpickerUpdate("#colegio_id");
430
431            // CANTONES / CIUDADES
432            let ciuOpt = "<option value=''>Seleccione una ciudad</option>";
433            $.each(data.cantones || [], function(i, fila){
434                ciuOpt += `<option value="${fila.id}">${fila.nombre} - ${fila.provincia}</option>`;
435            });
436            $("#ciudad_id").html(ciuOpt);
437            safeSelectpickerUpdate("#ciudad_id");
438            
439            // Re-initialize parroquia_id but keep it empty initially
440            $("#parroquia_id").html("<option value=''>Seleccione una parroquia</option>");
441
442            // ZONAS
443            let zonOpt = "<option value=''>Seleccione una zona</option>";
444            $.each(data.zonas || [], function(i, fila){
445                zonOpt += `<option value="${fila.id}">${fila.nombre}</option>`;
446            });
447            $("#zona_id").html(zonOpt);
448
449            // ENCUESTA
450            const pregunta = (data.encuesta && data.encuesta.pregunta && data.encuesta.pregunta.length > 0)
451                ? data.encuesta.pregunta
452                : "¿Por qué medio llegaste a nuestro sitio web?";
453            $("#lblPregunta").html(pregunta);
454            let selec = "<option value=''>Seleccione una respuesta</option>";
455            $.each(data.opciones || [], function (i, op) {
456                selec += `<option value="${op.id}">${op.descripcion}</option>`;
457            });
458            $("#opcion_id").html(selec);
459            safeSelectpickerUpdate("#opcion_id");
460        },
461        error: function (xhr, status, error) {
462            console.log(error);
463        },
464    });
465}
466
467function safeSelectpickerUpdate(selector) {
468    const $el = $(selector);
469    if (!$el.length) return;
470    if (typeof $el.selectpicker !== 'function') {
471        return;
472    }
473    try {
474        $el.selectpicker('destroy');
475    } catch(e) {
476
477    }
478    $el.selectpicker();
479    try {
480        $el.selectpicker('render');
481    } catch (e) {
482        try { $el.selectpicker('refresh'); } catch(e2) {}
483    }
484}
485
486function loadModalidaCarrera(id){
487    let opcion = "<option selected value=''>Seleccione una modalidad</option>";
488    $.ajax({
489        type: 'POST',
490        url: _root + 'ajaxsapp/ajax',
491        headers: {'X-Requested-With':'XMLHttpRequest'},
492        data: {
493            action: 'modalidad_idcarrera',
494            carreraid: id
495        },
496        success: function(data){
497            modalidads = data.modalidads || [];
498            horarios   = data.horarios || [];
499            if (modalidads.length > 0) {
500                $.each(modalidads, function(i, fila){
501                    opcion += `<option value="${fila.id}">${fila.descripcion}</option>`;
502                });
503            }
504            $("#modalidad_id").html(opcion);
505            $("#horario_id").html("<option selected value=''>Seleccione un horario</option>");
506            $("#infoHorario").addClass('d-none');
507            $("#pendiente_horario").val('');
508        }
509    });
510}
511
512function loadHorarios(){
513    const id = $("#modalidad_id").val();
514    let opcion = "<option value=''>Seleccione un horario</option>";
515    let hayHorarios = false;
516    $.each(horarios, function(i, fila){
517        if (id == fila.modalidad_id) {
518            hayHorarios = true;
519            opcion += `
520                <option 
521                    data-codigo="${fila.hcodigo}" 
522                    value="${fila.id}">
523                    ${fila.descripcion}
524                </option>`;
525        }
526    });
527    if (!hayHorarios) {
528        opcion += `
529            <option selected value="">
530                No hay horarios para la carrera – continuar con la pre-inscripción
531            </option>`;
532    }
533    $("#horario_id").html(opcion);
534    $("#codigohora").val('');
535    if (hayHorarios) {
536        $("#pendiente_horario").val('0');
537        $("#horario_id").prop('required', true);
538        $("#infoHorario").addClass('d-none');
539        $("#horario_id").val('');
540    } else {
541        $("#pendiente_horario").val('1');
542        $("#horario_id").prop('required', false);
543        $("#infoHorario").removeClass('d-none');
544    }
545}
546
547document.getElementById('horario_id').addEventListener('change', function () {
548    const selectedOption = this.options[this.selectedIndex];
549    const codigo = selectedOption.getAttribute('data-codigo');
550    document.getElementById('codigohora').value = codigo || '';
551});
552
553function ResetModalFrm() {
554    const hace15Anios = new Date(new Date().getFullYear() - 15, new Date().getMonth(), new Date().getDate()).toISOString().split("T")[0];
555    $("#fecha").val(hace15Anios);
556    $("#frmInscriptModal")[0].reset();
557    setTimeout(function() {
558        $(".selectpicker").selectpicker("refresh");
559
560    }, 100);
561    $("#litaAlertaErrores").html("<li class='list-group-item bg-transparent p-0 m-0'> ... </li>");
562    $("#alertFormError").addClass("d-none");
563    $("#contentBtns").removeClass("d-none");
564    $("#bar-save-modal").addClass("d-none");
565    $("#alertErrorDefault").addClass("d-none");
566}
567function SaveForm() {
568    'use strict';
569    const form = document.getElementById('frmInscriptModal');
570    if (!form.checkValidity()) {
571        let firstInvalidElement = null;
572        for(const element of form.elements) {
573            if(element.willValidate && !element.checkValidity()){
574                if (!firstInvalidElement) {
575                    firstInvalidElement = element;
576                }
577                console.error(`Campo: ${element.name || element.id}, Error: ${element.validationMessage}`);
578            }
579        }
580        if(firstInvalidElement) {
581            const tabIndex = firstInvalidElement.getAttribute('data-tab');
582            const tabs = document.querySelectorAll('#formInscripcionModal [role="tab"]');
583            if (tabIndex) {
584                const tabButton = document.querySelector(`#inicio-inscripcion-tab button[data-bs-percent="${parseInt(tabIndex)+1}"]`);
585                if (tabButton){
586                    const tab = new bootstrap.Tab(tabButton);
587                    tab.show();
588                }
589                ShowFrmBtns(parseInt(tabIndex)+1,tabs.length);
590            }
591        }
592    }else{
593        $("#bar-save-modal").removeClass("d-none");
594        $("#contentBtns").addClass("d-none");
595        let progressAnimation = animateProgressBar();
596        const formData = new FormData(form);
597        formData.append('action','addIscipcion');
598        $.ajax({
599            url: _root + 'ajaxsapp/ajax',
600            type: 'POST',
601            data: formData,
602            processData : false,
603            contentType : false,
604            headers : {'X-Requested-With': 'XMLHttpRequest'}
605        }).done(function(response){
606            // console.log('Respuesta AJAX:',response);
607            if(progressAnimation)progressAnimation.progress(1);
608            if(response.success){
609                ShowAlertModal(response.message,'success','formInscripcionModal',6);
610            }else{
611                ShowAlertModal(response.message,'error','formInscripcionModal',8);
612            }
613        }).fail(function(jqXHR, textStatus, errorThrown){
614            if (progressAnimation) progressAnimation.kill();
615            ShowAlertModal("Errror inesperado, intente m&aacute;s tarde",'error','frmInscriptModal',8);
616            console.error('Error en AJAX:',jqXHR);
617            console.error('Error en AJAX:', textStatus, errorThrown);
618        });
619        console.log('Formulario válido, listo para enviar.');
620    }
621    form.classList.add('was-validated');
622}
623function animateProgressBar(onCompleteCallback) {
624    const progressBar = document.querySelector('#bar-save-modal .progress-bar');
625    if (!progressBar) return null;
626    let progress = { percent: 0 };
627    gsap.set(progressBar, { width: '0%' });
628    progressBar.textContent = '0%';
629    const tl = gsap.timeline({
630        onComplete: () => {
631            if (typeof onCompleteCallback === 'function') {
632                onCompleteCallback();
633            }
634        }
635    });
636    tl.to(progress, {
637        percent: 100,
638        duration: 2.5,
639        ease: 'power1.inOut',
640        onUpdate: () => {
641            const roundedPercent = Math.round(progress.percent);
642            progressBar.style.width = `${roundedPercent}%`;
643            progressBar.textContent = `${roundedPercent}%`;
644        }
645    });
646    return tl;
647}
648function ShowAlertModal(texto='',tipo='success',idmodal='',duracion=6){
649    let alertBody = '';
650    if(tipo === 'success'){
651        alertBody = '<div class="alert alert-success" role="alert"><strong class="bi bi-exclamation-triangle-fill flex-shrink-0 me-2">'+
652            '<i class="fa-regular fa-thumbs-up fa-beat-fade text-success"></i></strong>'+texto+'<div class="progress" id="bar-alert-msj" style="height:2px;">'+
653            '<div class="progress-bar bg-success" role="progressbar" aria-label="Cerrar" style="width:0;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div></div>';
654    }else{
655        alertBody = '<div class="alert alert-danger" role="alert"><strong class="bi bi-exclamation-triangle-fill flex-shrink-0 me-2">'+
656            '<i class="fa-solid fa-bug fa-shake text-danger"></i></strong>'+texto+'<div class="progress" id="bar-alert-msj" style="height:2px;">'+
657            '<div class="progress-bar bg-naranja" role="progressbar" aria-label="Cerrar" style="width:0;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div></div>';
658    }
659    $("#contentFooter").html(alertBody);
660    const progressMsjBar = document.querySelector('#bar-alert-msj .progress-bar');
661    gsap.set(progressMsjBar,{width:'0%'});
662    let progress = {percent:0};
663    const tl = gsap.timeline({
664        onComplete : () => { if (idmodal.length > 3) $('#'+idmodal).modal('hide'); }
665    });
666    tl.to(progress,{
667        percent:100,
668        duration:duracion,
669        ease: 'power1.inOut',
670        onUpdate: () => {
671            const roundedPercent = Math.round(progress.percent);
672            progressMsjBar.style.width = `${roundedPercent}%`;
673        }
674    });
675}
676function nextTab(direction,tabsContainerId = 'formInscripcionModal'){
677    const tabs = document.querySelectorAll(`#${tabsContainerId} [role="tab"]`);
678    const tabContent = document.querySelectorAll("#pills-tabContent .tab-pane");
679    if (!tabs.length || !tabContent)return;
680    let activeIndex = -1;
681    tabs.forEach((tab, index) => {
682        if (tab.classList.contains('active')){
683            activeIndex = index;
684        }
685    });
686    if(activeIndex === -1)return;
687    let newIndex;
688    if(direction === '+'){
689        newIndex = (activeIndex+1)%tabs.length;
690    }else if(direction === '-'){
691        newIndex = (activeIndex-1+tabs.length)%tabs.length;
692    }else{
693        return;
694    }
695    if(newIndex !== activeIndex){
696        const tabInstance = bootstrap.Tab.getOrCreateInstance(tabs[newIndex]);
697        tabInstance.show();
698    }
699    ShowFrmBtns(newIndex,tabs.length);
700
701}
702function ShowFrmBtns(newIndex,tabs){
703    if(newIndex > 0){
704        $("#btn-back").removeClass("d-none");
705    }
706    if(newIndex == 0){
707        $("#btn-back").addClass("d-none");
708    }
709    $("#btn-next").removeClass("d-none");
710    $("#btn-finish").addClass("d-none");
711    if(newIndex == tabs-1){
712        $("#btn-next").addClass("d-none");
713        $("#btn-finish").removeClass("d-none");
714    }
715    var barraWidth = (newIndex+1)*33;
716    barraWidth = barraWidth>66?100:barraWidth;
717    $("#bar-inscript").css("width",barraWidth+"%");
718}
719$("#inpsrchome").on('keyup',function(){
720    const length = $(this).val().length;
721    const searchBox = $(".searchofertas");
722    if(length >= 40){
723        searchBox.css('width','100%');
724    }else if(length >= 27){
725        searchBox.css('width','450px');
726    }else if(length > 6) {
727        searchBox.css('width','300px');
728    }else{
729        searchBox.css('width','300px');
730    }
731    if(length > 3){
732        $("#contentSrcOfertas").addClass("animate__animated animate__flipInX");
733        $("#contentSrcOfertas").removeClass("d-none");
734        $("#ofertasGrupo").addClass("d-none");
735        srcOferta($(this).val());
736    }else if(length == 0){
737        $("#contentSrcOfertas").addClass("d-none");
738        $("#ofertasGrupo").addClass("animate__animated animate__bounceIn");
739        $("#ofertasGrupo").removeClass("d-none");
740    }
741});
742function srcOferta(src){
743    if (src.length>3){
744        $.ajax({
745            type: 'POST',
746            url: _root+'ajaxsapp/ajax',
747            headers: {'X-Requested-With':'XMLHttpRequest'},
748            data: {action:'query_carrera',s:src },
749            success: function(data){
750                var array = eval(data);
751                // console.log(array)
752                var listado='<div class="alert alert-secondary" role="alert"><i class="fas fa-magnifying-glass-arrow-right"></i> No se ha encontrado nada a&uacute;n, intente de nuevo...</div>';
753                if(array[0]!=0){
754                    listado='';
755                    $.each(array, function (i, fila) {
756                        listado += '<div class="col-md-3 m-1"><div class="card shadow" style="width:18rem;">' +
757                            '<img src="https://itb.edu.ec/'+fila['img']+'" class="card-img-top img-orange-filter" alt="...">' +
758                            '<div class="card-img-overlay h-50"></div> <div class="card-body">'+
759                            '<h3 class="card-title text-azul">'+fila['descripcion']+'</h3>'+
760                            '<a href="'+_root+fila['url']+'" class="btn btn-naranja card-button">M&aacute;s Informaci&oacute;n</a>' +
761                            '</div></div></div>';
762                    });
763                }
764                $("#ofertalista").html(listado);
765            }
766        });
767    }
768}
769function getCodigo(codigo) {
770    $("#lblCodigo").text($(codigo).val());
771}
772
773// Reset form when pasaporte checkbox is toggled
774document.getElementById('pasaporte').addEventListener('change', function() {
775    const isChecked = this.checked;
776    ResetModalFrm();
777    this.checked = isChecked;
778    const label = document.getElementById('lblPasaporte');
779    const cedulaInput = document.getElementById('cedula');
780    if (isChecked) {
781        label.textContent = 'Pasaporte';
782        cedulaInput.placeholder = 'Pasaporte *';
783        cedulaInput.setAttribute('maxlength', '40');
784        cedulaInput.setAttribute('minlength', '3');
785    } else {
786        label.textContent = 'Cédula';
787        cedulaInput.placeholder = 'Cédula *';
788        cedulaInput.setAttribute('maxlength', '10');
789        cedulaInput.setAttribute('minlength', '9');
790    }
791});
792
793$("#fecha").on("change", function () {
794    const aniooSeleccionado = new Date($(this).val()).getFullYear();
795    const anioActual = new Date().getFullYear();
796    const $input = $(this);
797    const $feedback = $input.next(".invalid-feedback");
798    if (aniooSeleccionado > anioActual - 15) {
799        $input.addClass("is-invalid");
800        $feedback.text("¡Debes seleccionar la fecha actual!").show();
801    } else {
802        $input.removeClass("is-invalid");
803        $feedback.hide();
804    }
805    if (aniooSeleccionado > anioActual - 15) {
806        // console.log("Error: Debes tener al menos 15 años.");
807        $(this).val(anioActual - 16 + "-01-01");
808    } else {
809        // console.log("Fecha válida (solo validación por año)");
810    }
811});
812
813$("#cedula").on("blur", function () {
814    const $input = $(this);
815    const $feedback = $input.next(".invalid-feedback");
816    if (!$("#pasaporte").is(":checked")) {
817        if (Cedula($(this).val()).length > 0) {
818            $input.addClass("is-invalid");
819            $feedback.text("¡Debes seleccionar la fecha actual!").show();
820        } else {
821            $input.removeClass("is-invalid");
822            $feedback.hide();
823        }
824    }
825});

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.