Solución alternativa de llenado automático del navegador AngularJS mediante el uso de una directiva


111

Al enviar un formulario en AngularJS y usar la funcionalidad de recordar contraseña del navegador, y en un intento de inicio de sesión posterior, deja que el navegador complete el formulario de inicio de sesión con el nombre de usuario y la contraseña, el $scopemodelo no se cambiará según el autocompletado.

El único truco sucio que encontré es usar la siguiente directiva:

app.directive("xsInputSync", ["$timeout" , function($timeout) {
    return {
        restrict : "A",
        require: "?ngModel",
        link : function(scope, element, attrs, ngModel) {
            $timeout(function() {
                if (ngModel.$viewValue && ngModel.$viewValue !== element.val()) {
                    scope.apply(function() {
                        ngModel.$setViewValue(element.val());
                    });
                }
                console.log(scope);
                console.log(ngModel.$name);
                console.log(scope[ngModel.$name]);
            }, 3000);
        }
    };
}]);

El problema es que ngModel.$setViewValue(element.val());no cambia el modelo ni la vista en función del element.val()valor devuelto. ¿Cómo puedo lograrlo?


Este código se ve bien a primera vista ... pero ¿dónde está el resto (marcado, etc.)? ¿Tiene un violín o un plunk que podamos ver?
Ben Lesh

Aquí está el plunk: plnkr.co/edit/CHrBAVU9Ycl2ex2DRr6R No estoy seguro de si funciona directamente en plunker porque se ejecuta en un iframe.
lucassp

1
No necesita el alcance. $ Aplicar dentro del $ timeout de angular. Es posible que lo necesite dentro de window.setTimeout nativo. Pero es una mejor idea usar el de angular.
gorpacrate

1
Hay una solución de polyfill "oficial" de Angular dev tbosch para este problema. Consulte los detalles en la respuesta stackoverflow.com/a/25687396/3009639 a continuación.
orszaczky

Desafortunadamente, la solución "oficial" es abandonware y no funciona en muchos navegadores. Se han presentado problemas pero no se han abordado, por lo que la búsqueda continúa ...
cyberwombat

Respuestas:


45

Aparentemente, este es un problema conocido con Angular y actualmente está abierto

No estoy seguro de qué podrías hacer aquí además de algún tipo de trabajo como lo estás intentando. Parece que estás en el camino correcto. No pude hacer que mi navegador intentara recordar una contraseña para su plunk, así que no estoy seguro de si esto funcionará, pero eche un vistazo:

app.directive('autoFillSync', function($timeout) {
   return {
      require: 'ngModel',
      link: function(scope, elem, attrs, ngModel) {
          var origVal = elem.val();
          $timeout(function () {
              var newVal = elem.val();
              if(ngModel.$pristine && origVal !== newVal) {
                  ngModel.$setViewValue(newVal);
              }
          }, 500);
      }
   }
});
<form name="myForm" ng-submit="login()">
   <label for="username">Username</label>
   <input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
   <label for="password">Password</label>
   <input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
   <button type="submit">Login</button>
</form>

Creo que solo necesitas simplificar un poco tu enfoque. Lo único que definitivamente recomiendo es verificar ngModel.$pristiney asegurarse de no sobrescribir la entrada de un usuario deficiente. Además, 3 segundos probablemente sea demasiado tiempo. No debería tener que llamar a $ apply () en un $ timeout, por cierto, debería poner en cola un $ digest automáticamente.

La verdadera trampa: ¿Su navegador superará a Angular en la ejecución? ¿Y mi navegador?

Esta es probablemente una guerra imposible de ganar, por lo que Angular (o Knockout) no ha podido resolverla fácilmente. No hay garantía del estado de los datos en su entrada en el momento de la ejecución inicial de la directiva. Ni siquiera en el momento de la inicialización de Angular ... Así que es un problema complicado de resolver.


1
Buen punto sobre $ prístino. Bueno, 3 segundos está ahí solo para fines de prueba. El cuadro de diálogo "Guardar contraseña" parece funcionar en Safari. Ese es otro tema que tengo que investigar.
lucassp

Engañarlo con el almacenamiento local. :) ... Seguiré pensando en ello. Pero tengo mis dudas sobre la viabilidad de hacer que el autocompletado funcione con cualquier enlace bidireccional.
Ben Lesh

Sí, pero aún necesito poder actualizar el modelo desde una directiva para que funcione.
lucassp

1
Estaba bromeando sobre localStorage, no querrías mantener las contraseñas allí.
Ben Lesh

1
Esto no funcionará con Safari y el autocompletado de la tarjeta de crédito, ya que el usuario activa este autocompletado en cualquier momento
Dallas Clark

35

Aquí hay una solución que es mucho menos hacky que otras soluciones presentadas y es semánticamente sólida AngularJS: http://victorblog.com/2014/01/12/fixing-autocomplete-autofill-on-angularjs-form-submit/

myApp.directive('formAutofillFix', function() {
  return function(scope, elem, attrs) {
    // Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
    elem.prop('method', 'POST');

    // Fix autofill issues where Angular doesn't know about autofilled inputs
    if(attrs.ngSubmit) {
      setTimeout(function() {
        elem.unbind('submit').submit(function(e) {
          e.preventDefault();
          elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
          scope.$apply(attrs.ngSubmit);
        });
      }, 0);
    }
  };
});

Luego, simplemente adjunte la directiva a su formulario:

<form ng-submit="submitLoginForm()" form-autofill-fix>
  <div>
    <input type="email" ng-model="email" ng-required />
    <input type="password" ng-model="password" ng-required />
    <button type="submit">Log In</button>
  </div>
</form>

2
Este funcionó. Probamos la mayoría de los anteriores sin ningún resultado. ¡¡¡Gracias!!! El resultado está en mobbr.com
Patrick Savalle

1
Como usa jQuery, no se espera que funcione sin jQuery.
Quinn Strahl

1
Aquí estamos en 2018 y esta sigue siendo la solución. ¡Gracias Ezekiel!
Scott Manny

35

No tienes que usar un $timeoutni nada parecido. Puede utilizar un sistema de eventos.

Creo que es más angular y no depende de jQuery o de la captura de eventos personalizados.

Por ejemplo, en su controlador de envío:

$scope.doLogin = function() {
    $scope.$broadcast("autofill:update");

    // Continue with the login.....
};

Y luego puedes tener una autofilldirectiva como esta:

.directive("autofill", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$on("autofill:update", function() {
                ngModel.$setViewValue(element.val());
            });
        }
    }
});

Finalmente, su HTML será como:

<input type="text" name="username" ng-model="user.id" autofill="autofill"/>

14
En mi caso, el botón de envío está deshabilitado mientras las entradas están vacías.
gorpacrate

4
¿Nunca has tenido problemas con esto ya que es asíncrono? ¿Dónde la llamada de inicio de sesión al servidor ya sale antes de que se replicara el evento y la directiva tuviera tiempo de actualizar el alcance?
Sander

12

¡Ya no es necesario piratear! Angular dev tbosch hizo un polyfill que desencadena un evento de cambio cuando el navegador cambia los campos de formulario sin desencadenar un evento de cambio:

https://github.com/tbosch/autofill-event

Por ahora, no integrarán esto en el código Angular, ya que es una corrección de errores para el navegador y también funciona sin Angular (por ejemplo, para aplicaciones jQuery simples).

"El polyfill comprobará los cambios en la carga del documento y también cuando se deje una entrada (solo en el mismo formulario). Sin embargo, puede activar la comprobación manualmente si lo desea.

El proyecto tiene pruebas unitarias y pruebas semiautomáticas, por lo que finalmente tenemos un lugar para recopilar todos los casos de uso diferentes junto con la configuración requerida del navegador.

Tenga en cuenta: este polyfill funciona con aplicaciones simples de AngularJS, con aplicaciones de AngularJS / jQuery, pero también con aplicaciones simples de jQuery que no usan Angular ".

Se puede instalar con:

bower install autofill-event --save

Agregue el script autofill-event.js después de jQuery o Angular en su página.

Esto hará lo siguiente:

  • después de DOMContentLoaded: verifique todos los campos de entrada
  • queda un campo: marque todos los demás campos en el mismo formulario

API (para activar manualmente la verificación):

  • $el.checkAndTriggerAutoFillEvent(): Ejecute la comprobación de todos los elementos DOM en el elemento jQuery / jQLite dado.

Cómo funciona

  1. Recuerde todos los cambios en los elementos de entrada por parte del usuario (escuchando eventos de cambio) y también por JavaScript (interceptando $ el.val () para los elementos jQuery / jQLite). Ese valor modificado se almacena en el elemento en una propiedad privada.

  2. Verificación de un elemento para autocompletar: Compare el valor actual del elemento con el valor recordado. Si es diferente, active un evento de cambio.

Dependencias

AngularJS o jQuery (funciona con uno o ambos)

Más información y fuente en la página de github .

El número original de Angular # 1460 en Github se puede leer aquí.


2
Esto no funciona para mis casos. checkAndTriggerAutoFillEvent () se activa y genera eventos, pero AngularJS nunca actualiza sus modelos y piensa que los campos están vacíos.
Splaktar

No he tenido ningún problema al utilizar este polyill. Si no puede hacer que funcione correctamente, debe crear una pregunta separada que incluya algún código. Si encuentra un error, debe verificar los tickets en github o abrir uno nuevo.
orszaczky

Sí, creo que está relacionado con este error: github.com/tbosch/autofill-event/issues/11
Splaktar

1
ngModelOptionscombinado con autofill-eventmedios que ahora puede especificar changecomo uno de los updateOneventos para asegurarse de que su modelo esté sincronizado correctamente.
morloch

10

Código sucio, verifique si el problema https://github.com/angular/angular.js/issues/1460#issuecomment-18572604 está solucionado antes de usar este código. Esta directiva desencadena eventos cuando se completa el campo, no solo antes del envío (es necesario si tiene que manejar la entrada antes de enviar)

 .directive('autoFillableField', function() {
    return {
                   restrict: "A",
                   require: "?ngModel",
                   link: function(scope, element, attrs, ngModel) {
                       setInterval(function() {
                           var prev_val = '';
                           if (!angular.isUndefined(attrs.xAutoFillPrevVal)) {
                               prev_val = attrs.xAutoFillPrevVal;
                           }
                           if (element.val()!=prev_val) {
                               if (!angular.isUndefined(ngModel)) {
                                   if (!(element.val()=='' && ngModel.$pristine)) {
                                       attrs.xAutoFillPrevVal = element.val();
                                       scope.$apply(function() {
                                           ngModel.$setViewValue(element.val());
                                       });
                                   }
                               }
                               else {
                                   element.trigger('input');
                                   element.trigger('change');
                                   element.trigger('keyup');
                                   attrs.xAutoFillPrevVal = element.val();
                               }
                           }
                       }, 300);
                   }
               };
});

5

Parece una solución clara y recta. No se necesita jQuery.

ACTUALIZAR:

  • El modelo se actualiza solo cuando el valor del modelo no es igual al valor de entrada real.
  • La verificación no se detiene en el primer autocompletado. En caso de que desee utilizar otra cuenta, por ejemplo.

app.directive('autofillable', ['$timeout', function ($timeout) {
    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            scope.check = function(){
                var val = elem[0].value;
                if(ctrl.$viewValue !== val){
                    ctrl.$setViewValue(val)
                }
                $timeout(scope.check, 300);
            };
            scope.check();
        }
    }
}]);

4
+1 buena solución, similar a la que se me ocurrió. La única adición que sugeriría es verificar primero si el modelo está $pristineantes de cambiarlo, y luego, después de cambiarlo, mantener su $pristineestado. De lo contrario, encontrará que si un formulario se carga sin datos de autocompletar, la directiva anterior aún actualizará el modelo y, posteriormente, lo hará dirtya pesar de que el control del formulario aún debe considerarse intacto. ejemplo aquí, usando su directiva. He comentado el código que le agregaría: jsfiddle.net/OACDesigns/L8Sja/4
OACDesigns

1
también, creo que scope:truedebería serscope:{}
OACDesigns

4

Solución 1 [Uso de $ timeout]:

Directiva:

app.directive('autoFillSync', function($timeout) {
    return {
      require: 'ngModel',
      link: function(scope, elem, attrs, model) {
          var origVal = elem.val();
          $timeout(function () {
              var newVal = elem.val();
              if(model.$pristine && origVal !== newVal) {
                  model.$setViewValue(newVal);
              }
          }, 500);
      }
    };
});

HTML:

<form name="myForm" ng-submit="login()">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
  <label for="password">Password</label>
  <input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
  <button type="submit">Login</button>
</form>

Solución 2 [Uso de eventos angulares]:

Ref: respuesta de Becko

Directiva:

app.directive("autofill", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$on("autofill:update", function() {
                ngModel.$setViewValue(element.val());
            });
        }
    };
});

HTML:

<form name="myForm" ng-submit="login()">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" ng-model="username" autofill/><br/>
  <label for="password">Password</label>
  <input type="password" id="password" name="password" ng-model="password" autofill/><br/>
  <button type="submit">Login</button>
</form>

Solución 3 [Uso de llamadas al método de retransmisión]:

Directiva:

app.directive('autoFill', function() {
    return {
        restrict: 'A',
        link: function(scope,element) {
            scope.submit = function(){
                scope.username = element.find("#username").val();
                scope.password = element.find("#password").val();
                scope.login();//call a login method in your controller or write the code here itself
            }

        }
    };
});

HTML:

<form name="myForm" auto-fill ng-submit="submit()">
   <label for="username">Username</label>
   <input type="text" id="username" name="username" ng-model="username" />
   <label for="password">Password</label>
   <input type="password" id="password" name="password" ng-model="password" />
   <button type="submit">Login</button>
</form>

2
La solución 1 funcionó muy bien durante un tiempo, pero ahora en angularjs 1.4.9 ya no funciona. La verificación model.$validde la directiva devuelve verdadero antes y después $setViewValue, pero el elemento todavía está con la ng-invalidclase
Juan

4

Bueno, la forma más fácil es emular el comportamiento del navegador, así que si hay un problema con el evento de cambio, simplemente enciéndalo usted mismo. Mucho más simple.

Directiva:

yourModule.directive('triggerChange', function($sniffer) {
    return {
        link : function(scope, elem, attrs) {
            elem.on('click', function(){
                $(attrs.triggerChange).trigger(
                    $sniffer.hasEvent('input') ? 'input' : 'change'
                );
            });
        },
        priority : 1
    }
});

HTML:

<form >
    <input data-ng-model="user.nome" type="text" id="username">

    <input data-ng-model="user.senha" type="password" id="password" >

    <input type="submit" data-ng-click="login.connect()" id="btnlogin" 
           data-trigger-change="#password,#username"/>
</form>

Puede hacer algunas variaciones, como poner la directiva en el formulario y activar el evento en todas las entradas con la clase .dirty en el envío del formulario.


3

Esta es la forma jQuery:

$(window).load(function() {
   // updates autofilled fields
   window.setTimeout(function() {
     $('input[ng-model]').trigger('input');
   }, 100);
 });

Esta es la forma angular:

 app.directive('autofill', ['$timeout', function ($timeout) {
    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            $timeout(function(){
                $(elem[0]).trigger('input');
                // elem.trigger('input'); try this if above don't work
            }, 200)
        }
    }
}]);

HTML

<input type="number" autofill /> 

2
Manera completamente no angular.
gorpacrate

1
@gorpacrate: Ahora compruébalo con forma angular.
Nishchit Dhanani

2
Las pestañas de Angular Way tienen demasiados espacios. Es broma. Me encanta el desplazamiento horizontal.
Daniel Birowsky Popeski

esto no funciona en angular ya que no hay un disparador de función en el elemento. Creo que esto requeriría incluir jquery
Tomas

1
triggerHandler('input')debería estar bien si no tiene jquery completo
Mutmatt

1

Aquí hay otra solución que es menos intrincada, pero requiere algo de código adicional en el controlador.

HTML:

<form ng-submit="submitForm()" ng-controller="FormController">
    <input type="text" ng-model="username" autocomplete-username>
    <input type="submit">
</form>

Directiva (CoffeeScript):

directives.directive 'autocompleteUsername', ->
    return (scope, element) ->
        scope.getUsername = ->
            element.val()

Controlador:

controllers.controller 'FormController', [->
    $scope.submitForm = ->
        username = $scope.getUsername?() ? $scope.username
        # HTTP stuff...
]

¿Tiene esto en JavaScript vainilla?
f1lt3r

CoffeeScript.org proporciona un traductor: hecho aquí
jab

1

Esta es la única solución que he encontrado que permitió que todas las validaciones de mi Angular funcionaran según lo diseñado, incluida la desactivación / activación del botón de envío. Se instala con bower y 1 etiqueta de script. ¡Bazinga!

https://github.com/tbosch/autofill-event


1
este relleno de polietileno no encaja con un botón de envío / enlace de entrada al estado del formulario. a menos que haga clic en la página en cualquier punto en el que desee activar un resumen (parece que la acción activa el navegador para realizar un relleno real), entonces el botón se habilita. Página de prueba con pruebas manuales
e-cloud

1

Cambiar el valor del modelo, en lugar de usar una función de tiempo de espera funcionó para mí.

Aquí está mi código:

module.directive('autoFill', [ function() {
    return {
        require: 'ngModel',
        link:function(scope, element, attr, ngModel) {
            var origVal = element.val();
            if(origVal){
                ngModel.$modelValue = ngModel.$modelValue || origVal;
            }
        }
    };
}]);

1

Solución alternativa de una sola línea en el controlador de envío (requiere jQuery):

if (!$scope.model) $scope.model = $('#input_field').val();

En realidad, no es una frase de una sola línea si está en una directiva, lo que ciertamente debería ser.
isherwood

0

Obligo un $ setValue (val ()) al enviar: (esto funciona sin jQuery)

   var ValidSubmit = ['$parse', function ($parse) {
    return {
        compile: function compile(tElement, tAttrs, transclude) {
            return {
                post: function postLink(scope, element, iAttrs, controller) {
                    var form = element.controller('form');
                    form.$submitted = false;
                    var fn = $parse(iAttrs.validSubmit);
                    element.on('submit', function(event) {
                        scope.$apply(function() {
                            var inputs = element.find('input');
                            for(var i=0; i < inputs.length; i++) {
                                var ele = inputs.eq(i);
                                var field = form[inputs[i].name];
                                field.$setViewValue(ele.val());
                            }
                            element.addClass('ng-submitted');
                            form.$submitted = true;
                            if(form.$valid) {
                                fn(scope, {$event:event});
                            }
                        });
                    });
                    scope.$watch(function() { return form.$valid}, function(isValid) {
                        if(form.$submitted == false) return;
                        if(isValid) {
                            element.removeClass('has-error').addClass('has-success');
                        } else {
                            element.removeClass('has-success');
                            element.addClass('has-error');
                        }
                    });
                }
            }
        }
    }
}]
app.directive('validSubmit', ValidSubmit);

0

Soy muy nuevo en Angularjs, pero encontré una solución simple a ese problema => Forzar Angular para reevaluar la expresión ... ¡cambiándola! (por supuesto, debe recordar el valor inicial para volver al estado inicial) Esta es la forma en que funciona en la función de su controlador para enviar el formulario:

    $scope.submit = function () {
                var oldpassword = $scope.password;
                $scope.password = '';
                $scope.password = oldpassword;
//rest of your code of the submit function goes here...

donde, por supuesto, el valor ingresado en la entrada de su contraseña ha sido establecido por Windows y no por el usuario.


0

Puedes probar este código:

yourapp.directive('autofill',function () {

    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            var origVal = elem.val();
            if (origVal != '') {
                elem.trigger('input');
            }
        }
    }
});

0

Una pequeña modificación a esta respuesta ( https://stackoverflow.com/a/14966711/3443828 ): use un $ interval en lugar de un $ timeout para que no tenga que competir con el navegador.

mod.directive('autoFillSync', function($interval) {
    function link(scope, element, attrs, ngModel) {
        var origVal = element.val();
        var refresh = $interval(function() {
          if (!ngModel.$pristine) {
            $interval.cancel(refresh);
          }else{
            var newVal = element.val();
            if (origVal !== newVal) {
              ngModel.$setViewValue(newVal);
              $interval.cancel(refresh);
            }
          }
        }, 100);
    }

    return {
      require: 'ngModel',
      link: link
    }
  });

0

Esta es la solución que terminé usando en mis formularios.

.directive('autofillSync', [ function(){
  var link = function(scope, element, attrs, ngFormCtrl){
    element.on('submit', function(event){
      if(ngFormCtrl.$dirty){
        console.log('returning as form is dirty');
        return;
      }   
      element.find('input').each(function(index, input){
        angular.element(input).trigger('input');
      }); 
    }); 
  };  
  return {
    /* negative priority to make this post link function run first */
    priority:-1,
    link: link,
    require: 'form'
  };  
}]);

Y la plantilla del formulario será

<form autofill-sync name="user.loginForm" class="login-form" novalidate ng-submit="signIn()">
    <!-- Input fields here -->
</form>

De esta manera, pude ejecutar cualquier analizador / formateador que tenga en mi modelo ng y tener la funcionalidad de envío transparente.


0

Solución sin directivas:

.run(["$window", "$rootElement", "$timeout", function($window, $rootElement, $timeout){

        var event =$window.document.createEvent("HTMLEvents");
        event.initEvent("change", true, true);

        $timeout(function(){

            Array.apply(null, $rootElement.find("input")).forEach(function(item){
                if (item.value.length) {
                    item.$$currentValue = item.value;
                    item.dispatchEvent(event);
                }
            });

        }, 500);
    }])

0

Esta es una solución simple que funciona para todos los casos que he probado tanto en Firefox como en Chrome. Tenga en cuenta que con la respuesta principal (directiva sin tiempo de espera) tuve problemas con:

  • Botones de avance / retroceso del navegador, no vuelva a activar los eventos de carga de la página (por lo que la solución no se aplica)
  • Carga de credenciales algún tiempo después de la carga de la página. por ejemplo, en Firefox, haga doble clic en el cuadro de inicio de sesión y seleccione entre las credenciales almacenadas.
  • Necesito una solución que se actualice antes del envío del formulario, ya que desactivo el botón Iniciar sesión hasta que se proporcione una entrada válida

Esta solución es obviamente muy tonta y hack, pero funciona el 100% del tiempo.

function myScope($scope, $timeout) {
    // ...
    (function autoFillFix() {
        $timeout(function() { 
            $('#username').trigger('change'); 
            $('#password').trigger('change'); 
            autoFillFix(); }, 500);                    
    })();
}

1
podría reemplazar $timeoutcon $intervalpara dar a los futuros desarrolladores un poco más de contexto y deshacerse de las llamadas recursivas de aspecto extraño que
ocurren

0

Ninguna de estas soluciones funcionó para mi caso de uso. Tengo algunos campos de formulario que usan ng-change para observar el cambio. Utilizando$watch no ayuda, ya que no se activa mediante el autocompletado. Como no tengo un botón de envío, no hay una manera fácil de ejecutar algunas de las soluciones y no tuve éxito al usar intervalos.

Terminé deshabilitando la función de autocompletar; no es lo ideal, pero es mucho menos confuso para el usuario.

<input readonly onfocus="this.removeAttribute('readonly');">

Encontré la respuesta aquí


-1

Si está utilizando jQuery, puede hacer esto en el envío del formulario:

HTML:

<form ng-submit="submit()">
    <input id="email" ng-model="password" required 
           type="text" placeholder="Your email">
    <input id="password" ng-model="password" required 
           type="password" placeholder="Password">
</form>

JS:

 $scope.submit = function() {
     $scope.password = $('#password').val();
}

1
Aunque esto podría funcionar, no es razonable para formas más complejas.
origin1tech

-1

Si desea mantenerlo simple, obtenga el valor usando javascript

En su controlador angular js:

var username = document.getElementById ('nombre de usuario'). valor;


Creo que no entiendes cuál es el problema
aelor
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.