DocumentaçãoDocumentationDocumentaciónOne Conecta
ÍndiceIndexÍndice
Baixar .mdDownload .mdBajar .md
Você está vendo esta documentação online. No topo você também pode baixar o PDF (mesmo conteúdo desta página, no idioma e modo atuais) e o Markdown (Funcional ou Técnica).You are viewing this documentation online. At the top you can also download the PDF (same content as this page, in the current language and mode) and the Markdown (Functional or Technical).Está viendo esta documentación en línea. Arriba también puede bajar el PDF (mismo contenido de esta página, en el idioma y modo actuales) y el Markdown (Funcional o Técnica).
Feature · PesquisasFeature · SurveysFeature · Encuestas

PesquisasSurveysEncuestas

As pesquisas que o representante de vendas responde dentro de uma visita: a lista do que se aplica àquele varejo e o fluxo de resposta — uma pergunta por página, com perguntas dependentes, evidências em foto ou documento e envio pelo Dispatcher. Leitura vem do cache; o envio é escrita. The surveys a sales rep answers inside a visit: the list of what applies to that retail and the answering flow — one question per page, with dependent questions, photo or document evidence and submission through the Dispatcher. Reading comes from cache; submitting is a write. Las encuestas que el representante de ventas responde dentro de una visita: la lista de lo que se aplica a ese punto de venta y el flujo de respuesta — una pregunta por página, con preguntas dependientes, evidencias en foto o documento y envío por el Dispatcher. La lectura viene del caché; el envío es escritura.

PúblicoAudiencePúblico
Representante · QA · Suporte · DevRep · QA · Support · DevRepresentante · QA · Soporte · Dev
Onde ficaWhereDónde
Detalhe da visita → Ferramentas → PesquisasVisit detail → Tools → SurveysDetalle de la visita → Herramientas → Encuestas
RelacionadoRelatedRelacionado
Visit Detail · SurveyResultUploadAPI
AtualizadoUpdatedActualizado
29/07/20262026-07-29
Disponível emAvailable inDisponible en BR CL ZA
01

O que é e para que serveWhat it is and what it's forQué es y para qué sirve

Pesquisas é o questionário que a empresa envia ao representante de vendas para ser respondido dentro de uma visita, sobre aquele varejo: censo de concorrência, checklist de encerramento, auditoria de preço, satisfação do ponto de venda. A feature tem duas telas — a lista do que se aplica àquele varejo e o fluxo de resposta — e este documento cobre as duas. Surveys is the questionnaire the company sends to the sales rep to be answered inside a visit, about that retail: competitor census, closing checklist, price audit, point-of-sale satisfaction. The feature has two screens — the list of what applies to that retail and the answering flow — and this document covers both. Encuestas es el cuestionario que la empresa envía al representante de ventas para responder dentro de una visita, sobre ese punto de venta: censo de competencia, checklist de cierre, auditoría de precio, satisfacción del punto de venta. La feature tiene dos pantallas — la lista de lo que se aplica a ese punto de venta y el flujo de respuesta — y este documento cubre las dos.

Quais pesquisas se aplicam?Which surveys apply?¿Qué encuestas se aplican?

Só as que o backend vinculou àquele varejo. As obrigatórias vêm primeiro; as de resposta única desaparecem depois de respondidas.Only the ones the backend linked to that retail. Mandatory ones come first; one-time surveys disappear once answered.Solo las que el backend vinculó a ese punto de venta. Las obligatorias vienen primero; las de respuesta única desaparecen tras responderlas.

Como se responde?How do you answer?¿Cómo se responde?

Uma pergunta por página. Escolher uma opção pode revelar perguntas dependentes, e algumas perguntas exigem foto ou documento.One question per page. Picking an option can reveal dependent questions, and some questions require a photo or document.Una pregunta por página. Elegir una opción puede revelar preguntas dependientes, y algunas preguntas exigen foto o documento.

O que acontece ao finalizar?What happens on finish?¿Qué pasa al finalizar?

O app confirma, envia o resultado ao backend e avisa em tela. As evidências vão junto, em blocos de até três arquivos.The app confirms, sends the result to the backend and shows an on-screen notice. Evidence goes along, in blocks of up to three files.La app confirma, envía el resultado al backend y avisa en pantalla. Las evidencias van junto, en bloques de hasta tres archivos.

Sempre dentro de uma visitaAlways inside a visitSiempre dentro de una visita Não existe pesquisa "solta": a tela é sempre aberta a partir de uma visita e o varejo vem dela. O acesso pela grade de ferramentas exige que a visita esteja iniciada; sem isso o app oferece iniciar antes de abrir a ferramenta. There is no "standalone" survey: the screen is always opened from a visit and the retail comes from it. Access through the tools grid requires the visit to be started; otherwise the app offers to start it before opening the tool. No existe encuesta "suelta": la pantalla siempre se abre desde una visita y el punto de venta viene de ella. El acceso por la grilla de herramientas exige que la visita esté iniciada; si no, la app ofrece iniciarla antes de abrir la herramienta.

02

Como acessarHow to openCómo acceder

  1. Pela grade de ferramentas da visitaFrom the visit tools gridDesde la grilla de herramientas de la visitaÉ o caminho principal: abra o Detalhe da visita e toque em Pesquisas. No Brasil existe também um segundo atalho, Ações de concorrência, que abre a mesma tela filtrada em outra categoria.This is the main path: open the Visit detail and tap Surveys. In Brazil there is also a second shortcut, Competitor actions, which opens the same screen filtered on another category.Es el camino principal: abra el Detalle de la visita y toque Encuestas. En Brasil existe también un segundo atajo, Acciones de competencia, que abre la misma pantalla filtrada en otra categoría.
  2. Pelas pendências de encerramentoFrom the visit-end pending itemsDesde las pendientes de cierreNo Brasil e na África do Sul, a tela de encerramento de visita lista Pesquisas obrigatórias e Pesquisas opcionais como pendências; o botão de ação de cada uma abre esta lista. As obrigatórias são bloqueantes — a visita não encerra com elas pendentes.In Brazil and South Africa, the visit-end screen lists Mandatory surveys and Optional surveys as pending items; each one's action button opens this list. The mandatory ones are blocking — the visit doesn't close while they are pending.En Brasil y Sudáfrica, la pantalla de cierre de visita lista Encuestas obligatorias y Encuestas opcionales como pendientes; el botón de acción de cada una abre esta lista. Las obligatorias son bloqueantes — la visita no cierra con ellas pendientes.
  3. Da lista para o fluxo de respostaFrom the list into the answering flowDe la lista al flujo de respuestaTocar num card abre o fluxo de resposta daquela pesquisa. É o único caminho para o fluxo — ele não é acessível por menu, aba inferior ou notificação.Tapping a card opens that survey's answering flow. It is the only way in — the flow isn't reachable from a menu, bottom tab or notification.Tocar una tarjeta abre el flujo de respuesta de esa encuesta. Es el único camino — el flujo no es accesible por menú, pestaña inferior o notificación.

Nada de aba nem de menuNo tab, no menuNi pestaña ni menú Pesquisas não tem entrada na barra inferior nem no menu lateral, e não aparece na Home. Quem chega aqui vem sempre de uma visita. Surveys has no bottom-bar entry and no side-menu entry, and it doesn't appear on Home. Whoever gets here always comes from a visit. Encuestas no tiene entrada en la barra inferior ni en el menú lateral, y no aparece en el Home. Quien llega aquí siempre viene de una visita.

03

Estrutura da telaScreen structureEstructura de la pantalla

Lista de pesquisasSurvey listLista de encuestas

Última sincronizaçãoLast syncÚltima sincronización
Faixa no topo com a data e hora em que as pesquisas foram baixadas.A strip at the top with the date and time the surveys were downloaded.Franja arriba con la fecha y hora en que se bajaron las encuestas.
Cartão do varejoRetail cardTarjeta del punto de venta
Nome e código SAP do varejo da visita — a lista é sempre daquele varejo.The visit retail's name and SAP code — the list is always for that retail.Nombre y código SAP del punto de venta de la visita — la lista es siempre de ese punto de venta.
Cabeçalho da categoriaCategory headerEncabezado de la categoría
Título que diz qual categoria está sendo mostrada: pesquisas em geral ou ações de concorrência.A title stating which category is being shown: general surveys or competitor actions.Título que dice qué categoría se muestra: encuestas en general o acciones de competencia.
Cards de pesquisaSurvey cardsTarjetas de encuesta
Um por pesquisa, com o nome e uma seta. Tocar abre o fluxo de resposta.One per survey, with the name and a chevron. Tapping opens the answering flow.Una por encuesta, con el nombre y una flecha. Tocar abre el flujo de respuesta.
Lista vaziaEmpty listLista vacía
Quando não há pesquisa aplicável, aparece o card de estado vazio com o ícone da feature.When no survey applies, the empty-state card with the feature icon is shown.Cuando no hay encuesta aplicable, aparece la tarjeta de estado vacío con el ícono de la feature.
Puxar para atualizarPull to refreshDeslizar para actualizar
Puxar a lista para baixo rebusca as pesquisas no servidor.Pulling the list down re-fetches the surveys from the server.Deslizar la lista hacia abajo vuelve a buscar las encuestas en el servidor.

A lista não tem busca, ordenação, filtros nem abas — o recorte é fixo: o varejo da visita e a categoria pela qual se entrou.The list has no search, sort, filters or tabs — the slice is fixed: the visit's retail and the category you came in through.La lista no tiene búsqueda, orden, filtros ni pestañas — el recorte es fijo: el punto de venta de la visita y la categoría por la que se entró.

Fluxo de respostaAnswering flowFlujo de respuesta

Contador de perguntasQuestion counterContador de preguntas
Texto do tipo "pergunta X de Y" no topo do card — é a única indicação de progresso (não há barra).A "question X of Y" text at the top of the card — it's the only progress indication (there is no bar).Texto tipo "pregunta X de Y" arriba de la tarjeta — es la única indicación de progreso (no hay barra).
Nome da pesquisaSurvey nameNombre de la encuesta
Repetido em todas as páginas, junto com o cabeçalho da categoria e a última sincronização.Repeated on every page, alongside the category header and the last-sync strip.Repetido en todas las páginas, junto con el encabezado de la categoría y la última sincronización.
Card da perguntaQuestion cardTarjeta de la pregunta
Enunciado, aviso de obrigatória quando for o caso, o campo de resposta e — se a pergunta pedir — o bloco de evidências.The wording, a required notice when applicable, the answer field and — if the question asks for it — the evidence block.El enunciado, aviso de obligatoria cuando corresponda, el campo de respuesta y — si la pregunta lo pide — el bloque de evidencias.
Perguntas dependentesDependent questionsPreguntas dependientes
Aparecem embaixo, como cards adicionais, quando a opção escolhida tem desdobramento. Elas mesmas podem revelar novas perguntas, sem limite de profundidade.They appear below, as extra cards, when the chosen option has a follow-up. They can themselves reveal further questions, with no depth limit.Aparecen abajo, como tarjetas adicionales, cuando la opción elegida tiene desdoblamiento. Ellas mismas pueden revelar nuevas preguntas, sin límite de profundidad.
Rodapé de navegaçãoNavigation footerPie de navegación
Anterior (escondido na primeira página) e Próxima, que na última pergunta passa a ser Finalizar. O rodapé desaparece enquanto o teclado está aberto.Previous (hidden on the first page) and Next, which becomes Finish on the last question. The footer disappears while the keyboard is open.Anterior (oculto en la primera página) y Siguiente, que en la última pregunta pasa a ser Finalizar. El pie desaparece mientras el teclado está abierto.

A troca de página só acontece pelos botões — não é possível arrastar de uma pergunta para outra.Page changes happen through the buttons only — you can't swipe from one question to another.El cambio de página ocurre solo por los botones — no se puede arrastrar de una pregunta a otra.

04

Tipos de pergunta e estadosQuestion types and statesTipos de pregunta y estados

São cinco tipos de pergunta, e o tipo define o campo de resposta que aparece no card:There are five question types, and the type defines the answer field shown in the card:Son cinco tipos de pregunta, y el tipo define el campo de respuesta que aparece en la tarjeta:

Opção únicaSingle optionOpción única
Lista de alternativas com letra (A, B, C…); escolher uma substitui a anterior. Passando de 26 alternativas, a letra dá lugar ao número da posição.A list of lettered alternatives (A, B, C…); picking one replaces the previous. Past 26 alternatives, the letter gives way to the position number.Lista de alternativas con letra (A, B, C…); elegir una reemplaza la anterior. Pasando de 26 alternativas, la letra da lugar al número de posición.
Múltipla escolhaMultiple selectSelección múltiple
Caixas de seleção; cada toque marca ou desmarca. Desmarcar tudo equivale a não ter respondido.Checkboxes; each tap checks or unchecks. Unchecking everything is the same as not having answered.Casillas de selección; cada toque marca o desmarca. Desmarcar todo equivale a no haber respondido.
NuméricaNumericNumérica
Só dígitos (sem decimal, sem sinal). Quando a pergunta define faixa, o valor fora dela aparece destacado em vermelho e trava o avanço.Digits only (no decimals, no sign). When the question defines a range, a value outside it is highlighted in red and blocks advancing.Solo dígitos (sin decimales, sin signo). Cuando la pregunta define rango, el valor fuera de él aparece destacado en rojo y traba el avance.
TextoTextTexto
Campo de várias linhas (3 a 6). Texto em branco conta como não respondida.A multi-line field (3 to 6 lines). Blank text counts as unanswered.Campo de varias líneas (3 a 6). Texto en blanco cuenta como no respondida.
DataDateFecha
Abre o calendário do app; a data escolhida fica no campo.Opens the app calendar; the chosen date stays in the field.Abre el calendario de la app; la fecha elegida queda en el campo.

Quando o botão de avançar fica desligadoWhen the advance button is disabledCuando el botón de avanzar queda apagado Quatro condições travam a página, e todas valem ao mesmo tempo: a pergunta é obrigatória e não foi respondida; o número está fora da faixa; a pergunta exige evidência e ainda falta chegar ao mínimo de arquivos; ou uma pergunta dependente obrigatória revelada segue sem resposta. Four conditions lock the page, and they all apply at once: the question is mandatory and unanswered; the number is out of range; the question requires evidence and the minimum number of files hasn't been reached; or a revealed mandatory dependent question is still unanswered. Cuatro condiciones traban la página, y todas valen al mismo tiempo: la pregunta es obligatoria y no fue respondida; el número está fuera del rango; la pregunta exige evidencia y aún falta llegar al mínimo de archivos; o una pregunta dependiente obligatoria revelada sigue sin respuesta.

Estados da pesquisa na listaSurvey states in the listEstados de la encuesta en la lista

ObrigatóriaMandatoryObligatoria
Sobe para o topo da lista (as demais vêm depois, em ordem alfabética) e é ela que alimenta a pendência bloqueante de encerramento de visita.Rises to the top of the list (the rest follow, alphabetically) and it's the one feeding the blocking visit-end pending item.Sube al tope de la lista (las demás siguen, en orden alfabético) y es la que alimenta la pendiente bloqueante de cierre de visita.
Resposta únicaOne-timeRespuesta única
Some da lista daquele varejo depois de contabilizada pelo backend. Enquanto a sincronização não traz essa contagem atualizada, ela continua visível mesmo já respondida.Disappears from that retail's list once the backend has counted it. Until sync brings that updated count, it stays visible even after being answered.Desaparece de la lista de ese punto de venta tras ser contabilizada por el backend. Mientras la sincronización no trae ese conteo actualizado, sigue visible aunque ya respondida.
Não aplicávelNot applicableNo aplicable
Pesquisa que o backend não vinculou àquele varejo simplesmente não aparece — não há estado "indisponível" na tela.A survey the backend didn't link to that retail simply doesn't show up — there is no "unavailable" state on screen.Una encuesta que el backend no vinculó a ese punto de venta simplemente no aparece — no hay estado "no disponible" en la pantalla.
05

Responder e enviarAnswer and submitResponder y enviar

Anexar evidênciaAttach evidenceAdjuntar evidencia

Duas formas: tirar foto (só câmera — a galeria não é oferecida) ou anexar documento (imagem, PDF ou documento). O bloco mostra o contador "atual/máximo", as miniaturas do que já foi anexado — cada uma removível — e, quando falta chegar ao mínimo, a mensagem em vermelho. Os botões ficam desligados ao atingir o máximo ou enquanto uma captura está em andamento.Two ways: take a photo (camera only — the gallery isn't offered) or attach a document (image, PDF or document). The block shows the "current/maximum" counter, thumbnails of what's attached — each removable — and, while the minimum isn't reached, the message in red. The buttons are disabled once the maximum is reached or while a capture is in progress.Dos formas: tomar foto (solo cámara — la galería no se ofrece) o adjuntar documento (imagen, PDF o documento). El bloque muestra el contador "actual/máximo", las miniaturas de lo adjuntado — cada una removible — y, mientras falte llegar al mínimo, el mensaje en rojo. Los botones quedan apagados al alcanzar el máximo o mientras una captura está en curso.

Sair sem enviarLeave without submittingSalir sin enviar

O botão de voltar recua uma pergunta. Na primeira página, se já houver alguma resposta ou evidência, aparece um modal de confirmação — confirmar sai e descarta tudo o que foi preenchido (nada fica salvo em rascunho). Sem nada preenchido, sai direto.The back button steps one question back. On the first page, if there is already an answer or evidence, a confirmation modal appears — confirming leaves and discards everything filled in (nothing is kept as a draft). With nothing filled in, it leaves straight away.El botón de volver retrocede una pregunta. En la primera página, si ya hay alguna respuesta o evidencia, aparece un modal de confirmación — confirmar sale y descarta todo lo completado (nada queda guardado como borrador). Sin nada completado, sale directo.

Finalizar e enviarFinish and submitFinalizar y enviar

  1. ConfirmaçãoConfirmationConfirmaciónTocar em Finalizar abre um modal; cancelar volta à pergunta sem enviar nada.Tapping Finish opens a modal; cancelling returns to the question without sending anything.Tocar Finalizar abre un modal; cancelar vuelve a la pregunta sin enviar nada.
  2. EnvioSubmissionEnvíoO app monta o resultado — cabeçalho, uma linha por resposta e as evidências em blocos de até três arquivos — e envia ao backend. O botão fica travado durante o envio.The app assembles the result — a header, one row per answer and the evidence in blocks of up to three files — and sends it to the backend. The button is locked during submission.La app arma el resultado — encabezado, una fila por respuesta y las evidencias en bloques de hasta tres archivos — y lo envía al backend. El botón queda trabado durante el envío.
  3. Resultado em telaOn-screen resultResultado en pantallaSucesso mostra o aviso verde e volta para a lista. Falha mostra o aviso vermelho e mantém tudo preenchido, para tentar de novo.Success shows the green notice and returns to the list. Failure shows the red notice and keeps everything filled in, so you can retry.El éxito muestra el aviso verde y vuelve a la lista. El fallo muestra el aviso rojo y mantiene todo completado, para intentar de nuevo.

Dois bloqueios no envioTwo blocks on submitDos bloqueos en el envío Evidência órfã: se algum arquivo foi anexado a uma pergunta que ficou sem resposta, o envio não acontece e aparece um aviso laranja — responda a pergunta ou remova o arquivo. Sem conexão: o envio de pesquisa não entra em fila de reenvio automático; offline ele falha e precisa ser refeito com rede (o registro fica no console de sincronização como erro). Orphan evidence: if a file was attached to a question left unanswered, submission doesn't happen and an orange notice appears — answer the question or remove the file. No connection: survey submission does not enter the automatic retry queue; offline it fails and has to be redone with network (the record stays in the sync console as an error). Evidencia huérfana: si algún archivo fue adjuntado a una pregunta que quedó sin respuesta, el envío no ocurre y aparece un aviso naranja — responda la pregunta o quite el archivo. Sin conexión: el envío de encuesta no entra en cola de reenvío automático; offline falla y hay que rehacerlo con red (el registro queda en la consola de sincronización como error).

06

Arquitetura e fluxo de dadosArchitecture & data flowArquitectura y flujo de datos

Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox, com dois caminhos distintos. A leitura é cache-first: um único RPC (getSurveys) traz o catálogo do mercado inteiro, que é gravado no ObjectBox e lido de lá pelas telas; o remoto só é acionado por pull-to-refresh ou pelo sweep de data freshness. A escrita não usa esse proto — o resultado respondido vai como JSON pelo Dispatcher.Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox, with two distinct paths. Reading is cache-first: a single RPC (getSurveys) brings the whole market catalog, which is written to ObjectBox and read from there by the screens; remote is only triggered by pull-to-refresh or by the data freshness sweep. Writing doesn't use that proto — the answered result goes as JSON through the Dispatcher.Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox, con dos caminos distintos. La lectura es cache-first: un único RPC (getSurveys) trae el catálogo del mercado entero, que se graba en ObjectBox y las pantallas leen de allí; el remoto solo se activa por pull-to-refresh o por el sweep de data freshness. La escritura no usa ese proto — el resultado respondido va como JSON por el Dispatcher.

Leitura — catálogo de pesquisasRead — survey catalogLectura — catálogo de encuestas

  • SurveysReplygRPC proto
    • toSurveysDTOSurveysDTODTO · Freezed
      • toDomainSurveysEntitydomain
        • toModelSurveysModelObjectBox
          • toDomainSurveysEntitydomain · cache
            • GetSurveysForAccountUseCaseSurveysNotifier + State
              • → UISurveysPage
                • getCachedBySfidSurveyAnswerNotifier + State
                  • → UISurveyAnswerPage

Escrita — resultado respondidoWrite — answered resultEscritura — resultado respondido

  • SurveyAnswerStateanswers + evidences
    • submitSurveyResultUploadDispatcherPayloadInputentities cruas
      • buildAllDispatcherEnvelope[]≤ 3 evidências por envelope
        • SubmitSurveyResultUploadUseCaseDispatcherOrchestrator
          • sendTransactionSurveyResultUploadAPIgRPC · Dispatcher
            • ackDispatchTransactionhistórico local

Os dois caminhos não se encontramThe two paths never meetLos dos caminos no se encuentran O envio não grava nada no domínio de pesquisas: não há marcador local de "respondida". Uma pesquisa de resposta única só sai da lista quando a sincronização traz accountData.resultsQuantity atualizado. A transação vive em 15 · SurveyResultUploadAPI. Submission writes nothing into the surveys domain: there is no local "answered" marker. A one-time survey only leaves the list when sync brings an updated accountData.resultsQuantity. The transaction lives in 15 · SurveyResultUploadAPI. El envío no graba nada en el dominio de encuestas: no hay marcador local de "respondida". Una encuesta de respuesta única solo sale de la lista cuando la sincronización trae accountData.resultsQuantity actualizado. La transacción vive en 15 · SurveyResultUploadAPI.

07

Modelo de dadosData modelModelo de datos

A mesma pesquisa existe em quatro representações quase idênticas ao longo das camadas — Proto (wire gRPC) → DTO (Freezed) → Model (ObjectBox) → Entity (domínio) — e cada fronteira é atravessada por um mapper. Os nomes dos campos se mantêm; muda muito pouco (um enum tipado, as relações e um campo que não existe no proto). O fetch é write-through: todo retorno remoto é gravado no ObjectBox e as telas passam a ler do cache.The same survey exists in four near-identical representations across the layers — Proto (gRPC wire) → DTO (Freezed) → Model (ObjectBox) → Entity (domain) — and each boundary is crossed by a mapper. Field names stay the same; very little changes (one typed enum, the relations and one field that doesn't exist in the proto). Fetch is write-through: every remote response is written to ObjectBox and the screens then read from cache.La misma encuesta existe en cuatro representaciones casi idénticas a lo largo de las capas — Proto (wire gRPC) → DTO (Freezed) → Model (ObjectBox) → Entity (dominio) — y cada frontera se cruza con un mapper. Los nombres de los campos se mantienen; cambia muy poco (un enum tipado, las relaciones y un campo que no existe en el proto). El fetch es write-through: toda respuesta remota se graba en ObjectBox y las pantallas leen del caché.

O catálogo chega num container SurveysEntity (lastSyncAt gerado no mapper + surveys[]); cada item é um Survey de 8 campos com três sub-estruturas — perguntas, alternativas e o vínculo com o varejo. A hierarquia é recursiva: uma alternativa pode carregar uma pergunta dependente, que tem suas próprias alternativas, que podem carregar novas dependentes, sem limite declarado. Só um enum é tipado na Entity (o tipo da pergunta); a categoria continua String em todas as camadas e é resolvida por getter. A seguir, na ordem: o proto que transporta tudo, as estruturas de dados campo-a-campo por camada, o valor de resposta que só existe em memória, e os mappers.The catalog arrives in a SurveysEntity container (lastSyncAt generated in the mapper + surveys[]); each item is an 8-field Survey with three sub-structures — questions, answer options and the retail link. The hierarchy is recursive: an option can carry a dependent question, which has its own options, which can carry further dependents, with no declared limit. Only one enum is typed in the Entity (the question type); the category stays a String in every layer and is resolved by a getter. Next, in order: the proto that carries everything, the field-by-field data structures per layer, the answer value that only exists in memory, and the mappers.El catálogo llega en un container SurveysEntity (lastSyncAt generado en el mapper + surveys[]); cada ítem es un Survey de 8 campos con tres sub-estructuras — preguntas, alternativas y el vínculo con el punto de venta. La jerarquía es recursiva: una alternativa puede cargar una pregunta dependiente, que tiene sus propias alternativas, que pueden cargar nuevas dependientes, sin límite declarado. Solo un enum está tipado en la Entity (el tipo de la pregunta); la categoría sigue siendo String en todas las capas y se resuelve por getter. A continuación, en orden: el proto que transporta todo, las estructuras de datos campo a campo por capa, el valor de respuesta que solo existe en memoria, y los mappers.

Proto

SurveysConectaRep.proto · proto3 · package mn.bat.conectarep.streambridge. Um serviço (SurveysConectaRepService), um método unário, e nenhum enum declarado — tipo de pergunta e categoria atravessam o wire como string:One service (SurveysConectaRepService), a single unary method, and no declared enum — question type and category cross the wire as string:Un servicio (SurveysConectaRepService), un método unario, y ningún enum declarado — tipo de pregunta y categoría cruzan el wire como string:

getSurveysunary
MétodoMethodMétodo

rpc getSurveys(SurveysRequest) returns (SurveysReply)

path /mn.bat.conectarep.streambridge.SurveysConectaRepService/getSurveys

Request · SurveysRequest
locationHierarchySfid
string · #1 · hierarquia do representante de vendas (resolvida no repository)sales rep hierarchy (resolved in the repository)jerarquía del representante de ventas (resuelta en el repository)
dateReference
string · #2 · optional — nenhum caller passa hoje (ver Pendências)optional — no caller passes it today (see Pending items)optional — ningún caller lo pasa hoy (ver Pendientes)
lastModifiedDate
string · #3 · optional — nenhum caller passa hoje (ver Pendências)optional — no caller passes it today (see Pending items)optional — ningún caller lo pasa hoy (ver Pendientes)
Reply · SurveysReply

repeated Survey surveyso catálogo do mercado (não é filtrado por varejo no servidor). Os 8 campos de Survey e as sub-estruturas estão detalhados nas Estruturas de dados abaixo.the market catalog (it isn't filtered per retail on the server). Survey's 8 fields and the sub-structures are detailed in Data structures below.el catálogo del mercado (no se filtra por punto de venta en el servidor). Los 8 campos de Survey y las sub-estructuras están detallados en Estructuras de datos abajo.

Estruturas de dadosData structuresEstructuras de datos

Um dropdown por estrutura, aninhados pela hierarquia (as linhas ligam pai e filhos). Cada tabela tem uma coluna por camada — Proto · DTO · Model · Entity; o texto em azul marca onde o tipo primeiro muda (relação ToMany/ToOne no Model, enum e rename na Entity). ¹ = optional no proto.One dropdown per structure, nested by hierarchy (lines link parent and children). Each table has one column per layer — Proto · DTO · Model · Entity; the blue text marks where the type first changes (ToMany/ToOne relation in the Model, enum and rename in the Entity). ¹ = optional in the proto.Un dropdown por estructura, anidados por jerarquía (las líneas unen padre e hijos). Cada tabla tiene una columna por capa — Proto · DTO · Model · Entity; el texto en azul marca dónde primero cambia el tipo (relación ToMany/ToOne en el Model, enum y rename en la Entity). ¹ = optional en el proto.

  • Survey raiz 8 campos
    CampoProtoDTOModelEntity
    sfidstringStringStringString
    namestringStringStringString
    isMandatoryboolboolboolbool
    isOneTimeSurveyboolboolboolbool
    categorystringStringStringString
    hasPromotionbool¹bool?bool?bool?
    questionsrepeated QuestionList<…DTO>ToMany<…Model>List<…Entity>
    accountDatarepeated AccountDataList<…DTO>ToMany<…Model>List<…Entity>

    A Entity adiciona o getter categoryType, que resolve category para SurveyCategory — o campo em si permanece String nas quatro camadas.The Entity adds the categoryType getter, which resolves category into SurveyCategory — the field itself stays a String in all four layers.La Entity agrega el getter categoryType, que resuelve category a SurveyCategory — el campo en sí permanece String en las cuatro capas.

    • Question Survey.questions[] 11 campos
      CampoProtoDTOModelEntity
      sfidstringStringStringString
      titlestringStringStringString
      sequenceint32intintint
      questionTypestringStringStringSurveyQuestionType type
      isMandatoryboolboolboolbool
      isEvidenceRequiredboolboolboolbool
      minNumberEvidencesint32intintint
      maxNumberEvidencesint32intintint
      numberRangeStartint32intintint
      numberRangeEndint32intintint
      answerOptionsrepeated AnswerOptionList<…DTO>ToMany<…Model>List<…Entity>

      A pergunta raiz não tem campo de dependência no proto — o desdobramento pende da alternativa, não da pergunta.The root question has no dependency field in the proto — the follow-up hangs off the option, not off the question.La pregunta raíz no tiene campo de dependencia en el proto — el desdoblamiento pende de la alternativa, no de la pregunta.

      • AnswerOption Question.answerOptions[] · DependentQuestion.answerOptions[] 4 campos
        CampoProtoDTOModelEntity
        sfidstringStringStringString
        namestringStringStringString
        questionAnswerOptionSfidstringStringStringString
        dependentQuestionDependentQuestion¹…DTO?ToOne<…Model>…Entity?

        sfid identifica a alternativa na UI (é o que o app guarda como resposta); questionAnswerOptionSfid é o identificador enviado ao backend no resultado. É a única relação ToOne da feature.sfid identifies the option in the UI (it's what the app stores as the answer); questionAnswerOptionSfid is the identifier sent to the backend in the result. This is the feature's only ToOne relation.sfid identifica la alternativa en la UI (es lo que la app guarda como respuesta); questionAnswerOptionSfid es el identificador enviado al backend en el resultado. Es la única relación ToOne de la feature.

        • DependentQuestion AnswerOption.dependentQuestion · recursivo 5 campos
          CampoProtoDTOModelEntity
          sfidstringStringStringString
          titlestringStringStringString
          questionTypestringStringStringSurveyQuestionType type
          isMandatoryboolboolboolbool
          answerOptionsrepeated AnswerOptionList<…DTO>ToMany<…Model>List<…Entity>

          Fecha o ciclo recursivo (AnswerOptionDependentQuestionAnswerOption). Note que ela tem 5 campos, e não os 11 da pergunta raiz: não há sequence, evidências nem faixa numérica.It closes the recursive cycle (AnswerOptionDependentQuestionAnswerOption). Note it has 5 fields, not the root question's 11: no sequence, no evidence, no numeric range.Cierra el ciclo recursivo (AnswerOptionDependentQuestionAnswerOption). Note que tiene 5 campos, no los 11 de la pregunta raíz: no hay sequence, ni evidencias, ni rango numérico.

    • SurveyAccountData Survey.accountData[] · proto AccountData 2 campos
      CampoProtoDTOModelEntity
      accountSfidstringStringStringString
      resultsQuantityint32intintint

      É a fonte única de elegibilidade: sem linha para o varejo da visita, a pesquisa não aparece; com resultsQuantity > 0 numa pesquisa de resposta única, ela desaparece. A estrutura substituiu as listas surveyResults e surveyMappings do topo do contrato, removidas em 07/07/20262026-07-07. O nome no proto é AccountData; do DTO em diante é SurveyAccountData….It is the single source of eligibility: with no row for the visit's retail, the survey doesn't show; with resultsQuantity > 0 on a one-time survey, it disappears. This structure replaced the top-level surveyResults and surveyMappings lists, removed on 07/07/20262026-07-07. The proto name is AccountData; from the DTO onward it's SurveyAccountData….Es la fuente única de elegibilidad: sin fila para el punto de venta de la visita, la encuesta no aparece; con resultsQuantity > 0 en una encuesta de respuesta única, desaparece. La estructura reemplazó las listas surveyResults y surveyMappings del tope del contrato, removidas el 07/07/20262026-07-07. El nombre en el proto es AccountData; del DTO en adelante es SurveyAccountData….

Valor de resposta (só em memória)Answer value (in memory only)Valor de respuesta (solo en memoria)

A resposta que o representante digita não tem representação em Proto/DTO/Model/Entity — ela vive num sealed class escrito à mão (SurveyAnswerValue, sem Freezed, sem serialização) dentro do State, e é convertida direto em JSON no builder do Dispatcher. Nada é persistido: fechar o fluxo descarta as respostas.The answer the rep types has no Proto/DTO/Model/Entity representation — it lives in a hand-written sealed class (SurveyAnswerValue, no Freezed, no serialization) inside the State, and is converted straight into JSON in the Dispatcher builder. Nothing is persisted: closing the flow discards the answers.La respuesta que el representante escribe no tiene representación en Proto/DTO/Model/Entity — vive en un sealed class escrito a mano (SurveyAnswerValue, sin Freezed, sin serialización) dentro del State, y se convierte directo en JSON en el builder del Dispatcher. Nada se persiste: cerrar el flujo descarta las respuestas.

SubtipoSubtypeSubtipoCampoFieldCampoTipos de pergunta que usamQuestion types using itTipos de pregunta que lo usan
SurveyOptionAnswerList<String> selectedOptionSfidsoption · multipleSelect
SurveyTextAnswerString texttext
SurveyNumericAnswerint valuenumeric
SurveyDateAnswerDateTime datedate

São 4 subtipos para 5 tipos de pergunta — opção única e múltipla escolha compartilham o mesmo subtipo, e o que as diferencia é o método do Notifier (substituir vs. alternar).Four subtypes for five question types — single option and multiple select share the same subtype, and what tells them apart is the Notifier method (replace vs. toggle).Son 4 subtipos para 5 tipos de pregunta — opción única y selección múltiple comparten el mismo subtipo, y lo que las diferencia es el método del Notifier (reemplazar vs. alternar).

Mappers

Seis arquivos de mapper (um por estrutura), todos como extension, com as 5 direções por tipo. Não existe direção de volta (Entity → DTO → Proto): o caminho de leitura é de mão única.Six mapper files (one per structure), all as extensions, with the 5 directions per type. There is no way back (Entity → DTO → Proto): the read path is one-way.Seis archivos de mapper (uno por estructura), todos como extension, con las 5 direcciones por tipo. No existe dirección de vuelta (Entity → DTO → Proto): el camino de lectura es de una sola vía.

DireçãoDirectionDirecciónMétodoMethodMétodo
JSON → DTOstatic fromMap(Map) (caminho mock)(mock path)(camino mock)
Proto → DTOtoSurveysDTO() / toDTO()
DTO → EntitytoDomain() (resolve o enum: SurveyQuestionType.fromProto)(resolves the enum: SurveyQuestionType.fromProto)(resuelve el enum: SurveyQuestionType.fromProto)
Entity → ModeltoModel() (enum → .protoValue; popula ToMany/ToOne)(enum → .protoValue; fills ToMany/ToOne)(enum → .protoValue; llena ToMany/ToOne)
Model → EntitytoDomain()

Os únicos deltasThe only deltasLos únicos deltas

  • questionType (String) vira SurveyQuestionType type na Entity — rename e enum na mesma coluna, nas duas perguntas (raiz e dependente)questionType (String) becomes SurveyQuestionType type in the Entity — rename and enum in the same column, on both questions (root and dependent)questionType (String) pasa a SurveyQuestionType type en la Entity — rename y enum en la misma columna, en las dos preguntas (raíz y dependiente)
  • category continua String nas 4 camadas; o enum só aparece no getter categoryTypecategory stays a String in all 4 layers; the enum only appears in the categoryType gettercategory sigue siendo String en las 4 capas; el enum solo aparece en el getter categoryType
  • relações viram ToMany no Model — e ToOne em AnswerOption.dependentQuestionrelations become ToMany in the Model — and ToOne on AnswerOption.dependentQuestionlas relaciones pasan a ToMany en el Model — y ToOne en AnswerOption.dependentQuestion
  • lastSyncAt não existe no proto: é gerado nos dois mappers de fronteira (proto e JSON) com DateTimeUtils.now() e mora só no containerdoesn't exist in the proto: it's generated in both boundary mappers (proto and JSON) with DateTimeUtils.now() and lives on the container onlyno existe en el proto: se genera en los dos mappers de frontera (proto y JSON) con DateTimeUtils.now() y vive solo en el container
  • rename AccountDataSurveyAccountData… a partir do DTO (a estrutura é a mesma, 2 campos)rename AccountDataSurveyAccountData… from the DTO onward (same structure, 2 fields)rename AccountDataSurveyAccountData… a partir del DTO (la estructura es la misma, 2 campos)
  • o caminho Entity → Model recanoniza o tipo da pergunta: um "OPTION" vindo do servidor é persistido como "Option" — a string original não é preservadathe Entity → Model path re-canonicalizes the question type: an "OPTION" from the server is persisted as "Option" — the original string isn't preservedel camino Entity → Model recanoniza el tipo de la pregunta: un "OPTION" del servidor se persiste como "Option" — la string original no se preserva
  • o id do ObjectBox não existe no DTO nem na Entity: cada gravação cria linhas novas (e o save é destrutivo — ver Repository)the ObjectBox id exists in neither the DTO nor the Entity: every write creates new rows (and the save is destructive — see Repository)el id de ObjectBox no existe en el DTO ni en la Entity: cada grabación crea filas nuevas (y el save es destructivo — ver Repository)
  • nenhum campo do reply é descartado: as 5 mensagens de retorno mapeiam 1:1 para DTOs com a mesma contagem de camposno reply field is dropped: the 5 response messages map 1:1 to DTOs with the same field countsningún campo del reply se descarta: los 5 mensajes de retorno mapean 1:1 a DTOs con la misma cantidad de campos
08

Repository

SurveysRepositoryImpl implementaimplementsimplementa SurveysRepositoryInterface e injeta os 3 datasources (mock/local/remote) + ConnectivityService + a flag useMockData + Ref. A interface tem 5 métodos, todos de leitura ou cache-write — não há método de envio aqui (o envio é do Dispatcher). Um dropdown por método, com assinatura, retorno e comportamento; o getSurveys() traz a árvore de decisão de fonte dentro do próprio detalhe.and injects the 3 datasources (mock/local/remote) + ConnectivityService + the useMockData flag + Ref. The interface has 5 methods, all read or cache-write — there is no submit method here (submitting belongs to the Dispatcher). One dropdown per method, with signature, return and behavior; getSurveys() carries the source decision tree inside its own detail.e inyecta los 3 datasources (mock/local/remote) + ConnectivityService + la flag useMockData + Ref. La interfaz tiene 5 métodos, todos de lectura o cache-write — no hay método de envío aquí (el envío es del Dispatcher). Un dropdown por método, con firma, retorno y comportamiento; getSurveys() trae el árbol de decisión de fuente dentro de su propio detalle.

getSurveys({source = local}) mock / local / remote

RetornaReturnsDevuelve Future<Result<SurveysEntity, Failure>>

Ponto de entrada do catálogo. O default é local — abrir a tela nunca vai à rede; o remoto só entra por pull-to-refresh ou pelo sweep de sincronização.The catalog's entry point. The default is local — opening the screen never hits the network; remote only comes in via pull-to-refresh or the sync sweep.Punto de entrada del catálogo. El default es local — abrir la pantalla nunca va a la red; el remoto solo entra por pull-to-refresh o por el sweep de sincronización.

Árvore de decisão de fonteSource decision treeÁrbol de decisión de fuente

  1. useMockData == true ouoro source == mock→ lê o asset JSON e mapeia, sem gravar no cache. A flag global tem precedência máxima — vence até um pedido explícito de remote.→ reads the JSON asset and maps it, without writing to cache. The global flag has top precedence — it even beats an explicit remote request.→ lee el asset JSON y mapea, sin grabar en caché. La flag global tiene máxima precedencia — vence incluso a un pedido explícito de remote.
  2. source == local ou offlineor offlineu offline→ cache; cache vazio devolve Error(NetworkFailure()) (o retorno é não-nulo, então "sem dados" precisa virar falha).→ cache; an empty cache returns Error(NetworkFailure()) (the return is non-nullable, so "no data" has to become a failure).→ caché; caché vacío devuelve Error(NetworkFailure()) (el retorno es no-nulo, así que "sin datos" tiene que volverse fallo).
  3. senão (remoto + conectado)otherwise (remote + connected)si no (remoto + conectado)→ lê currentResourceProvider; se null cai pro cache; senão chama o remoto com locationHierarchyId, mapeia e grava no cache (write-through, com o resultado do save ignorado); em erro, devolve o cache — e só devolve a falha se o cache também estiver vazio.→ reads currentResourceProvider; if null falls back to cache; else calls remote with locationHierarchyId, maps and writes to cache (write-through, with the save result ignored); on error, returns the cache — and only surfaces the failure if the cache is empty too.→ lee currentResourceProvider; si es null cae al caché; si no llama al remoto con locationHierarchyId, mapea y graba en caché (write-through, con el resultado del save ignorado); en error, devuelve el caché — y solo devuelve el fallo si el caché también está vacío.

O repository não avalia TTL: com local ele devolve o cache mesmo antigo. A decisão de "está obsoleto" é do orquestrador de sincronização, que força remote.The repository does not evaluate TTL: with local it returns the cache even when stale. The "is it stale" decision belongs to the sync orchestrator, which forces remote.El repository no evalúa TTL: con local devuelve el caché aunque sea antiguo. La decisión de "está obsoleto" es del orquestador de sincronización, que fuerza remote.

getCachedSurveys() local

RetornaReturnsDevuelve Future<Result<SurveysEntity?, Failure>>

Só cache. null vira Success(null), não erro. Em sessão mock, relê o asset em vez do ObjectBox.Cache only. null becomes Success(null), not an error. In a mock session it re-reads the asset instead of ObjectBox.Solo caché. null es Success(null), no error. En sesión mock, relee el asset en vez de ObjectBox.

getCachedSurveysLastSyncAt() local

RetornaReturnsDevuelve Future<DateTime?> (sem Result)(no Result)(sin Result)

Timestamp do container, para o DataLoadInfo e para o cálculo de obsolescência do sweep. Erro é logado e devolve null. Não tem ramo mock: numa sessão mock ele lê a box (vazia) e devolve null, o que faz o sweep tratar pesquisas como sempre obsoletas.The container's timestamp, for DataLoadInfo and for the sweep's staleness math. An error is logged and returns null. It has no mock branch: in a mock session it reads the (empty) box and returns null, which makes the sweep treat surveys as perpetually stale.Timestamp del container, para el DataLoadInfo y para el cálculo de obsolescencia del sweep. El error se loguea y devuelve null. No tiene rama mock: en sesión mock lee la box (vacía) y devuelve null, lo que hace que el sweep trate las encuestas como siempre obsoletas.

getCachedSurveyBySfid({surveySfid}) local · §28 cat. A

RetornaReturnsDevuelve Future<Result<SurveyEntity?, Failure>>

Busca 1 pesquisa no cache pelo sfid (varredura linear na relação do container). Alimenta o fluxo de resposta e nunca dispara remoto — ausente devolve Success(null), e é o Notifier que transforma isso em falha de cache.Fetches 1 survey from cache by sfid (linear scan over the container's relation). Feeds the answering flow and never triggers remote — missing returns Success(null), and it's the Notifier that turns that into a cache failure.Busca 1 encuesta en el caché por sfid (barrido lineal en la relación del container). Alimenta el flujo de respuesta y nunca dispara remoto — ausente devuelve Success(null), y es el Notifier quien lo transforma en fallo de caché.

saveSurveys({entity}) local · destrutivodestructivedestructivo

RetornaReturnsDevuelve Future<Result<void, Failure>>

Destrutivo: limpa todas as boxes (filhas antes das raízes) e regrava o container inteiro. É o cache-writer chamado após cada fetch remoto bem-sucedido — logo, uma resposta parcial do servidor substitui a árvore toda.Destructive: clears all boxes (children before roots) and rewrites the whole container. It's the cache-writer called after each successful remote fetch — so a partial server response replaces the entire tree.Destructivo: limpia todas las boxes (hijas antes de las raíces) y regraba el container entero. Es el cache-writer llamado tras cada fetch remoto exitoso — por lo tanto, una respuesta parcial del servidor reemplaza todo el árbol.

09

Datasources

Um card por datasource (dropdown). No corpo: método, envio, retorno, fluxo de uso e tratamento de erro.One card per datasource (dropdown). In the body: method, what it sends, return, usage flow and error handling.Un card por datasource (dropdown). En el cuerpo: método, envío, retorno, flujo de uso y manejo de errores.

Remote SurveysRemoteDataSource gRPC
getSurveys({locationHierarchySfid, dateReference?, lastModifiedDate?})
EnvioSendsEnvío
monta SurveysRequest e chama _client.getSurveys(request) no SurveysConectaRepServiceClient (via surveysServiceClientProvider). Os dois campos opcionais só entram no request se vierem não-nulos — e nenhum caller os passa, então o request sempre carrega apenas a hierarquia.builds SurveysRequest and calls _client.getSurveys(request) on SurveysConectaRepServiceClient (via surveysServiceClientProvider). The two optional fields only enter the request when non-null — and no caller passes them, so the request always carries the hierarchy only.arma SurveysRequest y llama _client.getSurveys(request) en SurveysConectaRepServiceClient (vía surveysServiceClientProvider). Los dos campos opcionales solo entran en el request si vienen no-nulos — y ningún caller los pasa, así que el request siempre lleva solo la jerarquía.
RetornoReturnRetorno
Future<SurveysDTO> (via response.toSurveysDTO())(via response.toSurveysDTO())(vía response.toSurveysDTO())
Fluxo de usoUsage flowFlujo de uso
chamado só pelo caminho remoto do repository, quando online, sem mock e com source: remote — ou seja, no pull-to-refresh e no sweep. O resultado é gravado no cache.called only by the repository's remote path, when online, not mocking and with source: remote — that is, on pull-to-refresh and on the sweep. The result is written to cache.llamado solo por el camino remoto del repository, online, sin mock y con source: remote — es decir, en el pull-to-refresh y en el sweep. El resultado se graba en caché.
Tratamento de erroError handlingManejo de errores
GrpcErrorGrpcExceptionHandler; outros → ServerException. Em erro, o repository faz fallback pro cache.GrpcErrorGrpcExceptionHandler; others → ServerException. On error, the repository falls back to cache.GrpcErrorGrpcExceptionHandler; otros → ServerException. En error, el repository hace fallback al caché.
Local SurveysLocalDataSource ObjectBox

Envio / fluxo: persistência local via ObjectBox (ObjectBoxDatabase), boxes SurveysModel (raiz única — só a primeira linha é usada) e SurveyModel + as boxes filhas — sem rede. Alimenta os caminhos cache do repository. Erro: cada método encapsula a falha em CacheException com mensagem própria (nada é engolido). O lastSyncAt não é estampado aqui — chega pronto do mapper.Sends / flow: local persistence via ObjectBox (ObjectBoxDatabase), SurveysModel (single root — only the first row is used) and SurveyModel boxes plus the child boxes — no network. Feeds the repository's cache paths. Error: each method wraps the failure in a CacheException with its own message (nothing is swallowed). lastSyncAt is not stamped here — it arrives ready from the mapper.Envío / flujo: persistencia local vía ObjectBox (ObjectBoxDatabase), boxes SurveysModel (raíz única — solo se usa la primera fila) y SurveyModel más las boxes hijas — sin red. Alimenta los caminos caché del repository. Error: cada método encapsula el fallo en CacheException con mensaje propio (nada se traga). El lastSyncAt no se estampa aquí — llega listo del mapper.

getCachedSurveys()
RetornoReturnRetorno
SurveysEntity?
ComportamentoBehaviorComportamiento
models.first.toDomain() — o agregado único, ou null se a box está vazia.models.first.toDomain() — the single aggregate, or null if the box is empty.models.first.toDomain() — el agregado único, o null si la box está vacía.
getCachedSurveyBySfid({surveySfid})
RetornoReturnRetorno
SurveyEntity?
ComportamentoBehaviorComportamiento
pega a raiz e itera a relação surveys comparando o sfid; sem match, null. É o método que alimenta o fluxo de resposta.takes the root and iterates the surveys relation comparing sfid; no match, null. This is the method feeding the answering flow.toma la raíz e itera la relación surveys comparando el sfid; sin match, null. Es el método que alimenta el flujo de respuesta.
getSurveysLastSyncAt()
RetornoReturnRetorno
DateTime?
ComportamentoBehaviorComportamiento
models.first.lastSyncAt — sem toDomain, é leitura direta do campo.— no toDomain, it's a direct field read.— sin toDomain, es lectura directa del campo.
saveSurveys({entity})
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
destrutivo: clearSurveys() + _box.put(entity.toModel()) (grava as boxes filhas em cascata). Cache-writer após cada fetch.destructive: clearSurveys() + _box.put(entity.toModel()) (writes child boxes in cascade). Cache-writer after each fetch.destructivo: clearSurveys() + _box.put(entity.toModel()) (graba las boxes hijas en cascada). Cache-writer tras cada fetch.
mergeAdhocSurveys({incoming, accountSfid})
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
lê o cache, funde as pesquisas daquele varejo (via AdhocSurveysMerge) e regrava. Não está na interface do repository — é usado só pelo coordenador de merge da visita ad hoc, descrito em Varejos.reads the cache, merges that retail's surveys (via AdhocSurveysMerge) and rewrites. It isn't on the repository interface — it's used only by the ad hoc visit merge coordinator, described in Retails.lee el caché, fusiona las encuestas de ese punto de venta (vía AdhocSurveysMerge) y regraba. No está en la interfaz del repository — lo usa solo el coordinador de merge de la visita ad hoc, descrito en Puntos de venta.
clearSurveys()
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
limpa na ordem AnswerOptionModelDependentQuestionModelQuestionModelSurveyAccountDataModelSurveyModelSurveysModel (filhas antes das raízes, para não deixar órfãos).clears in the order AnswerOptionModelDependentQuestionModelQuestionModelSurveyAccountDataModelSurveyModelSurveysModel (children before roots, to avoid orphans).limpia en el orden AnswerOptionModelDependentQuestionModelQuestionModelSurveyAccountDataModelSurveyModelSurveysModel (hijas antes de las raíces, para no dejar huérfanos).
Mock SurveysMockDataSource JSON
getSurveys()
EnvioSendsEnvío
carrega o asset assets/mocks/surveys/jsons/{mercado}_surveys.json ou, com a flag de mock real ligada, {mercado}_real_surveys.json — sem rede.loads the asset assets/mocks/surveys/jsons/{market}_surveys.json or, with the real-mock flag on, {market}_real_surveys.json — no network.carga el asset assets/mocks/surveys/jsons/{mercado}_surveys.json o, con la flag de mock real activa, {mercado}_real_surveys.json — sin red.
RetornoReturnRetorno
Future<SurveysDTO> (via SurveysDTOJsonMapper.fromMap)(via SurveysDTOJsonMapper.fromMap)(vía SurveysDTOJsonMapper.fromMap)
Fluxo de usoUsage flowFlujo de uso
usado quando useMockData está ligado ou source == mock. Diferente das outras features, não grava no cache — o mock é relido a cada chamada, inclusive nas de "cache".used when useMockData is on or source == mock. Unlike other features, it doesn't write to cache — the mock is re-read on every call, including the "cache" ones.usado cuando useMockData está activo o source == mock. A diferencia de otras features, no graba en caché — el mock se relee en cada llamada, incluso en las de "caché".
Tratamento de erroError handlingManejo de errores
no caminho de mock real, asset ausente devolve {} em silêncio (lista vazia); no caminho sintético, asset ausente ou JSON inválido propaga como CacheException.on the real-mock path, a missing asset silently returns {} (empty list); on the synthetic path, a missing asset or invalid JSON propagates as a CacheException.en el camino de mock real, un asset ausente devuelve {} en silencio (lista vacía); en el camino sintético, un asset ausente o JSON inválido propaga como CacheException.
10

Enums e labelsEnums & labelsEnums y labels

A feature tem quatro enums próprios (em lib/core/enums/surveys/) e nenhum enum declarado no proto — tipo e categoria trafegam como String. Só o tipo de pergunta é tipado na Entity. Lista completa de valores:The feature has four enums of its own (in lib/core/enums/surveys/) and no enum declared in the proto — type and category travel as String. Only the question type is typed in the Entity. Full value list:La feature tiene cuatro enums propios (en lib/core/enums/surveys/) y ningún enum declarado en el proto — tipo y categoría viajan como String. Solo el tipo de pregunta está tipado en la Entity. Lista completa de valores:

SurveyQuestionType 5 valores · fromProto5 values · fromProto5 valores · fromProto
caseprotoValuecampo de respostaanswer fieldcampo de respuesta
option"Option"SurveyOptionListWidget
multipleSelect"Multiple Select"SurveyMultipleSelectWidget
numeric"Numeric"SurveyNumericInputWidget
text"Text"SurveyTextInputWidget
date"Date"SurveyDateInputWidget

O parser se chama fromProto e compara em minúsculas nos dois lados, então "OPTION" e "option" casam normalmente. O fallback é text — não unknown, que não existe neste enum — e não é logado. Como "Text" também é um valor real, o caso text é alcançável pelas duas vias: casamento real e fallback. Consequência: um tipo desconhecido do backend vira uma caixa de texto livre em silêncio (ver Pendências).The parser is called fromProto and compares in lowercase on both sides, so "OPTION" and "option" match normally. The fallback is text — not unknown, which doesn't exist in this enum — and it is not logged. Since "Text" is also a real value, the text case is reachable both ways: real match and fallback. Consequence: a type the backend doesn't share silently becomes a free-text box (see Pending items).El parser se llama fromProto y compara en minúsculas en los dos lados, así que "OPTION" y "option" casan normalmente. El fallback es text — no unknown, que no existe en este enum — y no se loguea. Como "Text" también es un valor real, el caso text es alcanzable por las dos vías: casamiento real y fallback. Consecuencia: un tipo desconocido del backend se vuelve una caja de texto libre en silencio (ver Pendientes).

SurveyCategory 3 valores · fromString3 values · fromString3 valores · fromString
casevaluecabeçalho (i18n)header (i18n)encabezado (i18n)
survey"survey"surveysSectionHeader
surveyCompetitorActions"survey_competitor_actions"visitDetailCompetitorActions
unknown"unknown"surveysSectionHeader

O parser compara com igualdade exata (sem toLowerCase, sem normalizar espaço ou hífen): "Survey" ou "survey " não casam. Antes de devolver unknown ele chama logUnmappedEnumValue, que loga e dispara assert(false) — ou seja, valor desconhecido quebra em modo debug. Como "unknown" é um valor real do enum, um "unknown" literal do backend casa dentro do laço e não passa pelo log. Categoria vazia (o default do mapper) cai em unknown sem log. A extensão SurveyCategoryUx expõe headerTitleKey (usada no cabeçalho) e headerIcon (hoje sem chamador).The parser compares with exact equality (no toLowerCase, no whitespace or dash normalization): "Survey" or "survey " don't match. Before returning unknown it calls logUnmappedEnumValue, which logs and fires assert(false) — so an unknown value crashes in debug mode. Since "unknown" is a real enum value, a literal "unknown" from the backend matches inside the loop and never reaches the log. An empty category (the mapper's default) falls to unknown without logging. The SurveyCategoryUx extension exposes headerTitleKey (used in the header) and headerIcon (no caller today).El parser compara con igualdad exacta (sin toLowerCase, sin normalizar espacio ni guion): "Survey" o "survey " no casan. Antes de devolver unknown llama a logUnmappedEnumValue, que loguea y dispara assert(false) — es decir, un valor desconocido rompe en modo debug. Como "unknown" es un valor real del enum, un "unknown" literal del backend casa dentro del bucle y no pasa por el log. Categoría vacía (el default del mapper) cae en unknown sin log. La extensión SurveyCategoryUx expone headerTitleKey (usada en el encabezado) y headerIcon (hoy sin llamador).

SurveyResultUploadStatus 2 valores · só saída2 values · outbound only2 valores · solo salida
casevaluequandowhencuándo
complete"Complete"surveyConfig.completesResultOnUpload == true
inProgress"In Progress"surveyConfig.completesResultOnUpload == false

Enum de saída: não tem parser nem fallback, porque nada é lido de volta com ele. Vira o campo surveyStatus do payload — é o único efeito da configuração de mercado no envio.An outbound enum: it has no parser and no fallback, because nothing is read back with it. It becomes the payload's surveyStatus field — the only effect market configuration has on the submission.Enum de salida: no tiene parser ni fallback, porque nada se lee de vuelta con él. Se vuelve el campo surveyStatus del payload — es el único efecto de la configuración de mercado en el envío.

SurveySubmitOutcome 3 valores · sem wire3 values · no wire3 valores · sin wire
caseo que a Page fazwhat the Page doesqué hace la Page
successaviso verde + volta para a listagreen notice + back to the listaviso verde + vuelve a la lista
orphanEvidenceaviso laranja; permanece na pergunta, nada é enviadoorange notice; stays on the question, nothing is sentaviso naranja; permanece en la pregunta, nada se envía
failureaviso vermelho com a falha (ou mensagem padrão); estado preservadored notice with the failure (or default message); state preservedaviso rojo con el fallo (o mensaje por defecto); estado preservado

Enum puramente interno (sem valor de wire): é o retorno de submit(), consumido em switch exaustivo pela Page.A purely internal enum (no wire value): it's submit()'s return, consumed by an exhaustive switch in the Page.Enum puramente interno (sin valor de wire): es el retorno de submit(), consumido en switch exhaustivo por la Page.

Valores de pesquisas em enums de outras áreasSurvey values in other areas' enumsValores de encuestas en enums de otras áreas 4 enums
enumcasevaluemercados / usomarkets / usemercados / uso
DispatcherTypesurvey"SurveyResultUploadAPI"BR · CL · ZA
DataSyncTypesurveys"surveys"BR · CL · ZA
ModuleDetailTypevisitDetailToolSurveys"surveys"tile da grade de ferramentastools grid tiletile de la grilla de herramientas
ModuleDetailTypevisitDetailToolCompetitorActions"competitor_actions"tile que abre a mesma tela na outra categoriatile opening the same screen on the other categorytile que abre la misma pantalla en la otra categoría
VisitEndPendingTypemandatorySurveys"mandatory_surveys"pendência bloqueante (BR/ZA)blocking pending item (BR/ZA)pendiente bloqueante (BR/ZA)
VisitEndPendingTypeoptionalSurveys"optional_surveys"pendência não bloqueante (BR/ZA)non-blocking pending item (BR/ZA)pendiente no bloqueante (BR/ZA)

São só os valores que dizem respeito a pesquisas — cada enum tem outros valores, documentados na área dona (Dispatcher, sincronização, configuração de mercado, encerramento de visita).These are only the values concerning surveys — each enum has other values, documented in its owning area (Dispatcher, sync, market configuration, visit end).Son solo los valores que atañen a encuestas — cada enum tiene otros valores, documentados en el área dueña (Dispatcher, sincronización, configuración de mercado, cierre de visita).

11

UseCases

Dois UseCases de leitura (em usecases/surveys/) e dois de escrita (em usecases/dispatcher/surveys/). Um dropdown por UseCase; dentro, cada método com assinatura, o que retorna e uso. Fora daqui, quem também consome pesquisas é o UseCase de pendências de encerramento de visita, que conta as não respondidas — ele pertence à área de visitas.Two read UseCases (in usecases/surveys/) and two write ones (in usecases/dispatcher/surveys/). One dropdown per UseCase; inside, each method with its signature, what it returns and use. Beyond these, surveys are also consumed by the visit-end pendings UseCase, which counts the unanswered ones — it belongs to the visits area.Dos UseCases de lectura (en usecases/surveys/) y dos de escritura (en usecases/dispatcher/surveys/). Un dropdown por UseCase; dentro, cada método con su firma, qué devuelve y uso. Fuera de aquí, quien también consume encuestas es el UseCase de pendientes de cierre de visita, que cuenta las no respondidas — pertenece al área de visitas.

GetSurveysUseCase 4 · o catálogothe catalogel catálogo
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
execute({source = local})Result<SurveysEntity, Failure>Catálogo completo do mercado. Delega ao repository sem lógica extra. É o gancho que o sweep de sincronização chama com remote.The market's full catalog. Delegates to the repository with no extra logic. It's the hook the sync sweep calls with remote.Catálogo completo del mercado. Delega al repository sin lógica extra. Es el gancho que el sweep de sincronización llama con remote.
getCached()Result<SurveysEntity?, Failure>Só cache; null vira Success(null). O fluxo de resposta usa para pegar o lastSyncAt.Cache only; null becomes Success(null). The answering flow uses it to get lastSyncAt.Solo caché; null es Success(null). El flujo de respuesta lo usa para tomar el lastSyncAt.
getCachedLastSyncAt()DateTime?Timestamp da última sincronização (sem Result) — nome uniforme em todas as features, é o que o sweep lê para decidir obsolescência.Last-sync timestamp (no Result) — a uniform name across features, it's what the sweep reads to decide staleness.Timestamp de última sincronización (sin Result) — nombre uniforme en todas las features, es lo que el sweep lee para decidir obsolescencia.
getCachedBySfid({surveySfid})Result<SurveyEntity?, Failure>1 pesquisa do cache. Alimenta o fluxo de resposta (§28 cat. A — nunca remoto).1 survey from cache. Feeds the answering flow (§28 cat. A — never remote).1 encuesta del caché. Alimenta el flujo de respuesta (§28 cat. A — nunca remoto).
GetSurveysForAccountUseCase 1 · elegibilidadeeligibilityelegibilidad
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
execute({accountSfid, source = local})Result<SurveysEntity, Failure>Catálogo recortado para um varejo. Alimenta a lista e a contagem de pendências de encerramento.The catalog sliced for one retail. Feeds the list and the visit-end pending count.Catálogo recortado para un punto de venta. Alimenta la lista y el conteo de pendientes de cierre.

Regras de recorteSlicing rulesReglas de recorte

  1. AplicabilidadeApplicabilityAplicabilidadA pesquisa entra só se houver uma linha de accountData com aquele accountSfid; sem linha, é descartada.The survey is kept only if there is an accountData row with that accountSfid; with no row, it's dropped.La encuesta entra solo si hay una fila de accountData con ese accountSfid; sin fila, se descarta.
  2. Resposta única já usadaOne-time already usedRespuesta única ya usadaisOneTimeSurvey com resultsQuantity > 0 é descartada.isOneTimeSurvey with resultsQuantity > 0 is dropped.isOneTimeSurvey con resultsQuantity > 0 se descarta.
  3. OrdenaçãoSortingOrdenaciónObrigatórias primeiro; empate resolvido por nome (alfabético). O lastSyncAt do container é preservado no copyWith.Mandatory first; ties broken by name (alphabetical). The container's lastSyncAt is preserved through copyWith.Obligatorias primero; empate resuelto por nombre (alfabético). El lastSyncAt del container se preserva en el copyWith.

O filtro por categoria não está aqui — acontece no Notifier, com o valor que veio pela rota.The category filter isn't here — it happens in the Notifier, with the value that came in through the route.El filtro por categoría no está aquí — ocurre en el Notifier, con el valor que llegó por la ruta.

BuildSurveyResultUploadDispatcherPayloadUseCase 2 · payloadpayloadpayload
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
build({input})DispatcherEnvelopeContrato do DispatcherPayloadBuilder: monta um envelope com o JSON do resultado. Toda construção wire (rename, formatação de data, remoção de apóstrofo do nome de arquivo, campos fixos) mora aqui.The DispatcherPayloadBuilder contract: assembles one envelope with the result JSON. All wire construction (renames, date formatting, stripping apostrophes from the file name, fixed fields) lives here.El contrato del DispatcherPayloadBuilder: arma un envelope con el JSON del resultado. Toda construcción wire (renames, formateo de fecha, remoción del apóstrofo del nombre de archivo, campos fijos) vive aquí.
buildAll({input})List<DispatcherEnvelope>Método extra (fora da interface): fatia as evidências em blocos de 3 e devolve um envelope por bloco, repetindo cabeçalho e detalhes em todos. Sem evidências, ou com até 3, devolve um envelope só.An extra method (outside the interface): slices the evidence into blocks of 3 and returns one envelope per block, repeating header and details in all of them. With no evidence, or up to 3, it returns a single envelope.Método extra (fuera de la interfaz): corta las evidencias en bloques de 3 y devuelve un envelope por bloque, repitiendo encabezado y detalles en todos. Sin evidencias, o con hasta 3, devuelve un solo envelope.

O que o payload carregaWhat the payload carriesQué lleva el payload

surveyResult
array de um objeto: surveyCode (identificador gerado no envio), surveyID, storeID, visitID e surveyStatus (do mercado).an array with one object: surveyCode (identifier generated at submit time), surveyID, storeID, visitID and surveyStatus (from the market).array de un objeto: surveyCode (identificador generado en el envío), surveyID, storeID, visitID y surveyStatus (del mercado).
surveyResultdetails
uma linha por resposta — e uma linha por opção marcada na múltipla escolha. Perguntas dependentes entram achatadas na mesma lista (o mapa de respostas é chaveado por pergunta em qualquer profundidade). Cada linha tem score: "0" e surveyResult: "" fixos; a data é formatada como dd/MM/yyyy.one row per answer — and one row per checked option on multiple select. Dependent questions enter flattened into the same list (the answer map is keyed by question at any depth). Each row carries fixed score: "0" and surveyResult: ""; the date is formatted as dd/MM/yyyy.una fila por respuesta — y una fila por opción marcada en la selección múltiple. Las preguntas dependientes entran achatadas en la misma lista (el mapa de respuestas está indexado por pregunta a cualquier profundidad). Cada fila lleva score: "0" y surveyResult: "" fijos; la fecha se formatea como dd/MM/yyyy.
evidenceList
até 3 itens por envelope, cada um com base64, questionSfid e fileName.up to 3 items per envelope, each with base64, questionSfid and fileName.hasta 3 ítems por envelope, cada uno con base64, questionSfid y fileName.

A tabela completa campo-a-campo (tipo · origem · regra) e o exemplo de JSON estão em 15 · SurveyResultUploadAPI. O envelope leva serviceName "SurveyResultUploadAPI" (sem prefixo de promoção), o surveyCode como referência da transação e a data de envio em yyyy-MM-dd.The full field-by-field table (type · origin · rule) and the JSON example are in 15 · SurveyResultUploadAPI. The envelope carries serviceName "SurveyResultUploadAPI" (no promotion prefix), the surveyCode as the transaction reference and the submission date in yyyy-MM-dd.La tabla completa campo a campo (tipo · origen · regla) y el ejemplo de JSON están en 15 · SurveyResultUploadAPI. El envelope lleva serviceName "SurveyResultUploadAPI" (sin prefijo de promoción), el surveyCode como referencia de la transacción y la fecha de envío en yyyy-MM-dd.

SubmitSurveyResultUploadUseCase 1 · enviosubmitenvío
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
submit({envelopes})List<Result<DispatcherAck, Failure>>Despacha os envelopes em paralelo pelo DispatcherOrchestrator, com a lista de resultados alinhada por índice à de envelopes. Não monta payload e não conhece o tipo — ele vem dentro do envelope. O Notifier trata a primeira falha como falha do envio inteiro.Dispatches the envelopes in parallel through the DispatcherOrchestrator, with the result list index-aligned to the envelope list. It builds no payload and doesn't know the type — that comes inside the envelope. The Notifier treats the first failure as a failure of the whole submission.Despacha los envelopes en paralelo por el DispatcherOrchestrator, con la lista de resultados alineada por índice a la de envelopes. No arma payload y no conoce el tipo — viene dentro del envelope. El Notifier trata la primera falla como falla del envío entero.
12

Notifiers & State

São dois Notifiers, ambos @riverpod e ambos família: o da lista é indexado por visitSfid + category, o do fluxo por surveySfid. O SurveysNotifier segue o padrão canônico (build() magro → _load()refresh(), com o mixin AsyncGuard); o SurveyAnswerNotifier é um notifier de formulário: monta o State no próprio build(), não tem refresh() (a tela não tem pull-to-refresh) e cada método muta o State emitindo AsyncValue.data. O State é a fonte única de verdade das duas pages — inclusive das validações, que vivem em getters.There are two Notifiers, both @riverpod and both families: the list one is keyed by visitSfid + category, the flow one by surveySfid. SurveysNotifier follows the canonical pattern (thin build()_load()refresh(), with the AsyncGuard mixin); SurveyAnswerNotifier is a form notifier: it assembles the State in build() itself, has no refresh() (the screen has no pull-to-refresh) and each method mutates the State by emitting AsyncValue.data. The State is the single source of truth for both pages — including the validations, which live in getters.Son dos Notifiers, ambos @riverpod y ambos familia: el de la lista está indexado por visitSfid + category, el del flujo por surveySfid. El SurveysNotifier sigue el patrón canónico (build() magro → _load()refresh(), con el mixin AsyncGuard); el SurveyAnswerNotifier es un notifier de formulario: arma el State en el propio build(), no tiene refresh() (la pantalla no tiene pull-to-refresh) y cada método muta el State emitiendo AsyncValue.data. El State es la fuente única de verdad de las dos pages — incluidas las validaciones, que viven en getters.

Métodos — SurveysNotifier (lista)Methods — SurveysNotifier (list)Métodos — SurveysNotifier (lista)

build({visitSfid, category}) provider

RetornoReturnRetorno FutureOr<SurveysState>

Magro: observa os dois UseCases (pesquisas por varejo e visitas) e devolve guardedBuild(body: _load(...)).Thin: watches the two UseCases (surveys per retail and visits) and returns guardedBuild(body: _load(...)).Magro: observa los dos UseCases (encuestas por punto de venta y visitas) y devuelve guardedBuild(body: _load(...)).

_load({visitSfid, category, source = local}) private

RetornoReturnRetorno Future<SurveysState>

Dono único da montagem do State: dispara config de mercado e visita em paralelo, extrai os módulos visíveis da configuração de detalhe da visita, e — com o varejo da visita — busca as pesquisas elegíveis. Se a visita não existir no cache, lança BusinessFailure. Por fim filtra por categoryType == category e devolve nome/código do varejo, lista e lastSyncAt.Sole owner of building the State: fires market config and visit in parallel, extracts the visible modules from the visit detail configuration, and — using the visit's retail — fetches the eligible surveys. If the visit isn't in cache, it throws BusinessFailure. Finally it filters by categoryType == category and returns the retail's name/code, the list and lastSyncAt.Dueño único del armado del State: dispara config de mercado y visita en paralelo, extrae los módulos visibles de la configuración de detalle de la visita, y — con el punto de venta de la visita — busca las encuestas elegibles. Si la visita no existe en el caché, lanza BusinessFailure. Por último filtra por categoryType == category y devuelve nombre/código del punto de venta, lista y lastSyncAt.

refresh() pull-to-refresh

RetornoReturnRetorno Future<void>

Null-guard no State atual, reaproveita visitSfid/category dele e chama _load() com source: remote — é o único caminho da tela para a rede. Não seta AsyncValue.loading (o pull-to-refresh tem indicador próprio).Null-guards the current State, reuses its visitSfid/category and calls _load() with source: remote — the screen's only path to the network. It doesn't set AsyncValue.loading (pull-to-refresh has its own indicator).Null-guard en el State actual, reaprovecha su visitSfid/category y llama _load() con source: remote — es el único camino de la pantalla a la red. No setea AsyncValue.loading (el pull-to-refresh tiene su propio indicador).

Métodos — SurveyAnswerNotifier (fluxo)Methods — SurveyAnswerNotifier (flow)Métodos — SurveyAnswerNotifier (flujo)

build({surveySfid}) provider

RetornoReturnRetorno FutureOr<SurveyAnswerState>

Abre uma sessão de captura de arquivos (id novo, com limpeza registrada no onDispose), busca a pesquisa e o container no cache em paralelo, e monta o State. Pesquisa ausente vira CacheFailure; pesquisa sem perguntas vira BusinessFailure — as duas caem na tela de erro.Opens a file capture session (new id, with cleanup registered in onDispose), fetches the survey and the container from cache in parallel, and assembles the State. A missing survey becomes CacheFailure; a survey with no questions becomes BusinessFailure — both land on the error screen.Abre una sesión de captura de archivos (id nuevo, con limpieza registrada en el onDispose), busca la encuesta y el container en el caché en paralelo, y arma el State. Encuesta ausente se vuelve CacheFailure; encuesta sin preguntas se vuelve BusinessFailure — las dos caen en la pantalla de error.

selectOption({questionSfid, answerOptionSfid})

RetornoReturnRetorno void

Opção única: grava uma lista de um sfid, substituindo a escolha anterior.Single option: stores a list with one sfid, replacing the previous choice.Opción única: graba una lista de un sfid, reemplazando la elección anterior.

toggleOption({questionSfid, answerOptionSfid})

RetornoReturnRetorno void

Múltipla escolha: adiciona ou remove o sfid da seleção. Ficando vazia, a resposta é removida do mapa (volta a contar como não respondida).Multiple select: adds or removes the sfid from the selection. If it ends up empty, the answer is removed from the map (it counts as unanswered again).Selección múltiple: agrega o quita el sfid de la selección. Si queda vacía, la respuesta se elimina del mapa (vuelve a contar como no respondida).

setText({questionSfid, text})

RetornoReturnRetorno void

Texto vazio remove a resposta do mapa; qualquer outro valor grava SurveyTextAnswer.Empty text removes the answer from the map; any other value stores a SurveyTextAnswer.Texto vacío elimina la respuesta del mapa; cualquier otro valor graba SurveyTextAnswer.

setNumeric({questionSfid, value})

RetornoReturnRetorno void

Grava sempre — inclusive um valor fora da faixa, que é aceito no State e barrado pela validação de avanço.Always stores — including an out-of-range value, which is accepted into the State and blocked by the advance validation.Graba siempre — incluso un valor fuera del rango, que se acepta en el State y es bloqueado por la validación de avance.

setDate({questionSfid, date})

RetornoReturnRetorno void

Grava a data escolhida no calendário.Stores the date chosen in the calendar.Graba la fecha elegida en el calendario.

addEvidence({questionSfid, source}) câmeracameracámara

RetornoReturnRetorno Future<void>

Captura uma imagem na sessão do fluxo. A galeria é proibida por assert — só câmera. Ignora a chamada se outra captura está em curso; liga e desliga o indicador de carregamento do botão; cancelamento (retorno nulo) só desliga o indicador; erro é logado sem quebrar a tela.Captures an image in the flow's session. The gallery is forbidden by assert — camera only. It ignores the call if another capture is running; toggles the button's loading indicator; a cancellation (null return) only clears the indicator; an error is logged without breaking the screen.Captura una imagen en la sesión del flujo. La galería está prohibida por assert — solo cámara. Ignora la llamada si otra captura está en curso; enciende y apaga el indicador de carga del botón; la cancelación (retorno nulo) solo apaga el indicador; el error se loguea sin romper la pantalla.

addDocument({questionSfid}) imagem · PDF · documentoimage · PDF · documentimagen · PDF · documento

RetornoReturnRetorno Future<void>

Abre o seletor de arquivos restrito a imagem, PDF e documento, com o mesmo tratamento de ocupado/cancelamento/erro do método de câmera.Opens the file picker restricted to image, PDF and document, with the same busy/cancel/error handling as the camera method.Abre el selector de archivos restringido a imagen, PDF y documento, con el mismo manejo de ocupado/cancelación/error que el método de cámara.

removeEvidence({questionSfid, file})

RetornoReturnRetorno Future<void>

Remove o arquivo da lista da pergunta (pelo caminho), apaga a chave se a lista zerar, e só então apaga o arquivo do disco.Removes the file from the question's list (by path), drops the key if the list empties, and only then deletes the file from disk.Quita el archivo de la lista de la pregunta (por la ruta), elimina la clave si la lista queda vacía, y solo entonces borra el archivo del disco.

submit({visitSfid}) escritawriteescritura

RetornoReturnRetorno Future<SurveySubmitOutcome>

Único ponto de escrita da feature. Na ordem: guarda contra envio duplicado; devolve orphanEvidence se houver arquivo em pergunta sem resposta; liga isSubmitting; busca a visita no cache; converte cada evidência em base64; gera o surveyCode; lê surveyConfig.completesResultOnUpload da configuração de mercado; monta o input com as entities cruas (pesquisa, visita, mapa de respostas) + submittedAt: DateTimeUtils.now(); chama buildAll e despacha. A primeira falha vira failure com a Failure guardada no State; sucesso apenas desliga isSubmitting.The feature's only write point. In order: guards against a duplicate submit; returns orphanEvidence if a file sits on an unanswered question; turns isSubmitting on; fetches the visit from cache; converts each evidence to base64; generates the surveyCode; reads surveyConfig.completesResultOnUpload from market configuration; assembles the input with the raw entities (survey, visit, answer map) + submittedAt: DateTimeUtils.now(); calls buildAll and dispatches. The first failure becomes failure with the Failure kept in the State; success only turns isSubmitting off.Único punto de escritura de la feature. En orden: protege contra envío duplicado; devuelve orphanEvidence si hay archivo en pregunta sin respuesta; enciende isSubmitting; busca la visita en el caché; convierte cada evidencia a base64; genera el surveyCode; lee surveyConfig.completesResultOnUpload de la configuración de mercado; arma el input con las entities crudas (encuesta, visita, mapa de respuestas) + submittedAt: DateTimeUtils.now(); llama buildAll y despacha. La primera falla se vuelve failure con la Failure guardada en el State; el éxito solo apaga isSubmitting.

nextPage() · previousPage()

RetornoReturnRetorno void

Movem o índice da página em 1, com guarda nas pontas (última/primeira). A animação do PageView é reação da Page à mudança de índice, não do Notifier.Move the page index by 1, guarded at the ends (last/first). The PageView animation is the Page reacting to the index change, not the Notifier.Mueven el índice de la página en 1, con guarda en los extremos (última/primera). La animación del PageView es reacción de la Page al cambio de índice, no del Notifier.

State disponível para as PagesState available to the PagesState disponible para las Pages

SurveysState 7 campos + 2 métodos7 fields + 2 methods7 campos + 2 métodos
campotipodefault
visitSfidStringrequired
categorySurveyCategoryrequired
accountNameString""
accountCustomerCodeString""
surveysList<SurveyEntity>[]
lastSyncAtDateTime?null
visibleModulesList<ModuleConfig>[]

Métodos: getModule(type) e visibleDetailTypes({module, defaultOrder}) — ambos herdados do padrão de módulos por mercado e sem consumidor nos widgets desta feature hoje (ver Pendências). A lista já chega filtrada e ordenada do UseCase + Notifier: o State não faz filtragem nem ordenação.Methods: getModule(type) and visibleDetailTypes({module, defaultOrder}) — both inherited from the per-market module pattern and with no consumer in this feature's widgets today (see Pending items). The list already arrives filtered and sorted from the UseCase + Notifier: the State does no filtering or sorting.Métodos: getModule(type) y visibleDetailTypes({module, defaultOrder}) — ambos heredados del patrón de módulos por mercado y sin consumidor en los widgets de esta feature hoy (ver Pendientes). La lista ya llega filtrada y ordenada del UseCase + Notifier: el State no filtra ni ordena.

SurveyAnswerState 10 campos + 7 getters10 fields + 7 getters10 campos + 7 getters
campotipodefault
surveySurveyEntityrequired
sessionIdStringrequired
currentPageIndexint0
answersMap<String, SurveyAnswerValue>{}
evidencesMap<String, List<CapturedFileEntity>>{}
lastSyncAtDateTime?null
isCapturingEvidenceImageboolfalse
isPickingEvidenceDocumentboolfalse
isSubmittingboolfalse
submissionFailureFailure?null

Getters: hasAnyAnswer, hasOrphanEvidences (arquivo em pergunta sem resposta), isEvidenceBusy, totalRootQuestions, isFirstPage, isLastPage e canAdvance — este último combina, na ordem: obrigatória respondida, número dentro da faixa, mínimo de evidências atingido e dependentes obrigatórias reveladas respondidas (verificação recursiva). Cinco predicados privados sustentam esses getters. As respostas são indexadas por sfid de pergunta, num único mapa plano — perguntas dependentes convivem com as raízes nele.Getters: hasAnyAnswer, hasOrphanEvidences (a file on an unanswered question), isEvidenceBusy, totalRootQuestions, isFirstPage, isLastPage and canAdvance — the latter combines, in order: mandatory answered, number within range, evidence minimum reached and revealed mandatory dependents answered (a recursive check). Five private predicates back these getters. Answers are keyed by question sfid, in a single flat map — dependent questions live in it alongside the roots.Getters: hasAnyAnswer, hasOrphanEvidences (archivo en pregunta sin respuesta), isEvidenceBusy, totalRootQuestions, isFirstPage, isLastPage y canAdvance — este último combina, en orden: obligatoria respondida, número dentro del rango, mínimo de evidencias alcanzado y dependientes obligatorias reveladas respondidas (verificación recursiva). Cinco predicados privados sostienen esos getters. Las respuestas se indexan por sfid de pregunta, en un único mapa plano — las preguntas dependientes conviven con las raíces en él.

13

Pages e widgetsPages & widgetsPages y widgets

Duas pages. A da lista é um ConsumerWidget com loading/erro globais; a do fluxo é um ConsumerStatefulWidget, porque é dona do PageController e reage à mudança de índice animando o PageView. Os modais aparecem dentro da árvore, sob o widget que os abre.Two pages. The list one is a ConsumerWidget with global loading/error; the flow one is a ConsumerStatefulWidget, because it owns the PageController and reacts to index changes by animating the PageView. Modals appear inside the tree, under the widget that opens them.Dos pages. La de la lista es un ConsumerWidget con loading/error globales; la del flujo es un ConsumerStatefulWidget, porque es dueña del PageController y reacciona al cambio de índice animando el PageView. Los modales aparecen dentro del árbol, bajo el widget que los abre.

ListaListLista

  • SurveysPage visitSfid · category
    • AppPageShell displayBackButton
      • CustomLoadingIndicator loading
      • FailureStateView error → invalidate do provider
      • CustomPullToRefresh data → refresh()
        • DataLoadInfo lastSyncAt
        • AccountHeaderCard nome + SAP do varejo
        • SurveysHeaderWidget título por categoria (SurveyCategoryUx.headerTitleKey)
        • SurveysListWidget
          • CustomEmptyState lista vazia
          • SurveyCardWidget nome + chevron → goToSurveyAnswer(survey.categoryType)

Fluxo de respostaAnswering flowFlujo de respuesta

  • SurveyAnswerPage surveySfid · visitSfid · category · PageController
    • PopScope canPop: false → _handleBack
      • AppPageShell backButtonFunction → _handleBack
        • CustomLoadingIndicator loading
        • FailureStateView error → invalidate do provider
        • _SurveyAnswerBody data
          • PageView.builder NeverScrollableScrollPhysics · 1 página por pergunta raiz
            • DataLoadInfo lastSyncAt
            • SurveysHeaderWidget reusado da lista
            • SurveyQuestionWidget switch por SurveyQuestionType
              • SurveyQuestionCard contador · título · "obrigatória" · input · evidência
                • SurveyOptionListWidget option → selectOption
                  • SurveyOptionTile avatar de letra + label
                • SurveyMultipleSelectWidget multipleSelect → toggleOption
                • SurveyNumericInputWidget numeric → setNumeric · aviso de faixa
                • SurveyTextInputWidget text → setText · 3–6 linhas
                • SurveyDateInputWidget date → setDate
                  • CustomCalendarModalContent modal · devolve DateTime
                • SurveyEvidenceWidget contador atual/máx · foto → addEvidence · documento → addDocument · miniatura → removeEvidence
              • SurveyDependentQuestionsStackWidget revela dependentes das opções marcadas
                • _DependentQuestionCard SurveyQuestionCard sem contador e sem evidência
                  • SurveyDependentQuestionsStackWidget recursão · sem limite de profundidade
          • SurveyPaginationFooterWidget escondido com teclado aberto · canAdvance
            • SurveyFinishConfirmationModalContent modal · devolve bool → submit(visitSfid)
          • SurveyExitConfirmationModalContent modal do _handleBack · devolve bool → sai e descarta

O botão de voltar é interceptado: fora da primeira página ele recua uma pergunta; na primeira, com algo preenchido, abre o modal de saída; sem nada preenchido, sai. Depois do envio bem-sucedido, a Page mostra o aviso e volta — as duas ConectaNotice e a navegação ficam no widget (o disparo do envio, no Notifier).The back button is intercepted: off the first page it steps one question back; on the first page, with something filled in, it opens the exit modal; with nothing filled in, it leaves. After a successful submit, the Page shows the notice and goes back — both ConectaNotice calls and the navigation stay in the widget (the submit trigger stays in the Notifier).El botón de volver es interceptado: fuera de la primera página retrocede una pregunta; en la primera, con algo completado, abre el modal de salida; sin nada completado, sale. Tras el envío exitoso, la Page muestra el aviso y vuelve — las dos ConectaNotice y la navegación quedan en el widget (el disparo del envío, en el Notifier).

Notas por mercadoMarket notesNotas por mercado

Pesquisas é dirigido por configuração de mercado (End Market Configuration) em dois pontos independentes: o atalho na grade de ferramentas da visita e o bloco surveyConfig, que decide o status enviado no upload. Os dois gates de código (tipo de sincronização e tipo de transação) declaram os mesmos três mercados:Surveys is driven by market configuration (End Market Configuration) at two independent points: the shortcut in the visit tools grid and the surveyConfig block, which decides the status sent on upload. Both code gates (sync type and transaction type) declare the same three markets:Encuestas se rige por configuración de mercado (End Market Configuration) en dos puntos independientes: el atajo en la grilla de herramientas de la visita y el bloque surveyConfig, que decide el estado enviado en el upload. Los dos gates de código (tipo de sincronización y tipo de transacción) declaran los mismos tres mercados:

BRx CLx ZAx AR PY PE
disponívelavailabledisponible presente, desligadopresent, offpresente, apagado ausenteabsentausente

Atalhos na grade de ferramentas da visitaShortcuts in the visit tools gridAtajos en la grilla de herramientas de la visita

ChaveKeyClaveBRCLZAARPYPE
visitDetailConfigxxx
modules[visit_detail_tools_grid].isVisiblexxx
details[surveys].isVisiblexxx
details[competitor_actions].isVisiblex
posição do tile de pesquisassurveys tile positionposición del tile de encuestas8/97/105/9

O detalhe do tile tem exatamente 2 chaves no JSON (moduleDetailName e isVisible) — ordem é posicional, e ícone e rótulo vêm do código, não da configuração.The tile's detail has exactly 2 keys in the JSON (moduleDetailName and isVisible) — order is positional, and icon and label come from code, not from configuration.El detalle del tile tiene exactamente 2 claves en el JSON (moduleDetailName e isVisible) — el orden es posicional, y el ícono y la etiqueta vienen del código, no de la configuración.

surveyConfig

ChaveKeyClaveBRCLZAARPYPE
completesResultOnUploadfalsefalsetrue

É a única chave do bloco. Efeito real, medido no código: muda exatamente um campo do payload — surveyStatus vai como "In Progress" (BR/CL) ou "Complete" (ZA). Não altera endpoint, serviceName, fatiamento de evidências, número de linhas de detalhe, nem qualquer estado local ou de UI. Chave ausente resolve para false por default duplo (mapper e entity).It is the block's only key. Real effect, measured in code: it changes exactly one payload field — surveyStatus goes as "In Progress" (BR/CL) or "Complete" (ZA). It doesn't change the endpoint, the serviceName, evidence chunking, the number of detail rows, or any local or UI state. A missing key resolves to false through a double default (mapper and entity).Es la única clave del bloque. Efecto real, medido en el código: cambia exactamente un campo del payload — surveyStatus va como "In Progress" (BR/CL) o "Complete" (ZA). No altera el endpoint, el serviceName, el corte de evidencias, la cantidad de filas de detalle, ni ningún estado local o de UI. Clave ausente resuelve a false por default doble (mapper y entity).

Pendências de encerramento de visitaVisit-end pending itemsPendientes de cierre de visita

ChaveKeyClaveBRCLZAARPYPE
visitEndConfigxxx
categories[mandatory_surveys]xx
isVisibletruetrue
isBlockingtruetrue
allowedResourceTypes
categories[optional_surveys]xx
isVisibletruetrue
isBlockingfalsefalse
allowedResourceTypes

¹ Os 6 tipos de representante, idênticos nas duas categorias e nos dois mercados: Pre-sales Rep, Prompt-sales Rep, Universal Rep, Delivery Rep, Telesales Analyst, Web Agent - Direct. Lista vazia significaria "todos os tipos". mandatory_surveys é a única categoria bloqueante de todo o arquivo de configuração. O Chile tem o bloco, mas com apenas duas categorias (tarefas pendentes e transações não sincronizadas) — pesquisa não entra no encerramento lá.¹ The 6 rep types, identical in both categories and both markets: Pre-sales Rep, Prompt-sales Rep, Universal Rep, Delivery Rep, Telesales Analyst, Web Agent - Direct. An empty list would mean "all types". mandatory_surveys is the only blocking category in the whole configuration file. Chile has the block, but with only two categories (pending tasks and unsynced transactions) — surveys don't take part in visit end there.¹ Los 6 tipos de representante, idénticos en las dos categorías y en los dos mercados: Pre-sales Rep, Prompt-sales Rep, Universal Rep, Delivery Rep, Telesales Analyst, Web Agent - Direct. Lista vacía significaría "todos los tipos". mandatory_surveys es la única categoría bloqueante de todo el archivo de configuración. Chile tiene el bloque, pero con solo dos categorías (tareas pendientes y transacciones no sincronizadas) — la encuesta no entra en el cierre allí.

Frescor do dado e gates de códigoData freshness and code gatesFrescura del dato y gates de código

ChaveKeyClaveBRCLZAARPYPE
dataFreshnessConfigxxx
ttlSecondsByType.surveys360036003600
defaultTtlSeconds300300300
sweepIntervalSeconds606060
DataSyncType.surveys.enabledMarketsxxx
DispatcherType.survey.enabledMarketsxxx

1 hora de TTL põe pesquisas na faixa média (12× o default), junto com comunicados e o representante — bem acima de visitas e pedidos (10 min) e bem abaixo de catálogos (24 h). O sweep é o único componente que força o caminho remoto sozinho.A 1-hour TTL puts surveys in the middle tier (12× the default), alongside communications and the rep — well above visits and orders (10 min) and well below catalogs (24 h). The sweep is the only component that forces the remote path on its own.1 hora de TTL pone a las encuestas en la franja media (12× el default), junto con comunicados y el representante — bien arriba de visitas y pedidos (10 min) y bien abajo de catálogos (24 h). El sweep es el único componente que fuerza el camino remoto por sí solo.

Mocks por mercadoMocks per marketMocks por mercado

Arquivo (pesquisas)File (surveys)Archivo (encuestas)BRCLZAARPYPE
{mercado}_surveys.json313{}{}{}
{mercado}_real_surveys.json226

A flag de mock real está ligada hoje, então o caminho normal usa os arquivos _real_; onde eles não existem, o carregador devolve {} em silêncio. Os arquivos sintéticos de AR/PY/PE são objetos vazios de propósito — existem para o carregador não falhar.The real-mock flag is on today, so the normal path uses the _real_ files; where they don't exist, the loader silently returns {}. AR/PY/PE's synthetic files are empty objects on purpose — they exist so the loader doesn't fail.La flag de mock real está activa hoy, así que el camino normal usa los archivos _real_; donde no existen, el cargador devuelve {} en silencio. Los archivos sintéticos de AR/PY/PE son objetos vacíos a propósito — existen para que el cargador no falle.

BR

Duas portas de entradaTwo doors inDos puertas de entrada É o único mercado com o tile Ações de concorrência na grade da visita, que abre a mesma tela filtrada em survey_competitor_actions. Também é o mercado com mais categorias de encerramento de visita (6), duas delas de pesquisa. O mock real do Brasil traz um censo de concorrência e um checklist obrigatório de encerramento. It is the only market with the Competitor actions tile in the visit grid, which opens the same screen filtered on survey_competitor_actions. It is also the market with the most visit-end categories (6), two of them surveys. Brazil's real mock brings a competitor census and a mandatory closing checklist. Es el único mercado con el tile Acciones de competencia en la grilla de la visita, que abre la misma pantalla filtrada en survey_competitor_actions. También es el mercado con más categorías de cierre de visita (6), dos de ellas de encuesta. El mock real de Brasil trae un censo de competencia y un checklist obligatorio de cierre.

CL

Sem pesquisa no encerramentoNo survey at visit endSin encuesta en el cierre Tem o atalho na grade da visita e o mesmo status de upload do Brasil, mas o encerramento de visita não lista pesquisas — logo, aqui nada bloqueia o fim da visita por pesquisa pendente. O mock sintético é o mais magro dos três (uma pesquisa) e o mock real traz uma categoria que o app não reconhece (ver Pendências). It has the visit grid shortcut and the same upload status as Brazil, but visit end does not list surveys — so nothing here blocks closing a visit over a pending survey. Its synthetic mock is the thinnest of the three (one survey) and its real mock carries a category the app doesn't recognise (see Pending items). Tiene el atajo en la grilla de la visita y el mismo estado de upload que Brasil, pero el cierre de visita no lista encuestas — por lo tanto, aquí nada bloquea el fin de la visita por encuesta pendiente. Su mock sintético es el más delgado de los tres (una encuesta) y su mock real trae una categoría que la app no reconoce (ver Pendientes).

ZA

Resultado enviado como concluídoResult sent as completeResultado enviado como completo Único mercado com completesResultOnUpload: true — o app declara o resultado como "Complete" no upload, enquanto BR/CL enviam "In Progress" e deixam a finalização para o processo do backend. Tem as duas categorias de encerramento (obrigatórias bloqueantes) e o mock real mais rico dos três, com seis pesquisas e o único uso de hasPromotion: true — campo que o app ainda não usa (ver Pendências). The only market with completesResultOnUpload: true — the app declares the result as "Complete" on upload, while BR/CL send "In Progress" and leave finalization to the backend process. It has both visit-end categories (mandatory ones blocking) and the richest real mock of the three, with six surveys and the only use of hasPromotion: true — a field the app still doesn't use (see Pending items). Único mercado con completesResultOnUpload: true — la app declara el resultado como "Complete" en el upload, mientras BR/CL envían "In Progress" y dejan la finalización al proceso del backend. Tiene las dos categorías de cierre (obligatorias bloqueantes) y el mock real más rico de los tres, con seis encuestas y el único uso de hasPromotion: true — campo que la app todavía no usa (ver Pendientes).

ARPYPE

AR · PY · PE — sem a featureAR · PY · PE — feature absentAR · PY · PE — sin la feature Existem como mercados do app, mas com configuração mínima: o EMC deles tem apenas quatro blocos de topo, e nem surveyConfig nem visitDetailConfig estão entre eles. Sem a grade de ferramentas da visita não há atalho para chegar à tela, e sem visitEndConfig não há pendência de pesquisa no encerramento. Os dois gates de código (sincronização e transação) também não os incluem, o mock sintético é um objeto vazio e não existe mock real. Nada falha — a feature simplesmente não existe nesses mercados. They exist as app markets, but with minimal configuration: their EMC has only four top-level blocks, and neither surveyConfig nor visitDetailConfig is among them. With no visit tools grid there is no shortcut to reach the screen, and with no visitEndConfig there is no survey pending item at visit end. Both code gates (sync and transaction) exclude them too, the synthetic mock is an empty object and there is no real mock. Nothing fails — the feature simply doesn't exist in those markets. Existen como mercados de la app, pero con configuración mínima: su EMC tiene apenas cuatro bloques de tope, y ni surveyConfig ni visitDetailConfig están entre ellos. Sin la grilla de herramientas de la visita no hay atajo para llegar a la pantalla, y sin visitEndConfig no hay pendiente de encuesta en el cierre. Los dos gates de código (sincronización y transacción) tampoco los incluyen, el mock sintético es un objeto vacío y no existe mock real. Nada falla — la feature simplemente no existe en esos mercados.

Pendências / roadmapPending items / roadmapPendientes / roadmap

  • Sincronização incremental não implementada. dateReference e lastModifiedDate existem no request do proto e como parâmetros do datasource, mas nenhum caller os passa — todo fetch remoto traz o catálogo inteiro do mercado e regrava o cache do zero. O gancho de delta-sync está no contrato e inerte no app.Incremental sync not implemented. dateReference and lastModifiedDate exist on the proto request and as datasource parameters, but no caller passes them — every remote fetch brings the market's whole catalog and rewrites the cache from scratch. The delta-sync hook is in the contract and inert in the app.Sincronización incremental no implementada. dateReference y lastModifiedDate existen en el request del proto y como parámetros del datasource, pero ningún caller los pasa — todo fetch remoto trae el catálogo entero del mercado y regraba el caché desde cero. El gancho de delta-sync está en el contrato e inerte en la app.
  • hasPromotion é recebido, persistido e nunca usado. O campo atravessa as quatro camadas e é o único uso de promoção do contrato, mas não há badge na lista (nem chave de tradução para ele) e o builder do envio passa hasPromotion: false fixo — logo o serviceName nunca ganha o prefixo de promoção, mesmo numa pesquisa marcada como promocional. Hoje só os mocks da África do Sul o preenchem.hasPromotion is received, persisted and never used. The field crosses all four layers and is the contract's only promotion hook, but there is no badge in the list (nor a translation key for one) and the submit builder passes a hardcoded hasPromotion: false — so the serviceName never gets the promotion prefix, even on a survey flagged as promotional. Today only the South Africa mocks fill it in.hasPromotion se recibe, se persiste y nunca se usa. El campo atraviesa las cuatro capas y es el único gancho de promoción del contrato, pero no hay badge en la lista (ni clave de traducción para uno) y el builder del envío pasa hasPromotion: false fijo — por lo tanto el serviceName nunca recibe el prefijo de promoción, incluso en una encuesta marcada como promocional. Hoy solo los mocks de Sudáfrica lo completan.
  • Tipo de pergunta desconhecido vira texto livre, em silêncio. SurveyQuestionType.fromProto cai em text quando não reconhece o valor e não registra o log de enum não mapeado que os outros parsers do projeto registram. O mock real do Brasil já tem uma pergunta com "Number", que hoje é renderizada como campo de texto em vez de numérica — sem nenhum sinal para quem depura.An unknown question type silently becomes free text. SurveyQuestionType.fromProto falls back to text when it doesn't recognise the value and does not record the unmapped-enum log that the project's other parsers record. Brazil's real mock already has a question with "Number", which today renders as a text field instead of numeric — with no signal for whoever is debugging.Un tipo de pregunta desconocido se vuelve texto libre, en silencio. SurveyQuestionType.fromProto cae en text cuando no reconoce el valor y no registra el log de enum no mapeado que los otros parsers del proyecto registran. El mock real de Brasil ya tiene una pregunta con "Number", que hoy se renderiza como campo de texto en vez de numérico — sin ninguna señal para quien depura.
  • Categoria desconhecida esconde a pesquisa — e quebra em debug. SurveyCategory.fromString compara com igualdade exata e, ao falhar, loga e dispara assert(false). Como a lista só mostra as duas categorias conhecidas, qualquer outro valor faz a pesquisa desaparecer da tela. O mock real do Chile já traz "survey_action", uma pesquisa inalcançável hoje.An unknown category hides the survey — and crashes in debug. SurveyCategory.fromString compares with exact equality and, on failure, logs and fires assert(false). Since the list only shows the two known categories, any other value makes the survey vanish from the screen. Chile's real mock already carries "survey_action", a survey that is unreachable today.Una categoría desconocida esconde la encuesta — y rompe en debug. SurveyCategory.fromString compara con igualdad exacta y, al fallar, loguea y dispara assert(false). Como la lista solo muestra las dos categorías conocidas, cualquier otro valor hace que la encuesta desaparezca de la pantalla. El mock real de Chile ya trae "survey_action", una encuesta inalcanzable hoy.
  • Pergunta dependente numérica não tem faixa. DependentQuestion tem 5 campos no proto e não inclui numberRangeStart/numberRangeEnd; o widget que a renderiza passa 0 e 0, e o predicado trata "faixa 0–0" como ausência de faixa. Resultado: qualquer número é aceito numa dependente, sem validação. Dependentes também não podem exigir evidência, pela mesma razão.A numeric dependent question has no range. DependentQuestion has 5 fields in the proto and doesn't include numberRangeStart/numberRangeEnd; the widget rendering it passes 0 and 0, and the predicate treats "range 0–0" as no range. Result: any number is accepted on a dependent, with no validation. Dependents can't require evidence either, for the same reason.Una pregunta dependiente numérica no tiene rango. DependentQuestion tiene 5 campos en el proto y no incluye numberRangeStart/numberRangeEnd; el widget que la renderiza pasa 0 y 0, y el predicado trata "rango 0–0" como ausencia de rango. Resultado: cualquier número se acepta en una dependiente, sin validación. Las dependientes tampoco pueden exigir evidencia, por la misma razón.
  • Envio offline não é enfileirado. A fila de reenvio automático do Dispatcher atende só transações de visita; a de pesquisa vai direto ao transporte, falha e é gravada como erro — não entra no flush quando a rede volta. O reenvio manual existe (o payload é preservado no registro em erro), mas o tipo é marcado como "reenvio pode duplicar", então a operação não é idempotente do lado do backend.Offline submission isn't queued. The Dispatcher's automatic retry queue only serves visit transactions; the survey one goes straight to transport, fails and is recorded as an error — it doesn't enter the flush when the network returns. Manual resend exists (the payload is preserved on the error record), but the type is flagged as "resend may duplicate", so the operation isn't idempotent on the backend side.El envío offline no se encola. La cola de reenvío automático del Dispatcher atiende solo transacciones de visita; la de encuesta va directo al transporte, falla y se graba como error — no entra en el flush cuando la red vuelve. El reenvío manual existe (el payload se preserva en el registro en error), pero el tipo está marcado como "el reenvío puede duplicar", así que la operación no es idempotente del lado del backend.
  • Não há marcador local de "respondida" — e o desconto no encerramento de visita está inerte. Depois de um envio bem-sucedido, nada é gravado no domínio de pesquisas: a pesquisa de resposta única continua na lista até a próxima sincronização trazer resultsQuantity atualizado, e nada impede respondê-la de novo nesse intervalo. As pendências de encerramento de visita tentam descontar as respondidas lendo o histórico de despachos, mas comparam o sfid da pesquisa com o transactionReference do despacho — que é um UUID gerado no momento do envio, e por isso nunca casa. Como mandatory_surveys é a única categoria bloqueante de todo o arquivo de configuração (Brasil e África do Sul), uma pesquisa obrigatória mantém o botão de finalizar desabilitado mesmo depois de respondida com sucesso. Correção barata: usar o sfid da pesquisa como transactionReference — é o único builder do projeto que gera esse identificador na hora, em vez de usar um identificador de domínio.There is no local "answered" marker — and the visit-end discount is inert. After a successful submit, nothing is written to the surveys domain: a one-time survey stays in the list until the next sync brings an updated resultsQuantity, and nothing stops it from being answered again in that window. The visit-end pending items try to discount answered ones by reading the dispatch history, but they compare the survey's sfid against the dispatch's transactionReference — which is a UUID generated at submit time, so it never matches. Since mandatory_surveys is the only blocking category in the whole configuration file (Brazil and South Africa), a mandatory survey keeps the finish button disabled even after being answered successfully. Cheap fix: use the survey's sfid as the transactionReference — this is the project's only builder that generates that identifier on the spot instead of using a domain identifier.No hay marcador local de "respondida" — y el descuento en el cierre de visita está inerte. Tras un envío exitoso, nada se graba en el dominio de encuestas: la encuesta de respuesta única sigue en la lista hasta que la próxima sincronización traiga resultsQuantity actualizado, y nada impide responderla de nuevo en ese intervalo. Las pendientes de cierre de visita intentan descontar las respondidas leyendo el historial de despachos, pero comparan el sfid de la encuesta con el transactionReference del despacho — que es un UUID generado en el momento del envío, y por eso nunca coincide. Como mandatory_surveys es la única categoría bloqueante de todo el archivo de configuración (Brasil y Sudáfrica), una encuesta obligatoria mantiene el botón de finalizar deshabilitado incluso después de responderla con éxito. Corrección barata: usar el sfid de la encuesta como transactionReference — es el único builder del proyecto que genera ese identificador en el momento, en vez de usar un identificador de dominio.
  • Dúvida de contrato, a confirmar com o backend: nas respostas de texto, número e data, o campo questionAnswerOption do detalhe é preenchido com a primeira alternativa da pergunta (ou vazio, quando não há alternativa). Como essas perguntas normalmente não têm alternativas, o valor é quase sempre vazio — mas onde houver, será um identificador que o representante não escolheu.Contract question, to confirm with the backend: on text, numeric and date answers, the detail's questionAnswerOption field is filled with the question's first option (or empty, when there is none). Since those questions normally have no options, the value is almost always empty — but where there is one, it will be an identifier the rep didn't choose.Duda de contrato, a confirmar con el backend: en las respuestas de texto, número y fecha, el campo questionAnswerOption del detalle se completa con la primera alternativa de la pregunta (o vacío, cuando no hay alternativa). Como esas preguntas normalmente no tienen alternativas, el valor es casi siempre vacío — pero donde haya, será un identificador que el representante no eligió.
  • Rótulo do cabeçalho errado no Brasil. A tradução brasileira de surveys_section_header diz "Pesquisa de Concorrência" — texto idêntico ao do rótulo de ações de concorrência — então a lista genérica aparece com o título da outra categoria. Os outros cinco mercados têm o texto genérico correto.Wrong header label in Brazil. The Brazilian translation of surveys_section_header reads "Pesquisa de Concorrência" — text identical to the competitor-actions label — so the generic list shows up with the other category's title. The other five markets have the correct generic wording.Rótulo del encabezado equivocado en Brasil. La traducción brasileña de surveys_section_header dice "Pesquisa de Concorrência" — texto idéntico al rótulo de acciones de competencia — así que la lista genérica aparece con el título de la otra categoría. Los otros cinco mercados tienen el texto genérico correcto.
  • Estado carregado sem consumidor. O State da lista carrega visibleModules e expõe getModule/visibleDetailTypes, mas nenhum widget da feature os usa hoje — a configuração de detalhe da visita é buscada e descartada. Na mesma linha, SurveyCategoryUx.headerIcon não tem chamador e um predicado do State recebe um parâmetro que não usa.Loaded state with no consumer. The list State loads visibleModules and exposes getModule/visibleDetailTypes, but no widget in the feature uses them today — the visit detail configuration is fetched and discarded. In the same vein, SurveyCategoryUx.headerIcon has no caller and one State predicate takes a parameter it doesn't use.Estado cargado sin consumidor. El State de la lista carga visibleModules y expone getModule/visibleDetailTypes, pero ningún widget de la feature los usa hoy — la configuración de detalle de la visita se busca y se descarta. En la misma línea, SurveyCategoryUx.headerIcon no tiene llamador y un predicado del State recibe un parámetro que no usa.
  • Documentação de apoio defasada. O README dos mocks cita um arquivo de um mercado que não existe no app e afirma que a categoria de ações de concorrência é exclusiva do Brasil, embora os mocks da África do Sul a usem; e a versão anotada do arquivo de configuração de mercado marca as duas categorias de pesquisa como exclusivas da África do Sul (estão em BR também) e documenta uma chave actions que não existe nem no arquivo real nem no app.Stale supporting documentation. The mocks README cites a file for a market that doesn't exist in the app and claims the competitor-actions category is Brazil-only, although the South Africa mocks use it; and the annotated version of the market configuration file marks both survey categories as South Africa-only (they are in BR too) and documents an actions key that exists neither in the real file nor in the app.Documentación de apoyo desactualizada. El README de los mocks cita un archivo de un mercado que no existe en la app y afirma que la categoría de acciones de competencia es exclusiva de Brasil, aunque los mocks de Sudáfrica la usan; y la versión anotada del archivo de configuración de mercado marca las dos categorías de encuesta como exclusivas de Sudáfrica (están en BR también) y documenta una clave actions que no existe ni en el archivo real ni en la app.

Onde continuar lendoWhere to read nextDónde seguir leyendo A transação do envio está em 15 · SurveyResultUploadAPI. O atalho que abre esta tela e a visita que dá contexto a ela vivem em Detalhe da visita, cuja lista está em Visitas. O merge de pesquisas de uma visita ad hoc é descrito em Varejos. As pendências de encerramento (que contam pesquisas não respondidas) ficam na tela de encerramento de visita, ainda sem documento próprio. The submission transaction is in 15 · SurveyResultUploadAPI. The shortcut that opens this screen and the visit giving it context live in Visit detail, whose list is in Visits. The ad hoc visit survey merge is described in Retails. The visit-end pending items (which count unanswered surveys) live on the visit-end screen, still without a document of its own. La transacción del envío está en 15 · SurveyResultUploadAPI. El atajo que abre esta pantalla y la visita que le da contexto viven en Detalle de la visita, cuya lista está en Visitas. El merge de encuestas de una visita ad hoc se describe en Puntos de venta. Las pendientes de cierre (que cuentan encuestas no respondidas) están en la pantalla de cierre de visita, aún sin documento propio.