TipsProgramacion

Formularios duplicados: arregla el problema en el servidor

Evita formularios duplicados con una clave de idempotencia: una solución práctica para contactos, reservas y pedidos cuando la red falla.

Portátil con una herramienta de formularios online abierta

Hay un fallo que parece pequeño hasta que llega el primer susto: un cliente pulsa dos veces en enviar, el móvil pierde cobertura, el navegador reintenta la petición o una integración tarda demasiado en responder. Resultado: dos contactos iguales, dos reservas o, peor, dos acciones de negocio que no deberían repetirse.

Desactivar el botón ayuda a que la interfaz no invite al doble clic. Pero no es suficiente. Si la petición llega dos veces al servidor, la decisión importante tiene que estar allí.

Una forma bastante limpia de resolverlo es añadir una clave de idempotencia. La idea es sencilla: el navegador genera una clave única para ese intento de envío y el servidor la guarda cuando procesa la primera petición. Si vuelve a recibir la misma clave, no crea otro registro; devuelve la respuesta anterior o informa de que ese envío ya fue tratado.

Ejemplo mínimo en el cliente:

const form = document.querySelector("form");
const idempotencyKey = crypto.randomUUID();

form.addEventListener("submit", async (event) => {
  event.preventDefault();

  const data = new FormData(form);

  await fetch("/api/contacto", {
    method: "POST",
    headers: {
      "Idempotency-Key": idempotencyKey,
    },
    body: data,
  });
});

El detalle importante está en no generar la clave dentro de cada submit, porque entonces cada reintento parecería una petición nueva.

En el servidor, la versión real no debería quedarse en memoria si tienes varias instancias o procesos. Para una web seria usaría una tabla en base de datos, Redis o el mismo sistema donde guardas la solicitud:

const key = request.headers.get("Idempotency-Key");

if (!key) {
  return new Response("Falta Idempotency-Key", { status: 400 });
}

const previous = await db.submissions.findUnique({ where: { key } });

if (previous) {
  return Response.json({ ok: true, duplicated: true });
}

await db.submissions.create({
  data: {
    key,
    email,
    message,
  },
});

return Response.json({ ok: true, duplicated: false });

Esto no sustituye a un buen antispam, ni a validaciones, ni a un nonce si estás en WordPress. Resuelve otro problema: que una acción legítima no se ejecute dos veces por accidente.

Yo lo miraría especialmente en formularios de presupuesto, reservas, checkout, alta de leads, descargas con cupón o automatizaciones que disparan emails y tareas internas. En un formulario de contacto básico quizá no merezca montar Redis. En una reserva de cita o pedido, sí puede ahorrarte una llamada incómoda.

Fuentes

Hablemos de tu web

Sobre esto y otras muchas cosas puedo ayudar a tu negocio

Formulario de contacto