La Raya · backend (8): feedback sin cuentas — un formulario de contacto que no guarda datos

Publicado el 21/06/2026

La Raya no tiene (ni quiere de momento) cuentas de usuario. Pero necesitaba una vía de feedback: "este horario está mal", "falta tal bar", "la app peta al abrir noticias". El reto era hacerlo sin pedir datos personales y sin abrir la puerta a que alguien me inunde el buzón.

El principio: si no lo recojo, no tengo que protegerlo

La decisión de partida fue de minimización (y de tranquilidad con el RGPD): no almacenar nada del usuario. Nada de email, nada de nombre. El formulario solo manda:

  • un tema de una lista cerrada (una opción por sección de la app —Eventos, Gastronomía, Noticias, Turismo— más "Problema técnico" y "Otros"), y
  • un mensaje de texto libre.

Si el usuario no da su email, no puedo responderle; lo asumí a propósito. "Ellos envían, yo intento resolver o no". Menos datos, menos problemas.

El problema interesante: limitar abuso sin identificar a nadie

Sin login, ¿cómo evito que alguien envíe 10.000 mensajes y me llene la base de datos o el correo? Necesitaba identificar el dispositivo para poner un límite, pero sin invadir la privacidad. Las opciones:

  • Solo por IP: problemático. Las redes móviles y el NAT hacen que muchísima gente comparta IP → bloquearías a usuarios legítimos del mismo operador. Y es rotable cambiando de red.
  • Solo un identificador de dispositivo: la app genera un UUID y lo manda. Cómodo, pero rotable (reinstalas y a empezar) → no frena a un atacante decidido.

La solución fue combinar ambos con un matiz de privacidad: la app genera un UUID persistente y lo envía en una cabecera; el límite principal (5 mensajes/día) se cuenta por ese dispositivo, en base de datos (autoritativo). Y como respaldo anti-DoS, un tope por red pero guardando solo la IP truncada a /24 (los tres primeros octetos, sin el último). Una /24 es mucho menos identificativa que una IP completa y basta como "unidad de abuso". Además, un throttle de ráfaga barato (en Redis) corta avalanchas antes de tocar la BD.

Así: el dispositivo es rotable, pero la /24 hace de red de seguridad; y la /24 sola no identifica a una persona. Equilibrio razonable entre frenar el abuso y no espiar.

Lo que llega al otro lado

Cada mensaje válido se reenvía por email al administrador (con el SMTP que ya configuré en el capítulo de despliegue) y queda guardado en el admin de Django por si el email falla — el mensaje nunca se pierde. El envío es síncrono y best-effort: si el correo falla, se registra el aviso pero la respuesta al usuario es la misma.

Un detalle: el endpoint es público (sin auth ni CSRF, porque no hay sesión) y la cabecera del dispositivo va en la lista blanca de CORS para que la app pueda mandarla.

Lo que me llevo

  • La mejor protección de datos es no tenerlos. Si no recoges email ni nombre, no hay nada que filtrar, exportar ni que te reclamen. La minimización simplifica el diseño y la conciencia.
  • Identificar un dispositivo no es identificar a una persona — si lo haces con cuidado. Un UUID anónimo + una /24 (sin el último octeto) limitan el abuso sin construir un perfil de nadie.
  • Defensa en capas. Límite por dispositivo en BD (la regla de negocio) + tope por red + throttle de ráfaga (el anti-DoS). Cada capa cubre el agujero de la anterior.
  • Que el dato sobreviva al email. Guardar el mensaje en el admin además de mandarlo evita perder feedback cuando el SMTP tiene un mal día.

Y con esto se cierra la serie del backend de La Raya: de una web de fichas a un agregador multilingüe, desplegado, con una sección de gastronomía de cuatro fuentes y un canal de feedback que no guarda datos. La otra mitad de la historia —la app móvil— tiene su propia serie, que empieza en la siguiente entrada.