Formularios - Datos precargados y URL publica
Este documento es la guia operativa de los formularios publicos de Floogos, complementaria a Formularios - Captura de datos. Se centra en dos aspectos:
- Como se construye la URL que el usuario final visita.
- Como se cargan datos precargados (prefill) desde un workflow.
Si solo quieres entender que son los formularios y los nodos FormTrigger / FormRender, empieza por el documento general. Si ya los conoces y quieres saber el detalle de URLs, hashes, sesiones temporales y casos reales, sigue leyendo.
Anatomia de la URL publica
Sección titulada «Anatomia de la URL publica»Cuando publicas un formulario, Floogos genera una URL del tipo:
https://app.floogos.com/f/{public_hash}Las dos piezas son:
| Pieza | Descripcion |
|---|---|
app.floogos.com | Dominio del front-end (variable APP_HOST del backend; en desarrollo es http://localhost:5173) |
/f/ | Prefijo fijo de las rutas publicas de formularios |
{public_hash} | Token aleatorio de 32 caracteres hex (16 bytes) generado al marcar el formulario como publico |
Como se genera el public_hash
Sección titulada «Como se genera el public_hash»Cuando creas o publicas un formulario:
public_hash = crypto.randomBytes(16).toString('hex');// Ejemplo: "a1b2c3d4e5f67890fedcba0987654321"Este hash:
- Es unico por formulario.
- Se almacena en
forms.public_hash. - No se regenera al editar el formulario (la URL es estable).
- Se borra (NULL) si desactivas el flag
is_public, lo que invalida la URL inmediatamente. - Si vuelves a hacerlo publico, se genera uno nuevo — la URL anterior queda muerta.
Estados que validan que la URL sea servible
Sección titulada «Estados que validan que la URL sea servible»El backend (en GET /f/:hash) requiere que el formulario cumpla:
SELECT id FROM formsWHERE public_hash = ? AND is_public = TRUE AND status = 'published'Si falla cualquiera de estas condiciones, el usuario ve una pagina “Formulario no encontrado” (HTML estatico). Esto significa:
- Si el formulario esta en
draft, no se sirve aunque el hash sea correcto. - Si quitas el flag
is_public, la URL deja de funcionar pero el formulario sigue existiendo en tu cuenta. - Si rotas el hash (republicas), las URLs antiguas quedan invalidadas.
URL con datos precargados (prefill)
Sección titulada «URL con datos precargados (prefill)»Cuando un workflow usa el nodo FormRender, la URL incluye un parametro adicional:
https://app.floogos.com/f/{public_hash}?prefill={session_hash}| Pieza | Descripcion |
|---|---|
?prefill= | Parametro de query string que activa el modo precargado |
{session_hash} | Token aleatorio de 32 caracteres hex que apunta a una sesion temporal con los valores precargados |
Sin el parametro prefill, el formulario se sirve vacio (lo veria cualquier visitante anonimo). Con prefill, el formulario se carga con los valores asociados a esa sesion temporal.
Como se genera el session_hash
Sección titulada «Como se genera el session_hash»El nodo FormRender, al ejecutarse:
- Genera un hash unico de sesion:
crypto.randomBytes(16).toString('hex'). - Procesa el
field_mappingresolviendo todas las variables dinamicas ({{@Webhook.email}},{{var.foo}}, etc.) con los datos del workflow en ese momento. - Inserta una fila en la tabla
form_prefill_sessionscon:session_hash(la clave)form_idprefill_data(JSON con los valores procesados)custom_title,custom_description(opcionales)redirect_after_submit(opcional)workflow_data(snapshot completo del estado del workflow para auditoria)expires_at = NOW() + 24h
- Devuelve la URL ya construida:
${APP_HOST}/f/${form.public_hash}?prefill=${sessionHash}.
Es importante entender:
- Cada ejecucion del nodo
FormRendergenera un session_hash nuevo. Si el workflow corre 100 veces, hay 100 sesiones de prefill independientes. - La sesion caduca a las 24 horas. Despues de ese tiempo, la URL sigue cargando el formulario pero ya sin los valores precargados (como si fuese un visitante normal).
- La sesion no se borra al enviar el formulario: caduca por tiempo. Esto permite, por ejemplo, que el mismo enlace pre-llene varias veces si el usuario refresca antes de las 24h.
Como se cargan los datos en el cliente
Sección titulada «Como se cargan los datos en el cliente»Cuando el navegador abre /f/{public_hash}?prefill={session_hash}:
-
El backend sirve la pagina HTML estatica
form-render.html. -
El JS del front llama a
GET /f/{public_hash}/data?prefill={session_hash}. -
El backend valida en una sola consulta:
SELECT * FROM form_prefill_sessionsWHERE session_hash = ?AND form_id = ?AND expires_at > NOW() -
Si la sesion existe y no ha caducado:
- Se parsean los
prefill_data(con un fallback robusto para casos donde MySQL devuelve el JSON serializado en formato indexado). - Si la sesion tiene
custom_titleocustom_description, se sobrescriben los del formulario base. - La respuesta incluye
prefillData,prefillSessionyredirectAfterSubmit.
- Se parsean los
-
El front-end aplica los valores a los campos del formulario y los muestra al usuario.
-
Si la sesion no existe o ha caducado:
- El log del backend imprime
[PUBLIC FORM] Prefill session not found or expired. - El formulario se sirve vacio, pero el usuario no ve ningun error (degradacion silenciosa).
- El log del backend imprime
Flujo end-to-end de prefill
Sección titulada «Flujo end-to-end de prefill»WORKFLOW NAVEGADOR DEL USUARIO───────── ─────────────────────[FormRender] - genera session_hash - guarda en form_prefill_sessions - devuelve form_url
[SendMail] - envia email con form_url ────────> abre el enlace GET /f/{hash}?prefill={session} <──── HTML form-render.html GET /f/{hash}/data?prefill={session} <──── JSON con prefillData usuario ve campos pre-llenados
usuario completa y envia POST /f/{hash}/submit[FormTrigger] - inicia un nuevo workflow <──── - recibe los datos del formConfiguracion del nodo FormRender
Sección titulada «Configuracion del nodo FormRender»| Parametro | Tipo | Descripcion |
|---|---|---|
form_id | text | ID interno del formulario (no el hash publico) |
field_mapping | json | Diccionario campo -> valor o expresion dinamica |
output_type | select | url (default), html, redirect |
redirect_after_submit | boolean | Si despues de enviar el formulario el usuario debe ser redirigido |
custom_title | text | Titulo a mostrar (sobrescribe el del formulario base, soporta variables) |
custom_description | textarea | Descripcion personalizada (soporta variables) |
Modos de salida
Sección titulada «Modos de salida»url (valor por defecto): el nodo devuelve un objeto con la URL y los metadatos. El siguiente nodo del workflow recibe:
{ "success": true, "form_url": "https://app.floogos.com/f/a1b2c3d4...?prefill=9f8e7d6c...", "session_hash": "9f8e7d6c...", "form_id": 42, "form_name": "Solicitud de vacaciones", "prefill_data": { "nombre": "Ana", "email": "ana@empresa.com" }, "expires_at": "2026-04-11T11:30:00.000Z"}Util cuando el siguiente paso es enviar el enlace por correo, Slack, WhatsApp, etc.
redirect: el workflow detiene su ejecucion y devuelve una respuesta HTTP 302 Location: {form_url}. El navegador del usuario salta automaticamente al formulario. Solo tiene sentido si el workflow se invoco desde el propio navegador (por ejemplo, desde un boton “Editar perfil” que llama a un webhook publico).
html: el workflow detiene su ejecucion y devuelve un HTML completo con el formulario embebido. Util para entornos donde no quieres que el usuario navegue (algunos clientes de correo lo soportan, otros no).
Casos practicos
Sección titulada «Casos practicos»Caso 1 — Pre-llenar un formulario con datos de un CRM
Sección titulada «Caso 1 — Pre-llenar un formulario con datos de un CRM»Setup:
Webhook (recibe trigger del CRM con un cliente_id) -> HTTP Request (consulta /api/clients/:id en el CRM) -> FormRender form_id: 12 (Formulario "Actualizar datos del cliente") field_mapping: { "nombre": "{{@HTTP_Request.data.first_name}}", "apellidos": "{{@HTTP_Request.data.last_name}}", "email": "{{@HTTP_Request.data.email}}", "telefono": "{{@HTTP_Request.data.phone}}", "cliente_id": "{{@Webhook.cliente_id}}" } output_type: url -> SendMail to: "{{@HTTP_Request.data.email}}" subject: "Por favor revisa tus datos" body: "Hola {{@HTTP_Request.data.first_name}}, revisa tus datos en: {{@FormRender.form_url}}" -> EndQue pasa:
- El CRM dispara el webhook con un
cliente_id. - El workflow consulta el CRM y obtiene los datos completos del cliente.
- El nodo
FormRenderprocesa elfield_mapping, sustituyendo cada{{@...}}por el valor real del cliente. - Se crea una sesion con
session_hashaleatorio que vive 24h y contiene{nombre: "Juan", apellidos: "Perez", ...}. - Se genera la URL:
https://app.floogos.com/f/abc123...?prefill=def456.... - Se envia el correo al cliente con esa URL.
- El cliente abre el enlace, ve el formulario ya rellenado con sus datos, los corrige si hace falta y envia.
- Otro workflow con
FormTriggerrecibe los datos enviados y actualiza el CRM.
Punto clave: el cliente no necesita estar autenticado en Floogos. El session_hash actua como token unico de un solo uso (con caducidad), suficiente para identificarlo durante esa interaccion.
Caso 2 — Enlace de aprobacion para un manager
Sección titulada «Caso 2 — Enlace de aprobacion para un manager»Setup:
FormTrigger (formulario de solicitud de vacaciones rellenado por un empleado) -> SetVariable (guarda solicitud_id, empleado_email, fechas) -> FormRender form_id: 18 (Formulario "Aprobar solicitud") field_mapping: { "solicitud_id": "{{var.solicitud_id}}", "empleado": "{{var.empleado_email}}", "fecha_inicio": "{{var.fecha_inicio}}", "fecha_fin": "{{var.fecha_fin}}", "decision": "" } custom_title: "Aprobacion de vacaciones - {{var.empleado_email}}" output_type: url -> SendMail to: "manager@empresa.com" subject: "Aprueba la solicitud de {{var.empleado_email}}" body: "Revisa y aprueba aqui: {{@FormRender.form_url}}" -> EndQue pasa:
- El empleado envia su solicitud y se almacena.
- Se genera un formulario de aprobacion pre-llenado con los datos exactos de la solicitud (los campos no son editables porque el formulario base los marca como
readonly, exceptodecision). - Se envia un correo al manager con el enlace.
- El manager pulsa el enlace, ve los datos de la solicitud sin tener que copiarlos ni buscarlos, marca su decision y envia.
- Un
FormTriggerrecibe la respuesta del manager y dispara la notificacion al empleado.
Punto clave: aqui el field_mapping actua como contexto inmutable: el manager ve exactamente lo que el empleado pidio, sin posibilidad de manipular los campos de origen (porque vienen del prefill, no del usuario).
Caso 3 — URL precargada con campos calculados
Sección titulada «Caso 3 — URL precargada con campos calculados»Setup:
Webhook (recibe items de un carrito de compras) -> Transformation (suma totales, calcula impuestos) -> FormRender form_id: 25 (Formulario "Confirmar pedido") field_mapping: { "items": "{{@Transformation.items_resumen}}", "subtotal": "{{@Transformation.subtotal}}", "iva": "{{@Transformation.iva}}", "total": "{{@Transformation.total}}", "moneda": "EUR", "fecha_envio_estimada": "{{@Transformation.fecha_envio}}" } output_type: redirect -> EndQue pasa:
- El nodo
FormRenderconoutput_type: redirectno devuelve URL al workflow: emite directamente una respuesta HTTP 302 al navegador que invoco el webhook. - El usuario es redirigido al formulario
Confirmar pedidocon todos los valores ya calculados (totales, fecha estimada, items resumidos). - No hay correo intermedio, todo ocurre en una sola navegacion.
Punto clave: el modo redirect solo funciona si el workflow se disparo desde el propio navegador del usuario (porque el 302 se entrega como respuesta a esa peticion HTTP). No sirve para flujos disparados por cron o por otra API.
Variables dinamicas en field_mapping
Sección titulada «Variables dinamicas en field_mapping»El field_mapping acepta cualquier expresion soportada por el motor de variables dinamicas de Floogos:
| Sintaxis | Significado |
|---|---|
{{@NombreNodo.path.subpath}} | Valor de un nodo anterior del workflow |
{{@Webhook.body.email}} | Campo del payload recibido en el webhook |
{{var.nombre_variable}} | Variable definida con SetVariable |
{{$now}} | Funcion del sistema (fecha actual) |
valor literal | Strings, numeros, booleanos directos |
{ "json": "anidado" } | Objetos enteros que se serializan y procesan recursivamente |
Las expresiones pueden mezclarse:
{ "asunto": "Pedido {{var.numero_pedido}} para {{@HTTP_Request.data.cliente}}", "items_count": "{{@HTTP_Request.data.items.length}}"}Si una variable no existe en el contexto del workflow, se sustituye por una cadena vacia (no falla). Esto facilita que el formulario se cargue aunque algun dato sea opcional.
Buenas practicas
Sección titulada «Buenas practicas»- No expongas el
public_hashen logs ni en URLs cortas publicas. Aunque caduca al despublicar, sigue siendo un token que da acceso al formulario sin autenticacion adicional. - Programa el envio del enlace en cuanto generes el
session_hash. Si lo guardas para enviarlo despues, recuerda la caducidad de 24h. - Para sesiones que necesitan vivir mas de 24h, no uses prefill: usa el formulario base sin parametros y guarda los datos previos en
DataStoreoMemory, recuperandolos en elFormTriggercuando el usuario envie. - No metas datos sensibles en
field_mappingque no quieras que el usuario vea o edite. Si un campo se marca como editable en el formulario base, el usuario puede cambiarlo antes de enviar. Si no debe poder, marcalo comoreadonlyohiddenen el editor de formularios. - Usa
custom_titleycustom_descriptionpara personalizar el formulario por sesion. Por ejemplo:Aprobacion de vacaciones - {{var.empleado}}deja claro a quien pertenece la solicitud sin necesidad de campos adicionales.
Tablas implicadas
Sección titulada «Tablas implicadas»| Tabla | Rol |
|---|---|
forms | Definicion del formulario, incluye public_hash, is_public, status, form_json (estructura de campos) |
form_prefill_sessions | Sesiones temporales de prefill (24h), una por cada ejecucion de FormRender |
form_submissions | Respuestas enviadas por los usuarios |
Endpoints publicos relevantes
Sección titulada «Endpoints publicos relevantes»| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /f/:hash | Sirve la pagina HTML estatica del formulario publico |
| GET | /f/:hash/data?prefill={session} | Devuelve el JSON con definicion + (opcional) datos precargados |
| POST | /f/:hash/submit | Recibe la respuesta del usuario y dispara el FormTrigger correspondiente |
Estos endpoints no requieren autenticacion (excepto si el formulario tiene requires_auth = true, en cuyo caso se aplica el flujo de OTP o password configurado en settings.auth_config).
Solucion de problemas
Sección titulada «Solucion de problemas»| Sintoma | Causa probable | Solucion |
|---|---|---|
El formulario abre vacio aunque pase ?prefill=... | La sesion caduco (mas de 24h) | Vuelve a ejecutar el workflow para regenerar el session_hash |
| El formulario abre vacio en cuanto se genera | El form_id del nodo FormRender no coincide con el formulario al que apunta public_hash | Verifica que el form_id configurado existe y esta publicado |
| ”Formulario no encontrado” en la URL recien generada | El formulario esta en draft o is_public = FALSE | Cambia el estado a published y marca como publico |
Variables {{@...}} aparecen literales en el formulario | El nodo del que dependes no se ejecuto antes o el path es incorrecto | Verifica el orden de los nodos y revisa los logs del workflow |
| Una URL antigua deja de funcionar de un dia para otro | Republicaste el formulario y se regenero el public_hash | Regenera y reenvia los enlaces; o reactiva el flag sin tocar el hash |
Ver tambien
Sección titulada «Ver tambien»- Formularios - Captura de datos — vision general de los nodos
FormTriggeryFormRender - Variables dinamicas — sintaxis completa de las expresiones que puedes usar en
field_mapping - Triggers — comparacion entre webhook, cron y form trigger