Validador de formularios de registro
Valida en lote los formularios de alta de una web recibidos en JSON: nombre, email, contraseña segura que no contenga el nombre, confirmación, fecha real y edad mínima, código postal y teléfono, con un mensaje claro por campo. JSON.parse, expresiones regulares con Unicode y fechas.
- JSON.parse y try/catch
- Expresiones regulares (\p{L} y flag u)
- Date.UTC
- Object.fromEntries
- Map
- Optional chaining y ??
Enunciado
Una web de cursos recibe cientos de altas al día. El formulario ya valida en el navegador, pero eso se puede saltar (basta con desactivar JavaScript o enviar la petición a mano), así que el equipo quiere la misma validación en una función que se pueda ejecutar en el cliente y en el servidor con Node.js. La prueba se hace con un fichero de formularios en JSON, uno por línea.
Cada campo tiene sus reglas y el usuario debe recibir un único mensaje por campo, el del primer problema que se encuentre: no tiene sentido decirle a la vez que la contraseña es corta y que le faltan números. Los nombres pueden tener tildes, eñes, apóstrofos (O'Brien) y guiones (Ruiz-Pérez), así que [a-zA-Z] no sirve: hace falta la clase Unicode \p{L}.
Para que los resultados no dependan del día en que se ejecuten, la edad se calcula a una fecha de referencia fija: el 1 de septiembre de 2026.
Qué tiene que hacer el programa
- Cada línea no vacía es un formulario en JSON. Si no es JSON válido, o no es un objeto, escribe
Formulario N: JSON no válido(N cuenta todas las líneas no vacías). Los valores se convierten a texto y se les quitan los espacios de los extremos; un campo que falta o es null cuenta como vacío. nombre: obligatorio; entre 2 y 40 caracteres; empieza por una letra y solo tiene letras (de cualquier idioma), espacios, apóstrofos y guiones (solo letras, espacios, apóstrofos y guiones).email: obligatorio; algo sin espacios ni arrobas, una@, algo sin espacios ni arrobas, un punto y al menos dos letras al final (formato no válido).password: obligatoria; al menos 8 caracteres; con mayúsculas, minúsculas y números (necesita mayúsculas, minúsculas y números); y si la primera palabra del nombre tiene 3 letras o más, no puede contenerla sin distinguir mayúsculas (no puede contener tu nombre).confirmacion: si hay contraseña, tiene que ser igual (no coincide con la contraseña).nacimiento: obligatoria, con formatoAAAA-MM-DDy una fecha que exista (fecha no válida); la persona tiene que haber cumplido 16 años el 1 de septiembre de 2026 (hay que tener al menos 16 años).cp: obligatorio; 5 cifras con las dos primeras entre 01 y 52 (código postal no válido).telefono: opcional; si se escribe, quitando los espacios deben quedar 9 cifras que empiecen por 6, 7, 8 o 9 (teléfono no válido). Un campo vacío obligatorio daobligatorio.- Para cada formulario:
Formulario N: correcto, oFormulario N: E errores(«error» si es uno) seguido de una línea por campo con error, en el orden nombre, email, password, confirmacion, nacimiento, cp, telefono:- campo: mensaje. Las reglas de cada campo se comprueban en el orden en que aparecen aquí y solo se muestra el primer fallo. - Al final,
Correctos: C de NyCampo con más errores: campo (veces)(si empatan, el primero en el orden de los campos) oCampo con más errores: ninguno.
Entrada
Un formulario por línea, como objeto JSON con los campos nombre, email, password, confirmacion, nacimiento, cp y telefono.
Ejemplos de ejecución
Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.
Tres altas
Entrada
{"nombre": "Lucía Martín-Ruiz", "email": "lucia@correo.es", "password": "Clave2026", "confirmacion": "Clave2026", "nacimiento": "2004-05-17", "cp": "28013", "telefono": "612 345 678"}
{"nombre": "Ana", "email": "ana@correo", "password": "anaPerez99", "confirmacion": "anaperez99", "nacimiento": "2011-02-30", "cp": "53001"}
{"nombre": "O'Brien", "email": "obrien@mail.ie", "password": "Dublin1916", "confirmacion": "Dublin1916", "nacimiento": "2010-09-01", "cp": 8001}Salida por consola
Formulario 1: correcto Formulario 2: 5 errores - email: formato no válido - password: no puede contener tu nombre - confirmacion: no coincide con la contraseña - nacimiento: fecha no válida - cp: código postal no válido Formulario 3: 1 error - cp: código postal no válido Correctos: 1 de 3 Campo con más errores: cp (2)
Formularios incompletos o mal escritos
Entrada
{"email": " ", "password": "corta"}
{nombre: "sin comillas"}
[1, 2, 3]
{"nombre": "Ñuño Álvarez", "email": "nuno@x.com", "password": "abcdefgh", "nacimiento": "2010-08-31", "cp": "01001", "telefono": "91234567"}Salida por consola
Formulario 1: 6 errores - nombre: obligatorio - email: obligatorio - password: al menos 8 caracteres - confirmacion: no coincide con la contraseña - nacimiento: obligatorio - cp: obligatorio Formulario 2: JSON no válido Formulario 3: JSON no válido Formulario 4: 3 errores - password: necesita mayúsculas, minúsculas y números - confirmacion: no coincide con la contraseña - telefono: teléfono no válido Correctos: 0 de 4 Campo con más errores: password (2)
Guía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. JSON.parse puede fallar
Envuelve JSON.parse(linea) en un try/catch: con texto mal formado lanza un SyntaxError. Además, JSON.parse("42") o "[1, 2]" son JSON válido pero no son un formulario: comprueba que el resultado sea un objeto que no sea null ni un array.
2. Normaliza antes de validar
Convierte todos los campos a texto recortado de una vez, y las reglas se simplifican: un campo vacío es siempre "".
const v = Object.fromEntries(CAMPOS.map((c) => [c, datos[c] == null ? "" : String(datos[c]).trim()]));3. Reglas con else if
Para cada campo, encadena las comprobaciones con if / else if: en cuanto una falla, se guarda su mensaje y no se miran las siguientes. Guarda los errores en un objeto campo → mensaje.
4. Letras de cualquier idioma
Con el flag u, \p{L} encaja con cualquier letra: á, ñ, ç, ü… La regla del nombre queda así:
/^\p{L}[\p{L} '-]*$/u.test(v.nombre)5. Fechas que existen
new Date(Date.UTC(2026, 1, 30)) no da error: devuelve el 2 de marzo. Para saber si una fecha existe, créala y comprueba que su año, mes y día siguen siendo los que se escribieron. Para la edad, resta los años y quita uno si en la fecha de referencia aún no ha llegado el cumpleaños.
Resuélvelo aquí
El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.
Ejemplo
{"nombre": "Lucía Martín-Ruiz", "email": "lucia@correo.es", "password": "Clave2026", "confirmacion": "Clave2026", "nacimiento": "2004-05-17", "cp": "28013", "telefono": "612 345 678"}
{"nombre": "Ana", "email": "ana@correo", "password": "anaPerez99", "confirmacion": "anaperez99", "nacimiento": "2011-02-30", "cp": "53001"}
{"nombre": "O'Brien", "email": "obrien@mail.ie", "password": "Dublin1916", "confirmacion": "Dublin1916", "nacimiento": "2010-09-01", "cp": 8001}
Formulario 1: correcto Formulario 2: 5 errores - email: formato no válido - password: no puede contener tu nombre - confirmacion: no coincide con la contraseña - nacimiento: fecha no válida - cp: código postal no válido Formulario 3: 1 error - cp: código postal no válido Correctos: 1 de 3 Campo con más errores: cp (2)
Solución explicada
Ver la solución completa
1const lineas = require("fs").readFileSync(0, "utf8").split("\n");
2
3const HOY = new Date(Date.UTC(2026, 8, 1)); // fecha de referencia: 1 de septiembre de 2026
4const CAMPOS = ["nombre", "email", "password", "confirmacion", "nacimiento", "cp", "telefono"];
5
6/** Edad cumplida en la fecha de referencia. */
7function edad(nacimiento) {
8 let anios = HOY.getUTCFullYear() - nacimiento.getUTCFullYear();
9 const antesDelCumple = HOY.getUTCMonth() < nacimiento.getUTCMonth() ||
10 (HOY.getUTCMonth() === nacimiento.getUTCMonth() && HOY.getUTCDate() < nacimiento.getUTCDate());
11 return antesDelCumple ? anios - 1 : anios;
12}
13
14/** «2026-02-30» no es una fecha: Date la convertiría en el 2 de marzo, así que se comprueba que no cambie. */
15function fecha(texto) {
16 const m = texto.match(/^(\d{4})-(\d{2})-(\d{2})$/);
17 if (!m) return null;
18 const [a, mes, d] = m.slice(1).map(Number);
19 const f = new Date(Date.UTC(a, mes - 1, d));
20 return f.getUTCFullYear() === a && f.getUTCMonth() === mes - 1 && f.getUTCDate() === d ? f : null;
21}
22
23/** Errores de un formulario: un objeto campo → mensaje, como mucho uno por campo. */
24function validar(datos) {
25 const v = Object.fromEntries(CAMPOS.map((c) => [c, datos[c] == null ? "" : String(datos[c]).trim()]));
26 const errores = {};
27
28 if (!v.nombre) errores.nombre = "obligatorio";
29 else if (v.nombre.length < 2 || v.nombre.length > 40) errores.nombre = "entre 2 y 40 caracteres";
30 else if (!/^\p{L}[\p{L} '-]*$/u.test(v.nombre)) errores.nombre = "solo letras, espacios, apóstrofos y guiones";
31
32 if (!v.email) errores.email = "obligatorio";
33 else if (!/^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test(v.email)) errores.email = "formato no válido";
34
35 const nombrePropio = v.nombre.split(" ")[0].toLowerCase();
36 if (!v.password) errores.password = "obligatorio";
37 else if (v.password.length < 8) errores.password = "al menos 8 caracteres";
38 else if (!/[A-Z]/.test(v.password) || !/[a-z]/.test(v.password) || !/\d/.test(v.password)) errores.password = "necesita mayúsculas, minúsculas y números";
39 else if (nombrePropio.length >= 3 && v.password.toLowerCase().includes(nombrePropio)) errores.password = "no puede contener tu nombre";
40
41 if (v.password && v.confirmacion !== v.password) errores.confirmacion = "no coincide con la contraseña";
42
43 const nacimiento = fecha(v.nacimiento);
44 if (!v.nacimiento) errores.nacimiento = "obligatorio";
45 else if (!nacimiento) errores.nacimiento = "fecha no válida";
46 else if (edad(nacimiento) < 16) errores.nacimiento = "hay que tener al menos 16 años";
47
48 // Los dos primeros dígitos del código postal son la provincia: del 01 al 52
49 if (!v.cp) errores.cp = "obligatorio";
50 else if (!/^(0[1-9]|[1-4]\d|5[0-2])\d{3}$/.test(v.cp)) errores.cp = "código postal no válido";
51
52 if (v.telefono && !/^[6789]\d{8}$/.test(v.telefono.replace(/\s/g, ""))) errores.telefono = "teléfono no válido";
53 return errores;
54}
55
56let total = 0;
57let correctos = 0;
58const fallosPorCampo = new Map();
59for (const linea of lineas) {
60 if (!linea.trim()) continue;
61 total++;
62 let datos;
63 try {
64 datos = JSON.parse(linea);
65 } catch {
66 console.log(`Formulario ${total}: JSON no válido`);
67 continue;
68 }
69 if (datos === null || typeof datos !== "object" || Array.isArray(datos)) {
70 console.log(`Formulario ${total}: JSON no válido`);
71 continue;
72 }
73 const errores = validar(datos);
74 const campos = CAMPOS.filter((c) => c in errores); // en el orden del formulario
75 if (campos.length === 0) {
76 correctos++;
77 console.log(`Formulario ${total}: correcto`);
78 } else {
79 console.log(`Formulario ${total}: ${campos.length} ${campos.length === 1 ? "error" : "errores"}`);
80 for (const c of campos) {
81 console.log(` - ${c}: ${errores[c]}`);
82 fallosPorCampo.set(c, (fallosPorCampo.get(c) ?? 0) + 1);
83 }
84 }
85}
86
87console.log(`Correctos: ${correctos} de ${total}`);
88const peor = CAMPOS.filter((c) => fallosPorCampo.has(c)).sort((a, b) => fallosPorCampo.get(b) - fallosPorCampo.get(a))[0];
89console.log(`Campo con más errores: ${peor ? `${peor} (${fallosPorCampo.get(peor)})` : "ninguno"}`);La función validar es pura: recibe datos y devuelve errores, sin escribir nada ni tocar el DOM. Por eso se puede usar igual en el navegador (al enviar el formulario) y en el servidor (al recibirlo), que es donde la validación es obligatoria de verdad.
Normalizar todos los campos al principio (texto recortado, vacío si falta) elimina de golpe los casos raros de undefined, null o números, y cada regla solo tiene que mirar cadenas.
Las expresiones regulares concentran el formato: \p{L} con el flag u acepta nombres reales de cualquier idioma, y la del código postal comprueba a la vez la longitud y el rango de provincias. Lo que no es formato (la fecha que existe, la edad, el nombre dentro de la contraseña) se resuelve con código normal.
Date corrige en silencio las fechas imposibles; comprobar que la fecha no ha cambiado al crearla es la forma fiable de validarlas. Usar UTC y una fecha de referencia fija hace que el resultado no dependa de la zona horaria ni del día de ejecución.
Para ir más allá
- Conecta
validara un formulario HTML: muestra cada mensaje junto a su campo conaria-describedbyy valida al salir de cada campo (blur). - Comprueba si el email ya existe consultando una API con
fetchy muestra el error cuando llegue la respuesta. - Añade un medidor de fortaleza de la contraseña (longitud, variedad de caracteres, palabras comunes).