VALIDACIÓN DE FORMULARIOS EN JAVASCRIPT


1. Introducción

Validar un formulario significa comprobar que los datos introducidos cumplen ciertas reglas antes de enviarlos.

Hay dos tipos de validación:

  • Cliente (JavaScript) → mejora la experiencia del usuario.
  • Servidor (backend) → garantiza seguridad real.

Importante: La validación en JavaScript se puede saltar. Nunca sustituye la validación del servidor.


2. Estructura base del formulario

<form id="registroForm" novalidate>  
<div class="field">
    <label for="nombre">Nombre</label>
    <input type="text" id="nombre" name="nombre">
    <small class="error" data-for="nombre"></small>
  </div>  
<button type="submit">Enviar</button></form>

Explicación

  • id="registroForm" permite acceder al formulario desde JavaScript.
  • novalidate desactiva la validación automática de HTML5 para que podamos controlarla manualmente.
  • <small class="error"> es donde mostraremos mensajes de error dinámicamente.
  • data-for="nombre" conecta el mensaje con el campo correspondiente.

Esto separa contenido (HTML) de comportamiento (JS).


3. Capturar el evento submit

const form = document.getElementById("registroForm");
form.addEventListener("submit", function(e){
    e.preventDefault();   
 if(validarFormulario()){
        alert("Formulario válido. Enviando...");
        form.reset();
    }
});

Explicación

  • addEventListener("submit") escucha cuando el usuario intenta enviar el formulario.
  • e.preventDefault() evita que el formulario se envíe automáticamente.
  • Se llama a validarFormulario().
  • Si devuelve true, el formulario se considera válido.

Aquí estamos controlando el flujo manualmente.


4. Función limpiarErrores()

function limpiarErrores(){
document.querySelectorAll(".error").forEach(e => e.textContent = "");
document.querySelectorAll("input").forEach(i => i.removeAttribute("aria-invalid"));
}

Explicación

  • querySelectorAll(".error") selecciona todos los mensajes de error.
  • textContent = "" los vacía.
  • También eliminamos el atributo aria-invalid, que usamos para marcar visualmente campos erróneos.

Siempre limpiamos antes de volver a validar para evitar acumular errores antiguos.


5. Función mostrarError()

function mostrarError(campo, mensaje){
const error = document.querySelector(`.error[data-for="${campo}"]`);
error.textContent = mensaje; const input = document.getElementById(campo);
if(input){
input.setAttribute("aria-invalid", "true");
}
}

Explicación

  • Busca el elemento <small> correspondiente al campo.
  • Muestra el mensaje de error.
  • Marca el input con aria-invalid="true".

aria-invalid mejora accesibilidad y permite aplicar estilos CSS al campo incorrecto.


6. Validación SIN expresiones regulares


6.1 Campo obligatorio

if(nombre.trim() === ""){
mostrarError("nombre", "El nombre es obligatorio");
}

Explicación

  • trim() elimina espacios al inicio y final.
  • Si el resultado es cadena vacía, el campo no contiene datos reales.
  • Es la validación más básica y necesaria.

6.2 Longitud mínima

if(nombre.length < 3){
mostrarError("nombre", "Debe tener al menos 3 caracteres");
}

Explicación

  • length mide número de caracteres.
  • Permite exigir tamaño mínimo.
  • Útil para nombres, usuarios, contraseñas simples.

6.3 Detectar números en un texto

if([...nombre].some(c => c >= "0" && c <= "9")){
mostrarError("nombre", "No debe contener números");
}

Explicación

  • [...nombre] convierte la cadena en array de caracteres.
  • some() comprueba si al menos uno cumple la condición.
  • Se evalúa si algún carácter está entre «0» y «9».

Esto demuestra lógica sin usar regex.


6.4 Validar número y rango

const edad = Number(edadInput);if(!Number.isFinite(edad)){
mostrarError("edad", "Debe ser un número válido");
}
else if(edad < 16 || edad > 120){
mostrarError("edad", "Edad fuera de rango");
}

Explicación

  • Number() convierte texto en número.
  • Number.isFinite() verifica que sea número real.
  • Luego se comprueba rango lógico.

Se valida tipo y regla de negocio.


6.5 Email simple (sin regex)

function emailSimple(email){
const at = email.indexOf("@");
const dot = email.lastIndexOf(".");
return at > 0 && dot > at + 1 && dot < email.length - 1;
}

Explicación

  • Busca posición de @.
  • Busca posición del último punto.
  • Comprueba que:
    • El @ no esté al inicio.
    • El punto esté después del @.
    • El punto no esté al final.

No es perfecto, pero didáctico.


6.6 Comparar contraseñas

if(password !== password2){
mostrarError("password2", "Las contraseñas no coinciden");
}

Explicación

  • Compara cadenas directamente.
  • !== exige igualdad exacta.
  • Es una validación lógica, no de formato.

6.7 Validar radio obligatorio

const contacto = document.querySelector('input[name="contacto"]:checked');
if(!contacto){
    mostrarError("contacto", "Seleccione una opción");
}

Explicación

  • :checked selecciona el radio marcado.
  • Si devuelve null, ninguno está seleccionado.
  • Obliga al usuario a tomar decisión.

6.8 Validar checkbox obligatorio

if(!document.getElementById("condiciones").checked){
mostrarError("condiciones", "Debe aceptar las condiciones");
}

Explicación

  • .checked devuelve true o false.
  • Es típico en aceptación de condiciones legales.
  • Se usa como bloqueo final del envío.

7. VALIDACIÓN CON EXPRESIONES REGULARES

Las expresiones regulares permiten definir patrones complejos.


7.1 Email con regex

const RE_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;if(!RE_EMAIL.test(email)){
mostrarError("email", "Email inválido");
}

Explicación del patrón

  • ^ inicio de cadena
  • [^\s@]+ uno o más caracteres que no sean espacio ni @
  • @ símbolo obligatorio
  • [^\s@]+ dominio
  • \. punto literal
  • [^\s@]{2,} al menos 2 caracteres después del punto
  • $ final de cadena

.test() devuelve true o false.


7.2 Contraseña fuerte

const RE_PASS = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/;

Explicación

  • (?=.*[a-z]) al menos una minúscula
  • (?=.*[A-Z]) al menos una mayúscula
  • (?=.*\d) al menos un número
  • (?=.*[^A-Za-z0-9]) al menos un símbolo
  • .{8,} mínimo 8 caracteres

Los (?=...) son “lookaheads”: condiciones que deben cumplirse.


7.3 Teléfono (ejemplo España)

const RE_TEL = /^[6789]\d{8}$/;

Explicación

  • ^[6789] empieza por 6,7,8 o 9
  • \d{8} ocho dígitos
  • $ final de cadena

Valida números de 9 cifras típicos en España.


8. Validación en tiempo real

form.nombre.addEventListener("blur", validarFormulario);
form.password.addEventListener("input", validarFormulario);

Explicación

  • blur se activa al salir del campo.
  • input se activa cada vez que cambia el valor.
  • Permite validar mientras el usuario escribe.

Mejora experiencia.


9. Validación HTML5 nativa

<input type="email" required>
<input type="number" min="16" max="120">
<input type="text" pattern="[A-Za-z]{3,}">

Explicación

  • required obliga a rellenar.
  • min y max definen rango.
  • pattern usa expresión regular directamente en HTML.

Es útil, pero menos flexible que JavaScript personalizado.


La validación no es solo técnica. Es diseño de interacción. Es impedir datos absurdos sin tratar al usuario como enemigo.


Formas de enviar un formulario

1️⃣ Envío tradicional (HTML puro)

El navegador lo hace todo.

<form action="procesar.php" method="POST">
<input type="text" name="nombre">
<button type="submit">Enviar</button>
</form>

Explicación

  • action → URL donde se envían los datos.
  • method → GET o POST.
  • El navegador recarga la página.
  • No necesitamos JavaScript.

Es simple y robusto. Pero no es dinámico.


2️⃣ Envío tradicional + validación JS

Bloqueamos el envío si hay errores, pero dejamos que el navegador envíe normalmente si todo está bien.

form.addEventListener("submit", function(e){    
if(!validarFormulario()){
        e.preventDefault(); // bloquea envío
    }});

Explicación

Aquí no usamos preventDefault() siempre.
Solo bloqueamos si hay errores.

Si la validación devuelve true, el navegador enviará el formulario usando action y method.

Este es el enfoque híbrido más común.


3️⃣ Envío manual usando form.submit()

Podemos decidir cuándo enviar el formulario manualmente.

form.addEventListener("submit", function(e){
    e.preventDefault();  
 
 if(validarFormulario()){
        form.submit();
    }
});

Explicación

  • Se bloquea el envío automático.
  • Si todo está correcto, lo forzamos manualmente.

Útil si queremos hacer lógica adicional antes de enviar.


4️⃣ Envío con GET (construyendo URL manualmente)

form.addEventListener("submit", function(e){
    e.preventDefault();   
 if(validarFormulario()){        
        const nombre = form.nombre.value;
        const email = form.email.value;        
        const url = `procesar.php?nombre=${encodeURIComponent(nombre)}&email=${encodeURIComponent(email)}`;             
        window.location.href = url;
    }
});

Explicación

  • Construimos la URL manualmente.
  • encodeURIComponent() evita errores con espacios y símbolos.
  • El navegador navega a esa URL.

GET muestra los datos en la barra del navegador.


5️⃣ Envío con fetch (moderno y recomendado)

Aquí no recargamos la página.

form.addEventListener("submit", async function(e){
    e.preventDefault();    

if(!validarFormulario()) return;    

const datos = {
        nombre: form.nombre.value,
        email: form.email.value,
        edad: form.edad.value
    };  
 try{
        const respuesta = await fetch("procesar.php", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(datos)
        });       
 const resultado = await respuesta.text();       
 alert("Respuesta del servidor: " + resultado);   
 } catch(error){
        console.error("Error:", error);
    }});

Explicación

  • fetch() envía datos sin recargar página.
  • Convertimos el objeto a JSON.
  • El servidor debe estar preparado para recibir JSON.
  • await pausa hasta recibir respuesta.

Este es el método más moderno.


6️⃣ Envío con FormData (muy práctico)

Ideal si tienes muchos campos.

form.addEventListener("submit", async function(e){
    e.preventDefault();  

  if(!validarFormulario()) return;   

 const formData = new FormData(form);    

const respuesta = await fetch("procesar.php", {
        method: "POST",
        body: formData
    });  

const texto = await respuesta.text();
    alert(texto);
});

Explicación

  • new FormData(form) recoge todos los campos automáticamente.
  • No necesitas construir objeto manual.
  • Perfecto para enviar archivos.
  • No necesitas definir Content-Type.

Muy recomendable para formularios grandes.


7️⃣ Envío tipo AJAX clásico (XMLHttpRequest)

Más antiguo, pero didáctico.

const xhr = new XMLHttpRequest();
xhr.open("POST", "procesar.php");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = function(){
    console.log(xhr.responseText);
};
xhr.send(JSON.stringify({
    nombre: form.nombre.value
}));

Explicación

  • Era la forma anterior a fetch.
  • Más verboso.
  • Útil para entender evolución histórica.

8️⃣ Envío con confirmación previa

A veces queremos preguntar antes de enviar.

form.addEventListener("submit", function(e){
    e.preventDefault();   

if(!validarFormulario()) return;  

if(confirm("¿Seguro que quieres enviar los datos?")){
        form.submit();
    }
});

Explicación

  • confirm() devuelve true o false.
  • Solo enviamos si el usuario confirma.
  • Útil para acciones importantes.

9️⃣ Mostrar resumen antes de enviar

form.addEventListener("submit", function(e){
    e.preventDefault();   

 if(!validarFormulario()) return;  

const resumen = `
Nombre: ${form.nombre.value}
Email: ${form.email.value}
Edad: ${form.edad.value}
`; 
if(confirm(resumen + "\n\n¿Confirmar envío?")){
        form.submit();
    }
});

Explicación

  • Construimos texto resumen.
  • Mostramos al usuario los datos antes de enviarlos.
  • Evita errores humanos.

🔟 Envío parcial (solo algunos campos)

const datos = {
nombre: form.nombre.value,
email: form.email.value
};

Explicación

No siempre se envían todos los campos.
Podemos decidir qué mandar y qué no.


Comparación rápida

MétodoRecarga páginaModernoRecomendado
HTML puroBásico✔ para cosas simples
submit() manualIntermedio
fetch + JSONNoMuy moderno⭐⭐⭐
FormDataNoMuy práctico⭐⭐⭐
XMLHttpRequestNoAntiguoSolo didáctico