¿Ejecutar la función de JavaScript cuando el usuario termina de escribir en lugar de presionar la tecla?


463

Quiero activar una solicitud ajax cuando el usuario haya terminado de escribir en un cuadro de texto. No quiero que ejecute la función cada vez que el usuario escribe una letra porque eso generaría MUCHAS solicitudes de ajax, sin embargo, tampoco quiero que tengan que presionar el botón Intro.

¿Hay alguna manera de que pueda detectar cuándo el usuario ha terminado de escribir y luego hacer la solicitud ajax?

Usando jQuery aquí! Dave


21
Creo que tendrá que definir "terminar de escribir" para nosotros.
Surreal Dreams

8
Si bien la respuesta de @Surreal Dreams satisface la mayoría de sus requisitos, si el usuario comienza a escribir nuevamente DESPUÉS del tiempo de espera especificado, se enviarán varias solicitudes al servidor. Vea mi respuesta a continuación que almacena cada solicitud XHR en una variable y la cancela antes de disparar una nueva. Esto es realmente lo que Google hace en su búsqueda instantánea .
Marko

1
posible duplicado de jquery keyup delay?
CMS

1
¿Qué pasa con el desenfoque? Supongo que el usuario definitivamente ha terminado de escribir cuando el elemento de entrada pierde el foco.
Don P

3
Una simple búsqueda en Google podría haber obtenido la respuesta simple: schier.co/blog/2014/12/08/…
Cannicide

Respuestas:


689

Entonces, supongo que terminar de escribir significa que debes detenerte por un tiempo, digamos 5 segundos. Con eso en mente, comencemos un temporizador cuando el usuario suelte una tecla y la borre cuando presione una. Decidí que la entrada en cuestión será #myInput.

Haciendo algunas suposiciones ...

//setup before functions
var typingTimer;                //timer identifier
var doneTypingInterval = 5000;  //time in ms, 5 second for example
var $input = $('#myInput');

//on keyup, start the countdown
$input.on('keyup', function () {
  clearTimeout(typingTimer);
  typingTimer = setTimeout(doneTyping, doneTypingInterval);
});

//on keydown, clear the countdown 
$input.on('keydown', function () {
  clearTimeout(typingTimer);
});

//user is "finished typing," do something
function doneTyping () {
  //do something
}

44
Gracias :) Empecé a escribir mi comentario sobre la pregunta y me di cuenta de que tenía una idea decente.
Surreal Dreams

26
Esta respuesta no funciona correctamente, siempre se disparará después de 5 segundos a menos que el usuario escriba muy lentamente. Ver la solución de trabajo a continuación.
partir

3
Si tiene problemas aquí, porque el temporizador se dispara inmediatamente, intente agregar comillas alrededor de la llamada a la función: setTimeout ('functionToBeCalled', doneTypingInterval);
Largo

3
Veo algunos comentarios en los que la función de devolución de llamada, doneTypingen el ejemplo, se ejecuta de inmediato. Eso suele ser el resultado de incluir accidentalmente ()después del nombre de la función. Tenga en cuenta que debe decir setTimeout(doneTyping,nosetTimeout(doneTyping(),
Anthony Disanti

2
@Jessica para trabajar con pegar, debe agregar el evento 'pegar' de esta manera: on ('keyup paste',
Sergey

397

La respuesta elegida arriba no funciona.

Debido a que typingTimer se establece ocasionalmente varias veces (pulsación de tecla dos veces antes de que se active la tecla para los tipos rápidos, etc.), entonces no se borra correctamente.

La solución a continuación resuelve este problema y llamará X segundos después de finalizar como lo solicitó el OP. Tampoco requiere más la función redundante de keydown. También he agregado una comprobación para que su llamada a la función no se realice si su entrada está vacía.

//setup before functions
var typingTimer;                //timer identifier
var doneTypingInterval = 5000;  //time in ms (5 seconds)

//on keyup, start the countdown
$('#myInput').keyup(function(){
    clearTimeout(typingTimer);
    if ($('#myInput').val()) {
        typingTimer = setTimeout(doneTyping, doneTypingInterval);
    }
});

//user is "finished typing," do something
function doneTyping () {
    //do something
}

Y el mismo código en la solución JavaScript vainilla:

//setup before functions
let typingTimer;                //timer identifier
let doneTypingInterval = 5000;  //time in ms (5 seconds)
let myInput = document.getElementById('myInput');

//on keyup, start the countdown
myInput.addEventListener('keyup', () => {
    clearTimeout(typingTimer);
    if (myInput.value) {
        typingTimer = setTimeout(doneTyping, doneTypingInterval);
    }
});

//user is "finished typing," do something
function doneTyping () {
    //do something
}

Esta solución usa ES6 pero no es necesaria aquí. Simplemente reemplace letcon vary la función de flecha con una función regular.


55
Bueno, podría ser útil llamar a la función aún si la entrada está vacía. ¿Qué sucede si desea borrar algunos resultados de búsqueda (por ejemplo) cuando la entrada está en blanco nuevamente?
rvighne

77
Creo que también deberíamos considerar la condición en la que el usuario simplemente pega la cadena dentro del campo.
Vishal Nair

10
úselo $('#myInput').on('input', function() {si desea que esto funcione con copiar y pegar. No veo el punto de la comprobación si la entrada está vacía. Suponiendo que quiere borrar lo que escribió, esto no podrá hacer la llamada ajax.
billynoah

3
¿Por qué es en if ($('#myInput').val)lugar de if ($('#myInput').val())? Debería .valser una función?
wlnirvana

44
¿Por qué no usar $ (this) .val ()?
JoelFan

76

Es solo una línea con la función underscore.js debounce:

$('#my-input-box').keyup(_.debounce(doSomething , 500));

Esto básicamente dice doSomething 500 milisegundos después de que la tipificación de parada.

Para más información: http://underscorejs.org/#debounce


55
También parece estar disponible para jQuery: code.google.com/p/jquery-debounce y github.com/diaspora/jquery-debounce
koppor

63
Me sorprende cómo la gente promociona "UNA LÍNEA DE CÓDIGO" como algo sumamente importante. Excelente. UNA LÍNEA de código que requiere un archivo JS de 1.1k para cargar. No estoy rechazando esto porque es una solución. Pero la línea audaz me molesta y me lleva a un código inflado.
Wolfie

1
@Wolfie, estoy de acuerdo con usted sobre la hinchazón de código, pero Underscore y Lodash son las dos principales utilidades dependientes de NPM, por lo que esta puede ser una solución de una línea para muchas personas.
Paul

3
@Paul Estoy de acuerdo en que si está cargando las bibliotecas para otros fines, está bien y es realmente beneficioso usar un código común. Pero cargar sobre una K de código con el propósito de una línea no es eficiente. Especialmente con plataformas móviles que se convierten en datos cada vez menos ilimitados. Y muchas zonas del euro también pagan internet por los datos. Por lo tanto, es bueno prestar atención a la hinchazón cuando sea razonable hacerlo.
Wolfie

por qué tengo:Uncaught ReferenceError: _ is not defined
Wilf

44

Sí, puede establecer un tiempo de espera de, por ejemplo, 2 segundos en todos y cada uno de los eventos de activación de tecla que activará una solicitud ajax. También puede almacenar el método XHR y abortarlo en eventos de pulsación de teclas posteriores para ahorrar aún más ancho de banda. Aquí hay algo que he escrito para un script de autocompletado mío.

var timer;
var x;

$(".some-input").keyup(function () {
    if (x) { x.abort() } // If there is an existing XHR, abort it.
    clearTimeout(timer); // Clear the timer so we don't end up with dupes.
    timer = setTimeout(function() { // assign timer a new timeout 
        x = $.getJSON(...); // run ajax request and store in x variable (so we can cancel)
    }, 2000); // 2000ms delay, tweak for faster/slower
});

Espero que esto ayude,

Marko


No recomendaría esto. El código activa una solicitud de API cada vez que se presiona una tecla. Luego cancela la solicitud si se presiona otra tecla. Aunque esta solución evita que la devolución de llamada AJAX se active mientras el usuario escribe, seguirá afectando al servidor con solicitudes.
lxg

Ixg, no, esto no lo hará. La función clearTimeout que usa borra los eventos de tiempo de espera anteriores, por lo que invoca la llamada API.
John Smith

@JohnSmith, no estoy de acuerdo, probablemente sea mejor borrar primero el temporizador, luego verificar si xhr está vacío o cualquiera que sea su condición y finalmente ejecutar el temporizador con la llamada ajax
Fleuv

20
var timer;
var timeout = 1000;

$('#in').keyup(function(){
    clearTimeout(timer);
    if ($('#in').val) {
        timer = setTimeout(function(){
            //do stuff here e.g ajax call etc....
             var v = $("#in").val();
             $("#out").html(v);
        }, timeout);
    }
});

ejemplo completo aquí: http://jsfiddle.net/ZYXp4/8/


Esto funcionó para mí, aunque tenga en cuenta que cuando el usuario separa el cuadro de texto, no obtendrá un evento de keyup ya que ha perdido el foco en ese momento. Establecerlo en keydown parece resolver el problema.
horatius83

12

Las dos respuestas principales no funcionan para mí. Entonces, aquí está mi solución:

var timeout = null;

$('#myInput').keyup(function() {
    clearTimeout(timeout);

    timeout = setTimeout(function() {
        //do stuff here
    }, 500);
});

11

Me gusta la respuesta de Surreal Dream, pero descubrí que mi función "doneTyping" se activaría por cada pulsación de tecla, es decir, si escribes "Hola" muy rápido; en lugar de disparar solo una vez cuando dejas de escribir, la función se disparará 5 veces.

El problema era que la función setTimeout de javascript no parece sobrescribir o eliminar los viejos tiempos de espera que se han establecido, pero si lo hace usted mismo, ¡funciona! Así que acabo de agregar una llamada clearTimeout justo antes de setTimeout si typingTimer está configurado. Vea abajo:

//setup before functions
var typingTimer;                //timer identifier
var doneTypingInterval = 5000;  //time in ms, 5 second for example

//on keyup, start the countdown
$('#myInput').on("keyup", function(){
    if (typingTimer) clearTimeout(typingTimer);                 // Clear if already set     
    typingTimer = setTimeout(doneTyping, doneTypingInterval);
});

//on keydown, clear the countdown 
$('#myInput').on("keydown", function(){
    clearTimeout(typingTimer);
});

//user is "finished typing," do something
function doneTyping () {
    //do something
}

Nota: me hubiera gustado haber agregado esto como un comentario a la respuesta de Surreal Dream, pero soy un usuario nuevo y no tengo suficiente reputación. ¡Lo siento!


10

Modificando la respuesta aceptada para manejar casos adicionales como pegar:

//setup before functions
var typingTimer;                //timer identifier
var doneTypingInterval = 2000;  //time in ms, 2 second for example
var $input = $('#myInput');

// updated events 
$input.on('input propertychange paste', function () {
    clearTimeout(typingTimer);
    typingTimer = setTimeout(doneTyping, doneTypingInterval);      
});

//user is "finished typing," do something
function doneTyping () {
  //do something
}

6

No creo que el evento keyDown sea necesario en este caso (dígame por qué si me equivoco). En mi script (no jquery), una solución similar se ve así:

var _timer, _timeOut = 2000; 



function _onKeyUp(e) {
    clearTimeout(_timer);
    if (e.keyCode == 13) {      // close on ENTER key
        _onCloseClick();
    } else {                    // send xhr requests
        _timer = window.setTimeout(function() {
            _onInputChange();
        }, _timeOut)
    }

}

Es mi primera respuesta en Stack Overflow, así que espero que esto ayude a alguien, algún día :)


6

Declare la siguiente delayfunción:

var delay = (function () {
    var timer = 0;
    return function (callback, ms) {
        clearTimeout(timer);
        timer = setTimeout(callback, ms);
    };
})()

y luego úsalo:

let $filter = $('#item-filter');
$filter.on('keydown', function () {
    delay(function () {            
        console.log('this will hit, once user has not typed for 1 second');            
    }, 1000);
});    

¡Su solución es fantástica, hermano!
Desarrollador

1
@Developer Muchas gracias. También he agregado otra respuesta para resolver un escenario un poco más complejo (múltiples controles por página).
Fabian Bigler

buena solución. Estoy usando esto pero en lugar de keydown estoy usando el evento 'input'
Budyn

Con el evento keyup, funcionó muy bien para mí. Gracias.
Sinan Eldem

6

Respuesta tardía, pero la agrego porque es 2019 y esto se puede lograr completamente usando ES6 bonito, sin bibliotecas de terceros, y encuentro que la mayoría de las respuestas altamente calificadas son voluminosas y pesadas con demasiadas variables.

Elegante solución tomada de esta excelente publicación de blog.

function debounce(callback, wait) {
  let timeout;
  return (...args) => {
      const context = this;
      clearTimeout(timeout);
      timeout = setTimeout(() => callback.apply(context, args), wait);
  };
}

window.addEventListener('keyup', debounce( () => {
    // code you would like to run 1000ms after the keyup event has stopped firing
    // further keyup events reset the timer, as expected
}, 1000))

1
Esta realmente debería ser la mejor respuesta dado el año
Dark Hippo

4

Bueno, estrictamente hablando no, ya que la computadora no puede adivinar cuándo el usuario ha terminado de escribir. Por supuesto, podría disparar un temporizador al presionar la tecla hacia arriba y restablecerlo en cada tecla subsiguiente. Si el temporizador caduca, el usuario no ha escrito durante la duración del temporizador; podría llamarlo "finalización de la escritura".

Si espera que los usuarios hagan pausas mientras escriben, no hay forma de saber cuándo terminan.

(A menos que, por supuesto, se pueda distinguir de los datos cuando hayan terminado)


3

Siento que la solución es algo más simple con el input evento:

var typingTimer;
var doneTypingInterval = 500;

$("#myInput").on("input", function () {
    window.clearTimeout(typingTimer);
    typingTimer = window.setTimeout(doneTyping, doneTypingInterval);
});

function doneTyping () {
    // code here
}

3

de acuerdo con la respuesta de @going. Otra solución similar que funcionó para mí es la siguiente. La única diferencia es que estoy usando .on ("input" ...) en lugar de keyup. Esto solo captura cambios en la entrada. otras teclas como Ctrl, Shift, etc. se ignoran

var typingTimer;                //timer identifier
var doneTypingInterval = 5000;  //time in ms (5 seconds)

//on input change, start the countdown

$('#myInput').on("input", function() {    
    clearTimeout(typingTimer);
    typingTimer = setTimeout(function(){
        // doSomething...
    }, doneTypingInterval);
});

3

Acabo de descubrir un código simple para esperar a que el usuario termine de escribir:

paso 1: establece el tiempo de espera en nulo y luego borra el tiempo de espera actual cuando el usuario está escribiendo.

paso 2: activa el tiempo de espera claro para la variable definir antes de que se active el evento keyup.

paso 3: definir el tiempo de espera para la variable declarada anteriormente;

<input type="text" id="input" placeholder="please type" style="padding-left:20px;"/>
<div class="data"></div>

código javascript

var textInput = document.getElementById('input');
var textdata = document.querySelector('.data');
// Init a timeout variable to be used below
var timefired = null;

// Listen for keystroke events
// Init a timeout variable to be used below
var timefired = null;// Listen for keystroke events
textInput.onkeyup = function (event) {
clearTimeout(timefired);
timefired = setTimeout(function () {
    textdata.innerHTML = 'Input Value:'+ textInput.value;
  }, 600);
};

1
la pregunta ya está respondida ... y es bastante similar a esto
Mixone

3

Estaba implementando la búsqueda en mi listado y necesitaba que se basara en ajax. Eso significa que en cada cambio clave, los resultados buscados deben actualizarse y mostrarse. Esto da como resultado tantas llamadas ajax enviadas al servidor, lo que no es bueno.

Después de trabajar un poco, hice un enfoque para hacer ping al servidor cuando el usuario deja de escribir.

Esta solución funcionó para mí:

$(document).ready(function() {
    $('#yourtextfield').keyup(function() {
        s = $('#yourtextfield').val();
        setTimeout(function() { 
            if($('#yourtextfield').val() == s){ // Check the value searched is the latest one or not. This will help in making the ajax call work when client stops writing.
                $.ajax({
                    type: "POST",
                    url: "yoururl",
                    data: 'search=' + s,
                    cache: false,
                    beforeSend: function() {
                       // loading image
                    },
                    success: function(data) {
                        // Your response will come here
                    }
                })
            }
        }, 1000); // 1 sec delay to check.
    }); // End of  keyup function
}); // End of document.ready

Notarás que no es necesario usar ningún temporizador al implementar esto.


2

Este es un código JS simple que escribí:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pt-br" lang="pt-br">
<head><title>Submit after typing finished</title>
<script language="javascript" type="text/javascript">
function DelayedSubmission() {
    var date = new Date();
    initial_time = date.getTime();
    if (typeof setInverval_Variable == 'undefined') {
            setInverval_Variable = setInterval(DelayedSubmission_Check, 50);
    } 
}
function DelayedSubmission_Check() {
    var date = new Date();
    check_time = date.getTime();
    var limit_ms=check_time-initial_time;
    if (limit_ms > 800) { //Change value in milliseconds
        alert("insert your function"); //Insert your function
        clearInterval(setInverval_Variable);
        delete setInverval_Variable;
    }
}

</script>
</head>
<body>

<input type="search" onkeyup="DelayedSubmission()" id="field_id" style="WIDTH: 100px; HEIGHT: 25px;" />

</body>
</html>

1

Puede usar el evento onblur para detectar cuándo el cuadro de texto pierde el foco: https://developer.mozilla.org/en/DOM/element.onblur

Eso no es lo mismo que "deja de escribir", si le importa el caso en el que el usuario escribe un montón de cosas y luego se sienta allí con el cuadro de texto aún enfocado.

Para eso, sugeriría vincular un setTimeout al evento onclick, y asumir que después de x cantidad de tiempo sin presionar las teclas, el usuario ha dejado de escribir.


1

¿Por qué no solo usar onfocusout?

https://www.w3schools.com/jsreF/event_onfocusout.asp

Si se trata de un formulario, siempre dejarán el foco de cada campo de entrada para hacer clic en el botón de envío para que sepa que ninguna entrada se perderá al llamar a su controlador de eventos onfocusout.


1
onfocusout tenía poco apoyo cuando se hizo la pregunta (hace casi 7 años), y también el efecto con onfocusout sería algo diferente. Tendría que esperar a que el usuario deje el foco en el elemento, mientras que con la solución de tiempo de espera / antirrebote se "dispara" tan pronto como el usuario deja de escribir y no está obligado a cambiar el foco. Un ejemplo de caso de uso sería uno de esos formularios de registro donde, cuando el usuario deja de ingresar un nombre de usuario potencial, aparece una marca de verificación o una "X" a la derecha del campo del formulario que indica que el nombre está disponible.
David Zorychta

si la barra de búsqueda está enfocada, nadie necesita salir, después de algún tiempo el resultado se mostrará directamente ya que ya no se está escribiendo nada abierto
P Satish Patro

1

Si es necesario que el usuario se aleje del campo, podemos usar "onBlur" en lugar de Onchange en Javascript

  <TextField id="outlined-basic"  variant="outlined" defaultValue={CardValue} onBlur={cardTitleFn} />

Si eso no es necesario, configurar el temporizador sería la buena opción.


0

Una vez que detecte el foco en el cuadro de texto, al presionar la tecla, verifique el tiempo de espera y reinícielo cada vez que se active.

Cuando finalice el tiempo de espera, haga su solicitud ajax.


0

Si está buscando una longitud específica (como un campo de código postal):

$("input").live("keyup", function( event ){
if(this.value.length == this.getAttribute('maxlength')) {
        //make ajax request here after.
    }
  });

No es una mala idea hacer una validación limitada antes de enviar la solicitud ajax.
chim

0

No estoy seguro de si mis necesidades son un poco raras, pero necesitaba algo similar a esto y esto es lo que terminé usando:

$('input.update').bind('sync', function() {
    clearTimeout($(this).data('timer'));            
    $.post($(this).attr('data-url'), {value: $(this).val()}, function(x) {
        if(x.success != true) {
            triggerError(x.message);    
        }
    }, 'json');
}).keyup(function() {
    clearTimeout($(this).data('timer'));
    var val = $.trim($(this).val());
    if(val) {
        var $this = $(this);
        var timer = setTimeout(function() {
            $this.trigger('sync');
        }, 2000);
        $(this).data('timer', timer);
    }
}).blur(function() {
    clearTimeout($(this).data('timer'));     
    $(this).trigger('sync');
});

Lo que me permite tener elementos como este en mi aplicación:

<input type="text" data-url="/controller/action/" class="update">

Que se actualizan cuando el usuario "termina de escribir" (sin acción durante 2 segundos) o pasa a otro campo (se borra del elemento)


0

Si necesita esperar hasta que el usuario termine de escribir, use esto:

$(document).on('change','#PageSize', function () {
    //Do something after new value in #PageSize       
});

Ejemplo completo con llamada ajax - esto funciona para mi buscapersonas - recuento de elementos por lista:

$(document).ready(function () {
    $(document).on('change','#PageSize', function (e) {
        e.preventDefault();
        var page = 1;
        var pagesize = $("#PageSize").val();
        var q = $("#q").val();
        $.ajax({
            url: '@Url.Action("IndexAjax", "Materials", new { Area = "TenantManage" })',
            data: { q: q, pagesize: pagesize, page: page },
            type: 'post',
            datatype: "json",
            success: function (data) {
                $('#tablecontainer').html(data);
               // toastr.success('Pager has been changed', "Success!");
            },
            error: function (jqXHR, exception) {
                ShowErrorMessage(jqXHR, exception);
            }
        });  
    });
});    

0

Múltiples temporizadores por página

Todas las otras respuestas solo funcionan para un control ( incluida mi otra respuesta ). Si tiene varios controles por página (por ejemplo, en un carrito de compras), solo se llamará al último control donde el usuario escribió algo. . En mi caso, este no es el comportamiento deseado: cada control debe tener su propio temporizador.

Para resolver esto, simplemente tiene que pasar una ID a la función y mantener un diccionario timeoutHandles como en el siguiente código:

Declaración de funciones:

var delayUserInput = (function () {
    var timeoutHandles = {};    
    return function (id, callback, ms) {        
        if (timeoutHandles[id]) {
            clearTimeout(timeoutHandles[id]);
        }

        timeoutHandles[id] = setTimeout(callback, ms);             
    };
})();

Uso de la función:

  delayUserInput('yourID', function () {
     //do some stuff
  }, 1000);

0

Simple y fácil de entender.

var mySearchTimeout;
$('#ctl00_mainContent_CaseSearch').keyup(function () {
   clearTimeout(mySearchTimeout);
   var filter = $(this).val();
   mySearchTimeout = setTimeout(function () { myAjaxCall(filter); }, 700);
   return true;
});

0

Para pasar parámetros a su función junto con la sintaxis de ES6.

$(document).ready(() => {
    let timer = null;
     $('.classSelector').keydown(() => {
     clearTimeout(timer); 
     timer = setTimeout(() => foo('params'), 500);
  });
});

const foo = (params) => {
  console.log(`In foo ${params}`);
}

-2

¡Guau, incluso 3 comentarios son bastante correctos!

  1. La entrada vacía no es una razón para omitir la llamada a la función, por ejemplo, elimino el parámetro de desperdicio de la URL antes de redirigir

  2. .on ('input', function() { ... });debe usarse para desencadenar keyup, pastey changeeventos

  3. definitivamente .val()o .valuedebe ser utilizado

  4. Puede usar la $(this)función de evento interno en lugar de #idtrabajar con múltiples entradas

  5. (mi decisión) utilizo función anónima en lugar de doneTypingen setTimeoutforma fácil acceso $(this)desde n.4, pero hay que guardarlo primero comovar $currentInput = $(this);

EDITAR Veo que algunas personas no entienden las instrucciones sin la posibilidad de copiar y pegar el código listo. Aqui estas

var typingTimer;
//                  2
$("#myinput").on('input', function () {
    //             4     3
    var input = $(this).val();
    clearTimeout(typingTimer);
    //                           5
    typingTimer = setTimeout(function() {
        // do something with input
        alert(input);
    }, 5000);      
});

¿Cómo responde esto a la pregunta?
Thomas Orlita

@ThomasOrlita esta respuesta complementa las 3 respuestas mejor calificadas. Eche un vistazo a uno aceptado: 1. no es compatible paste, no usa this, etc. (Creo que es importante pero no quiero
perderme

1
Esto no responde la pregunta. Si desea comentar sobre otra respuesta, hay un sistema de comentarios (¡lo estoy usando ahora mismo!)
GrayedFox
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.