Headless Feedback ManagementPara executivosPara arquitetosPosicionamento profundo30 min de leitura

Headless Salesforce Feedback Management: cómo usar Salesforce como motor de encuesta y construir el frontend por fuera

Cómo pilotar una encuesta definida en Feedback Management — con páginas, ramificación, lógica condicional, preguntas requeridas e invitaciones — desde un frontend externo en React, Next.js o móvil, sin reimplementar el motor y sin bajar cada SObject a mano. Documento escrito para usted, desde la mirada de un arquitecto técnico en IA y Agentforce.

Apresentação executivaVersão slide a slide do conteúdo — ideal para compartilhar ou apresentar.
Jonathan Gomez·Arquitecto técnico · IA & AgentforceAtualizado: 13 de agosto de 2026
Feedback ManagementSalesforce SurveysConnect REST APIHeadlessExperience CloudIntegration ArchitectureReactNext.jsBFFOAuth
Arquitectura de plataforma Salesforce con capas de datos, agentes y experiencia — base sobre la que se apoya un frontend headless de Feedback Management.
Fuente: Salesforce · Agentforce Platform.

Resumo executivo

Salesforce Feedback Management expone un juego oficial de APIs REST (Connect REST API para participantes autenticados y una familia unAuth separada sobre el dominio de Omni-Channel Engagement) que permiten iniciar una respuesta, enviar respuestas por página y dejar que Salesforce decida server-side cuál es la siguiente página según las reglas de branching configuradas en Survey Builder. Ese contrato hace posible construir una experiencia visual completamente custom — React, Next.js, móvil o portal — mientras Salesforce sigue siendo dueño de la definición de encuesta, la ramificación, la persistencia en SurveyResponse / SurveyQuestionResponse y las invitaciones. Este documento revisa la superficie oficial de la API, verifica qué está soportado y qué no, compara reconstruir el motor a mano contra usar el API oficial, propone una arquitectura con BFF, y cierra con un POC concreto de cuatro páginas.

Statement técnico

La tesis en una página

Sí — Salesforce Feedback Management puede operarse como un motor de encuesta headless. Existe una superficie oficial de APIs REST (Connect REST API para participantes autenticados, y una familia unAuth Response API separada para participantes públicos) que permite iniciar una respuesta, enviar las respuestas de la página actual, pedir la siguiente página y dejar que Salesforce decida server-side cuál toca según la ramificación configurada en Survey Builder. Esa separación es real y está documentada. Lo que no debe hacer es reconstruir el motor bajando SurveyQuestion, SurveyQuestionChoice y SurveyPage vía SOQL: pierde branching, display logic, versionamiento y persistencia oficial en un solo movimiento.

Este documento está escrito desde la mirada de un arquitecto técnico especializado en integración Salesforce y frontends externos. La audiencia son directores de plataforma, arquitectos de integración y líderes de producto digital que necesitan encuestas dentro de una experiencia propia (web, móvil, portal) sin renunciar a que Salesforce siga siendo el sistema de registro de la voz del cliente. No es una postura comercial: es una postura de ingeniería con verificación explícita contra la documentación oficial vigente.

Parte 0 · Para todos

Sin tecnicismos: la analogía del restaurante

Salesforce mantiene la cocina; usted pone el comedor. La receta, los ingredientes y el chef siguen siendo de Salesforce. Lo que cambia es el envase — no el contenido.

Piense en Feedback Management como un restaurante que normalmente le sienta en su propio comedor: los formularios prefabricados que Salesforce arma por usted. En modo headless, el restaurante cierra el comedor al público pero deja la cocina funcionando. Usted pone su propio comedor — su web, su app móvil, un bot de WhatsApp, un kiosco, lo que sea — y le pide platillos a la cocina a través de una ventanilla (la API). La cocina cocina, valida, guarda y decide qué sigue en el menú. El cliente final se sienta en su espacio, pero come lo que salió de la misma cocina de siempre.

Esa separación es la que hace posible que Salesforce siga siendo el sistema de registro de la voz del cliente aunque la persona nunca vea una pantalla de Salesforce. Reportes, dashboards, automatizaciones y trazabilidad siguen funcionando exactamente igual.

En la cocina de Salesforce

Lo que se queda del lado servidor

El cerebro de la encuesta: reglas de 'si contesta X, salta a Y', preguntas que solo aparecen bajo cierta condición, campos obligatorios y traducciones. La memoria: cada respuesta se guarda en los mismos objetos de siempre. La sesión: Salesforce recuerda en qué página va cada persona. El vínculo con el contexto: la encuesta se puede amarrar al caso, la sesión de WhatsApp, la llamada de voz, la cuenta.

En su comedor

Lo que le toca a usted

Pintar las preguntas con su diseño propio en su canal (web, móvil, WhatsApp, voz). Un intermediario ligero (BFF) que traduzca entre su frontend y Salesforce — no es opcional, es la puerta segura a la cocina. Y dos botones: 'siguiente' y 'anterior'. La navegación real la decide el servidor.

Las limitantes reales — sin endulzar

  • Solo dos verbos de navegación: 'Next' y 'Back'. Nada de saltar tres páginas, guardar borrador y volver mañana con la vida resuelta, ni editar una respuesta anterior una vez que avanzó.
  • La API oficial no expone todos los tipos de pregunta con contrato público confirmado. Ranking, Slider, Date/DateTime, Picklist y Scoring existen en la plataforma pero no todos tienen esquema publicado en la Business API — hay que validarlos org por org.
  • El modo público-anónimo (unAuth) requiere licencia Feedback Management Growth. Sin esa licencia, solo puede encuestar a usuarios ya identificados.
  • El navegador no puede llamar directo a Salesforce en modo unAuth por reglas de seguridad de dominio (CORS). Por eso el BFF es obligatorio — no es un lujo arquitectónico, es la puerta.
  • La lógica vive del lado servidor. Si Salesforce no le devuelve la pregunta 4, su frontend no la puede adivinar ni forzar. Está atado al orden en que la cocina despacha.
  • El momento en que la encuesta termina lo decide Salesforce, no usted. La 'página de gracias' se puede estilizar libremente, pero el punto final del flujo lo marca el servidor.

Parte 1 · Marco

Qué significa 'headless' aquí — y por qué importa

Headless, aplicado a Feedback Management, significa una cosa concreta: Salesforce sigue siendo dueño de la definición y ejecución de la encuesta — encuesta, versión, páginas, preguntas, opciones, ramificación, invitaciones, respuestas — y la aplicación externa es dueña únicamente de la presentación y la experiencia. La aplicación no toma decisiones sobre el flujo de la encuesta; pregunta a Salesforce qué mostrar y le envía lo que el usuario respondió.

Salesforce (motor)

Dueño de la lógica y del dato

Survey y SurveyVersion. Pages, Questions y Choices. Page branching logic. Question display logic. Required flags. Merge fields. SurveyInvitation (creación y validación). SurveyResponse y SurveyQuestionResponse (persistencia). Idioma y traducciones. Reglas de fatiga si están configuradas en Feedback Management.

Frontend externo (experiencia)

Dueño de la UX y del canal

Look & feel completo. Componentes por tipo de pregunta. Responsive y accesibilidad. Animaciones y transiciones. Persistencia visual de progreso. Analítica de interacción (dwell time, abandono por página). Canal: web propia, app móvil, portal white-label, WebView embebida en app de partner.

Parte 2 · Superficie oficial de la API

Qué expone Salesforce hoy — endpoints, verbos y campos clave

La Salesforce Feedback Management Developer Guide agrupa las APIs de respuesta bajo el nombre 'Business APIs'. Existen tres familias de endpoints y una utilidad de token para el caso público. Todas las URIs, verbos y nombres de campo listados en esta sección están tomados textualmente de la guía oficial vigente.

Familia 1 · Participantes autenticados (Bearer OAuth)

Recurso Connect RESTVerbosUso
/connect/surveys/{surveyId}/survey-responsePOST · PATCHPOST inicia una respuesta creando la invitación server-side con la configuración enviada en el body. PATCH envía las respuestas de la página actual y navega.
/connect/surveys/{surveyId}/invitation/{surveyInvitationId}/survey-responsePOST · PATCHVariante que usa una SurveyInvitation ya existente (creada previamente por Flow, invocable o Apex). Útil cuando la invitación se genera en un journey de negocio y el frontend solo la 'consume'.
/connect/surveys/{surveyId}/survey-invitation-emailsPOSTEnvío masivo de invitaciones por email a hasta 300 leads, contacts o users. No es parte del flujo de rendering, pero es la contraparte natural cuando la invitación llega por email.

Familia 2 · Participantes no autenticados (unAuth Response API)

Esta familia no vive en el dominio estándar de Salesforce. Vive en el dominio de Omni-Channel Engagement — un host separado terminado en salesforce-scrt.com — bajo el prefijo /surveys/v1. Requiere Feedback Management Growth y habilitar explícitamente 'Unauthenticated Survey Participation' en Setup > Survey Settings.

Recurso unAuthVerboUso
<MyDomain>.salesforce-scrt.com/surveys/v1/accessTokenPOSTEmite un access token per-org (TTL 3600s) a partir del Organization Id de 18 caracteres. No es OAuth — es un token de aplicación propio de la unAuth API.
<MyDomain>.salesforce-scrt.com/surveys/v1/survey-responsePOST · PATCHMismo patrón POST-inicia / PATCH-navega que la familia autenticada. El body requiere adicionalmente surveyDeveloperName e invitationUuid para reforzar la seguridad de la sesión.

Los tres campos que sostienen toda la sesión

Cualquiera de las dos familias devuelve al iniciar la respuesta un trío de valores que el cliente debe conservar y echar de vuelta en cada PATCH. Sin ellos el servidor no reconoce la sesión.

  • flowInterviewState — string opaco que representa el estado del flow interview server-side (Feedback Management ejecuta cada respuesta como un Flow interview). El cliente no lo interpreta; solo lo re-envía.
  • invitationId — el Id de la SurveyInvitation asociada a esta respuesta (autenticada) o generada por la unAuth API (pública).
  • invitationUuid — un identificador aleatorio adicional que la unAuth API exige en cada PATCH como refuerzo de seguridad. Solo aplica al camino público.

navigationAction — el enum verdadero

Parte 3 · Flujo end-to-end

Cómo se ve una respuesta desde el primer clic hasta la Thank You

Este es el flujo canónico para una encuesta multipágina con branching, usando el endpoint autenticado con invitación existente. El unAuth es idéntico en forma; solo cambia el host, el token y dos campos adicionales en el body (invitationUuid y surveyDeveloperName).

Ciclo POST-start · PATCH-next · Thank You

  ┌────────────────────────────────────────────────────────────────────┐
  │  Frontend  (React / Next.js / móvil)                               │
  └───────────────────────┬────────────────────────────────────────────┘
                          │  1. POST /survey-response         (start)
                          ▼
  ┌────────────────────────────────────────────────────────────────────┐
  │  Salesforce  ← devuelve Survey Description Output:                 │
  │    · responseId, invitationId, flowInterviewState                  │
  │    · surveyPage = Página 1 (label, name, surveyQuestions[])        │
  │    · navigationActions = ["Next"]                                  │
  └───────────────────────┬────────────────────────────────────────────┘
                          │  2. Renderiza Página 1 · usuario responde
                          │
                          │  3. PATCH /survey-response         (Next)
                          │     body: { navigationAction: "Next",      │
                          │             surveyPageResponses: {...} }   │
                          ▼
  ┌────────────────────────────────────────────────────────────────────┐
  │  Salesforce evalúa branching server-side según la respuesta        │
  │  ← devuelve Survey Response Output:                                │
  │    · flowInterviewState (nuevo)                                    │
  │    · surveyPage = Página 2 O Página 5 (según branching)            │
  │    · navigationActions = ["Next", "Back"]                          │
  └───────────────────────┬────────────────────────────────────────────┘
                          │  4. Repite PATCH por cada página
                          │
                          │  5. PATCH última página · Next
                          ▼
  ┌────────────────────────────────────────────────────────────────────┐
  │  Salesforce ← devuelve Survey Response Output con:                 │
  │    · surveyPage = Survey Thank You Page Output                     │
  │      (thankYouMessage, messageDescription, redirectUrl, urlButtons)│
  │    · navigationActions = []                                        │
  │  Persistencia:                                                     │
  │    · SurveyResponse.Status → Completed                             │
  │    · SurveyQuestionResponse por cada pregunta contestada           │
  └────────────────────────────────────────────────────────────────────┘

1 · POST inicial (start)

Request
POST /services/data/v57.0/connect/surveys/0Kdxx0000000H46CAE/invitation/0Kixx0000004EsOCAU/survey-response
Host: yourInstance.my.salesforce.com
Authorization: Bearer <access_token>
Content-Type: application/json

{}
Response · Survey Description Output (conceptual, campos verbatim)
{
  "status": "Success",
  "responseId": "0Myxx0000004CYqCAM",
  "invitationId": "0Kixx0000004EsOCAU",
  "flowInterviewState": "state1",
  "languageCode": "en",
  "navigationActions": ["Next"],
  "surveyDetail": {
    "label": "Post-purchase feedback",
    "name": "Post_purchase_feedback",
    "versionNumber": 3,
    "surveyPage": {
      "label": "Página 1",
      "name": "p_d84a...",
      "surveyQuestions": [
        {
          "name": "q_856a...",
          "label": "¿Actualmente eres cliente?",
          "questionType": "RadioButton",
          "isResponseRequired": true,
          "questionChoices": [
            { "name": "c_yes", "label": "Sí" },
            { "name": "c_no",  "label": "No" }
          ]
        }
      ]
    }
  },
  "errors": [],
  "warnings": []
}

2 · PATCH para navegar a la siguiente página

Request (autenticado)
PATCH /services/data/v57.0/connect/surveys/0Kdxx.../invitation/0Kixx.../survey-response
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "flowInterviewState": "state1",
  "invitationId": "0Kixx0000004EsOCAU",
  "languageCode": "en",
  "navigationAction": "Next",
  "surveyPageResponses": {
    "questionResponses": [
      {
        "name": "q_856a...",
        "questionType": "RadioButton",
        "responses": [ { "name": "c_yes" } ]
      }
    ]
  }
}
Response · Survey Response Output (Salesforce decidió la siguiente página)
{
  "status": "Success",
  "responseId": "0Myxx0000004CYqCAM",
  "invitationId": "0Kixx0000004EsOCAU",
  "flowInterviewState": "state2",
  "languageCode": "en",
  "navigationActions": ["Next", "Back"],
  "surveyPage": {
    "label": "Página 2 — datos de tu experiencia",
    "name": "p_c73e...",
    "surveyQuestions": [
      {
        "name": "q_ab12...",
        "label": "¿Qué productos usaste esta semana?",
        "questionType": "MultiChoice",
        "isResponseRequired": true,
        "questionChoices": [
          { "name": "c_p1", "label": "Producto A" },
          { "name": "c_p2", "label": "Producto B" },
          { "name": "c_p3", "label": "Producto C" }
        ]
      },
      {
        "name": "q_cd34...",
        "label": "Del 0 al 10, ¿qué tan probable es que nos recomiendes?",
        "questionType": "NPS",
        "isResponseRequired": true,
        "minScale": 0,
        "maxScale": 10
      }
    ]
  },
  "errors": []
}

3 · PATCH final — Salesforce responde con la Thank You page

Response del último PATCH
{
  "status": "Success",
  "responseId": "0Myxx0000004CYqCAM",
  "flowInterviewState": "final",
  "navigationActions": [],
  "surveyPage": {
    "label": "Gracias",
    "name": "p_thanks",
    "thankYouMessage": "Gracias por tu tiempo.",
    "messageDescription": "Tu opinión fue registrada correctamente.",
    "redirectUrl": "https://miempresa.com/gracias",
    "urlButtons": [
      { "label": "Volver al inicio", "url": "https://miempresa.com" }
    ]
  }
}

Parte 4 · Lógica condicional

Page branching vs question display logic — qué evalúa Salesforce y qué le toca al frontend

Esta es la parte donde se juega la propuesta técnica completa. La pregunta operativa es: ¿puedo evitar reimplementar reglas condicionales en React? La respuesta corta es sí para page branching (confirmado por la forma del contrato) y muy probablemente sí para display logic, aunque este último no está confirmado verbatim en la documentación pública.

Page Branching Logic

Regla: 'si Q1 = Sí, ir a Página 2; si no, ir a Página 5'

Se configura en Survey Builder a nivel página. Cambia qué página aparece a continuación en función de las respuestas ya enviadas. Es una decisión de flujo — determina el orden de navegación.

Question Display Logic

Regla: 'muestra Q3 solo si Q2 = A o B'

Se configura a nivel pregunta. No cambia la página siguiente — cambia qué preguntas dentro de la página actual son visibles. Es una decisión de visibilidad — determina qué se ve en una misma pantalla.

Page branching — resuelto server-side (confirmado por contrato)

La forma del contrato lo hace obvio: la respuesta de PATCH contiene un surveyPage con label, name y surveyQuestions[] — pero no contiene reglas, ni un nextPage lookup, ni metadata condicional. El frontend no tiene la información necesaria para decidir la siguiente página. Y aún así, al enviar el PATCH con navigationAction: Next, el servidor devuelve la página correcta. La única forma en que eso funciona es que Salesforce evalúa la ramificación del lado del servidor. La feature table oficial confirma explícitamente que 'Apply branching logic' y 'Page branching logic based on merge fields' están en las tres licencias (Response Pack, Starter y Growth).

Question display logic — muy probablemente server-side (requiere validación)

El schema oficial de Survey Question Output expone exactamente seis campos: description, isResponseRequired, label, name, questionType y responseDataType. No hay un campo dependsOn, displayIf ni visibility. Eso deja dos hipótesis: (a) Salesforce filtra server-side las preguntas ocultas y solo devuelve las visibles en surveyQuestions[], o (b) la lógica de display no está soportada vía API y solo funciona en el renderer nativo de Salesforce. La evidencia circunstancial — que la feature aparece publicitada como 'Dynamic Surveys' en las licencias Starter y Growth y que el schema no expone reglas al cliente — sugiere fuertemente la opción (a), pero la documentación pública no lo confirma con una frase textual.

Lo que sí queda del lado del frontend, siempre

  • Validación de required en la UI antes de mandar PATCH — para no incurrir en un 400 evitable. El campo isResponseRequired ya viene en cada Survey Question Output.
  • Formato local: máscaras de texto, orden de opciones en radio, límite de caracteres visual, contadores. Nada de esto llega desde la API — es UX pura.
  • Estado transicional entre PATCHs: spinner, progreso, animación de página. La API no maneja transitions.
  • Persistencia optimista del último borrador local antes de mandar el PATCH — por si hay pérdida de red. Independiente del partial-save server-side de v63.0+.
  • Analítica de interacción: dwell time, número de cambios de respuesta, abandono por pregunta. Nada de esto va a Salesforce a menos que usted lo mande explícito.

Parte 5 · Frontend

Cómo se ve la arquitectura de componentes del cliente

El árbol de componentes es sencillo porque el motor no está en el frontend. Solo hay un contenedor que orquesta el estado de sesión, un renderer de página que itera por surveyQuestions[] y un dispatcher por tipo de pregunta. Ese es todo el shape.

Árbol de componentes React
SurveyContainer
    │  · guarda { invitationId, invitationUuid?, flowInterviewState, responseId }
    │  · maneja fetch a start / next / back
    │  · decide si render <SurveyPage /> o <ThankYouPage />
    │
    ├── SurveyPage
    │     │  · recibe surveyQuestions[]
    │     │  · valida required antes de habilitar 'Next'
    │     │  · muestra 'Back' cuando navigationActions incluye "Back"
    │     │
    │     └── QuestionRenderer   (por cada pregunta)
    │           │  switch(questionType)
    │           │
    │           ├── <SingleChoice />       questionType = "RadioButton"
    │           ├── <MultiChoice />        questionType = "MultiChoice"
    │           ├── <BooleanYesNo />       questionType = "Boolean"
    │           ├── <Rating />             questionType = "Rating"
    │           ├── <NPS />                questionType = "NPS"
    │           ├── <TextInput />          questionType = "ShortText"
    │           ├── <TextArea />           questionType = "FreeText"
    │           └── <Unsupported />        fallback + telemetría
    │
    └── ThankYouPage
          · label · thankYouMessage · messageDescription · redirectUrl · urlButtons[]

Dispatcher por tipo de pregunta (TypeScript)

QuestionRenderer.tsx (pseudocódigo)
type Question = {
  name: string;
  label: string;
  questionType:
    | "RadioButton"
    | "MultiChoice"
    | "Boolean"
    | "Rating"
    | "NPS"
    | "ShortText"
    | "FreeText";
  isResponseRequired: boolean;
  questionChoices?: { name: string; label: string }[];
  minScale?: number;
  maxScale?: number;
};

export function QuestionRenderer({ question, value, onChange }: Props) {
  switch (question.questionType) {
    case "RadioButton":
      return <SingleChoice question={question} value={value} onChange={onChange} />;
    case "MultiChoice":
      return <MultiChoice   question={question} value={value} onChange={onChange} />;
    case "Boolean":
      return <BooleanYesNo  question={question} value={value} onChange={onChange} />;
    case "Rating":
      return <Rating        question={question} value={value} onChange={onChange} />;
    case "NPS":
      return <NPS           question={question} value={value} onChange={onChange} />;
    case "ShortText":
      return <TextInput     question={question} value={value} onChange={onChange} />;
    case "FreeText":
      return <TextArea      question={question} value={value} onChange={onChange} />;
    default:
      // Nunca falle silencioso: logueé el tipo y muestre un placeholder claro.
      return <Unsupported type={question.questionType} />;
  }
}

Contrato uniforme de respuesta hacia arriba

Cada componente hoja emite el mismo shape hacia arriba: un objeto con name (nombre API de la pregunta), questionType (para que el mapper reconstruya el questionResponse correcto) y ya sea responses[] (array de choice names) para preguntas de selección o responseValue (string o entero) para texto y NPS. Ese mapper es la única pieza no trivial del cliente — y no tiene reglas, solo forma.

buildSurveyPageResponses.ts (conceptual)
export function buildSurveyPageResponses(state: Record<string, any>, questions: Question[]) {
  return {
    questionResponses: questions.map((q) => {
      const raw = state[q.name];
      switch (q.questionType) {
        case "RadioButton":
        case "Boolean":
        case "Rating":
          return { name: q.name, questionType: q.questionType, responses: [{ name: raw }] };

        case "MultiChoice":
          return {
            name: q.name,
            questionType: q.questionType,
            responses: (raw as string[]).map((n) => ({ name: n })),
          };

        case "NPS":
          return { name: q.name, questionType: q.questionType, responseValue: Number(raw) };

        case "ShortText":
        case "FreeText":
          return { name: q.name, questionType: q.questionType, responseValue: String(raw) };
      }
    }),
  };
}

Parte 6 · Participantes

Autenticado vs no autenticado — dos APIs, dos hosts, dos contratos

La decisión entre autenticado y no autenticado no es cosmética. Cambia la API que usa, el host donde vive, cómo obtiene el token, qué edición de Feedback Management necesita y qué configuración de sharing debe existir en la org. Esta tabla resume las diferencias reales.

DimensiónAutenticadoNo autenticado (unAuth)
HostyourInstance.my.salesforce.comyourInstance.my.salesforce-scrt.com (Omni-Channel Engagement URL)
Prefijo/services/data/vXX.0/connect/surveys/…/surveys/v1/…
AutenticaciónOAuth 2.0 estándar — Authorization: Bearer <access_token>Access token per-org emitido por /surveys/v1/accessToken usando Organization Id · TTL 3600s · header Authorization: Bearer <accessToken>
Identidad del participanteDebe ser un User (interno o Experience Cloud) o vincularse a Contact / Lead vía invitaciónNo requiere identidad Salesforce. La invitación se genera contra un Contact o Lead (los User no se soportan en unAuth)
Edición mínima requeridaFeedback Management Starter o GrowthFeedback Management Growth (obligatorio)
Body extra requeridoflowInterviewState, invitationId, navigationAction, surveyPageResponses, languageCode?Los mismos + invitationUuid + surveyDeveloperName
Sharing / configuraciónPerfiles y permisos estándar del User o Experience Cloud siteEncuesta compartida al perfil Guest User · sharing de merge-field records · Setup > Survey Settings > Unauthenticated Survey Participation habilitado
Rate limits documentados5,400 POST/min · 2,700 PATCH/min a nivel org3,600 POST/min · 1,800 PATCH/min a nivel org
Content-Type aceptadoapplication/jsonapplication/json (verbatim: 'only accepts Content-Type: application/json')

Cuál elegir en cada caso

Autenticado

Cuando el participante ya es alguien conocido

Portal de clientes con login, App móvil con sesión, empleado en una intranet, agente en el Service Console, contacto que abre un link de invitación con el token asociado a su SurveyInvitation. La invitación puede haber sido generada por Flow, invocable, journey de Marketing Cloud o llamada directa al endpoint POST survey-invitation-emails.

No autenticado

Cuando la encuesta se sirve en abierto

Landing pública, encuesta post-checkout embebida en el sitio corporativo, QR en tienda física, campaña de captura de leads con incentivo, encuesta de investigación de mercado. El participante llega sin login, y aún así la respuesta debe quedar asociada a un Contact o Lead (nunca a un User, restricción explícita).

Parte 7 · Integración

¿Llamar Salesforce directo desde el browser, o pasar por un BFF?

La pregunta se responde sola en cuanto se listan los requisitos reales. Cualquier llamada al camino autenticado exige un access token OAuth — ese token no puede vivir en el bundle de JavaScript sin que un atacante lo cosecha en la primera visita. En el camino unAuth, el token es 'per-org' — filtrarlo compromete todas las encuestas públicas de la org. Y encima hay dos hosts distintos (salesforce.com y salesforce-scrt.com) sin evidencia oficial de whitelist CORS para un third-party origin. La conclusión es directa: pase por un BFF.

Sin BFF (browser → SF)

Antipatrón

OAuth token expuesto en el cliente. Access token unAuth expuesto en el cliente. CORS resuelto ad-hoc con extensiones o proxies frágiles. Log de errores del cliente en consola del usuario final. Rotación de credenciales imposible sin redeploy del bundle. Auditoría distribuida entre origins que no controlas.

Con BFF (browser → BFF → SF)

Camino recomendado

El BFF guarda el client_id / client_secret / refresh_token en variables de entorno del servidor. Emite tokens de sesión cortos hacia el navegador con solo lo mínimo necesario. Concentra retries, rate limiting, logging estructurado y feature flags. Aísla al cliente del host de Salesforce y evita CORS de raíz.

Superficie mínima del BFF

Endpoints públicos del BFF hacia el frontend
POST   /api/surveys/{surveyDeveloperName}/start
       ├─ crea la invitación (autenticada o unAuth según el flujo)
       ├─ traduce a Salesforce y guarda { invitationId, invitationUuid?, flowInterviewState }
       └─ devuelve al cliente { pageId, questions[], canGoBack, canGoNext }

PATCH  /api/surveys/{surveyDeveloperName}/next
       ├─ recibe answers[] del cliente
       ├─ arma el surveyPageResponses.questionResponses[] correcto
       ├─ PATCH a Salesforce con navigationAction: "Next"
       └─ devuelve { pageId, questions[], canGoBack, canGoNext }  · O  { finished: true, thankYou }

PATCH  /api/surveys/{surveyDeveloperName}/back
       └─ igual que /next pero con navigationAction: "Back"

GET    /api/surveys/{surveyDeveloperName}/state
       └─ (opcional) devuelve la página actual sin navegar — útil tras refresh del browser

Qué NO debe hacer el BFF

  • No debe reinterpretar branching ni display logic. Su rol es fielmente pasar respuestas y devolver páginas.
  • No debe cachear el schema de la encuesta indefinidamente. La SurveyVersion activa puede cambiar; una versión cacheada rompe la respuesta en vuelo si el usuario tarda entre pageviews.
  • No debe traducir preguntas. Salesforce ya lo hace por languageCode — el BFF solo forwardea.
  • No debe agregar validación de negocio propia (ej: aceptar o rechazar una respuesta según reglas propias). Si necesita eso, es porque la encuesta está mal diseñada — corríjala en Survey Builder.
  • No debe persistir respuestas fuera de Salesforce. La verdad vive en SurveyResponse. Si necesita observabilidad, envíe eventos de journey (start / page-completed / abandon) a su plataforma de analítica, no las respuestas mismas.

Parte 8 · Decisión clave

Bajar los SObjects a mano vs. usar la API oficial — comparación honesta

Casi cualquier equipo que ataca este problema por primera vez propone la Opción A (bajar SurveyQuestion / SurveyQuestionChoice / SurveyPage con SOQL o Objects REST y armar el motor en el frontend). Es una tentación entendible porque el modelo de datos es transparente y las queries salen rápidas en el Developer Console. La Opción B (usar la Business API — Survey Response Connect API y su gemela unAuth) parece 'menos flexible' porque el frontend recibe una página a la vez. Esta comparación es lo que necesita ver alguien antes de decidir cuál camino toma.

DimensiónOpción A · Reconstruir con SObjectsOpción B · Business API oficial
BranchingDebe reimplementar toda la lógica de ramificación en TypeScript, replicando lo que hace Survey Builder. Cualquier cambio en el builder requiere sincronizar código.Salesforce evalúa server-side. El frontend nunca ve la regla — recibe la página correcta.
Display logicDebe interpretar reglas condicionales de visibilidad. La API de Objects no expone estas reglas de forma limpia.Muy probablemente resuelto server-side (verificar en POC). Su código no lo toca.
VersionamientoCada publish de una nueva SurveyVersion puede romper el mapping. Debe manejar la migración de respuestas parciales.Cada invitación queda anclada a una SurveyVersion. Salesforce garantiza la coherencia de la sesión.
Merge fieldsNo los ejecuta — necesita re-implementar el motor de merge y traer los records referenciados desde Salesforce.Salesforce entrega el label de la pregunta ya renderizado con merge fields resueltos.
PersistenciaDebe crear SurveyResponse y SurveyQuestionResponse a mano via DML o Composite API. Fácil equivocarse en el shape.Salesforce persiste automáticamente. Fila por respuesta, fila por pregunta contestada.
InvitacionesDebe generar SurveyInvitation, calcular UUID, validar expiración y unicidad.Salesforce crea la invitación en el POST inicial (o consume una existente creada por Flow / invocable).
IdiomasDebe traer traducciones desde el objeto TranslationValue asociado y aplicarlas en cliente.El endpoint respeta languageCode y devuelve labels traducidos si la traducción existe en la encuesta.
Partial-save / resumeDebe diseñarlo. No hay 'PartiallyCompleted' semántico si no lo emula.shouldLoadPartiallyCmplSurvey (v63.0+) más SurveyResponse.Status = PartiallyCompleted lo hacen nativo.
Seguridad de participantes públicosEl equipo diseña autenticación y anti-abuso desde cero.unAuth Response API + Sharing Guest User + token TTL 3600s ya está pensado.
Esfuerzo inicialAlto. Fácilmente 6–10 semanas de ingeniería + QA para llegar a paridad funcional con el motor nativo.Bajo. Un sprint alcanza para un POC bien hecho.
MantenimientoAlto. Cada feature nueva en Feedback Management no llega gratis.Bajo. Nuevas capacidades del motor (por ejemplo, merge fields nuevos) llegan sin código adicional.
Flexibilidad de UXMáxima. Puede reordenar preguntas de una página, romper con la estructura de páginas del builder, dividir preguntas entre pantallas.Alta pero acotada al contrato: una página por request, orden respetado por el server. Puede estilizar libremente cada pregunta.
Dependencia de SalesforceAlta pero implícita — cada vez que el builder cambia, el código debe adaptarse.Alta y explícita — Salesforce es el motor y el sistema de registro. Contrato bien definido.

Parte 9 · Persistencia

Dónde terminan las respuestas — y por qué eso importa

Usar la Business API oficial no rompe el modelo de datos estándar. Todo lo que se envíe por PATCH termina en los mismos objetos que popularía una respuesta enviada por el renderer nativo de Salesforce. Esa es una de las razones por las que la Opción B es tan valiosa: no se está construyendo un silo — se está alimentando el sistema de registro.

Cadena de objetos estándar
Survey                        · plantilla del cuestionario
  │
  └── SurveyVersion           · versión publicada (cada publish crea una nueva)
        │
        ├── SurveyPage        · páginas de esa versión
        │     │
        │     └── SurveyQuestion       · preguntas de esa página
        │           │
        │           └── SurveyQuestionChoice
        │
        └── SurveyInvitation  · invitación a un Contact / Lead / User específico
              │
              └── SurveyResponse       · una fila por respuesta
                    │
                    └── SurveyQuestionResponse  · una fila por pregunta contestada

Consecuencias de que la persistencia sea la estándar

  • Los dashboards de Customer Lifecycle Analytics siguen funcionando sin cambios — se alimentan de los mismos objetos.
  • Data Mapper puede seguir disparando acciones al recibir SurveyQuestionResponse (por ejemplo: 'si NPS < 6, abre un Case y asigna al gerente de cuenta').
  • Las Intelligent Survey Reminders siguen operando sobre SurveyInvitation.
  • Reportes y list views existentes no requieren migración.
  • La integración a Data Cloud vía Data Mapper y las políticas de retention estándar funcionan igual.
  • Cualquier automatización (Flow, Trigger, Process, invocable) que ya escuche cambios en SurveyResponse sigue viva.

Parte 10 · Arquitectura final

Vista lógica de una solución headless de extremo a extremo

Esta es la arquitectura de referencia. Muestra roles y fronteras de responsabilidad — no productos puntuales. Cada capa es explícita sobre qué le toca hacer y qué no debe intentar hacer.

Arquitectura headless · Salesforce Feedback Management + Frontend externo
┌────────────────────────────────────────────────────────────────────────────────┐
│                                 SALESFORCE                                     │
│                        (Motor de encuesta · Sistema de registro)               │
│                                                                                │
│                            Survey Builder / Feedback Management                │
│                                        │                                       │
│         Survey → SurveyVersion → SurveyPage → SurveyQuestion → Choice          │
│                                        │                                       │
│                     Page branching  ·  Question display logic                  │
│                     Required flags  ·  Merge fields  ·  Traducciones           │
│                                        │                                       │
│                             SurveyInvitation                                   │
│                                        │                                       │
│                          Business API (Connect REST)                           │
│                     /connect/surveys/…/survey-response                         │
│                     salesforce-scrt.com/surveys/v1/…                           │
│                                        │                                       │
│                     SurveyResponse  ·  SurveyQuestionResponse                  │
│                                        │                                       │
│                     Data Mapper → Case, Task, Flow, Data Cloud                 │
└────────────────────────────────────┬───────────────────────────────────────────┘
                                     │  POST /survey-response  (start)
                                     │  PATCH /survey-response (Next / Back)
                                     ▼
┌────────────────────────────────────────────────────────────────────────────────┐
│                                 BACKEND / BFF                                  │
│                    (Broker seguro · Sesión · Observabilidad)                   │
│                                                                                │
│      OAuth 2.0 client-credentials / JWT bearer  ·  Token unAuth per-org        │
│      Sesión server-side (httpOnly cookie + Redis) con:                         │
│                    invitationId · invitationUuid · flowInterviewState          │
│                                                                                │
│      Endpoints públicos hacia el cliente:                                      │
│        POST   /api/surveys/{name}/start                                        │
│        PATCH  /api/surveys/{name}/next                                         │
│        PATCH  /api/surveys/{name}/back                                         │
│        GET    /api/surveys/{name}/state                                        │
│                                                                                │
│      Responsabilidades: normalización · retries · rate limit · logging         │
│      Prohibido: reinterpretar branching · cachear schema · validar negocio     │
└────────────────────────────────────┬───────────────────────────────────────────┘
                                     │  Payload normalizado (page + questions)
                                     ▼
┌────────────────────────────────────────────────────────────────────────────────┐
│                              CUSTOM FRONTEND                                   │
│                        (Look & feel · UX · Canal)                              │
│                                                                                │
│        SurveyContainer                                                         │
│          ├── SurveyPage                                                        │
│          │     └── QuestionRenderer                                            │
│          │           ├── SingleChoice   MultiChoice   BooleanYesNo             │
│          │           ├── Rating         NPS                                    │
│          │           └── TextInput      TextArea      (Unsupported fallback)   │
│          └── ThankYouPage                                                      │
│                                                                                │
│      Responsabilidades: rendering · validación required en UI · a11y · i18n    │
│      Prohibido: mantener el mapa de branching · mutar SurveyQuestion           │
└────────────────────────────────────────────────────────────────────────────────┘

Parte 11 · Riesgos y limitaciones

Lo que la documentación oficial soporta — y lo que no

Ningún approach técnico es completo sin sus zonas grises. Esta tabla clasifica cada aspecto contra la documentación oficial vigente y los hallazgos empíricos del POC de campo. Los estados usan cuatro niveles: soportado (afirmación oficial verificada o probada), parcialmente soportado (docs contienen matices que hay que respetar), requiere validación (evidencia circunstancial pero sin confirmación explícita), no soportado (contradice la documentación o excede el contrato).

AspectoEstadoComentario
Tipos: RadioButton, MultiChoice, Boolean, Rating, NPS, ShortText, FreeTextSoportadoDocumentados con ejemplos de request y response en la Feedback Management Developer Guide.
Tipos: Date, DateTime, Ranking, Slider, Scoring, PicklistRequiere validaciónExisten en el objeto SurveyQuestionScore, pero no hay ejemplos de Business API para estos tipos. Validar en POC si aparecen en surveyQuestions[] o si el server los omite.
Matrix questionsRequiere validaciónDisponibles en Starter y Growth según feature table, pero sin schema Connect REST publicado.
Attachment questionsRequiere validaciónDisponibles en Starter y Growth, pero el upload de archivos vía Business API no está documentado en los mismos ejemplos.
Page branching logicParcialmente soportadoServer evalúa server-side EN Standard Survey. Pero: Standard es rechazado por la unAuth API — el path unAuth solo acepta Basic, y Basic explícitamente NO soporta page branching (verbatim en Object Reference). Vía: usar path autenticado con Standard, o esperar activación de Conversational Survey.
Question display logicParcialmente soportadoIgual que branching: Standard lo soporta, Basic no. La Object Reference lista display logic entre lo que Basic excluye explícitamente. En path autenticado con Standard debería funcionar server-side (schema del cliente no expone reglas — inferencia fuerte).
SurveyType compatible con unAuth APIParcialmente soportadounAuth Response API responde 'Specify a basic or conversational survey' — solo Basic y Conversational aceptados. Standard rechazado. Conversational no está en docs públicas ni en el picklist SurveyType estándar (parece pilot/EA). El path autenticado sí acepta Standard.
surveyPageResponses.name en PATCHSoportado — pero doc engañosoEl campo aparece en docs como 'Reserved for future use' pero en la práctica es REQUERIDO en cada PATCH. Debe llevar el nombre de la página actual (el 'name' que devolvió el surveyPage previo). Confirmado empíricamente en el POC.
questionResponses[] count enforcementSoportadoEl array debe tener una entrada por cada pregunta de la página, incluso las no contestadas. Para no contestadas: 'responses: []' en selection types o omitir 'responseValue' en NPS/Text. Salesforce rechaza con 'Specify the same number of questions in the survey and the input representation'.
Sesión completa en cookie httpOnlyNo soportadoEl flowInterviewState pesa ~3.8 KB y la sesión JSON serializada (+ base64 + firma HMAC + gzip) queda ~4.8 KB — por encima del límite ~4096 bytes del browser, que rechaza la cookie silenciosamente. Diseño obligado: session store server-side, cookie solo lleva un ID corto.
Labels/choices HTML-encodedSoportadoSalesforce Survey Builder devuelve labels con entities HTML ('Tecnolog&iacute;a' vs 'Tecnología'). El BFF debe decodificarlos server-side antes de mandar al cliente. No es dificultad técnica pero sí sorpresa.
Required questionsSoportadoCampo isResponseRequired disponible en cada Survey Question Output.
Back navigationSoportadonavigationAction: 'Back' documentado; el server devuelve la página previa con la misma sesión.
Save & resumeSoportado (v63.0+)shouldLoadPartiallyCmplSurvey: true en POST + SurveyResponse.Status = PartiallyCompleted. Requiere Starter o Growth.
Merge fields en labels y branchingSoportadoSe resuelven server-side. En unAuth, los records referenciados deben estar compartidos al Guest User.
SurveyVersion switching en vueloNo soportadoLa respuesta queda anclada a la versión activa al momento del POST inicial. Cambios de publish en curso no afectan respuestas activas.
Autenticado OAuthSoportadoBearer estándar contra el My Domain de Salesforce. Client credentials, JWT bearer o auth code según su patrón de integración.
Unauth participantsSoportado (Growth)Endpoint separado en salesforce-scrt.com/surveys/v1 con access token per-org.
CORS directo desde browserRequiere validaciónNo documentación de CORS whitelist para orígenes externos hacia salesforce-scrt.com. Un BFF elimina el problema.
Content-Type distinto a JSONNo soportadoDoc oficial verbatim: 'only accepts Content-Type: application/json'.
Licencia 'Survey Response Pack' baseNo soportadoLa feature table confirma que 'basic survey via Survey Response Connect API' es No para Response Pack — necesita Starter o Growth.
Rate limits nivel orgSoportado5,400 POST/min y 2,700 PATCH/min autenticado; 3,600 / 1,800 unAuth. Diseñe backoff en el BFF.
Persistencia en SurveyResponse / SurveyQuestionResponseSoportadoDoc verbatim: 'The survey responses are stored in the SurveyResponse object and the responses to individual questions in the SurveyQuestionResponse object.'
SurveyPageResponse como objeto persistidoNo soportadoNo existe como objeto estándar. Es solo forma de payload en la API.
Envío masivo por email desde la APISoportadoPOST /connect/surveys/{surveyId}/survey-invitation-emails a hasta 300 recipients.
Recipient tipo User en el flujo unAuthNo soportadounAuth Response API acepta únicamente Contact o Lead como recipientId.

Parte 12 · Recomendación · POC comprobado

Qué implementaría yo — y el POC que efectivamente se comprobó

Sí — Salesforce Feedback Management puede operar como un motor de encuesta headless. La respuesta es Opción B: use la Business API oficial (Connect REST autenticada o unAuth Response API según el tipo de participante), interpónga un BFF, deje al frontend hacer solo rendering y validación de UI. Ese patrón produce una experiencia visual completamente propia mientras Salesforce sigue siendo dueño de la definición y la persistencia. Es sostenible, versionable y no requiere reimplementar el motor. Con un matiz que solo salió en el POC: por el path unAuth solo funciona Basic Survey — que no soporta page branching ni question display logic. Si necesita esos dos features, va por el path autenticado con Standard.

Dos POCs — uno comprobado, uno de referencia

POC A · unAuth · Comprobado 2026-08

Basic Survey lineal · web sin login

5 páginas · 10 preguntas · 7 tipos · flujo lineal · sin branching · sin display logic. Persiste en SurveyResponse + SurveyQuestionResponse. Recorre POST /accessToken → POST /survey-response → PATCH loop → Thank You Page. Es el que sirve para web pública anónima. Implementado y publicado como receta en el portfolio.

POC B · Autenticado · Referencia

Standard Survey con branching + display logic

Diseño ideal de 4-6 páginas con dos rutas por Q1 y display logic Q → Q. Salesforce evalúa branching server-side. Requiere path autenticado (usuario logueado, invitación asociada a Contact/Lead/User) — el unAuth lo rechaza. Diseño de referencia para casos con identidad conocida (portal de clientes, empleados, agente Agentforce in-org).

POC A · La encuesta que sí probamos

Descubrimiento Agentforce · Basic Survey · v1 publicada
┌─────────────────────────────────────────────────────────────────────┐
│  Página 1 · Perfil                                                  │
│    Q1  Industria           · Single selection  · Requerida          │
│    Q2  Rol                 · Single selection  · Requerida          │
└─────────────────────────────────────────────────────────────────────┘
                                    │  Next
                                    ▼
┌─────────────────────────────────────────────────────────────────────┐
│  Página 2 · Estado actual                                           │
│    Q3  ¿Ya usan Agentforce?  · Sí/No           · Requerida          │
│    Q4  Canales desplegados   · Multi-select    · Opcional           │
│    Q5  Satisfacción actual   · Rating 1-5      · Opcional           │
└─────────────────────────────────────────────────────────────────────┘
                                    │  Next
                                    ▼
┌─────────────────────────────────────────────────────────────────────┐
│  Página 3 · Priorización                                            │
│    Q6  Probabilidad IA este año · NPS 0-10     · Requerida          │
│    Q7  ¿Qué te frena?           · Single sel.  · Requerida          │
│    Q8  Cuéntanos más            · Long text    · Opcional           │
└─────────────────────────────────────────────────────────────────────┘
                                    │  Next
                                    ▼
┌─────────────────────────────────────────────────────────────────────┐
│  Página 4 · Contacto                                                │
│    Q9  ¿Sesión con arquitecto?  · Sí/No        · Requerida          │
│    Q10 Email                    · Short text   · Opcional           │
└─────────────────────────────────────────────────────────────────────┘
                                    │  Next
                                    ▼
┌─────────────────────────────────────────────────────────────────────┐
│  Página 5 · Thank You                                               │
│    thankYouMessage: "Gracias por tu tiempo."                        │
│    redirectUrl:  /es/insights/headless-feedback-management-…        │
│    SurveyResponse.Status → Completed                                │
└─────────────────────────────────────────────────────────────────────┘

Objetivos técnicos — qué quedó demostrado

  • ✅ Iniciar una respuesta anónima vía POST /surveys/v1/survey-response con invitationSettings.collectAnonymousResponse: true — Salesforce devuelve Survey Description Output con la Página 1 renderable.
  • ✅ Renderizar los 7 tipos de pregunta (RadioButton, MultiChoice, Boolean, Rating, NPS, ShortText, FreeText) con un solo switch(questionType) — cero condicionales por página.
  • ✅ Validar isResponseRequired en la UI antes de habilitar 'Next' — el flag viene por pregunta desde la API.
  • ✅ Navegar página a página vía PATCH /survey-response con navigationAction: 'Next' y 'Back' — se preservan las respuestas al regresar.
  • ✅ Detectar finalización por polimorfía del surveyPage devuelto (thankYouMessage vs surveyQuestions[]) — no hay un booleano isFinished.
  • ✅ Persistir en objetos estándar: SurveyResponse.Status = Completed y una SurveyQuestionResponse por pregunta contestada — dashboards y Data Mapper heredados siguen funcionando.
  • ❌ Comprobar branching server-side — NO se pudo con el path unAuth: SurveyType 'Survey' (Standard) rechazado con INVALID_INPUT_COMBINATION. Basic no lo soporta. Queda como POC B pendiente por autenticado o Conversational.
  • ❌ Comprobar display logic server-side — misma razón. Basic no expone reglas condicionales. Pendiente para POC B.

Stack efectivamente implementado

Frontend

Next.js 16 (App Router) + TypeScript

Componentes de encuesta en components/survey/ (SurveyRunner + QuestionInputs). Un dispatcher por questionType. Client component con estado de máquina idle → running → error → thankyou. Sin dependencias adicionales.

BFF

Route Handlers de Next.js + Session Store

POST /api/surveys/[name]/start y PATCH /api/surveys/[name]/navigate. Cliente unAuth con cache in-memory del accessToken. Session store en Map anclado a globalThis (sobrevive HMR) — la cookie solo lleva un ID de 64 bytes. Decoder de HTML entities server-side.

Salesforce

Org Enterprise + Feedback Management Growth

Encuesta como Basic Survey (5 páginas lineales). Setup → Survey Settings → Unauthenticated Survey Participation habilitado (con propagación de ~30s). Sharing manual al Guest User Profile del Experience Cloud site elegido.

Trampas empíricas — hallazgos no documentados por Salesforce

  • SurveyType 'Survey' (Standard) es rechazado por la unAuth API — solo acepta Basic o 'Conversational' (feature no publicada). Docs no lo dicen; el error INVALID_INPUT_COMBINATION lo revela.
  • SurveyType no se puede modificar por DML después de crear la encuesta — hay que borrar y recrear si se eligió el tipo equivocado.
  • surveyPageResponses.name es REQUERIDO en cada PATCH aunque el brief lo describe como 'reserved for future use'. Sin él: 400 'Invalid request content'.
  • questionResponses[] debe incluir una entrada por cada pregunta de la página, incluso las no contestadas. Sin eso: 'Specify the same number of questions in the survey and the input representation'.
  • El flowInterviewState comprimido pesa ~4.8 KB — se pasa del límite ~4096 bytes de cookie del browser. Diseño obligado: session store server-side con solo un ID corto en la cookie.
  • Labels llegan HTML-encoded ('Tecnolog&iacute;a'). Debe decodificarse en el BFF antes de mandar al cliente.
  • El toggle 'Unauthenticated Survey Participation' tiene ~30 segundos de propagación después de guardarse. Reintentos inmediatos siguen fallando con el mismo error de 'API isn't enabled'.
  • El DeveloperName de la encuesta lo Salesforce normaliza a minúsculas — hay que guardar el valor case-sensitive real (no el que se escribió en el UI).

Criterios de éxito — qué se cumplió

  • ✅ Cero líneas de código de flujo condicional en el frontend — auditable buscando 'if(answer' o 'goToPage'. El único condicional es 'page.kind === thankyou'.
  • ✅ Un solo switch(questionType) en QuestionRenderer resuelve los 7 tipos de pregunta.
  • ✅ Persistencia verificada en SurveyResponse (Status=Completed) y SurveyQuestionResponse — filas anclada a la SurveyVersion activa.
  • ✅ El mismo SurveyRunner sirve para cualquier encuesta Basic — cambia el env SF_LAILA_SURVEY_DEV_NAME, cambia todo. Zero redeploy.
  • ❌ Cobertura del flujo autenticado — queda pendiente (POC B).

Referencias

Fuentes oficiales

Foi útil?

Compartilhe com seu time ou vamos conversar em uma chamada de arquitetura.

Vamos conversar