PageSourceSearch

https://auve.org/accounts/login/?next=/documentacion-para-socios/

html auve.org collected 2026-09-24 11:26:09 UTC 62,013 bytes, 1,749 lines download raw bytes

1
2
3
4
5
6  <!DOCTYPE html>
7  <html lang="es" class="html">
8  <head>
9    <meta charset="utf-8"/>
10    <title>
11      Asociación de Usuarios de Vehículos Eléctricos | 
12    </title>
13    <meta http-equiv="X-UA-Compatible" content="IE=edge">
14    <meta content="width=device-width, initial-scale=1" name="viewport"/>
15    <meta content="Timtul" name="author"/>
16
17
18    <meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
19	<meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet">
20
21  	
21<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
21
22    
22<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.1/jquery-migrate.min.js" integrity="sha512-KgffulL3mxrOsDicgQWA11O6q6oKeWcV00VxgfJw4TcM8XRQT8Df9EsrYxDf7tpVpfl3qcYD96BpyPvA4d1FDQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
22
23
24    <link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet" type="text/css"/>
25    <link  href="//cdnjs.cloudflare.com/ajax/libs/fotorama/4.6.4/fotorama.css" rel="stylesheet">
vendor: 119 bytes, lines 25-28
25
26	<script src="https://www.google.com/recaptcha/api.js?onload=tt_recaptcha&render=explicit" async defer></script>
27
28    
28<link rel="stylesheet" href="/static/CACHE/css/abc33ae170c2.css" type="text/css" />
29    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.11.2/css/bootstrap-select.min.css">
30    
30<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js"></script>
30
31    
31<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.11.2/js/bootstrap-select.min.js"></script>
31
32    <link rel="shortcut icon" href="/favicon.ico"/>
33    
33<script type="text/javascript" src="/static/admin/js/i18n-periodpicker.js"></script>
33
34
35  
36            <!-- FAVICON -->
37<link rel="icon" type="image/png" href="https://media.timtul.com/media/web_auve/favicon-96x96_20241203163113.png" sizes="96x96" />
38<link rel="icon" type="image/svg+xml" href="https://media.timtul.com/media/web_auve/favicon_20241203163110.svg" />
39<link rel="shortcut icon" href="https://media.timtul.com/media/web_auve/favicon_20241203163110.ico" />
40<link rel="apple-touch-icon" sizes="180x180" href="https://media.timtul.com/media/web_auve/apple-touch-icon_20241203163110.png" />
41<meta name="apple-mobile-web-app-title" content="AUVE" />
42<link rel="manifest" href="https://media.timtul.com/media/web_auve/site_20241203163207.webmanifest" />
43
44<!-- FUENTES -->
45<link rel="preconnect" href="https://fonts.googleapis.com">
46<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
47<link href="https://fonts.googleapis.com/css2?family=Fira+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Quicksand:[email protected]&display=swap" rel="stylesheet">
48
49<!-- Add the slick-theme.css if you want default styling -->
50<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]/slick/slick.css"/>
51<!-- Add the slick-theme.css if you want default styling -->
52<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]/slick/slick-theme.css"/>
53<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/slick/slick.min.js"></script>
53
54<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
55
56<!-- Google tag (gtag.js) -->
vendor: 65 bytes, lines 56-57
56
57<script async src="https://www.googletagmanager.com/gtag/js?id=
57G-N4DBWWNKRT
vendor: 13 bytes, line 57
57"></script>
58<script>
59  
vendor: 138 bytes, lines 59-63
59window.dataLayer = window.dataLayer || [];
60  function gtag(){dataLayer.push(arguments);}
61  gtag('js', new Date());
62
63  gtag('config', '
63G-N4DBWWNKRT
vendor: 5 bytes, line 63
63');
64</script>
64
65  
66
67  </head>
68      
69      <body style="margin-bottom:0px;padding-top:0px;" class="modal-base-body">
70          <div class="login-container">
71      
72
73
74<script type="text/javascript">
75
76    
77var tt_recaptcha = function() {
78     $( ".g-recaptcha-modal" ).each(function( index ) {
79           var key = $(this).attr("data-sitekey");
80		   var lang = "es"
81		   if( lang == "pr") {
82                lang = "pt";
83		   }
84           recaptcha = grecaptcha.render(this, {
85             'sitekey' : key,
86             'hl': lang
87           });
88         });
89};
90    
91
92$( document ).ready(function() {
93  
94  
95});
96</script>
96
97
98
99<!--
100    <div>
101        <ul>
102        	
103        	<li><a href="/accounts/login/">Sign In</a></li>
104        	<li><a href="/accounts/signup/">Sign Up</a></li>
105        	
106      </ul>
107    </div>
108  -->
109    
110
111<h3>Inicia sesión</h3>
112
113
114
115
116<p>Si todavía no has creado una cuenta, entonces por favor
117<a href="/accounts/signup/?next=%2Fdocumentacion-para-socios%2F">regístrate</a> primero.</p>
118
119
120<form class="login" method="POST" action="/accounts/login/">
121  <input type='hidden' name='csrfmiddlewaretoken' value='LKZrCog8GIMMvXau5NOA3IeT7SwVQ1nEGUwTtt6uFhv2f0UT89szOvRtYcBXFhR1' />
122  <p><label for="id_login">Email:</label> <input autofocus="autofocus" id="id_login" name="login" placeholder="Dirección email" type="email" required /></p>
123<p><label for="id_password">Contraseña:</label> <input id="id_password" name="password" placeholder="Contraseña" type="password" required /></p>
124<p><label for="id_remember">Recordarme:</label> <input id="id_remember" name="remember" type="checkbox" /></p>
125  
126  <input type="hidden" name="next" value="/documentacion-para-socios/" />
127  
128  <button type="button" class="btn btn-danger" data-dismiss="modal" aria-hidden="true" aria-labelledby="Cancelar">Cancelar</button>
129  <button class="primaryAction" type="submit" aria-labelledby="Inicia sesión">Inicia sesión</button>
130  <a class="button secondaryAction" href="/accounts/password/reset/" style="display:block" role="button" aria-labelledby="Recordar contraseña">Recordar contraseña</a>
131</form>
132
133
134
135
136
137
138
139
140
141    </div>
142  </body>
143</html>
144
145
146<script type="text/javascript">
147
148    try {
149        function addWayetTooltipAndLeyendInRegisterForm(){
150    const checkbox = document.getElementById('id_custom_WAYLET-PERMISO');
151
152    if (!checkbox) {
153        return;
154    }
155
156    const field = checkbox.closest('.form-group');
157    const label = field ? field.querySelector('label.control-label') : null;
158    const inputGroup = field ? field.querySelector('.input-group') : null;
159
160    if (!field || !label) {
161        return;
162    }
163
164    field.classList.add('waylet-permiso-field');
165
166    label.insertBefore(checkbox, label.firstChild);
167
168    if (inputGroup && inputGroup.children.length === 0) {
169        inputGroup.remove();
170    }
171
172    const tooltipId = 'waylet-permiso-tooltip';
173
174    const infoIcon = document.createElement('span');
175    infoIcon.className = 'waylet-info-icon';
176    infoIcon.textContent = '?';
177    infoIcon.setAttribute('tabindex', '0');
178    infoIcon.setAttribute('role', 'button');
179    infoIcon.setAttribute('aria-describedby', tooltipId);
180
181    const tooltip = document.createElement('span');
182    tooltip.id = tooltipId;
183    tooltip.className = 'waylet-tooltip';
184    tooltip.setAttribute('role', 'tooltip');
185    tooltip.textContent = 'Al marcar esta casilla autorizas a AUVE a enviar tu NIF a Repsol para incorporarte al colectivo de socios de AUVE en Waylet y disfrutar de sus ventajas. Es el único dato que se comparte. Tu NIF debe coincidir con el de tu cuenta de Waylet. Puedes revocar la autorización en cualquier momento desmarcando la casilla.';
186
187    label.appendChild(infoIcon);
188    label.appendChild(tooltip);
189}
190
191addWayetTooltipAndLeyendInRegisterForm();
192
193/* ------------------ CUSTOM LOGIN WIDGET ------------------ */
194
195const initLoginWidget = () => {
196    const widget = document.querySelector('.custom-profile__dropdown');
197    if (!widget) {
198        console.warn('Custom login widget not found.');
199         return;
200    }
201   
202
203    const widgetLoginToggle = widget.querySelector('.custom-profile__dropdown-toggle');
204
205    widgetLoginToggle.addEventListener('click', (event) => {
206        event.preventDefault();
207        event.stopPropagation(); // Evita que el click llegue al document
208        widget.classList.toggle('open');
209    });
210
211    document.addEventListener('click', (event) => {
212        if (!widget.contains(event.target)) {
213            widget.classList.remove('open');
214        }
215    });
216}
217
218initLoginWidget();
219
220
221
222
223////EDITS DEL FORMULARIO DE REGISTRO
224
225const labelPassword1 = document.querySelector('label[for="id_password1"]');
226const placeHolderPassword = document.getElementById('id_password1');
227
228
229if (labelPassword1) {
230    labelPassword1.textContent = "Contraseña: (mínimo de 6 caracteres)";
231    placeHolderPassword.placeholder = "Contraseña: (mínimo 6 caracteres)";
232}
233
234
235const requiresAsterisco = document.querySelectorAll(`
236  .form-group.custom-field:has(:is(input, select, textarea):required) > label.control-label,
237  p:has(:is(input, select, textarea):required) > label
238`);
239
240const labelLastName = document.querySelector('label[for="id_last_name"]');
241if (labelLastName && !labelLastName.querySelector(".required-asterisk")) {
242  const asterisk = document.createElement("span");
243  asterisk.textContent = " *";
244  asterisk.className = "required-asterisk";
245  asterisk.setAttribute("aria-hidden", "true");
246  labelLastName.appendChild(asterisk);
247}
248
249
250requiresAsterisco.forEach(label => {
251  if (!label.querySelector(".required-asterisk")) {
252    const asterisk = document.createElement("span");
253    asterisk.textContent = " *";
254    asterisk.className = "required-asterisk";
255    asterisk.setAttribute("aria-hidden", "true");
256    label.appendChild(asterisk);
257  }
258});
259
260
261const signup_form = document.getElementById("signup_form");
262
263if (signup_form) {
264    signup_form.insertAdjacentHTML(
265        "beforebegin",
266        `<p style="font-size: 0.9em !important;">Los campos <span style="color: #58c0cc; font-weight: bold !important;">resaltados con asterisco*</span> son obligatorios</p>`
267    );
268}
269
270(function () {
271  const NEW_TEXT = "Nada seleccionado";
272
273  function fixNothingSelected(root = document) {
274    root.querySelectorAll(".filter-option").forEach(el => {
275      if (el.textContent.trim() === "Nothing selected") {
276        el.textContent = NEW_TEXT;
277      }
278    });
279
280    root.querySelectorAll('button[title="Nothing selected"]').forEach(btn => {
281      btn.title = NEW_TEXT;
282    });
283  }
284
285  // 1) Intento inmediato
286  fixNothingSelected();
287
288  // 2) Reaplicar cuando bootstrap-select refresca / cambia
289  $(document).on("loaded.bs.select refreshed.bs.select changed.bs.select", function () {
290    fixNothingSelected();
291  });
292
293  // 3) Por si lo pinta después (AJAX / render tardío)
294  const obs = new MutationObserver(() => fixNothingSelected());
295  obs.observe(document.body, { subtree: true, childList: true, characterData: true });
296
297  // 4) Reintentos cortos (por si el render va con delay)
298  setTimeout(fixNothingSelected, 50);
299  setTimeout(fixNothingSelected, 200);
300  setTimeout(fixNothingSelected, 800);
301})();
302
303
304
305
306
307// --> ---------------
308// --> Fix Date Inputs
309
310function fixDateInputs() {
311    var $inputs = $('input.date-field, input.datetime-field, input[type="date"], input[type="datetime-local"]');
312
313    $inputs.each(function(index, input_bucle){
314        var $input = $(input_bucle), type = $input.attr('type');
315
316        if (!(type === "date" || type === "datetime-local")) {
317            if ($input.hasClass('date-field')) {
318                $input.attr('type', 'date');
319            } else {
320                $input.attr('type', 'datetime-local');
321            }
322        }
323    });
324
325    $(document).on('click', 'input.date-field, input.datetime-field, input[type="date"], input[type="datetime-local"]', function(){
326        if($(this).parents('.widget').hasClass('widget-event-registration')) return;
327
328        $(this)[0].showPicker?.();
329        $(this).focus();
330    });
331}
332
333$(window).on('load', fixDateInputs);
334
335$(window).on("load", function(){
336    var year = new Date().getFullYear();
337    var maxyear = year - 18;
338    var minyear = year - 110;
339
340    $("input[type='date']").attr("min", minyear + "-01-01");
341    $("input[type='date']").attr("max", maxyear + "-12-31");
342});
343
344function validateDateProfile () {
345    const widgetProfile = document.querySelector('.widget-user-profile-edit');
346    
347    // OJO: Si por alguna razón tu widget no se llama exactamente así, 
348    // esta línea está matando la función antes de que empiece.
349    if (!widgetProfile) {
350        console.warn('El widget profile no se encontró. La función se detuvo.');
351        return; 
352    }
353    
354    const inputFecha = document.getElementById('id_custom_FECHA-NACIMIENTO-NUEVA');
355
356    if (inputFecha) {
357        let valorOriginal = inputFecha.getAttribute('value');
358
359        if (valorOriginal) {
360            let valorCorregido = '';
361
362            if (valorOriginal.includes('/')) {
363                const partes = valorOriginal.split('/');
364                if (partes.length === 3) {
365                    const dia = partes[0].trim();
366                    const mes = partes[1].trim();
367                    const anio = partes[2].trim();
368                    valorCorregido = `${anio}-${mes}-${dia}`;
369                }
370            } else if (valorOriginal.includes('-')) {
371                const partes = valorOriginal.split('-');
372                
373                if (partes.length === 3) {
374                    const parte1 = partes[0].trim();
375                    const parte2 = partes[1].trim();
376                    const parte3 = partes[2].trim();
377                    
378                    // Si fuera dd-mm-aaaa por error, esto lo ignora para evitar bugs.
379                    if (parte1.length === 4) {
380                        valorCorregido = `${parte1}-${parte2}-${parte3}`;
381                    }
382                }
383            }
384
385            // Si logramos capturar y formatear la fecha correctamente, ejecutamos los cambios
386            if (valorCorregido !== '') {
387                inputFecha.removeAttribute('value');
388                inputFecha.value = '';                 
389                inputFecha.setAttribute('type', 'date');
390                
391                setTimeout(function() {
392                    inputFecha.value = valorCorregido;
393                    inputFecha.setAttribute('value', valorCorregido);
394                    
395                    console.log('¡Ahora sí! Asignado después del redibujado: ', inputFecha.value);
396                }, 500);                
397            } else {
398                console.warn('El formato de fecha no es válido ni como dd/mm/aaaa ni como aaaa-mm-dd');
399            }
400        }
401    }
402}
403
404document.addEventListener('DOMContentLoaded', validateDateProfile);
405
406
407
408
409
410////////////////////////////////////////////////////////////////////////////////
411//variables globales
412let prevScrollpos = window.pageYOffset;
413let currentScrollPos = window.pageYOffset;
414
415// funcion para calcular y ajustar el height del slider según su cabecera y demás elemmentos
416function slider_height(){
417
418    //sizes
419    const $nav_height = $('.with-region-nav').height(); //nav
420    const $window_height = window.innerHeight; //window
421    let $header_height = 0; //se esconde en versión mobile
422    let $total_height = 0; //
423
424    //si header está visible, get su height
425    if($('header.region-header').is(":visible")) $header_height = $('header.region-header').height();
426
427    //total height de la cabecera
428    $total_height = $header_height + $nav_height;
429    
430    $(document).find('.box--slider').css('min-height', `${$window_height - $total_height}px`)
431}
432
433//cambiar titular de la página profile
434if($('.CoreApp.profile').length > 0){
435    $('h1').html('Perfil').css('opacity',1)
436}
437
438//cambiar titular de la página de noticias
439if($('body[class*=" entry-"]').length > 0){
440    $('.widget-single-article .details .title').appendTo($('.region-cabecera .widget-simple-title >.content')).animate({opacity:1, 'top':0},500).siblings().remove();
441    $('.date').appendTo($('.picture')).animate({opacity:1},0);
442} else if($('body[class*=" tag-"][class*="NewsApp"]').length > 0) {
443    const url = new URL(window.location.href);
444    let tag = url.pathname.split('/').pop();
445    $('.region-cabecera .widget-simple-title >.content *').html('Etiqueta: ' + tag).css({'opacity':1});
446}
447
448///////////////////////////////////////////
449// EVENTOS
450
451if($('body[class*=event-]').length > 0){
452    //copiar titular en cabecera
453    $('[data-level="1"]').find('h1').html($('.title').text()).css({'opacity':1})
454    $('.widget-social-share').appendTo('.share').css({'opacity':1})
455}
456///////////////////////////////////////////////////////////////////////////////
457// FETCH SECCIONES NOTICIA
458
459const urls = [
460    'aparicion-en-medios',
461    'convenios',
462    'notas-de-prensa',
463    'noticias',
464    'participacion-en-eventos',
465    'reuniones'
466]
467
468async function getTotalSeccion(urls) {
469    
470    const path = '/api/v1/news/search?stream='
471    const totales = [];
472
473    for (const url of urls) {
474        try {
475            const response = await fetch(path + url);
476            const data = await response.json();
477            const total = data.total;
478            totales.push(total);
479        } catch (error) {
480            console.error(`Error al obtener los datos de ${url}:`, error);
481            totals.push(null);
482        }
483    }
484
485    return totales;
486}
487
488getTotalSeccion(urls).then(totales => {
489    totales.forEach( (total, index) => {
490        $('.menu-secciones ul li:nth-child('+ (index + 1 ) +')').children().append('<span class="li-num-seccion">'+ total +'</span>');
491    });
492})
493
494
495////////////////////////////////////////////////////////////////////////////////
496
497$(document).on('click', 'button.navbar-toggle', function(){
498    $('body').toggleClass('o-hidden')
499})
500
501$(document).on('click', '.login-container button[aria-labelledby="Cancelar"]', function(){
502    window.history.back();
503    return false;
504})
505
506////////////////////////////////////////////////////////////////////////////////
507// SLICK NOTICIAS
508
509//añadir tag picture para poder hacer zoom a la imagen dentro de su espacio
510$('.widget-article-list .card:not(.empty), .widget-event-list .card:not(.empty)').each(function(){
511    $(this).find('img').wrap('<picture></picture>');
512})
513
514$('.region-hero >.row').slick({
515    infinite: true,           // Desplazamiento infinito
516    slidesToShow: 1,          // Número de slides visibles al mismo tiempo
517    slidesToScroll: 1,        // Número de slides que se desplazan al mismo tiempo
518    autoplay: true,           // Activar autoplay
519    autoplaySpeed: 5000,      // Velocidad del autoplay en ms (0 para movimiento continuo) ES LA VELOCIDAD DE LA BARRA. BUSCAD EN EL CSS 'BARRA'
520    speed: 1000,              // Velocidad de transición más lenta para suavizar el movimiento
521    cssEase: 'linear',        // Transición lineal para un movimiento continuo
522    arrows: false,            // Sin flechas de navegación
523    dots: true,              // Sin puntos de navegación
524    pauseOnHover: true,
525    pauseOnFocus: false,      // No pausar cuando el slider esté enfocado
526    fade: true,             // fade
527//     responsive: [
528//         {
529//             breakpoint: 992.1,
530//             settings: {
531//                 arrows: false,
532//                 centerMode: true,
533//                 centerPadding: '15vw',
534//                 slidesToShow: 1
535//             }
536//         }
537//   ]
538});
539
540//////////////////////////////////////////////////////////////////////////////
541// WIDGET SEARCH
542
543if($('.widget-search.eventos').length > 0){
544
545    var search_custom_item_markup = `
546        <article class="event">
547            <div class="event-header">
548                <figure>
549                    <img class="event-img card-img-top" loading="lazy" decoding="async"/>
550                    <div class="event-date">
551                        <span class="event-date-day"></span>
552                        <span class="event-date-month"></span>
553                        <span class="event-date-year"></span>
554                    </div>
555                </figure>
556            </div>
557            <div class="event-body">
558                <div class="event-info">
559                    <div class="event-horario">
560                        <i class="bi bi-clock"></i>
561                        <div class="event-horario-info">
562                            <span class="event-horario-start"></span>
563                            <span class="event-horario-end"></span>
564                        </div>
565                    </div>
566                    <div class="event-city">
567                        <i class="bi bi-geo-alt"></i>
568                        <span class="event-city-text"></span>
569                    </div>
570                </div>
571                <a class="event-link">
572                    <h3 class="event-title"></h3>
573                </a>
574            </div>
575        </article>        
576    `;
577
578    $(document).on('results', '.widget-search.eventos', function(event, res){
579        const items = res.data.items
580        let card = $('.event');
581        console.table(items);
582
583        card.each(function(index){
584
585            //fecha esquina superior
586            let fecha = items[index].date.split(' ') 
587
588            //fechas card body start
589            let horario_start =  items[index].start_time.split(' '),
590                dia_start = horario_start[0].split('-').reverse().join('/'),
591                hora_start = horario_start[1].substring(0,5)
592            
593            //fechas card body end
594            let horario_end =  items[index].end_time.split(' '),
595                dia_end = horario_end[0].split('-').reverse().join('/'),
596                hora_end = horario_end[1].substring(0,5)
597            
598            //insertar datos en CARD
599            $(this).find('.event-img').attr({
600                'alt': items[index].title,
601                'title': items[index].title,
602                'src': items[index].image
603            })
604            $(this).find('.event-date-day').html(fecha[0])
605            $(this).find('.event-date-month').html(fecha[1].substring(0,3))
606            $(this).find('.event-date-year').html(fecha[2])
607            $(this).find('.event-city-text').html(items[index].city)
608            $(this).find('.event-title').html(items[index].title)
609            $(this).find('.event-horario-start').html(dia_start + ' h: ' + hora_start + ' -')
610            $(this).find('.event-horario-end').html(dia_end + ' h: ' + hora_end)
611            $(this).find('.event-link').attr({
612                'href': items[index].url,
613                'target': '_self' 
614            })
615        });
616
617        $(this).animate({opacity : 1}, 500)
618
619    })
620}
621
622// Mini buscador de noticias del sidebar
623if ($('.widget-search.sidebar-search').length > 0) {
624
625    var search_custom_item_markup = `
626        <li class="item-search">
627            <a class="item-link"></a>
628        </li>
629    `;
630
631    $(document).on(
632        'results',
633        '.widget-search.sidebar-search',
634        function (event, response) {
635            const $widget = $(this);
636            const $results = $widget.find('.content-results');
637            const items = response?.data?.items || [];
638            const $cards = $results.find('.item-search');
639
640            $cards.each(function (index) {
641                const item = items[index];
642                const $card = $(this);
643
644                if (!item) {
645                    $card.remove();
646                    return;
647                }
648
649                $card
650                    .find('.item-link')
651                    .attr({
652                        href: item.url,
653                        target: '_self'
654                    })
655                    .text(item.title);
656            });
657
658            if (items.length) {
659                $results
660                    .stop(true, true)
661                    .css({
662                        opacity: 1,
663                        zIndex: 3,
664                        pointerEvents: 'auto'
665                    });
666            } else {
667                $results
668                    .stop(true, true)
669                    .css({
670                        opacity: 0,
671                        zIndex: 0,
672                        pointerEvents: 'none'
673                    });
674            }
675        }
676    );
677
678    $('.widget-search.sidebar-search').each(function () {
679        const $widget = $(this);
680        const $input = $widget.find('.sidebar-input');
681        const $results = $widget.find('.content-results');
682
683        $input
684            .off('.sidebarSearch')
685            .on('focusin.sidebarSearch', function () {
686                if ($results.children().length) {
687                    $results
688                        .stop(true, true)
689                        .css({
690                            opacity: 1,
691                            zIndex: 3,
692                            pointerEvents: 'auto'
693                        });
694                }
695            })
696            .on('focusout.sidebarSearch', function () {
697                setTimeout(function () {
698                    $results
699                        .stop(true, true)
700                        .css({
701                            opacity: 0,
702                            zIndex: 0,
703                            pointerEvents: 'none'
704                        });
705                }, 200);
706            });
707    });
708}
709
710function connectSidebarSearchToArticleList() {
711    try {
712        const $sidebarSearch = $('.widget-search.sidebar-search');
713        const $articleList = $('.region-content .widget-article-list');
714
715        if (!$sidebarSearch.length || !$articleList.length) {
716            return;
717        }
718
719        const $articleContent = $articleList.children('.content').first();
720
721        if (!$articleContent.length) {
722            return;
723        }
724
725        /*
726         * Guardamos las noticias originales para recuperarlas
727         * cuando el buscador vuelva a estar vacío.
728         */
729        const originalArticleHtml = $articleContent.html();
730
731        function escapeHtml(value) {
732            return $('<div>')
733                .text(value == null ? '' : String(value))
734                .html();
735        }
736
737        function createArticleCard(item) {
738            const title = escapeHtml(item.title || '');
739            const url = escapeHtml(item.url || '#');
740            const date = escapeHtml(item.date || '');
741
742            const image =
743                item.image ||
744                item.thumbnail ||
745                item.picture ||
746                '';
747
748            const abstract = escapeHtml(
749                item.abstract ||
750                item.description ||
751                ''
752            );
753
754            const imageHtml = image
755                ? `
756                    <picture class="sidebar-search-picture">
757                        <img
758                            class="card-img-top"
759                            src="${escapeHtml(image)}"
760                            alt="${title}"
761                            loading="lazy"
762                            decoding="async"
763                        >
764                    </picture>
765                `
766                : '';
767            return `
768                <div class="card with-footer col_4">
769                    ${imageHtml}
770
771                    <div class="card-block">
772                        <h4
773                            class="card-title"
774                            title="${title}"
775                        >
776                            <a href="${url}">
777                                ${title}
778                            </a>
779                        </h4>
780
781                        ${
782                            date
783                                ? `
784                                    <p class="card-subtitle text-muted date">
785                                        ${date}
786                                    </p>
787                                    <br>
788                                `
789                                : ''
790                        }
791
792                        ${
793                            abstract
794                                ? `
795                                    <div class="card-text">
796                                        <p>${abstract}</p>
797                                    </div>
798                                `
799                                : ''
800                        }
801                    </div>
802
803                    <div class="card-footer text-muted">
804                        <a
805                            href="${url}"
806                            class="readmore"
807                        >
808                            Leer más
809                        </a>
810                    </div>
811                </div>
812            `;
813        }
814
815        function renderArticles(items) {
816            if (!items.length) {
817                $articleContent.html(`
818                    <div class="sidebar-search-no-results">
819                        No se han encontrado noticias.
820                    </div>
821                `);
822
823                return;
824            }
825
826            let html = '<div class="card-deck-wrapper">';
827
828            /*
829             * Las tarjetas originales están agrupadas de tres en tres.
830             */
831            for (let index = 0; index < items.length; index += 3) {
832                const group = items.slice(index, index + 3);
833
834                html += '<div class="card-deck">';
835
836                group.forEach(function (item) {
837                    html += createArticleCard(item);
838                });
839
840                /*
841                 * Rellenamos el último grupo para conservar
842                 * la estructura visual de tres columnas.
843                 */
844                while (group.length < 3) {
845                    html += '<div class="card col_4 empty"></div>';
846                    group.push(null);
847                }
848
849                html += '</div>';
850            }
851
852            html += '</div>';
853
854            $articleContent.html(html);
855        }
856
857        $(document)
858            .off(
859                'results.sidebarArticleList',
860                '.widget-search.sidebar-search'
861            )
862            .on(
863                'results.sidebarArticleList',
864                '.widget-search.sidebar-search',
865                function (event, response) {
866                    const searchTerm = $(this)
867                        .find('input[name="term"]')
868                        .val()
869                        .trim();
870
871                    /*
872                     * Al borrar el buscador recuperamos el listado
873                     * y la paginación originales.
874                     */
875                    if (!searchTerm) {
876                        $articleContent.html(originalArticleHtml);
877                        return;
878                    }
879
880                    const items =
881                        response &&
882                        response.data &&
883                        Array.isArray(response.data.items)
884                            ? response.data.items
885                            : [];
886
887                    // console.table(items);
888
889                    renderArticles(items);
890                }
891            );
892
893    } catch (error) {
894        console.error(
895            'Error en connectSidebarSearchToArticleList:',
896            error
897        );
898    }
899}
900
901$(window).on('load', function () {
902    connectSidebarSearchToArticleList();
903});
904
905
906// $(document).on('focus', '.sidebar-input', function(){
907//     $('.content-results').slideDown();
908// }).on('focusout', function(){
909//     $('.content-results').slideUp();    
910// })
911
912
913//rellenar input datalist
914// if($('.widget-search.sidebar-search').length > 0){
915//     var search_custom_item_markup = `
916//         <option value="">       
917//     `;
918//     $(document).on('results', '.widget-search.sidebar-search', function(event, res){
919//         const items = res.data.items
920//         let card = $('option')
921//         // console.table(items)
922//         card.each(function(index){
923//             $(this).val(items[index].title)
924//         });
925//         $(this).animate({opacity : 1}, 500)
926//     })
927// }
928
929
930//////////////////////////////////////////////////////////////////////////////
931// MEDIATECAS 
932
933//box tipo VER DOCUMENTO
934if($('.widget-media-library.recursos').length > 0){
935    //plantilla
936    var media_custom_item_markup = `
937        <!-- start -->
938            <article class="card files">
939                <div class="content display-grid">
940                    <div class="card-background"></div>
941                    <div class="card-data card-data-info">
942                        <p class="card-date">__date__</p>
943                        <h3 clasS="card-title">__title__</h3>
944                        <p class="card-descripcion"></p>
945                    </div>
946                    <div class="card-data card-data-download">
947                        <a class="" target="_blank" href="__download__url">
948                            <h4>VER DOCUMENTO</h4>
949                        </a>
950                    </div>
951                </div>
952            </article>
953        <!-- end -->
954    `;
955    
956    //get datos
957    $(document).on('results','.widget-media-library.recursos', function(event, res){
958        console.table(res.data.resources);
959        const notas = res.data.resources;//datos
960
961        $(this).find('.card').each(function(index){
962            $(this).attr({
963                'data-index': index,
964                'data-name': notas[index].title
965            })
966            $(this).find('.card-date').html(notas[index].date)
967            $(this).find('.card-descripcion').html(notas[index].custom.DESCRIPCIÓN)
968        })
969    });
970}
971
972//box tipo card COCHE background
973if($('.widget-media-library.documentacion').length > 0){
974    //plantilla
975    var media_custom_item_markup = `
976        <!-- start -->
977            <article class="card files">
978                <div class="content display-grid">
979                    <div class="card-background"></div>
980                    <img src="https://media.timtul.com/media/web_auve/isotipo-auve-linear-gradient_20241227152746.svg" class="card-car"/>
981                    <div class="card-data card-data-info">
982                        <h3 clasS="card-title">__title__</h3>
983                        <a class="" target="_blank" href="__download__url">
984                            DESCARGAR
985                        </a>
986                    </div>
987                </div>
988            </article>
989        <!-- end -->
990    `;
991    
992    //get datos
993    $(document).on('results','.widget-media-library.documentacion', function(event, res){
994        console.table(res.data.resources);
995        const notas = res.data.resources;//datos
996        
997        $(this).find('.card').each(function(index){
998            $(this).attr({
999                'data-index': index,
1000                'data-name': notas[index].title
1001            })
1002            $(this).find('.card-date').html(notas[index].date)
1003            $(this).find('.card-descripcion').html(notas[index].custom.DESCRIPCIÓN)
1004        })
1005    });
1006}
1007
1008//box color SOLID
1009if($('.widget-media-library.legislacion').length > 0){
1010    //plantilla
1011    var media_custom_item_markup = `
1012        <!-- start -->
1013            <article class="card files">
1014                <div class="content display-grid">
1015                    <div class="card-data card-data-info">
1016                        <a class="" target="_blank" href="__download__url">
1017                            <h3 clasS="card-title">__title__</h3>
1018                        </a>
1019                        <p class="card-descripcion"></p>
1020                    </div>
1021                </div>
1022            </article>
1023        <!-- end -->
1024    `;
1025    
1026    //get datos
1027    $(document).on('results','.widget-media-library.legislacion', function(event, res){
1028        console.table(res.data.resources);
1029        const notas = res.data.resources;//datos
1030        
1031        $(this).find('.card').each(function(index){
1032            $(this).attr({
1033                'data-index': index,
1034                'data-name': notas[index].title
1035            })
1036            $(this).find('.card-date').html(notas[index].date)
1037            $(this).find('.card-descripcion').html(notas[index].custom.DESCRIPCIÓN)
1038        })
1039    });
1040}
1041
1042
1043if ($('input#term').length > 0){
1044    $('input#term').attr('placeholder', $('input#term').prev().text())
1045    .focus(function(){
1046        $('::placeholder', this).css({'opacity':'0'})
1047    })
1048}
1049
1050//cambiar input fecha a input data
1051function inputDateFunction () {
1052    const widgetProfile = document.querySelector('.widget-user-profile-edit');
1053    if (widgetProfile) return;
1054
1055    if ($('input[type=date]').length > 0){
1056        $('input[type=date]').each(function(){
1057            $($(this)).attr({
1058                'type': 'date',
1059                'placeholder' : $(this).prev().text()
1060            })   
1061            .focus(function(){$(this).attr('type', 'date')})
1062            .blur(function(){$(this).attr('type', 'text')})
1063        })
1064    }
1065}
1066
1067inputDateFunction();
1068
1069/////////////////////////////////////////////////////////////////////////////////////////
1070// 
1071$(".widget-mini-calendar.crazy").on("results", function(evt, data) { 
1072    var eventos = data.data;
1073    var days =$(".widget-mini-calendar .day")
1074
1075    eventos.forEach(function(event, index){
1076        var date = new Date(Date.parse(event.day));
1077        var categorias="";
1078        event["tags"].forEach(function(tag, index){categorias+= tag + " ";});
1079        days.each(function(){
1080            var diaEvento = new Date($(this).attr('data-dt'));
1081            if(diaEvento.toString() === date.toString()){
1082                var adins = `<div class="event-cal"><a href="${event.url}" class="${categorias}">
1083                    ${event.title}
1084                </a></div>`;
1085                $(this).append(adins);
1086            }
1087        });
1088    });
1089});
1090
1091function buscarAuto() {
1092    $('.media-library-search input:not([role="textbox"])').on('keyup', function(){$('.media-library-search .btn-search-files').trigger('submit')});
1093    $('.media-library-search select').on('change', function(){$('.media-library-search .btn-search-files').trigger('submit')});
1094    $('.widget-search input:not([role="textbox"])').on('keyup', function(){$('.widget-search form').trigger('submit')});    
1095    $('.widget-search input[type="date"]').on('keyup', function(){$('.widget-search form').trigger('submit')});
1096    $('.widget-search select').on('change', function(){$('.widget-search form').trigger('submit')});
1097    $(document).on('change', '.widget-search input[type="date"]', function(){$('.widget-search form').trigger('submit')}); 
1098    $('.widget-users-search input:not([role="textbox"])').on('keyup', function(){$('.widget-users-search .submit-form').click()});
1099    $('.widget-users-search select').on('change', function(){$('.widget-users-search .submit-form').click()});
1100}
1101
1102//////////////////////////////////////////////////////////////////////////////
1103// HEADERMENU
1104
1105function headerScroll(){
1106    currentScrollPos = window.pageYOffset;
1107    if (currentScrollPos > 250) $(".with-region-nav").addClass('scroll'); else $(".with-region-nav").removeClass('scroll'); 
1108}
1109
1110//onloads//////////////////////////////////////////////////
1111document.addEventListener("scroll", (event) => {
1112    headerScroll(); 
1113});
1114
1115//carga antes que los widgets
1116document.addEventListener("DOMContentLoaded", (event) => {
1117    // jarallaxs();
1118    // owlcustom();
1119    // drawNoticia();
1120    // activarContador($('.region-contadores'));
1121    // $('body').append('
1121<script>AOS.init();<\/script>');
1122});
1123
1124//carga después de los widgets
1125window.addEventListener("load", (event) => {
1126    // if($('body.body.modal-open').length>0) $('#login-modal').addClass('in').css('display','inline');
1127    // $('.jarallax').jarallax({speed: 0.4});
1128    buscarAuto() 
1129    slider_height()
1130    validSpanishDNIOnSignup()
1131    makeLastNameRequiredOnSignupForm();
1132    cambiarTitleLogout()
1133});
1134
1135
1136
1137
1138
1139
1140
1141// --> -----------------------------------------------------------------------------
1142
1143$("#contact-form .modelo-vehiculo, #contact-form .tipo-de-vehiculo, #contact-form .marca-vehiculo, #contact-form .profesion").find("input").removeAttr("required");
1144
1145
1146
1147
1148// --> -----------------------------------
1149// --> Fix Register para que lleve al alta
1150
1151function waitForElement(selector, { root = document, timeout = 10000 } = {}) {
1152    return new Promise((resolve, reject) => {
1153        const found = root.querySelector(selector);
1154
1155        if (found) return resolve(found);
1156
1157        const obs = new MutationObserver(() => {
1158            const el = root.querySelector(selector);
1159            
1160            if (el) {
1161                obs.disconnect();
1162                resolve(el);
1163            }
1164        });
1165
1166        obs.observe(root, { childList: true, subtree: true });
1167
1168        if (timeout) {
1169            setTimeout(() => {
1170                obs.disconnect();
1171                reject(new Error(`waitForElement timeout (${selector})`));
1172            }, timeout);
1173        }
1174    });
1175}
1176
1177function fixRegister(){   
1178    // var $popup = $('#login-modal');
1179
1180    // $popup.on('shown.bs.modal', function(){
1181    //     var popup = this;
1182
1183    //     waitForElement('a[href*="/accounts/signup"]', {root: popup})
1184    //     .then(a => {$(a).attr('href', $(a));})
1185    //     .catch(console.warn);
1186    // });
1187
1188    // $(window).on('load', function(){
1189    //     $('a[href*="/accounts/signup"]').attr('href', '/alta-socios-auve');
1190
1191    // });
1192
1193    $(document).on('click', 'a[href*="/accounts/signup"], a[href*="/accounts/register"]', function(event){
1194        event.preventDefault();
1195        var href = $(this).attr('href'),
1196            $popup = $('#login-modal');
1197
1198        $popup.removeClass('in');
1199
1200        if (!href) return;
1201
1202        setTimeout(function(){window.location = href;}, 500);
1203        return false;
1204    });
1205}
1206
1207
1208fixRegister();
1209$(window).on('load', fixRegister);
1210
1211
1212
1213
1214// --> -------------------------
1215// --> Arreglar los desplegables
1216
1217function destroySelect2(select2){
1218    var $select2 = select2;
1219
1220    $select2.select2('destroy');
1221}
1222
1223function renderSelectpicker(){
1224    $('select', 'form.signup').each(function(){
1225        var $select = $(this);
1226
1227        $select.selectpicker('destroy');
1228        $select.selectpicker().addClass('tt-selectpicker');
1229    });
1230}
1231
1232function fixDropdowns(){
1233    $('.tt-selectpicker, select', 'form.signup').each(function(){
1234        $selectpicker = $(this);
1235
1236        if(!$selectpicker.hasClass('select2-hidden-accessible')) return;
1237        destroySelect2($selectpicker);
1238    });
1239}
1240
1241
1242$(window).on('load', function(){fixDropdowns(); renderSelectpicker();});
1243
1244
1245
1246
1247// --> -----------------------------------
1248
1249function cambiarTituloRegister() {
1250    if (!window.location.pathname.includes('/register/') && !window.location.pathname.includes('/signup/')) return;
1251
1252    var $titulo = $('.modal-base-body > .login-container > h3');
1253    $titulo.text('¡Hazte Socio!');
1254}
1255
1256cambiarTituloRegister();
1257$(window).on('load', cambiarTituloRegister);
1258
1259$(window).on('load', function(){
1260    // var $titulo = $('.modal-base-body >.login-container >h3');
1261    // $('<a href="/">Volver a la web de AUVE</a>').insertBefore($titulo);
1262    let htmlMessage = '<a href="/">Volver a la web de AUVE</a> <div class="js-box"><h4>No disponible temporalmente</h4> <p>Estamos realizando trabajos de mejora en la plataforma. Os notificaremos por email cuando volvamos a tener el accesso disponible. <br><br>¡Gracias por confiar en AUVE!</p></div>';
1263    // $('.modal-base-body >.login-container:has(form[action="/accounts/login/"])').prepend(htmlMessage);
1264});
1265
1266// Cuando se abre el modal
1267$('#login-modal').on('shown.bs.modal', function (e) {
1268    let htmlMessage = '<div class="js-box"><h4>No disponible temporalmente</h4> <p>Estamos realizando trabajos de mejora en la plataforma. Os notificaremos por email cuando volvamos a tener el accesso disponible. <br><br>¡Gracias por confiar en AUVE!</p></div>';
1269
1270    // $('#login-modal .loginmodal-container').prepend(htmlMessage);
1271    // setTimeout(function(){$('#login-modal .loginmodal-container').prepend(htmlMessage);}, 300);
1272    // setTimeout(function(){$('#login-modal .loginmodal-container').prepend(htmlMessage);}, 600);
1273});
1274
1275// --> -----------------------------------
1276
1277// CALCULADORA CAE
1278
1279function initCaeCalculator() {
1280
1281    const form = document.getElementById('cae-form');
1282
1283    if (!form) {return}
1284    // --- DATA FROM BOE ---
1285    const BOE_DATA = {
1286        annualMileage: { Turismo: 13073, Furgoneta: 15815, Camion: 53508, Autobus: 46607, Motocicleta: 2831, Ciclomotor: 1759 },
1287        conversionFactor: { Gasolina: 9.19, Diesel: 10.0, GLP: 7.16, GasNatural: 13.33 },
1288        defaultCVA: { 
1289            Turismo: { 
1290                Gasolina: 7.89, 
1291                Diesel: 6.99, 
1292                HibridoGasolina: 5.135,
1293                HibridoDiesel: 5.18,
1294                GLP: 6.45, 
1295                GasNatural: 4.88 
1296            }, 
1297            Furgoneta: { Gasolina: 7.05, Diesel: 5.63, GasNatural: 8.28 }, 
1298            Camion: { Diesel: 35 }, 
1299            Autobus: { Diesel: 21 }, 
1300            Motocicleta: { Gasolina: 2.9 }, 
1301            Ciclomotor: { Gasolina: 2.9 } 
1302        },
1303        defaultCVN: { Turismo: 17.92, Furgoneta: 20.84, Camion: 118.53, Autobus: 86.70, Motocicleta: 4.84, Ciclomotor: 19.45 }
1304    };
1305    
1306    // --- FORM ELEMENTS ---
1307    
1308    const vehicleTypeSelect = document.getElementById('vehicle-type');
1309    const fuelTypeSelect = document.getElementById('fuel-type');
1310    const cvaInput = document.getElementById('cva');
1311    const cvaHelperText = document.getElementById('cva-helper-text');
1312    const resultsContainer = document.getElementById('results-container');
1313    const resultElement = document.getElementById('result');
1314    const resultWarningElement = document.getElementById('result-warning');
1315    const detailsList = document.querySelector('#calculation-details .details-list');
1316    const auvePromo = document.getElementById('auve-promo');
1317
1318    // --- DYNAMIC UX FUNCTIONS ---
1319    function handleVehicleAndFuelChange() {
1320        const selectedVehicle = vehicleTypeSelect.value;
1321        const selectedFuel = fuelTypeSelect.value;
1322
1323        if (selectedVehicle === 'Turismo' && (selectedFuel === 'Gasolina' || selectedFuel === 'Diesel')) {
1324            const fixedValue = BOE_DATA.defaultCVA.Turismo[selectedFuel];
1325            cvaInput.value = fixedValue;
1326            // cvaInput.disabled = true;
1327            cvaHelperText.innerHTML = `Para Turismos de ${selectedFuel}, el BOE establece un consumo fijo de&nbsp;<strong>${fixedValue} l/100km</strong>.`;
1328        } else {
1329            cvaInput.disabled = false;
1330            cvaInput.value = ''; 
1331            cvaHelperText.innerHTML = `Si no conoces el dato, déjalo en blanco. Puedes consultarlo en: <a href="https://coches.idae.es/base-datos/marca-y-modelo" target="_blank" class="text-blue-600 hover:underline">IDAE Actual</a> o <a href="https://coches.idae.es/historico-emisiones-consumos" target="_blank" class="text-blue-600 hover:underline">IDAE Histórico</a>.`;
1332        }
1333        updateCVAPlaceholder();
1334    }
1335    
1336    function updateFuelOptions() {
1337        const selectedVehicle = vehicleTypeSelect.value;
1338        const availableFuels = Object.keys(BOE_DATA.defaultCVA[selectedVehicle]);
1339        const currentFuel = fuelTypeSelect.value;
1340        fuelTypeSelect.innerHTML = '';
1341        availableFuels.forEach(fuel => {
1342            const option = document.createElement('option');
1343            option.value = fuel;
1344            let fuelName;
1345            switch (fuel) {
1346                case 'GasNatural': fuelName = 'Gas Natural'; break;
1347                case 'HibridoGasolina': fuelName = 'Híbrido Gasolina'; break;
1348                case 'HibridoDiesel': fuelName = 'Híbrido Diésel'; break;
1349                default: fuelName = fuel;
1350            }
1351            option.textContent = fuelName;
1352            fuelTypeSelect.appendChild(option);
1353        });
1354
1355        if (availableFuels.includes(currentFuel)) {
1356            fuelTypeSelect.value = currentFuel;
1357        }
1358        handleVehicleAndFuelChange();
1359    }
1360
1361    function updateCVAPlaceholder() {
1362        if (cvaInput.disabled) return;
1363        const selectedFuel = fuelTypeSelect.value;
1364        const units = selectedFuel === 'GasNatural' ? 'kg/100km' : 'l/100km';
1365        cvaInput.placeholder = `Ej: 6.5 (${units})`;
1366    }
1367
1368    // --- EVENT LISTENERS ---
1369    vehicleTypeSelect.addEventListener('change', updateFuelOptions);
1370    fuelTypeSelect.addEventListener('change', handleVehicleAndFuelChange);
1371    document.addEventListener('DOMContentLoaded', updateFuelOptions);
1372
1373    form.addEventListener('submit', function(event) {
1374        event.preventDefault();
1375        detailsList.innerHTML = '';
1376        resultWarningElement.textContent = '';
1377        resultElement.textContent = '';
1378        auvePromo.classList.add('hidden');
1379
1380        const vehicleType = vehicleTypeSelect.value;
1381        const fuelType = fuelTypeSelect.value;
1382        const userInputCVA = cvaInput.value;
1383        const userInputCVN = document.getElementById('cvn').value;
1384
1385        const L = BOE_DATA.annualMileage[vehicleType];
1386        
1387        const conversionFactorKey = fuelType.includes('Gasolina') ? 'Gasolina' : (fuelType.includes('Diesel') ? 'Diesel' : fuelType);
1388        const f = BOE_DATA.conversionFactor[conversionFactorKey];
1389        
1390        let cvaSource;
1391        let CVA;
1392
1393        if (cvaInput.disabled) {
1394            CVA = parseFloat(cvaInput.value);
1395            cvaSource = 'Valor fijo (BOE)';
1396        } else if (userInputCVA) {
1397            CVA = parseFloat(userInputCVA);
1398            cvaSource = 'Valor introducido';
1399        } else {
1400            CVA = BOE_DATA.defaultCVA[vehicleType][fuelType];
1401            cvaSource = 'Valor por defecto (BOE)';
1402        }
1403        
1404        if (CVA === undefined) {
1405            resultWarningElement.textContent = 'La combinación de vehículo y combustible seleccionada no es válida.';
1406            resultsContainer.classList.remove('hidden');
1407            return;
1408        }
1409
1410        let cvnSource = 'Valor por defecto (BOE)';
1411        let CVN = BOE_DATA.defaultCVN[vehicleType];
1412        if (userInputCVN) { CVN = parseFloat(userInputCVN); cvnSource = 'Valor introducido'; }
1413        
1414        const energyConsumptionOld = CVA * f;
1415        const savingsPer100km = energyConsumptionOld - CVN;
1416        const totalSavingsKWh = (savingsPer100km / 100) * L;
1417        
1418        if (totalSavingsKWh > 0) {
1419            const totalSavingsMWh = totalSavingsKWh / 1000;
1420            resultElement.textContent = `${totalSavingsMWh.toFixed(3).replace('.', ',')} MWh/año`;
1421            auvePromo.classList.remove('hidden');
1422        } else {
1423            resultElement.textContent = '0 MWh/año';
1424            resultWarningElement.textContent = 'Según los datos, no se genera un ahorro energético.';
1425        }
1426
1427        const cvaUnits = fuelType === 'GasNatural' ? 'kg/100km' : 'l/100km';
1428        const fuelTypeNameForDetails = fuelTypeSelect.options[fuelTypeSelect.selectedIndex].text;
1429
1430        detailsList.innerHTML = `<ul>
1431            <li><strong>Kilometraje anual (L):</strong>&nbsp;${L.toLocaleString('es-ES')} km</li>
1432            <li><strong>Consumo vehículo antiguo (CVA):</strong>&nbsp;${CVA} ${cvaUnits} <span>
1432(${cvaSource})</span></li>
1433            <li><strong>Consumo vehículo nuevo (CVN):</strong>&nbsp;${CVN} kWh/100km <span>(${cvnSource})</span></li>
1434            <li><strong>Factor de conversión (f):</strong>&nbsp;${f} <span>(para ${fuelTypeNameForDetails})</span></li></ul>
1435        `;
1436        
1437        resultsContainer.classList.remove('hidden');
1438        resultsContainer.scrollIntoView({ behavior: 'smooth' });
1439    });
1440
1441}
1442$(function () {
1443    initCaeCalculator();
1444});
1445
1446
1447
1448/**
1449 * Valida NIF (DNI) y NIE (España). NO valida CIF.
1450 * @param {string} value
1451 * @returns {boolean}
1452 */
1453function validSpanishNifNie(value) {
1454    const dni = (value || "").toUpperCase().trim();
1455    const letras = "TRWAGMYFPDXBNJZSQVHLCKE";
1456
1457    // NIF: 8 dígitos + letra
1458    if (/^\d{8}[A-Z]$/.test(dni)) {
1459        const numero = parseInt(dni.slice(0, 8), 10);
1460        const letra = dni[8];
1461        return letra === letras[numero % 23];
1462    }
1463
1464    // NIE: X/Y/Z + 7 dígitos + letra
1465    if (/^[XYZ]\d{7}[A-Z]$/.test(dni)) {
1466        const reemplazo = { X: "0", Y: "1", Z: "2" };
1467        const numero = reemplazo[dni[0]] + dni.slice(1, 8);
1468        const letra = dni[8];
1469        return letra === letras[parseInt(numero, 10) % 23];
1470    }
1471
1472    return false;
1473}
1474
1475function validSpanishDNIOnSignup() {
1476    try {
1477        const formBASE = document.querySelector(
1478            'body.modal-base-body > .login-container > form#signup_form[action="/accounts/signup/"]'
1479        );
1480        if (!formBASE) return;
1481
1482        console.log("Estamos validando DNI");
1483
1484        const form = document.getElementById("signup_form");
1485        if (!form) return;
1486        const submitButton = form.querySelector("button[type='submit']");
1487        submitButton?.classList.add("disabled");
1488
1489        const nifInput = document.getElementById("id_custom_NIF");
1490        const countrySelect = document.getElementById("id_custom_PAIS");
1491        if (!nifInput) return;
1492
1493        const ensureNifErrorContainer = () => {
1494        let nifError = document.getElementById("id_custom_NIF-error");
1495        if (nifError) return nifError;
1496
1497        const parent = nifInput.parentElement;
1498        if (!parent) return null;
1499
1500        nifError = document.createElement("div");
1501        nifError.id = "id_custom_NIF-error";
1502        parent.appendChild(nifError);
1503        return nifError;
1504        };
1505
1506        const getNifError = () => ensureNifErrorContainer();
1507
1508        const isSpainSelected = () =>
1509        countrySelect && (countrySelect.value || "").trim().toLowerCase() === "españa";
1510
1511        const setNifError = (message = "") => {
1512        const nifError = getNifError();
1513
1514        if (nifError) {
1515            nifError.textContent = message;
1516            nifError.style.color = message ? "red" : "";
1517            if (message) {
1518                nifError.setAttribute("role", "alert");
1519                submitButton?.classList.add("disabled");
1520            }
1521            else {
1522                nifError.removeAttribute("role");
1523                submitButton?.classList.remove("disabled");
1524            }
1525        }
1526
1527        nifInput.setCustomValidity(message);
1528        if (message) nifInput.setAttribute("aria-invalid", "true");
1529        else nifInput.removeAttribute("aria-invalid");
1530        };
1531
1532        const validateNifIfNeeded = () => {
1533        setNifError("");
1534
1535        if (!isSpainSelected()) {
1536            submitButton?.classList.remove("disabled");
1537            return true;
1538        }
1539        const value = (nifInput.value || "").trim();
1540        if (!value) {
1541            setNifError("Introduce un NIF o NIE.");
1542            return false;
1543        }
1544
1545        if (!validSpanishNifNie(value)) {
1546            setNifError("NIF/NIE no válido. Ej: 12345678Z o X1234567L.");
1547            return false;
1548        }
1549
1550        setNifError("");
1551        return true;
1552        };
1553
1554        form.addEventListener("submit", (e) => {
1555        if (!validateNifIfNeeded()) {
1556            e.preventDefault();
1557            e.stopPropagation();
1558            nifInput.reportValidity?.();
1559            nifInput.focus?.();
1560        }
1561        });
1562
1563        nifInput.addEventListener("blur", validateNifIfNeeded);
1564        nifInput.addEventListener("keyup", () => {
1565        if (isSpainSelected()) validateNifIfNeeded();
1566        else setNifError("");
1567        });
1568
1569        countrySelect?.addEventListener("change", () => {
1570        if (!isSpainSelected()) setNifError("");
1571        else validateNifIfNeeded();
1572        });
1573
1574        validateNifIfNeeded();
1575    } catch (error) {
1576        console.error(
1577            "Error en la función validSpanishDNIOnSignup:",
1578            error
1579        );
1580    }
1581}
1582
1583
1584
1585
1586
1587function cambiarTitleLogout(){
1588
1589    // console.log("entra");
1590
1591    var $logout = $("form[action='/accounts/logout/']").parent();
1592
1593    $logout.find("h3").text("¡Cerrar sesión!");
1594
1595};
1596
1597function makeLastNameRequiredOnSignupForm() {
1598  const form = document.getElementById("signup_form");
1599
1600  if (!form) return;
1601
1602  const lastNameInput = form.querySelector("#id_last_name");
1603   console.log("lastNameInput" + lastNameInput);
1604
1605  if (!lastNameInput) return;
1606
1607  lastNameInput.setAttribute("required", "required");
1608};
1609
1610
1611//////// AUTO SELECCIONAR OTROS EN PROVINCIA CUANDO NO SE ESCOJA ESPAÑA COMO PAÍS EN EL REGISTRATION / SIGNUP_FORM
1612function initSignupCountryProvinceControl() {
1613    try {
1614        var $signupForm = $('#signup_form');
1615
1616        if (!$signupForm.length) {
1617            return;
1618        }
1619
1620        var countrySelector = '#signup_form #id_custom_PAIS';
1621        var provinceSelector = '#signup_form #id_custom_PROVINCIA-REAL';
1622
1623        function updateProvinceByCountry() {
1624            var $country = $(countrySelector);
1625            var $province = $(provinceSelector);
1626
1627            if (!$country.length || !$province.length) {
1628                return;
1629            }
1630
1631            var countryValue = $.trim($country.val() || '');
1632            var isSpain = countryValue === 'España';
1633
1634            // Contenedor visual creado por Bootstrap Select.
1635            var $provinceBootstrap = $province.closest('.bootstrap-select');
1636            var $provinceButton = $provinceBootstrap.find(
1637                'button.dropdown-toggle'
1638            );
1639
1640            if (!isSpain) {
1641                // El select real permanece habilitado para que se envíe.
1642                $province.prop('disabled', false);
1643
1644                // Seleccionar "Otros".
1645                if (typeof $.fn.selectpicker === 'function') {
1646                    $province.selectpicker('val', 'Otros');
1647                    $province.selectpicker('refresh');
1648                } else {
1649                    $province.val('Otros').trigger('change');
1650                }
1651
1652                // Solo bloqueamos la interacción visual.
1653                $provinceBootstrap
1654                    .addClass('province-visually-locked')
1655                    .attr('aria-disabled', 'true');
1656
1657                $provinceButton
1658                    .attr('aria-disabled', 'true')
1659                    .attr('tabindex', '-1');
1660
1661            } else {
1662                // Provincia vuelve a ser editable.
1663                $province.prop('disabled', false);
1664
1665                if (typeof $.fn.selectpicker === 'function') {
1666                    $province.selectpicker('refresh');
1667                }
1668
1669                $provinceBootstrap
1670                    .removeClass('province-visually-locked')
1671                    .removeAttr('aria-disabled');
1672
1673                $provinceButton
1674                    .removeAttr('aria-disabled')
1675                    .removeAttr('tabindex');
1676            }
1677        }
1678
1679        /*
1680         * Evita abrir el desplegable de Provincia cuando esté
1681         * bloqueado visualmente.
1682         */
1683        $(document)
1684            .off(
1685                'click.signupProvinceLock',
1686                '#signup_form .province-visually-locked button.dropdown-toggle'
1687            )
1688            .on(
1689                'click.signupProvinceLock',
1690                '#signup_form .province-visually-locked button.dropdown-toggle',
1691                function (event) {
1692                    event.preventDefault();
1693                    event.stopImmediatePropagation();
1694                    return false;
1695                }
1696            );
1697
1698        /*
1699         * Detecta cambios en País, también los generados por
1700         * Bootstrap Select.
1701         */
1702        $(document)
1703            .off(
1704                'change.signupCountryProvince ' +
1705                'changed.bs.select.signupCountryProvince',
1706                countrySelector
1707            )
1708            .on(
1709                'change.signupCountryProvince ' +
1710                'changed.bs.select.signupCountryProvince',
1711                countrySelector,
1712                updateProvinceByCountry
1713            );
1714
1715        /*
1716         * Refuerzo antes del envío.
1717         * No es necesario habilitar Provincia porque nunca se deshabilita.
1718         */
1719        $signupForm
1720            .off('submit.signupCountryProvince')
1721            .on('submit.signupCountryProvince', function () {
1722                var $country = $(this).find('#id_custom_PAIS');
1723                var $province = $(this).find(
1724                    '#id_custom_PROVINCIA-REAL'
1725                );
1726
1727                if ($.trim($country.val() || '') !== 'España') {
1728                    $province.val('Otros');
1729                }
1730            });
1731
1732        updateProvinceByCountry();
1733
1734    } catch (error) {
1735        console.error(
1736            'Error en initSignupCountryProvinceControl:',
1737            error
1738        );
1739    }
1740}
1741
1742$(window).on('load', function () {
1743    setTimeout(initSignupCountryProvinceControl, 300);
1744});
1745    }
1746    catch(err) {
1747        console.log("[ERROR Custom JS]", err.message);
1748    }
1749</script>

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.