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 · Gerenciar equipeFeature · Manage staffFeature · Gestionar equipo

Gerenciar equipeManage staffGestionar equipo

A equipe do varejo vista e editada pelo representante de vendas durante a visita: lista de contatos (CRM) e, no Brasil, os cadastros do Conecta Você. É a única tela do app que cria, edita e exclui pessoas do varejo — cada gravação vira uma transação do Dispatcher. The retail's team as seen and edited by the sales rep during the visit: the contact list (CRM) and, in Brazil, the Conecta Você registrations. It is the only screen in the app that creates, edits and deletes retail people — every save becomes a Dispatcher transaction. El equipo del punto de venta visto y editado por el representante de ventas durante la visita: la lista de contactos (CRM) y, en Brasil, los registros de Conecta Você. Es la única pantalla de la app que crea, edita y elimina personas del punto de venta — cada guardado se convierte en una transacción del Dispatcher.

PúblicoAudiencePúblico
Representante · QA · Suporte · DevRep · QA · Support · DevRepresentante · QA · Soporte · Dev
Onde ficaWhereDónde
Detalhe do varejo → Gerenciar EquipeRetail detail → Manage staffDetalle del punto de venta → Gestionar equipo
RelacionadoRelatedRelacionado
AtualizadoUpdatedActualizado
28/07/20262026-07-28
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

Gerenciar equipe é o cadastro das pessoas de um varejo. O representante de vendas abre a tela a partir do detalhe do varejo, durante uma visita, e mantém quem trabalha ali: nome, cargo, telefone, e-mail, aniversário, quem é o contato principal. São duas telas: a lista (Equipe do Varejo) e o detalhe do colaborador, onde tudo é editado. Manage staff is the register of a retail's people. The sales rep opens it from the retail detail, during a visit, and maintains who works there: name, role, phone, e-mail, birthday, who the main contact is. There are two screens: the list (Retail Team) and the staff member detail, where everything is edited. Gestionar equipo es el registro de las personas de un punto de venta. El representante de ventas lo abre desde el detalle del punto de venta, durante una visita, y mantiene a quien trabaja allí: nombre, cargo, teléfono, correo, cumpleaños, quién es el contacto principal. Son dos pantallas: la lista (Equipo del Comercio) y el detalle del colaborador, donde todo se edita.

Quem trabalha no varejo?Who works at the retail?¿Quién trabaja en el comercio?

Um card por pessoa, com nome, cargo e as etiquetas de situação.One card per person, with name, role and status tags.Una tarjeta por persona, con nombre, cargo y las etiquetas de situación.

Cadastrar e corrigirRegister and fixRegistrar y corregir

Adicionar uma pessoa nova, corrigir dados de quem já existe ou excluir quem saiu.Add a new person, fix an existing one's data or delete whoever left.Agregar una persona nueva, corregir datos de quien ya existe o eliminar a quien salió.

Conecta Você (só BR)Conecta Você (BR only)Conecta Você (solo BR)

Uma segunda aba com os cadastros do programa, que podem ser ativados ou inativados.A second tab with the programme's registrations, which can be activated or inactivated.Una segunda pestaña con los registros del programa, que pueden activarse o inactivarse.

Exige visita iniciadaRequires a started visitRequiere visita iniciada A tela só abre se a visita daquele varejo estiver iniciada. Se não estiver, aparece um aviso perguntando se você quer iniciar a visita agora; recusando, a tela não abre. The screen only opens if that retail's visit is started. If it isn't, a prompt asks whether you want to start the visit now; if you decline, the screen doesn't open. La pantalla solo abre si la visita de ese punto de venta está iniciada. Si no lo está, aparece un aviso que pregunta si desea iniciar la visita ahora; al rechazar, la pantalla no abre.

Quais campos aparecem depende do mercadoWhich fields appear depends on the marketQué campos aparecen depende del mercado Não há formulário fixo: cada mercado declara na configuração quais campos são visíveis e quais são editáveis. Por isso o Brasil mostra "Nome completo" e a África do Sul mostra "Nome" + "Sobrenome" + três campos extras. A matriz completa está na seção Mercados. There is no fixed form: each market declares in configuration which fields are visible and which are editable. That's why Brazil shows "Full name" while South Africa shows "First name" + "Last name" plus three extra fields. The full matrix is in the Markets section. No hay formulario fijo: cada mercado declara en la configuración qué campos son visibles y cuáles editables. Por eso Brasil muestra "Nombre completo" y Sudáfrica muestra "Nombre" + "Apellido" más tres campos extra. La matriz completa está en la sección Mercados.

02

Como acessarHow to openCómo acceder

  1. Abra o detalhe do varejoOpen the retail detailAbra el detalle del punto de ventaPela lista de varejos ou pelo detalhe da visita, chegue à tela de dados do varejo.From the retail list or from the visit detail, reach the retail data screen.Desde la lista de puntos de venta o desde el detalle de la visita, llegue a la pantalla de datos del punto de venta.
  2. Toque em "Gerenciar Equipe"Tap "Manage staff"Toque "Gestionar equipo"O botão fica ao lado do campo Número de colaboradores. Aparece só nos mercados onde esse bloco está habilitado.The button sits next to the Number of employees field. It only appears in markets where that block is enabled.El botón está al lado del campo Número de colaboradores. Solo aparece en los mercados donde ese bloque está habilitado.
  3. Confirme a visita, se pedidoConfirm the visit, if askedConfirme la visita, si se solicitaSe a visita ainda não foi iniciada, o app pergunta se você quer iniciá-la. Só depois a lista abre.If the visit hasn't started yet, the app asks whether to start it. Only then does the list open.Si la visita aún no comenzó, la app pregunta si desea iniciarla. Solo entonces abre la lista.
  4. Toque num card para editarTap a card to editToque una tarjeta para editarCada card abre o detalhe do colaborador. O botão Adicionar no fim da lista abre o mesmo detalhe em branco, para cadastrar alguém novo.Each card opens the staff member detail. The Add button at the end of the list opens the same detail blank, to register someone new.Cada tarjeta abre el detalle del colaborador. El botón Agregar al final de la lista abre el mismo detalle en blanco, para registrar a alguien nuevo.
03

Estrutura da telaScreen structureEstructura de la pantalla

Lista — Equipe do VarejoList — Retail TeamLista — Equipo del Comercio

Data de sincronizaçãoSync dateFecha de sincronización
Linha no topo com a data/hora da última sincronização dos dados da visita. Mostra - quando não há.Top line with the date/time of the visit data's last sync. Shows - when there is none.Línea superior con la fecha/hora de la última sincronización de los datos de la visita. Muestra - cuando no hay.
Cabeçalho do varejoRetail headerEncabezado del punto de venta
Card com o nome do varejo, o código SAP e um marcador de inadimplência quando aplicável.Card with the retail name, the SAP code and an overdue marker when applicable.Tarjeta con el nombre del punto de venta, el código SAP y un marcador de morosidad cuando aplica.
Título da seçãoSection titleTítulo de la sección
"Equipe do Varejo"."Retail Team"."Equipo del Comercio".
Abas (condicionais)Tabs (conditional)Pestañas (condicionales)
Cadastro e Conecta Você. Só aparecem quando o mercado usa o programa e o varejo está inscrito nele; caso contrário a lista de contatos aparece direto, sem abas.Registration and Conecta Você. They only appear when the market uses the programme and the retail is enrolled in it; otherwise the contact list shows directly, without tabs.Registro y Conecta Você. Solo aparecen cuando el mercado usa el programa y el punto de venta está inscrito; si no, la lista de contactos aparece directamente, sin pestañas.
Cards de pessoaPerson cardsTarjetas de persona
Um por pessoa: etiquetas no topo, nome em destaque, cargo abaixo e uma seta à direita. Toque abre o detalhe.One per person: tags on top, name in bold, role below and a caret on the right. Tapping opens the detail.Una por persona: etiquetas arriba, nombre destacado, cargo abajo y una flecha a la derecha. Al tocar abre el detalle.
Lista vaziaEmpty listLista vacía
"Nenhum colaborador cadastrado", com o botão Adicionar ainda disponível abaixo."No staff registered", with the Add button still available below."Sin colaboradores registrados", con el botón Agregar todavía disponible abajo.
Botão AdicionarAdd buttonBotón Agregar
Botão circular com "+" no fim de cada aba. Sempre presente, mesmo com a lista vazia.Circular "+" button at the end of each tab. Always present, even with an empty list.Botón circular con "+" al final de cada pestaña. Siempre presente, incluso con la lista vacía.
Puxar para atualizarPull to refreshDeslizar para actualizar
Puxar a lista para baixo rebusca os dados da visita no servidor.Pulling the list down re-fetches the visit data from the server.Deslizar la lista hacia abajo vuelve a buscar los datos de la visita en el servidor.

Detalhe do colaboradorStaff member detailDetalle del colaborador

O detalhe tem duas variantes, escolhidas pela aba de origem — Dados do Colaborador (contato) e Dados Conecta Você (cadastro do programa). Em ambas, cada campo só aparece se o mercado o declarar visível:The detail has two variants, chosen by the originating tab — Employee Data (contact) and Conecta Você Data (programme registration). In both, each field appears only if the market declares it visible:El detalle tiene dos variantes, elegidas por la pestaña de origen — Datos del Colaborador (contacto) y Datos Conecta Você (registro del programa). En ambas, cada campo aparece solo si el mercado lo declara visible:

CampoFieldCampoVarianteVariantVarianteTipo de controleControl typeTipo de controlObservaçãoNoteObservación
Nome completoFull nameNombre completoContato · Conecta VocêContact · Conecta VocêContacto · Conecta VocêTextoTextTextoObrigatório. Ao enviar é quebrado em nome e sobrenome.Required. Split into first and last name on submit.Obligatorio. Al enviar se divide en nombre y apellido.
Nome / SobrenomeFirst name / Last nameNombre / ApellidoContatoContactContactoDois campos de textoTwo text fieldsDos campos de textoAlternativa ao nome completo (África do Sul). Ambos obrigatórios.Alternative to full name (South Africa). Both required.Alternativa al nombre completo (Sudáfrica). Ambos obligatorios.
Designação de contatoContact designationDesignación de contactoContatoContactContactoLista de seleçãoDropdownLista de selecciónOpções vêm da configuração do mercado. Não é obrigatório.Options come from market configuration. Not required.Las opciones vienen de la configuración del mercado. No es obligatorio.
Preferência de idiomaLanguage preferencePreferencia de idiomaContatoContactContactoLista de seleçãoDropdownLista de selecciónOpções vêm dos dados de referência do servidor.Options come from the server's reference data.Las opciones vienen de los datos de referencia del servidor.
Método de contato preferidoPreferred method of contactMétodo de contacto preferidoContatoContactContactoLista de seleçãoDropdownLista de selecciónCelular, telefone, e-mail ou SMS, conforme a configuração.Mobile, phone, e-mail or SMS, per configuration.Móvil, teléfono, correo o SMS, según la configuración.
CargoRoleCargoContato · Conecta VocêContact · Conecta VocêContacto · Conecta VocêLista de seleçãoDropdownLista de selecciónNo contato vem dos dados de referência; no Conecta Você são só Atendente e Gerente.For contacts it comes from reference data; for Conecta Você only Attendant and Manager.En el contacto viene de los datos de referencia; en Conecta Você solo Atendente y Gerente.
Contato principalMain contactContacto principalContatoContactContactoChave liga/desligaToggle switchInterruptorFica na mesma linha do cargo. Só um contato por varejo pode ser o principal.Sits on the same row as the role. Only one contact per retail can be the main one.Está en la misma fila que el cargo. Solo un contacto por punto de venta puede ser el principal.
Documento (CPF/RUT/ID)Document (CPF/RUT/ID)Documento (CPF/RUT/ID)Conecta VocêConecta VocêConecta VocêTexto numérico com máscaraMasked numeric textTexto numérico con máscaraObrigatório e validado por mercado (ver Mercados).Required and validated per market (see Markets).Obligatorio y validado por mercado (ver Mercados).
CelularCell phoneTeléfono móvilContato · Conecta VocêContact · Conecta VocêContacto · Conecta VocêTexto de telefone com máscaraMasked phone textTexto de teléfono con máscaraObrigatório e validado no formato do mercado.Required and validated in the market's format.Obligatorio y validado en el formato del mercado.
E-mailContato · Conecta VocêContact · Conecta VocêContacto · Conecta VocêTextoTextTextoObrigatório e validado como endereço de e-mail.Required and validated as an e-mail address.Obligatorio y validado como dirección de correo.
Status B2BB2B statusEstado B2BContatoContactContactoTexto somente leituraRead-only textTexto de solo lecturaAparece apenas em contatos já existentes; vem do servidor e não é editável.Shows only on existing contacts; comes from the server and is not editable.Aparece solo en contactos existentes; viene del servidor y no es editable.
Data de nascimentoDate of birthFecha de nacimientoContato · Conecta VocêContact · Conecta VocêContacto · Conecta VocêSeletor de calendárioCalendar pickerSelector de calendarioO calendário não permite datas que impliquem menos de 18 anos.The calendar does not allow dates implying less than 18 years of age.El calendario no permite fechas que impliquen menos de 18 años.

No fim da variante de contato ficam os botões Salvar Alterações e — só para quem já existe — Excluir Colaborador. Na variante Conecta Você ficam Salvar Alterações e, quando o cadastro já existe, Ativar ou Inativar. O botão de salvar só habilita depois de alguma mudança real no formulário.At the bottom of the contact variant sit the Save Changes and — only for existing records — Delete Employee buttons. In the Conecta Você variant there are Save Changes and, when the registration already exists, Activate or Inactivate. The save button only enables after a real change in the form.Al final de la variante de contacto están los botones Guardar Cambios y — solo para los ya existentes — Eliminar Colaborador. En la variante Conecta Você están Guardar Cambios y, cuando el registro ya existe, Activar o Inactivar. El botón de guardar solo se habilita tras un cambio real en el formulario.

04

Status e etiquetasStatuses & tagsEstados y etiquetas

Os cards mostram etiquetas coloridas. São de três famílias distintas — não se misturam:Cards show colored tags. They come from three distinct families — they don't mix:Las tarjetas muestran etiquetas de color. Son de tres familias distintas — no se mezclan:

Contatos — status do portal B2BContacts — B2B portal statusContactos — estado del portal B2B

AtivoActiveActivo Desativação pendentePending deactivationDesactivación pendiente Inativo · Desativado · sem informaçãoInactive · Deactivated · no informationInactivo · Desactivado · sin información

Além dessa, os contatos de um varejo B2B ganham uma etiqueta neutra B2B ao lado.On top of that, contacts of a B2B retail get a neutral B2B tag alongside.Además, los contactos de un punto de venta B2B reciben una etiqueta neutra B2B al lado.

Conecta Você — situação do cadastroConecta Você — registration stateConecta Você — situación del registro

AtivoActiveActivo Inativo · sem informaçãoInactive · no informationInactivo · sin información Não engajadoNot engagedNo engajado

"Não engajado" é uma etiqueta adicional, exibida somente quando o servidor informa explicitamente que a pessoa não está engajada no programa."Not engaged" is an additional tag, shown only when the server explicitly reports that the person is not engaged in the programme."No engajado" es una etiqueta adicional, mostrada solo cuando el servidor informa explícitamente que la persona no está engajada en el programa.

Cargos vêm do servidorRoles come from the serverLos cargos vienen del servidor O texto do cargo do contato (Clerk, Supervisor, Manager, Attendant, Owner, Primary Contact, Staff) é um código de negócio em inglês, vindo do servidor e exibido como está. É decisão de produto, não falta de tradução. The contact's role text (Clerk, Supervisor, Manager, Attendant, Owner, Primary Contact, Staff) is an English business code, coming from the server and shown as-is. This is a product decision, not a missing translation. El texto del cargo del contacto (Clerk, Supervisor, Manager, Attendant, Owner, Primary Contact, Staff) es un código de negocio en inglés, que viene del servidor y se muestra tal cual. Es una decisión de producto, no una falta de traducción.

05

AçõesActionsAcciones

São cinco ações, todas no detalhe do colaborador e todas com confirmação em modal antes de enviar. Depois de um envio bem-sucedido, o app mostra um aviso verde e volta para a lista; em erro, mostra um aviso vermelho e mantém o formulário preenchido.There are five actions, all in the staff member detail and all with a confirmation modal before sending. After a successful submit the app shows a green notice and returns to the list; on error it shows a red notice and keeps the form filled.Son cinco acciones, todas en el detalle del colaborador y todas con confirmación en modal antes de enviar. Tras un envío exitoso la app muestra un aviso verde y vuelve a la lista; en error muestra un aviso rojo y mantiene el formulario lleno.

AçãoActionAcciónQuando apareceWhen it appearsCuándo apareceO que aconteceWhat happensQué ocurre
Criar contatoCreate contactCrear contactoDetalhe aberto pelo botão Adicionar da aba Cadastro.Detail opened from the Add button on the Registration tab.Detalle abierto desde el botón Agregar de la pestaña Registro.Valida os campos obrigatórios, pede confirmação ("Criar Colaborador") e envia o cadastro. O contato passa a aparecer na lista.Validates the required fields, asks for confirmation ("Create Staff Member") and sends the registration. The contact starts showing in the list.Valida los campos obligatorios, pide confirmación ("Crear Colaborador") y envía el registro. El contacto empieza a aparecer en la lista.
Editar contatoEdit contactEditar contactoDetalhe aberto por um card existente.Detail opened from an existing card.Detalle abierto desde una tarjeta existente.Mesmo caminho, com a confirmação "Salvar Alterações".Same path, with the "Save Changes" confirmation.Mismo camino, con la confirmación "Guardar Cambios".
Excluir contatoDelete contactEliminar contactoSó em contato já existente.Only on an existing contact.Solo en contacto existente.Confirmação "Excluir Colaborador" (aviso de que a ação não pode ser desfeita) e envio da exclusão. Não valida o formulário."Delete Employee" confirmation (warning that it cannot be undone) and submission of the deletion. It does not validate the form.Confirmación "Eliminar Colaborador" (aviso de que no puede deshacerse) y envío de la eliminación. No valida el formulario.
Salvar Conecta VocêSave Conecta VocêGuardar Conecta VocêVariante Conecta Você (só Brasil).Conecta Você variant (Brazil only).Variante Conecta Você (solo Brasil).Valida nome, documento, celular e e-mail, confirma e envia dois pedidos em sequência: o cadastro e a situação (ativo por padrão).Validates name, document, cell phone and e-mail, confirms and sends two requests in sequence: the registration and the state (active by default).Valida nombre, documento, móvil y correo, confirma y envía dos solicitudes en secuencia: el registro y la situación (activo por defecto).
Ativar / InativarActivate / InactivateActivar / InactivarCadastro Conecta Você já existente e com situação conhecida.Existing Conecta Você registration with a known state.Registro Conecta Você existente y con situación conocida.O botão mostra o inverso da situação atual. Ao inativar com alterações pendentes no formulário, o app salva e inativa na mesma operação.The button shows the inverse of the current state. When inactivating with pending form changes, the app saves and inactivates in one operation.El botón muestra el inverso de la situación actual. Al inactivar con cambios pendientes en el formulario, la app guarda e inactiva en la misma operación.

Troca do contato principalSwitching the main contactCambio del contacto principal

Se você marcar Contato Principal em alguém e o varejo já tiver outro principal, o app avisa antes de salvar, nomeando quem perderá a marcação. Confirmando, o app envia dois pedidos: primeiro rebaixa o principal antigo, depois grava o novo. Se o rebaixamento falhar, nada é gravado. Nesse caminho não há o modal de "Salvar Alterações" — o aviso da troca já é a confirmação.If you tick Main Contact on someone and the retail already has another main contact, the app warns before saving, naming who will lose the flag. On confirmation the app sends two requests: it first demotes the old main contact, then saves the new one. If the demotion fails, nothing is saved. In this path there is no "Save Changes" modal — the switch warning is the confirmation.Si marca Contacto Principal en alguien y el punto de venta ya tiene otro principal, la app avisa antes de guardar, nombrando a quien perderá la marca. Al confirmar, la app envía dos solicitudes: primero degrada al principal anterior, luego graba el nuevo. Si la degradación falla, nada se graba. En este camino no hay modal de "Guardar Cambios" — el aviso del cambio ya es la confirmación.

Exige conexãoRequires connectionRequiere conexión Todas as gravações desta tela vão direto ao servidor — não há fila offline. Sem conexão, a ação falha com aviso vermelho e precisa ser reenviada pela Central de dados. E, ao voltar do detalhe, a lista não se atualiza sozinha: puxe para atualizar para ver a mudança. Every save on this screen goes straight to the server — there is no offline queue. With no connection the action fails with a red notice and must be re-sent from the Data Center. Also, on returning from the detail the list does not refresh by itself: pull to refresh to see the change. Todos los guardados de esta pantalla van directo al servidor — no hay cola offline. Sin conexión la acción falla con aviso rojo y debe reenviarse desde la Central de datos. Además, al volver del detalle la lista no se actualiza sola: deslice para actualizar y ver el cambio.

06

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

Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. A equipe do varejo não é um agregado próprio: ela vive aninhada dentro da Visit — VisitsEntity → VisitEntity.accountData → .staff → .contacts[] / .clerks[]. Logo a leitura é cache-only (projeção do cache da Visit) e a escrita é remote-first pelo Dispatcher, com o cache local mutado só depois do ack. São dois grafos distintos.Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. The retail team is not its own aggregate: it lives nested inside the Visit — VisitsEntity → VisitEntity.accountData → .staff → .contacts[] / .clerks[]. So reads are cache-only (a projection of the Visit cache) and writes are remote-first through the Dispatcher, with the local cache mutated only after the ack. Two distinct graphs.Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. El equipo del punto de venta no es un agregado propio: vive anidado dentro de la Visit — VisitsEntity → VisitEntity.accountData → .staff → .contacts[] / .clerks[]. Por eso la lectura es cache-only (proyección del caché de la Visit) y la escritura es remote-first por el Dispatcher, con el caché local mutado solo tras el ack. Son dos grafos distintos.

1 · Leitura (projeção do cache da Visit)1 · Read (projection of the Visit cache)1 · Lectura (proyección del caché de la Visit)

Nenhum RPC próprio: os contatos chegam no getVisitList da Visit e são gravados no ObjectBox como parte dela. As duas telas leem só do cache (getCachedVisitBySfid) — o único fetch remoto acontece no pull-to-refresh da lista, que passa DataSourceType.remote para o container de visitas.No RPC of its own: contacts arrive in the Visit's getVisitList and are written to ObjectBox as part of it. Both screens read from cache only (getCachedVisitBySfid) — the only remote fetch happens on the list's pull-to-refresh, which passes DataSourceType.remote to the visits container.Ningún RPC propio: los contactos llegan en el getVisitList de la Visit y se graban en ObjectBox como parte de ella. Ambas pantallas leen solo del caché (getCachedVisitBySfid) — el único fetch remoto ocurre en el pull-to-refresh de la lista, que pasa DataSourceType.remote al container de visitas.

  • VisitReplygRPC proto · getVisitList
    • toVisitsDTOVisitsDTO → StaffDTODTO · Freezed
      • toDomainStaffEntitydomain
        • toModelStaffModelObjectBox · Account.staff
          • toDomainStaffEntitydomain · cache
            • getCachedVisitBySfidGetStaffsByVisitUseCasevisit.accountData.staff
              • watchManageStaffNotifier + State
                • → UIManageStaffPage
                  • goToStaffDetailsStaffDetailsPageStaffDetailsNotifier + State

2 · Escrita (remote-first via Dispatcher)2 · Write (remote-first via the Dispatcher)2 · Escritura (remote-first vía Dispatcher)

O StaffOrchestrator é o dono da escrita: monta o envelope com o builder, envia pelo DispatcherOrchestrator e só grava no ObjectBox depois do ack de sucesso. São três transações distintas — o contato (RetailerUploadAPI, contrato wire em Account Contact Upload) e as duas do Conecta Você (ContactConectaVoce e ContactUpdateStatus).The StaffOrchestrator owns the write: it builds the envelope with the builder, sends it through the DispatcherOrchestrator and only writes to ObjectBox after a successful ack. There are three distinct transactions — the contact one (RetailerUploadAPI, wire contract in Account Contact Upload) and the two Conecta Você ones (ContactConectaVoce and ContactUpdateStatus).El StaffOrchestrator es el dueño de la escritura: arma el envelope con el builder, lo envía por el DispatcherOrchestrator y solo graba en ObjectBox tras el ack de éxito. Son tres transacciones distintas — la del contacto (RetailerUploadAPI, contrato wire en Account Contact Upload) y las dos de Conecta Você (ContactConectaVoce y ContactUpdateStatus).

  • StaffDetailsNotifiersaveContact · deleteContact · saveClerk · setClerkStatus
    • composeStaffOrchestratorContactEntity / ClerkEntity
      • buildBuild*DispatcherPayloadUseCaseDispatcherEnvelope
        • submitSubmit*UseCase → DispatcherOrchestratorsendTransaction
          • ack okSaveStaffUpdateUseCaseRetailUpdateRepository
            • upsert / removeVisitLocalDataSourceObjectBox · ContactModel / ClerkModel
07

Modelo de dadosData modelModelo de datos

A mesma equipe 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 quase em todas as camadas; muda pouco (enums tipados, datas parseadas, dois renames de id, relações). O fetch é write-through: todo retorno do getVisitList é gravado no ObjectBox e a UI passa a ler do cache.The same team 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 nearly the same across layers; little changes (typed enums, parsed dates, two id renames, relations). Fetch is write-through: every getVisitList response is written to ObjectBox and the UI then reads from cache.El mismo equipo 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 se mantienen casi en todas las capas; cambia poco (enums tipados, fechas parseadas, dos renames de id, relaciones). El fetch es write-through: toda respuesta de getVisitList se graba en ObjectBox y la UI lee del caché.

Não há container próprio nem lastSyncAt próprio: a raiz é Staff (3 campos), pendurada em Account.staff dentro de Visit, e o lastSyncAt exibido vem do container VisitsEntity — gerado no mapper com DateTimeUtils.now(), porque o proto não traz o campo. Staff tem duas coleções: Contact (14 campos, o cadastro CRM) e Clerk (9 campos, o cadastro Conecta Você). Os enums só existem tipados na Entity; em Proto/DTO/Model trafegam como String. A seguir, na ordem: o proto que transporta tudo, as estruturas de dados campo-a-campo por camada, e os mappers que ligam as camadas.There is no container of its own and no lastSyncAt of its own: the root is Staff (3 fields), hanging off Account.staff inside Visit, and the displayed lastSyncAt comes from the VisitsEntity container — generated in the mapper with DateTimeUtils.now(), because the proto does not carry the field. Staff has two collections: Contact (14 fields, the CRM record) and Clerk (9 fields, the Conecta Você record). Enums are only typed in the Entity; in Proto/DTO/Model they travel as String. Next, in order: the proto that carries everything, the field-by-field data structures per layer, and the mappers that link the layers.No hay container propio ni lastSyncAt propio: la raíz es Staff (3 campos), colgada de Account.staff dentro de Visit, y el lastSyncAt mostrado viene del container VisitsEntity — generado en el mapper con DateTimeUtils.now(), porque el proto no trae el campo. Staff tiene dos colecciones: Contact (14 campos, el registro CRM) y Clerk (9 campos, el registro Conecta Você). Los enums solo están tipados en la Entity; en Proto/DTO/Model viajan como String. A continuación, en orden: el proto que transporta todo, las estructuras de datos campo a campo por capa, y los mappers que unen las capas.

Proto

VisitConectaRep.proto · proto3 · package mn.bat.conectarep.streambridge. Um serviço (VisitConectaRepService), um método unário — não há RPC de contato:One service (VisitConectaRepService), a single unary method — there is no contact RPC:Un servicio (VisitConectaRepService), un método unario — no hay RPC de contacto:

getVisitListunary
MétodoMethodMétodo

rpc getVisitList(VisitRequest) returns (VisitReply)

path /mn.bat.conectarep.streambridge.VisitConectaRepService/getVisitList

Request · VisitRequest
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
lastModifiedDate
string · #3 · optional (não usado hoje)optional (not used today)optional (no usado hoy)
Reply · VisitReply

repeated Visit visitLista equipe chega em Visit.accountData (#3) → Account.staff (#42) → Staff.contacts / Staff.clerks. Os campos estão detalhados nas Estruturas de dados abaixo.the team arrives at Visit.accountData (#3) → Account.staff (#42) → Staff.contacts / Staff.clerks. The fields are detailed in Data structures below.el equipo llega en Visit.accountData (#3) → Account.staff (#42) → Staff.contacts / Staff.clerks. Los campos están detallados en Estructuras de datos abajo.

As opções de role e languagePreference do formulário vêm de um segundo proto: ReferenceDataConectaRep.proto · rpc getReferenceData(ReferenceDataRequest) returns (ReferenceDataReply), campos repeated string contactRoles = 11 e repeated string languagePreferences = 12.The form's role and languagePreference options come from a second proto: ReferenceDataConectaRep.proto · rpc getReferenceData(ReferenceDataRequest) returns (ReferenceDataReply), fields repeated string contactRoles = 11 and repeated string languagePreferences = 12.Las opciones de role y languagePreference del formulario vienen de un segundo proto: ReferenceDataConectaRep.proto · rpc getReferenceData(ReferenceDataRequest) returns (ReferenceDataReply), campos repeated string contactRoles = 11 y repeated string languagePreferences = 12.

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; a célula em accent marca onde o tipo primeiro muda (relação ToMany/ToOne no Model, parse de data no Model, enum na Entity, rename onde ocorre).One dropdown per structure, nested by hierarchy (lines link parent and children). Each table has one column per layer — Proto · DTO · Model · Entity; the accent cell marks where the type first changes (ToMany/ToOne relation in the Model, date parse in the Model, enum in the Entity, rename where it happens).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; la celda en accent marca dónde primero cambia el tipo (relación ToMany/ToOne en el Model, parseo de fecha en el Model, enum en la Entity, rename donde ocurre).

  • Visits VisitsEntity · container · lastSyncAt · VisitEntity → AccountData.staff 2 campos
    CampoProtoDTOModelEntity
    lastSyncAtDateTimeDateTimeDateTime
    visitsrepeated Visit visitListList<VisitDTO> visitsToMany<VisitModel>List<VisitEntity>
  • Staff Account.staff · raiz 3 campos
    CampoProtoDTOModelEntity
    contactsrepeated ContactList<ContactDTO>ToMany<ContactModel>List<ContactEntity>
    clerksrepeated ClerkList<ClerkDTO>ToMany<ClerkModel>List<ClerkEntity>
    isRegisteredInConectaVoceboolboolboolbool
    • Contact Staff.contacts[] · CRM 14 campos
      CampoProtoDTOModelEntity
      idstring #1StringcontactId StringString
      namestring #2StringStringString
      rolestring #3String?String?ContactRole?
      phonestring #4String?String?String?
      emailstring #5String?String?String?
      birthdatestring #6String?DateTime?DateTime?
      taxIdstring #7String?String?String?
      isMainContactbool #8boolboolbool
      b2bPortalStatusstring #9String?String?B2bPortalStatus?
      statusstring #10StringStringString
      contactDesignationstring #11String?String?ContactDesignation?
      languagePreferencestring #12String?String?LanguagePreference?
      preferredMethodOfContactstring #13String?String?PreferredContactMethod?
      isRewardNominatedbool #14bool?bool?bool?
    • Clerk Staff.clerks[] · Conecta Você 9 campos
      CampoProtoDTOModelEntity
      idstring #1StringclerkId StringString
      taxIdstring #2String?String?String?
      namestring #3StringStringString
      programLayerCodestring #4String?String?programLayer ClerkProgramLayer
      phonestring #5String?String?String?
      emailstring #6String?String?String?
      birthdaystring #7String?DateTime?DateTime?
      statusstring #8String?String?ClerkStatus?
      isEngagedbool #9bool?bool?bool?
  • StaffConfig EMC · accountEditionConfig.staffConfig 11 campos

    Configuração de mercado (não é dado do varejo, não tem proto nem ObjectBox) — decide quais campos aparecem e o que vai no payload.Market configuration (not retail data, no proto and no ObjectBox) — decides which fields show and what goes in the payload.Configuración de mercado (no es dato del punto de venta, sin proto ni ObjectBox) — decide qué campos aparecen y qué va en el payload.

    CampoTipoTypeTipoDefaultDefaultDefault
    contactDesignationsList<String>[]
    preferredContactMethodsList<String>[]
    hasConectaVoceStaffboolfalse
    contactRecordTypeIdString""
    contactPreferredLanguageString""
    generatesMainContactCpidboolfalse
    sendsPreferredContactMethodboolfalse
    sendsContactDesignationAndLanguagePreferenceboolfalse
    deactivatesB2bStatusOnDeleteboolfalse
    contactFieldsList<StaffFieldConfig>[]
    clerkFieldsList<StaffFieldConfig>[]

    StaffFieldConfig = field StaffFieldType (default unknown) · isVisible bool (default true) · isEditable bool (default true). Métodos: contactFieldVisible, contactFieldEditable, clerkFieldVisible, clerkFieldEditablecampo ausente ⇒ false (escondido).Methods: contactFieldVisible, contactFieldEditable, clerkFieldVisible, clerkFieldEditableabsent field ⇒ false (hidden).Métodos: contactFieldVisible, contactFieldEditable, clerkFieldVisible, clerkFieldEditablecampo ausente ⇒ false (oculto).

Mappers

As conversões entre as camadas, todas como extension em data/mappers/visit/ (staff_mapper.dart, contact_mapper.dart, clerk_mapper.dart) — 5 direções por tipo:The conversions between layers, all as extensions in data/mappers/visit/ (staff_mapper.dart, contact_mapper.dart, clerk_mapper.dart) — 5 directions per type:Las conversiones entre capas, todas como extension en data/mappers/visit/ (staff_mapper.dart, contact_mapper.dart, clerk_mapper.dart) — 5 direcciones por tipo:

DireçãoDirectionDirecciónMétodoMethodMétodo
JSON → DTOstatic fromMap(Map) (campos obrigatórios caem para ""/false)(required fields fall back to ""/false)(campos obligatorios caen a ""/false)
Proto → DTOtoDTO() (guardas hasX(): valor-default vira null)(hasX() guards: a default value becomes null)(guardas hasX(): valor por defecto pasa a null)
DTO → EntitytoDomain() (resolve enums via fromLabel/fromString/fromWire/fromProgramLayerCode; datas via DateTimeUtils.tryParse)(resolves enums via fromLabel/fromString/fromWire/fromProgramLayerCode; dates via DateTimeUtils.tryParse)(resuelve enums vía fromLabel/fromString/fromWire/fromProgramLayerCode; fechas vía DateTimeUtils.tryParse)
Entity → ModeltoModel() (enums → .label, exceto b2bPortalStatus.value, ClerkStatus.wireValue e ClerkProgramLayer.programLayerCode; popula ToMany/ToOne)(enums → .label, except b2bPortalStatus.value, ClerkStatus.wireValue and ClerkProgramLayer.programLayerCode; fills ToMany/ToOne)(enums → .label, excepto b2bPortalStatus.value, ClerkStatus.wireValue y ClerkProgramLayer.programLayerCode; llena ToMany/ToOne)
Model → EntitytoDomain()

Os únicos deltasThe only deltasLos únicos deltas

  • enums tipados só na Entity (String nas outras camadas)enums typed only in the Entity (String in the other layers)enums tipados solo en la Entity (String en las demás capas)
  • renamerenamerename idcontactId / idclerkId no Model (o id do Model é o @Id() int do ObjectBox)in the Model (the Model's id is ObjectBox's @Id() int)en el Model (el id del Model es el @Id() int de ObjectBox)
  • renamerenamerename programLayerCodeprogramLayer na Entity (+ enum tipado, nunca nulo — cai para unknown)in the Entity (+ typed enum, never null — falls back to unknown)en la Entity (+ enum tipado, nunca nulo — cae a unknown)
  • birthdate / birthday StringDateTime? (via DateTimeUtils.tryParse)(via DateTimeUtils.tryParse)(vía DateTimeUtils.tryParse)
  • relações viram ToMany/ToOne no Model; Account.staff nulo no Model colapsa para StaffEntity() vaziorelations become ToMany/ToOne in the Model; a null Account.staff in the Model collapses to an empty StaffEntity()las relaciones pasan a ToMany/ToOne en el Model; Account.staff nulo en el Model colapsa a StaffEntity() vacío
  • lastSyncAt não existe no proto — é gerado no mapper do container com DateTimeUtils.now()does not exist in the proto — it is generated in the container mapper with DateTimeUtils.now()no existe en el proto — se genera en el mapper del container con DateTimeUtils.now()
  • os campos de Contact/Clerk não são optional no proto3, então hasX() não distingue "ausente" de valor-default: "" chega como null no DTO e isRewardNominated: false chega como nullContact/Clerk fields are not optional in proto3, so hasX() cannot tell "absent" from a default value: "" arrives as null in the DTO and isRewardNominated: false arrives as nulllos campos de Contact/Clerk no son optional en proto3, así que hasX() no distingue "ausente" de valor por defecto: "" llega como null en el DTO e isRewardNominated: false llega como null
  • o round-trip pelo Model transforma enum null em .unknown (o label vazio persiste como "", não como null)the Model round-trip turns a null enum into .unknown (the empty label persists as "", not null)el round-trip por el Model convierte un enum null en .unknown (el label vacío persiste como "", no como null)
  • na escrita os mesmos campos usam outras serializações: languagePreference vai como .isoCode (não .label) e b2bPortalStatus como .wireValue (não .value)on write the same fields use different serializations: languagePreference goes as .isoCode (not .label) and b2bPortalStatus as .wireValue (not .value)en la escritura los mismos campos usan otras serializaciones: languagePreference va como .isoCode (no .label) y b2bPortalStatus como .wireValue (no .value)
08

Repository

Não existe StaffRepository. A feature usa dois repositories: o VisitRepositoryImpl (leitura, pois a equipe vive na Visit) e o RetailUpdateRepositoryImpl (gravação local após o ack do Dispatcher). Um terceiro, o DispatcherRepositoryImpl, é o transporte da escrita remota.There is no StaffRepository. The feature uses two repositories: VisitRepositoryImpl (read, since the team lives in the Visit) and RetailUpdateRepositoryImpl (local write after the Dispatcher ack). A third one, DispatcherRepositoryImpl, is the remote-write transport.No existe StaffRepository. La feature usa dos repositories: VisitRepositoryImpl (lectura, ya que el equipo vive en la Visit) y RetailUpdateRepositoryImpl (grabación local tras el ack del Dispatcher). Un tercero, DispatcherRepositoryImpl, es el transporte de la escritura remota.

Um dropdown por método — assinatura, retorno e comportamento. O getVisits() traz a árvore de decisão de fonte dentro do próprio detalhe.One dropdown per method — signature, return and behavior. getVisits() carries the source decision tree inside its own detail.Un dropdown por método — firma, retorno y comportamiento. getVisits() trae el árbol de decisión de fuente dentro de su propio detalle.

Leitura ·Read ·Lectura · VisitRepositoryImpl

VisitRepositoryImpl implementaimplementsimplementa VisitRepositoryInterface e injeta os 3 datasources (mock/local/remote) + ConnectivityService + a flag useMockData + Ref. Só os métodos usados por esta feature:and injects the 3 datasources (mock/local/remote) + ConnectivityService + the useMockData flag + Ref. Only the methods used by this feature:e inyecta los 3 datasources (mock/local/remote) + ConnectivityService + la flag useMockData + Ref. Solo los métodos usados por esta feature:

getVisits({source}) mock / local / remote

RetornaReturnsDevuelve Result<VisitsEntity?, Failure>

Chamado pelo ManageStaffNotifier apenas para disparar a sincronização e ler o lastSyncAt — os contatos em si vêm do cache. Write-through: todo fetch grava no ObjectBox.Called by ManageStaffNotifier only to trigger the sync and read lastSyncAt — the contacts themselves come from cache. Write-through: every fetch writes to ObjectBox.Llamado por ManageStaffNotifier solo para disparar la sincronización y leer el lastSyncAt — los contactos mismos vienen del caché. Write-through: todo fetch graba en ObjectBox.

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

  1. useMockData == true ouoro source == mock_fetchFromMock(): lê o mock, mapeia e grava no cache. A flag global tem precedência máxima._fetchFromMock(): reads the mock, maps and writes to cache. The global flag has top precedence._fetchFromMock(): lee el mock, mapea y graba en caché. La flag global tiene máxima precedencia.
  2. source == local ou offlineor offlineu offlinegetCachedVisits() (sem rede). É o caminho do primeiro build() das duas telas.getCachedVisits() (no network). This is the path of both screens' first build().getCachedVisits() (sin red). Es el camino del primer build() de ambas pantallas.
  3. senão (remoto + conectado)otherwise (remote + connected)si no (remoto + conectado)_fetchFromRemoteWithFallback(): lê currentResourceProvider; se null cai pro cache; senão chama o remoto com locationHierarchyId, mapeia, grava no cache; em erro, fallback pro cache (cache vazio ⇒ propaga o erro). É o caminho do pull-to-refresh._fetchFromRemoteWithFallback(): reads currentResourceProvider; if null falls back to cache; else calls remote with locationHierarchyId, maps, writes to cache; on error, falls back to cache (empty cache ⇒ the error propagates). This is the pull-to-refresh path._fetchFromRemoteWithFallback(): lee currentResourceProvider; si null cae al caché; si no llama al remoto con locationHierarchyId, mapea, graba en caché; en error, fallback al caché (caché vacío ⇒ propaga el error). Es el camino del pull-to-refresh.
getCachedVisitBySfid({visitSfid}) local

RetornaReturnsDevuelve Result<VisitEntity, Failure>

O método central da feature. Busca a visita no cache pelo sfid; ausente → Error(CacheFailure). É dele que sai o accountData.staff lido pelas duas telas e mutado pelas quatro escritas. Nunca dispara remoto (§28 cat. A).The feature's central method. Fetches the visit from cache by sfid; missing → Error(CacheFailure). It yields the accountData.staff that both screens read and the four writes mutate. Never triggers remote (§28 cat. A).El método central de la feature. Busca la visita en el caché por sfid; ausente → Error(CacheFailure). De él sale el accountData.staff leído por ambas pantallas y mutado por las cuatro escrituras. Nunca dispara remoto (§28 cat. A).

getCachedVisits() local

RetornaReturnsDevuelve Result<VisitsEntity?, Failure>

Só cache. null vira Success(null), não erro. Usado pelo StaffDetailsNotifier apenas para obter o lastSyncAt.Cache only. null becomes Success(null), not an error. Used by StaffDetailsNotifier only to obtain lastSyncAt.Solo caché. null es Success(null), no error. Usado por StaffDetailsNotifier solo para obtener el lastSyncAt.

saveVisits({entity}) local · cache-writer

RetornaReturnsDevuelve Result<void, Failure>

Destrutivo: limpa e regrava todas as boxes da Visit (inclusive StaffModel, ContactModel e ClerkModel), preservando só o progresso client-side da visita. Cache-writer chamado após cada fetch — é por isso que um pull-to-refresh sobrescreve as edições locais que ainda não voltaram do servidor.Destructive: clears and rewrites every Visit box (including StaffModel, ContactModel and ClerkModel), preserving only the visit's client-side progress. Cache-writer called after each fetch — this is why a pull-to-refresh overwrites local edits that haven't come back from the server yet.Destructivo: limpia y regraba todas las boxes de la Visit (incluidas StaffModel, ContactModel y ClerkModel), preservando solo el progreso client-side de la visita. Cache-writer llamado tras cada fetch — por eso un pull-to-refresh sobrescribe las ediciones locales que aún no volvieron del servidor.

Escrita local ·Local write ·Escritura local · RetailUpdateRepositoryImpl

RetailUpdateRepositoryImpl implementaimplementsimplementa RetailUpdateRepositoryInterface e injeta apenas o RetailUpdateLocalDataSource — sem mock, sem remote, sem ConnectivityService: o envio ao backend é responsabilidade do Dispatcher. Todos os métodos compartilham o mesmo catchFailureMapper.fromException + AppLogger.logError(category: LogCategory.dataSync)Error(failure).and injects only RetailUpdateLocalDataSource — no mock, no remote, no ConnectivityService: sending to the backend is the Dispatcher's job. All methods share the same catchFailureMapper.fromException + AppLogger.logError(category: LogCategory.dataSync)Error(failure).e inyecta solo el RetailUpdateLocalDataSource — sin mock, sin remote, sin ConnectivityService: el envío al backend es responsabilidad del Dispatcher. Todos los métodos comparten el mismo catchFailureMapper.fromException + AppLogger.logError(category: LogCategory.dataSync)Error(failure).

saveContactUpdate({visitSfid, newContact}) local · upsert

RetornaReturnsDevuelve Result<ContactEntity, Failure>

Delega ao upsertContactInVisit: insere ou substitui o contato dentro do StaffModel daquela visita e devolve o próprio newContact. Chamado só após ack de sucesso do Dispatcher.Delegates to upsertContactInVisit: inserts or replaces the contact inside that visit's StaffModel and returns newContact itself. Called only after a successful ack from the Dispatcher.Delega a upsertContactInVisit: inserta o reemplaza el contacto dentro del StaffModel de esa visita y devuelve el propio newContact. Llamado solo tras el ack de éxito del Dispatcher.

removeContact({visitSfid, contactId}) local · delete

RetornaReturnsDevuelve Result<void, Failure>

Delega ao removeContactFromVisit: remove a relação e apaga o ContactModel (delete físico). Chamado só após ack de sucesso.Delegates to removeContactFromVisit: removes the relation and deletes the ContactModel (hard delete). Called only after a successful ack.Delega a removeContactFromVisit: elimina la relación y borra el ContactModel (borrado físico). Llamado solo tras el ack de éxito.

saveClerkUpdate({visitSfid, newClerk}) local · upsert

RetornaReturnsDevuelve Result<ClerkEntity, Failure>

Delega ao upsertClerkInVisit. Recebe o clerk já com a situação derivada (active/inactive) pelo orquestrador.Delegates to upsertClerkInVisit. Receives the clerk already carrying the state derived (active/inactive) by the orchestrator.Delega a upsertClerkInVisit. Recibe el clerk ya con la situación derivada (active/inactive) por el orquestador.

saveAccountUpdate({visitSfid, newAccount}) local · não usado aquinot used hereno usado aquí

RetornaReturnsDevuelve Result<AccountDataEntity, Failure>

Quarto método da interface, usado pela edição do varejo, não por esta tela. Relevante aqui só por uma garantia: ele reanexa o StaffModel existente, para que editar o varejo não apague a equipe.The interface's fourth method, used by the retail edit flow, not by this screen. Relevant here for one guarantee only: it re-attaches the existing StaffModel, so editing the retail does not wipe the team.Cuarto método de la interfaz, usado por la edición del punto de venta, no por esta pantalla. Relevante aquí solo por una garantía: reanexa el StaffModel existente, para que editar el punto de venta no borre el equipo.

Escrita remota ·Remote write ·Escritura remota · DispatcherRepositoryImpl

send({envelope}) gRPC · único métodosingle methodúnico método

RetornaReturnsDevuelve Result<DispatcherAck, Failure>

Único membro de DispatcherRepositoryInterface. Registra breadcrumbs (dispatchSending/dispatchAck) e delega ao DispatcherGateway. Qualquer exceção é colapsada em NetworkFailure(category: LogCategory.dispatch). Detalhe do contrato wire em Account Contact Upload.The single member of DispatcherRepositoryInterface. Logs breadcrumbs (dispatchSending/dispatchAck) and delegates to DispatcherGateway. Any exception is collapsed into NetworkFailure(category: LogCategory.dispatch). Wire contract detail in Account Contact Upload.Único miembro de DispatcherRepositoryInterface. Registra breadcrumbs (dispatchSending/dispatchAck) y delega al DispatcherGateway. Cualquier excepción se colapsa en NetworkFailure(category: LogCategory.dispatch). Detalle del contrato wire en Account Contact Upload.

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 VisitRemoteDataSource gRPC
getVisits({locationHierarchySfid, dateReference?})
EnvioSendsEnvío
monta VisitRequest (só locationHierarchySfid, e dateReference quando não nulo) e chama _client.getVisitList(request) no VisitConectaRepServiceClient (via visitServiceClientProvider).builds VisitRequest (only locationHierarchySfid, plus dateReference when non-null) and calls _client.getVisitList(request) on VisitConectaRepServiceClient (via visitServiceClientProvider).arma VisitRequest (solo locationHierarchySfid, y dateReference cuando no es nulo) y llama _client.getVisitList(request) en VisitConectaRepServiceClient (vía visitServiceClientProvider).
RetornoReturnRetorno
VisitsDTO (via response.toVisitsDTO()) — a equipe vem dentro de visitList[].accountData.staff.(via response.toVisitsDTO()) — the team comes inside visitList[].accountData.staff.(vía response.toVisitsDTO()) — el equipo viene dentro de visitList[].accountData.staff.
Fluxo de usoUsage flowFlujo de uso
chamado só pelo caminho remoto do repository (_fetchFromRemoteWithFallback), ou seja no pull-to-refresh da lista; o resultado é gravado no cache.called only by the repository's remote path (_fetchFromRemoteWithFallback), i.e. on the list's pull-to-refresh; the result is written to cache.llamado solo por el camino remoto del repository (_fetchFromRemoteWithFallback), es decir en el pull-to-refresh de la lista; el resultado se graba en caché.
Tratamento de erroError handlingManejo de errores
GrpcErrorGrpcExceptionHandler; outros → ServerException("Failed to fetch Visits from gRPC"). Em erro, o repository faz fallback pro cache.GrpcErrorGrpcExceptionHandler; others → ServerException("Failed to fetch Visits from gRPC"). On error, the repository falls back to cache.GrpcErrorGrpcExceptionHandler; otros → ServerException("Failed to fetch Visits from gRPC"). En error, el repository hace fallback al caché.
Local VisitLocalDataSource ObjectBox · CRUD

Envio / fluxo: persistência local via ObjectBox (ObjectBoxDatabase), boxes VisitsModel/VisitModel + as boxes ad-hoc StaffModel, ContactModel, ClerkModel, AccountDataModel — sem rede. Alimenta os caminhos cache do repository e recebe as mutações pós-ack. Erro: toda falha vira CacheException com mensagem própria por método (não engolida).Sends / flow: local persistence via ObjectBox (ObjectBoxDatabase), VisitsModel/VisitModel boxes plus the ad-hoc StaffModel, ContactModel, ClerkModel, AccountDataModel boxes — no network. Feeds the repository's cache paths and receives the post-ack mutations. Error: every failure becomes a CacheException with a per-method message (not swallowed).Envío / flujo: persistencia local vía ObjectBox (ObjectBoxDatabase), boxes VisitsModel/VisitModel más las boxes ad-hoc StaffModel, ContactModel, ClerkModel, AccountDataModel — sin red. Alimenta los caminos caché del repository y recibe las mutaciones post-ack. Error: todo fallo se convierte en CacheException con mensaje propio por método (no tragado).

getVisitBySfid({visitSfid})
RetornoReturnRetorno
VisitEntity?
ComportamentoBehaviorComportamiento
busca linear em _visitBox.getAll() pelo sfid e toDomain(). É a leitura que alimenta as duas telas.linear search over _visitBox.getAll() by sfid, then toDomain(). This is the read feeding both screens.búsqueda lineal en _visitBox.getAll() por sfid y toDomain(). Es la lectura que alimenta ambas pantallas.
upsertContactInVisit({visitSfid, contact})
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
acha a visita e a conta (senão CacheException), pega account.staff.target ou cria um StaffModel(), procura por contactId; se existir reaproveita o @Id() int e remove a relação antiga; adiciona o novo, grava StaffModel e reanexa em account.staff.finds the visit and the account (else CacheException), takes account.staff.target or creates a StaffModel(), looks up by contactId; if present it reuses the @Id() int and removes the old relation entry; adds the new one, writes StaffModel and re-attaches it to account.staff.encuentra la visita y la cuenta (si no CacheException), toma account.staff.target o crea un StaffModel(), busca por contactId; si existe reutiliza el @Id() int y elimina la relación antigua; agrega el nuevo, graba StaffModel y lo reanexa en account.staff.
removeContactFromVisit({visitSfid, contactId})
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
se não há StaffModel, retorna em silêncio. Senão remove todos os ContactModel com aquele contactId da relação e os apaga da box (delete físico), e grava o StaffModel.if there is no StaffModel it returns silently. Otherwise it removes every ContactModel with that contactId from the relation and deletes them from the box (hard delete), then writes StaffModel.si no hay StaffModel, retorna en silencio. Si no, elimina todos los ContactModel con ese contactId de la relación y los borra de la box (borrado físico), y graba el StaffModel.
upsertClerkInVisit({visitSfid, clerk})
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
idêntico ao upsert de contato, casando por clerkId.identical to the contact upsert, matching on clerkId.idéntico al upsert de contacto, casando por clerkId.
getVisits() · getVisitsLastSyncAt()
RetornoReturnRetorno
VisitsEntity? · DateTime?
ComportamentoBehaviorComportamiento
models.first.toDomain() / models.first.lastSyncAt — o agregado único, ou null se o cache está vazio. É a origem do DataLoadInfo.models.first.toDomain() / models.first.lastSyncAt — the single aggregate, or null if the cache is empty. It is the source of DataLoadInfo.models.first.toDomain() / models.first.lastSyncAt — el agregado único, o null si el caché está vacío. Es el origen del DataLoadInfo.
saveVisits({entity}) · clearVisits()
RetornoReturnRetorno
void
ComportamentoBehaviorComportamiento
clearVisits() limpa 35 boxes na ordem filhas→raízes, incluindo ContactModel, ClerkModel, StaffModel e AccountDataModel; saveVisits chama-o e regrava tudo, reaplicando só o progresso client-side da visita.clearVisits() clears 35 boxes children→roots, including ContactModel, ClerkModel, StaffModel and AccountDataModel; saveVisits calls it and rewrites everything, re-applying only the visit's client-side progress.clearVisits() limpia 35 boxes hijas→raíces, incluidas ContactModel, ClerkModel, StaffModel y AccountDataModel; saveVisits lo llama y regraba todo, reaplicando solo el progreso client-side de la visita.
Local RetailUpdateLocalDataSource delegação finathin delegationdelegación fina

Envio / fluxo: casca fina sobre o VisitLocalDataSource — existe para dar à camada de escrita um datasource próprio sem duplicar a lógica ObjectBox. Erro: não tem try/catch; as CacheException propagam para o RetailUpdateRepositoryImpl.Sends / flow: a thin shell over VisitLocalDataSource — it exists to give the write layer its own datasource without duplicating the ObjectBox logic. Error: it has no try/catch; CacheExceptions propagate to RetailUpdateRepositoryImpl.Envío / flujo: cáscara fina sobre el VisitLocalDataSource — existe para dar a la capa de escritura un datasource propio sin duplicar la lógica ObjectBox. Error: no tiene try/catch; las CacheException propagan al RetailUpdateRepositoryImpl.

upsertContactInVisit · removeContactFromVisit · upsertClerkInVisit · updateAccountInVisit
RetornoReturnRetorno
void (os quatro)(all four)(los cuatro)
ComportamentoBehaviorComportamiento
delegam 1:1 ao método homônimo do VisitLocalDataSource, sem lógica própria.delegate 1:1 to the same-named method on VisitLocalDataSource, with no logic of their own.delegan 1:1 al método homónimo del VisitLocalDataSource, sin lógica propia.
Remote DispatcherGateway gRPC · escritawriteescritura
send({envelope})
EnvioSendsEnvío
jsonEncode(envelope.payload) em InboxTransactionRequest.message, com endpoint = type.destination.value, serviceName, dateReference, transactionReference, username do resource, dados do aparelho, deviceUuid: "REP" e tid; chama sendTransaction com authorization: Bearer <token>.jsonEncode(envelope.payload) in InboxTransactionRequest.message, with endpoint = type.destination.value, serviceName, dateReference, transactionReference, the resource's username, device data, deviceUuid: "REP" and tid; calls sendTransaction with authorization: Bearer <token>.jsonEncode(envelope.payload) en InboxTransactionRequest.message, con endpoint = type.destination.value, serviceName, dateReference, transactionReference, el username del resource, datos del dispositivo, deviceUuid: "REP" y tid; llama sendTransaction con authorization: Bearer <token>.
RetornoReturnRetorno
DispatcherAck (status, transactionId, message mapeados 1:1 do InboxTransactionReply; isSuccess = status 0 ou 5)(status, transactionId, message mapped 1:1 from InboxTransactionReply; isSuccess = status 0 or 5)(status, transactionId, message mapeados 1:1 del InboxTransactionReply; isSuccess = status 0 o 5)
Fluxo de usoUsage flowFlujo de uso
com useMockData devolve um ack sintético (status: 0) sem rede; senão envia de verdade. Chamado pelo DispatcherRepositoryImpl, sempre a partir do DispatcherOrchestrator.with useMockData it returns a synthetic ack (status: 0) without network; otherwise it sends for real. Called by DispatcherRepositoryImpl, always from the DispatcherOrchestrator.con useMockData devuelve un ack sintético (status: 0) sin red; si no envía de verdad. Llamado por el DispatcherRepositoryImpl, siempre desde el DispatcherOrchestrator.
Tratamento de erroError handlingManejo de errores
offline → NetworkException antes de qualquer chamada; StatusCode.unauthenticateduma nova tentativa com token renovado; outros GrpcErrorGrpcExceptionHandler; resto → ServerException.offline → NetworkException before any call; StatusCode.unauthenticatedone retry with a refreshed token; other GrpcErrors → GrpcExceptionHandler; the rest → ServerException.offline → NetworkException antes de cualquier llamada; StatusCode.unauthenticatedun reintento con token renovado; otros GrpcErrorGrpcExceptionHandler; el resto → ServerException.
Mock VisitMockDataSource JSON
getVisits()
EnvioSendsEnvío
carrega o asset assets/mocks/visits/jsons/{market}_visits.json (ou {market}_real_visits.json quando useRealMockData) — sem rede.loads the asset assets/mocks/visits/jsons/{market}_visits.json (or {market}_real_visits.json when useRealMockData) — no network.carga el asset assets/mocks/visits/jsons/{market}_visits.json (o {market}_real_visits.json cuando useRealMockData) — sin red.
RetornoReturnRetorno
VisitsDTO (via VisitsDTOJsonMapper.fromMap)(via VisitsDTOJsonMapper.fromMap)(vía VisitsDTOJsonMapper.fromMap)
Fluxo de usoUsage flowFlujo de uso
usado quando useMockData está ligado ou source == mock; grava no cache como um fetch normal.used when useMockData is on or source == mock; writes to cache like a normal fetch.usado cuando useMockData está activo o source == mock; graba en caché como un fetch normal.
Tratamento de erroError handlingManejo de errores
asset ausente ou JSON inválido → CacheException("Failed to load mock Visit data from assets").missing asset or invalid JSON → CacheException("Failed to load mock Visit data from assets").asset ausente o JSON inválido → CacheException("Failed to load mock Visit data from assets").
Local / Remote / Mock ReferenceData*DataSource opções dos dropdownsdropdown optionsopciones de los dropdowns

Envio / fluxo: trio paralelo que serve só as opções de Cargo e Preferência de idioma. Remoto: getReferenceData(locationHierarchySfid, lastModifiedDate?)ReferenceDataDTO, com contactRoles e languagePreferences como repeated string. Local: getReferenceData()/saveReferenceData() sobre a box ReferenceDataModel (as duas listas são propriedades escalares, não boxes). Mock: assets/mocks/reference_data/jsons/{market}_reference_data.json. Erro: GrpcErrorGrpcExceptionHandler; outros no remoto → ServerException; local/mock → CacheException. Falha aqui não derruba a tela: as listas caem para vazio.Sends / flow: a parallel trio serving only the options for Role and Language preference. Remote: getReferenceData(locationHierarchySfid, lastModifiedDate?)ReferenceDataDTO, with contactRoles and languagePreferences as repeated string. Local: getReferenceData()/saveReferenceData() over the ReferenceDataModel box (both lists are scalar properties, not boxes). Mock: assets/mocks/reference_data/jsons/{market}_reference_data.json. Error: GrpcErrorGrpcExceptionHandler; other remote ones → ServerException; local/mock → CacheException. A failure here does not break the screen: the lists fall back to empty.Envío / flujo: trío paralelo que sirve solo las opciones de Cargo y Preferencia de idioma. Remoto: getReferenceData(locationHierarchySfid, lastModifiedDate?)ReferenceDataDTO, con contactRoles y languagePreferences como repeated string. Local: getReferenceData()/saveReferenceData() sobre la box ReferenceDataModel (ambas listas son propiedades escalares, no boxes). Mock: assets/mocks/reference_data/jsons/{market}_reference_data.json. Error: GrpcErrorGrpcExceptionHandler; otros en el remoto → ServerException; local/mock → CacheException. Un fallo aquí no rompe la pantalla: las listas caen a vacío.

10

Enums e labelsEnums & labelsEnums y labels

Os enums de dado só existem tipados na camada Entity; em DTO/Model/Proto trafegam como String. Os de controle (StaffSource, StaffFieldType, ContactUploadAction, StaffDetailsConfirmAction) nunca saem do app; ClerkStatusAction é de controle mas seu wireValue vai cru no payload. Lista completa de valores:Data enums are only typed in the Entity layer; in DTO/Model/Proto they travel as String. The control ones (StaffSource, StaffFieldType, ContactUploadAction, StaffDetailsConfirmAction) never leave the app; ClerkStatusAction is a control enum but its wireValue goes raw into the payload. Full value list:Los enums de dato solo están tipados en la capa Entity; en DTO/Model/Proto viajan como String. Los de control (StaffSource, StaffFieldType, ContactUploadAction, StaffDetailsConfirmAction) nunca salen de la app; ClerkStatusAction es de control pero su wireValue va crudo en el payload. Lista completa de valores:

ContactRole 8 · label · i18n
caselabeli18n key
clerk"Clerk"contactRoleClerk
supervisor"Supervisor"contactRoleSupervisor
manager"Manager"contactRoleManager
attendant"Attendant"contactRoleAttendant
owner"Owner"contactRoleOwner
primaryContact"Primary Contact"contactRolePrimaryContact
staff"Staff"contactRoleStaff
unknown""— (rawFallback)

fromLabel compara sem caixa; vazio ou sem match → unknown. Extension ContactRoleUx.localizedLabel (sem cores). As opções do dropdown vêm de ReferenceDataReply.contactRoles, não de values.compares case-insensitively; empty or no match → unknown. Extension ContactRoleUx.localizedLabel (no colors). The dropdown options come from ReferenceDataReply.contactRoles, not from values.compara sin distinguir caja; vacío o sin match → unknown. Extension ContactRoleUx.localizedLabel (sin colores). Las opciones del dropdown vienen de ReferenceDataReply.contactRoles, no de values.

B2bPortalStatus 5 · value + wireValue + corcolorcolor
casevaluewireValuepillVariant
active"active""Active"positive
pendingDeactivation"pending_deactivation""Pending Deactivation"negative
inactive"inactive""Inactive"neutral
deactivated"deactivated""Deactivated"neutral
unknown"unknown"""neutral

fromString casa contra ambos value e wireValue (minúsculas); nullunknown. O cache guarda .value e o payload envia .wireValue. O .value também é a chave i18n do campo somente-leitura na tela.fromString matches against both value and wireValue (lowercased); nullunknown. The cache stores .value and the payload sends .wireValue. .value is also the i18n key for the read-only field on screen.fromString casa contra ambos value y wireValue (minúsculas); nullunknown. El caché guarda .value y el payload envía .wireValue. El .value también es la clave i18n del campo de solo lectura en pantalla.

ContactDesignation 4 · label · i18n
caselabeli18n key
collectionIncharge"Collection Incharge"contactDesignationCollectionIncharge
ordersIncharge"Orders Incharge"contactDesignationOrdersIncharge
deliveryReturnsIncharge"Delivery Returns Incharge"contactDesignationDeliveryReturnsIncharge
unknown""— (rawFallback)

fromLabel normaliza trim + minúsculas + _→espaço — por isso a config do mercado pode usar collection_incharge. Extension ContactDesignationUx.localizedLabel.normalizes trim + lowercase + _→space — that's why the market config can use collection_incharge. Extension ContactDesignationUx.localizedLabel.normaliza trim + minúsculas + _→espacio — por eso la config del mercado puede usar collection_incharge. Extension ContactDesignationUx.localizedLabel.

PreferredContactMethod 5 · label · i18n
caselabeli18n key
mobile"Mobile"preferredContactMethodMobile
phone"Phone"preferredContactMethodPhone
email"Email"preferredContactMethodEmail
sms"SMS"preferredContactMethodSms
unknown""— (rawFallback)

fromLabel normaliza trim + minúsculas + _→espaço. As opções do dropdown vêm de staffConfig.preferredContactMethods.normalizes trim + lowercase + _→space. The dropdown options come from staffConfig.preferredContactMethods.normaliza trim + minúsculas + _→espacio. Las opciones del dropdown vienen de staffConfig.preferredContactMethods.

LanguagePreference 30 · label + isoCode
caselabelisoCode
english"English""en_US"
afrikaans"Afrikaans""af"
xhosa"Xhosa""xh"
zulu"Zulu""zu"
sesotho"Sesotho""st"
tsonga"Tsonga""ts"
portuguese"Portuguese""pt"
spanish"Spanish""es"
mandarin"Mandarin""zh"
french"French""fr"
russian"Russian""ru"
ukrainian"Ukrainian""uk"
german"German""de"
italian"Italian""it"
polish"Polish""pl"
chinese"Chinese""zh"
dutch"Dutch""nl"
romanian"Romanian""ro"
arabic"Arabic""ar"
cantonese"Cantonese""yue"
korean"Korean""ko"
vietnamese"Vietnamese""vi"
bengaliBangladesh"Bengali (Bangladesh)""bn"
tamil"Tamil""ta"
malay"Malay""ms"
hindi"Hindi""hi"
urduPakistan"Urdu (Pakistan)""ur"
englishAustralian"English_Australian""en_AU"
estonian"Estonia""et"
unknown""""

fallbackIsoCode = "en_US". fromLabel casa só pelo label (sem _→espaço); isoCodeFromKey devolve o isoCode ou o fallback. Sem extension de UX — a tela exibe o label cru. O cache guarda o .label; o payload envia o .isoCode.fromLabel matches by label only (no _→space); isoCodeFromKey returns the isoCode or the fallback. No UX extension — the screen shows the raw label. The cache stores .label; the payload sends .isoCode.fromLabel casa solo por el label (sin _→espacio); isoCodeFromKey devuelve el isoCode o el fallback. Sin extension de UX — la pantalla muestra el label crudo. El caché guarda el .label; el payload envía el .isoCode.

ClerkStatus 3 · wireValue + corcolorcolor
casewireValuei18n keypillVariant
active"Ativo"clerkStatusActivepositive
inactive"Inativo"clerkStatusInactivenegative
unknown""negative

Os wireValue são em português — o Conecta Você é um programa brasileiro. fromWire compara sem caixa; null/vazio → unknown. Extension ClerkStatusUx (localizedLabel + pillVariant): só active é positivo, tudo o mais é negativo (inclusive unknown).The wireValues are in Portuguese — Conecta Você is a Brazilian programme. fromWire compares case-insensitively; null/empty → unknown. Extension ClerkStatusUx (localizedLabel + pillVariant): only active is positive, everything else is negative (including unknown).Los wireValue están en portugués — Conecta Você es un programa brasileño. fromWire compara sin distinguir caja; null/vacío → unknown. Extension ClerkStatusUx (localizedLabel + pillVariant): solo active es positivo, todo lo demás es negativo (incluido unknown).

ClerkProgramLayer 3 · programLayerCode + wireRole
caseprogramLayerCodewireRolei18n key
attendant"BATCONVOCE23ATEND""Conecta Voce - Atendente"contactRoleAttendant
manager"BATCONVOCE23GER""Conecta Voce - Gerente"contactRoleManager
unknown""""

fromProgramLayerCode compara em maiúsculas; vazio/sem match → unknown. fromStaffRole mapeia ContactRole.clerkattendant e qualquer outromanager (nunca unknown). O dropdown da tela oferece attendant e manager, com unknown exibido como placeholder.compares uppercased; empty/no match → unknown. fromStaffRole maps ContactRole.clerkattendant and anything elsemanager (never unknown). The on-screen dropdown offers only attendant and manager, with unknown shown as the placeholder.compara en mayúsculas; vacío/sin match → unknown. fromStaffRole mapea ContactRole.clerkattendant y cualquier otromanager (nunca unknown). El dropdown de la pantalla ofrece solo attendant y manager, con unknown mostrado como placeholder.

StaffFieldType 14 · wireValue · chave da configconfig keyclave de la config
casewireValue
fullName"full_name"
firstName"first_name"
lastName"last_name"
contactDesignation"contact_designation"
languagePreference"language_preference"
preferredMethodOfContact"preferred_method_of_contact"
role"role"
mainContact"main_contact"
cellPhone"cell_phone"
email"email"
b2bStatus"b2b_status"
dateOfBirth"date_of_birth"
cpf"cpf"
unknown""

fromWire casa exato, sem match → unknown. É a chave que liga o EMC (contactFields/clerkFields) aos fieldVisible/fieldEditable do State. O formulário do Conecta Você reusa as chaves do contato: nome do clerk sob full_name, cargo sob role, celular sob cell_phone, aniversário sob date_of_birth, documento sob cpf.fromWire matches exactly, no match → unknown. It is the key linking the EMC (contactFields/clerkFields) to the State's fieldVisible/fieldEditable. The Conecta Você form reuses the contact keys: clerk name under full_name, role under role, cell phone under cell_phone, birthday under date_of_birth, document under cpf.fromWire casa exacto, sin match → unknown. Es la clave que une el EMC (contactFields/clerkFields) a los fieldVisible/fieldEditable del State. El formulario de Conecta Você reutiliza las claves del contacto: nombre del clerk bajo full_name, cargo bajo role, móvil bajo cell_phone, cumpleaños bajo date_of_birth, documento bajo cpf.

StaffSource 2 · parâmetro de rotaroute parameterparámetro de ruta
case
contact
clerk

Enum simples, sem propriedades. Decide se o detalhe edita um ContactEntity ou um ClerkEntity — e, por consequência, se fieldVisiblecontactFields ou clerkFields.A plain enum, no properties. It decides whether the detail edits a ContactEntity or a ClerkEntity — and, consequently, whether fieldVisible reads contactFields or clerkFields.Enum simple, sin propiedades. Decide si el detalle edita un ContactEntity o un ClerkEntity — y, por consecuencia, si fieldVisible lee contactFields o clerkFields.

ContactUploadAction 3 · wireValue
casewireValue
create"Create"
update"Update"
delete"Delete"

Getters isCreate e isDelete. O wireValue nunca entra no payload — o builder só ramifica nos dois getters (esvaziar contactId na criação, forçar Status: "Inactive" na exclusão).Getters isCreate and isDelete. wireValue never enters the payload — the builder only branches on the two getters (blank contactId on create, force Status: "Inactive" on delete).Getters isCreate e isDelete. El wireValue nunca entra en el payload — el builder solo ramifica en los dos getters (vaciar contactId al crear, forzar Status: "Inactive" al eliminar).

ClerkStatusAction 2 · wireValue
casewireValue
activate"activate"
inactivate"inactivate"

Vai cru na chave action do payload ContactUpdateStatus. Sem factory, sem extension.Goes raw into the action key of the ContactUpdateStatus payload. No factory, no extension.Va crudo en la clave action del payload ContactUpdateStatus. Sin factory, sin extension.

StaffDetailsConfirmAction 5 · copy dos modaismodal copycopy de los modales
casetitleKeymessageKeyconfirmKey
createstaffDetailsCreateConfirmTitlestaffDetailsCreateConfirmMessagestaffDetailsCreateConfirmButton
editstaffDetailsEditConfirmTitlestaffDetailsEditConfirmMessagestaffDetailsEditConfirmButton
deletestaffDetailsDeleteConfirmTitlestaffDetailsDeleteConfirmMessagestaffDetailsDeleteConfirmButton
activatestaffDetailsEditConfirmTitlestaffDetailsActivateConfirmMessagestaffDetailsActivate
inactivatestaffDetailsEditConfirmTitlestaffDetailsInactivateConfirmMessagestaffDetailsInactivate

Enum puro de apresentação: cada case resolve as três chaves de tradução do modal de confirmação. Note que activate/inactivate reusam o título de edição.A pure presentation enum: each case resolves the confirmation modal's three translation keys. Note that activate/inactivate reuse the edit title.Enum puro de presentación: cada case resuelve las tres claves de traducción del modal de confirmación. Note que activate/inactivate reutilizan el título de edición.

DispatcherType 3 usados aqui · serviceName + mercados3 used here · serviceName + markets3 usados aquí · serviceName + mercados
caseserviceNamedestinationenabledMarkets
retailUpdate"RetailerUploadAPI"salesforceBR · CL · ZA
contactClerkUpdate"ContactConectaVoce"none ("")BR
contactClerkStatus"ContactUpdateStatus"none ("")BR

Não existe um case accountContactUpload: a escrita de contato usa retailUpdate, compartilhando o serviceName com a edição do varejo (dois builders, dois payloads, um serviceName). O case cujo serviceName é "AccountContactUploadAPI" é o retailNew, do cadastro de novo varejo — outra feature. enabledMarkets é declarativo: não há consumidor em runtime; a proteção real é a tela ser inalcançável fora de BR/CL/ZA.There is no accountContactUpload case: the contact write uses retailUpdate, sharing the serviceName with the retail edit (two builders, two payloads, one serviceName). The case whose serviceName is "AccountContactUploadAPI" is retailNew, from the new-retail flow — a different feature. enabledMarkets is declarative: it has no runtime consumer; the real protection is that the screen is unreachable outside BR/CL/ZA.No existe un case accountContactUpload: la escritura de contacto usa retailUpdate, compartiendo el serviceName con la edición del punto de venta (dos builders, dos payloads, un serviceName). El case cuyo serviceName es "AccountContactUploadAPI" es retailNew, del registro de nuevo punto de venta — otra feature. enabledMarkets es declarativo: no tiene consumidor en runtime; la protección real es que la pantalla es inalcanzable fuera de BR/CL/ZA.

DispatchTransactionStatus 4 · trilha de auditoriaaudit trailrastro de auditoría
casevalueack que produzproducing ackack que produce
success"success"0 · 5
duplicate"duplicate"1
error"error"qualquer outroany othercualquier otro
pending"pending"— (só para transações de visita)— (visit transactions only)— (solo transacciones de visita)

Getters isPending, isSuccessEquivalent (success || duplicate), isError, isResendable (= isError). Toda tentativa desta feature é gravada com um destes status; em success o payload é apagado do histórico, em error é mantido para reenvio manual.Getters isPending, isSuccessEquivalent (success || duplicate), isError, isResendable (= isError). Every attempt from this feature is recorded with one of these statuses; on success the payload is cleared from the history, on error it is kept for manual re-send.Getters isPending, isSuccessEquivalent (success || duplicate), isError, isResendable (= isError). Todo intento de esta feature se graba con uno de estos estados; en success el payload se borra del historial, en error se mantiene para reenvío manual.

11

UseCases

Um dropdown por UseCase; dentro, cada método com assinatura, o que retorna e uso. Os de leitura delegam ao repository sem lógica extra; os de escrita são coordenados pelo StaffOrchestrator. Quase todos os providers são keepAlive — a exceção é o getReferenceDataUseCaseProvider.One dropdown per UseCase; inside, each method with its signature, what it returns and use. The read ones delegate to the repository with no extra logic; the write ones are coordinated by the StaffOrchestrator. Almost every provider is keepAlive — the exception is getReferenceDataUseCaseProvider.Un dropdown por UseCase; dentro, cada método con su firma, qué devuelve y uso. Los de lectura delegan al repository sin lógica extra; los de escritura son coordinados por el StaffOrchestrator. Casi todos los providers son keepAlive — la excepción es getReferenceDataUseCaseProvider.

GetStaffsByVisitUseCase 1 · a listathe listla lista
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
execute({visitSfid})Result<StaffEntity, Failure>Projeta visit.accountData.staff a partir de repository.getCachedVisitBySfid. Cache puro — nunca dispara rede. Chamado pelo ManageStaffNotifier.Projects visit.accountData.staff from repository.getCachedVisitBySfid. Pure cache — never triggers network. Called by ManageStaffNotifier.Proyecta visit.accountData.staff desde repository.getCachedVisitBySfid. Caché puro — nunca dispara red. Llamado por el ManageStaffNotifier.
GetVisitsUseCase 5 · visita + varejo + lastSyncAtvisit + retail + lastSyncAtvisita + punto de venta + lastSyncAt
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
execute({source})Result<VisitsEntity?, Failure>Dispara a sincronização e devolve o container. Usado no _load da lista só para o lastSyncAt; o refresh() passa remote.Triggers the sync and returns the container. Used in the list's _load only for lastSyncAt; refresh() passes remote.Dispara la sincronización y devuelve el container. Usado en el _load de la lista solo para el lastSyncAt; el refresh() pasa remote.
getCachedBySfid({visitSfid})Result<VisitEntity, Failure>O método mais usado da feature: fonte do accountData (cabeçalho do varejo) e do staff nas duas telas e nas quatro escritas. Ausente → Error(CacheFailure).The feature's most used method: source of accountData (retail header) and of staff on both screens and in all four writes. Missing → Error(CacheFailure).El método más usado de la feature: fuente del accountData (encabezado del punto de venta) y del staff en ambas pantallas y en las cuatro escrituras. Ausente → Error(CacheFailure).
getCached()Result<VisitsEntity?, Failure>Só cache; null vira Success(null). É de onde o detalhe tira o lastSyncAt.Cache only; null becomes Success(null). This is where the detail takes lastSyncAt from.Solo caché; null es Success(null). De ahí saca el detalle el lastSyncAt.
getCachedLastSyncAt()DateTime?Timestamp da última sincronização (sem Result). Não usado por esta feature — as duas telas leem o lastSyncAt do container.Last-sync timestamp (no Result). Not used by this feature — both screens read lastSyncAt from the container.Timestamp de última sincronización (sin Result). No usado por esta feature — ambas pantallas leen el lastSyncAt del container.
getCachedByAccountSfid({accountSfid})Result<VisitEntity?, Failure>Visita de um varejo (busca linear no cache). Não usado por esta feature — aqui a chave é sempre o visitSfid.A retail's visit (linear cache search). Not used by this feature — here the key is always visitSfid.Visita de un punto de venta (búsqueda lineal en caché). No usado por esta feature — aquí la clave es siempre el visitSfid.
GetReferenceDataUseCase 3 · opções dos dropdownsdropdown optionsopciones de los dropdowns
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
execute({source})Result<ReferenceDataEntity, Failure>O build() do detalhe lê contactRoles e languagePreferences daqui. Falha é tolerada: as listas caem para vazias, a tela não quebra.The detail's build() reads contactRoles and languagePreferences from here. Failure is tolerated: the lists fall back to empty, the screen doesn't break.El build() del detalle lee contactRoles y languagePreferences de aquí. El fallo es tolerado: las listas caen a vacías, la pantalla no se rompe.
getCached()Result<ReferenceDataEntity?, Failure>Só cache. Não usado por esta feature.Cache only. Not used by this feature.Solo caché. No usado por esta feature.
getCachedLastSyncAt()DateTime?Timestamp do cache de reference data. Não usado por esta feature.The reference-data cache timestamp. Not used by this feature.Timestamp del caché de reference data. No usado por esta feature.
GetEndMarketConfigurationUseCase 1 · StaffConfig
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
execute()Result<MarketConfiguration, Failure>Consumido indiretamente via marketConfigurationProvider, que também observa o remoteConfigActivatedProvider — mudança de Remote Config reconfigura o formulário ao vivo. As duas telas leem accountEditionConfig.staffConfig. Mercado ausente → Error(BusinessFailure).Consumed indirectly via marketConfigurationProvider, which also watches remoteConfigActivatedProvider — a Remote Config change reconfigures the form live. Both screens read accountEditionConfig.staffConfig. Missing market → Error(BusinessFailure).Consumido indirectamente vía marketConfigurationProvider, que también observa el remoteConfigActivatedProvider — un cambio de Remote Config reconfigura el formulario en vivo. Ambas pantallas leen accountEditionConfig.staffConfig. Mercado ausente → Error(BusinessFailure).
SaveStaffUpdateUseCase 3 · persistência locallocal persistencepersistencia local
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
saveContact({visitSfid, contact})Result<ContactEntity, Failure>Upsert no cache da visita. Chamado pelo orquestrador só após ack de sucesso.Upsert into the visit cache. Called by the orchestrator only after a successful ack.Upsert en el caché de la visita. Llamado por el orquestador solo tras el ack de éxito.
removeContact({visitSfid, contactId})Result<void, Failure>Remove o contato do cache após a exclusão confirmada no servidor.Removes the contact from cache after the server-confirmed deletion.Elimina el contacto del caché tras la eliminación confirmada en el servidor.
saveClerk({visitSfid, clerk})Result<ClerkEntity, Failure>Upsert do cadastro Conecta Você, já com a situação derivada da ação.Upsert of the Conecta Você record, already carrying the state derived from the action.Upsert del registro Conecta Você, ya con la situación derivada de la acción.
StaffOrchestrator 4 · remote-firstremote-firstremote-first
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
saveContact({visitSfid, contact, account, staffConfig, isCreation})Result<ContactEntity, Failure>Gera um id local (uuid v4) na criação, envia create/update e só então grava no ObjectBox. Falha no envio ⇒ nada é gravado.Generates a local id (uuid v4) on create, sends create/update and only then writes to ObjectBox. A send failure ⇒ nothing is written.Genera un id local (uuid v4) al crear, envía create/update y solo entonces graba en ObjectBox. Fallo en el envío ⇒ nada se graba.
deleteContact({visitSfid, contact, account, staffConfig})Result<void, Failure>Envia delete, depois remove do cache.Sends delete, then removes from cache.Envía delete, luego elimina del caché.
saveClerk({visitSfid, clerk, account, isCreation, statusAction})Result<ClerkEntity, Failure>Dois envios em sequência com o mesmo submittedAt: o cadastro (exige ack.status == 0, mais estrito que os outros) e a situação; só então persiste com a nova situação.Two sends in sequence with the same submittedAt: the record (requires ack.status == 0, stricter than the others) and the state; only then does it persist with the new state.Dos envíos en secuencia con el mismo submittedAt: el registro (exige ack.status == 0, más estricto que los demás) y la situación; solo entonces persiste con la nueva situación.
setClerkStatus({visitSfid, clerk, action})Result<ClerkEntity, Failure>Só o envio de situação, depois persiste com active/inactive.Only the state send, then persists with active/inactive.Solo el envío de situación, luego persiste con active/inactive.

Privados: _sendContact, _sendClerkStatus (montam o input, chamam builder + submit) e _toVoidResult (ack fora do previsto ⇒ Error(UnknownFailure())). Ids locais vêm de StaffIdentifierUtils (generateLocalStaffId, generateLocalClerkId, generateContactCpid — todos uuid v4; o CPID é gerado sempre, mesmo quando o mercado não o envia).Private: _sendContact, _sendClerkStatus (assemble the input, call builder + submit) and _toVoidResult (an unexpected ack ⇒ Error(UnknownFailure())). Local ids come from StaffIdentifierUtils (generateLocalStaffId, generateLocalClerkId, generateContactCpid — all uuid v4; the CPID is always generated, even when the market doesn't send it).Privados: _sendContact, _sendClerkStatus (arman el input, llaman builder + submit) y _toVoidResult (ack fuera de lo previsto ⇒ Error(UnknownFailure())). Los ids locales vienen de StaffIdentifierUtils (generateLocalStaffId, generateLocalClerkId, generateContactCpid — todos uuid v4; el CPID se genera siempre, incluso cuando el mercado no lo envía).

BuildAccountContactUploadDispatcherPayloadUseCase 1 · RetailerUploadAPI
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
build({input})DispatcherEnvelopeImplementa DispatcherPayloadBuilder<AccountContactUploadDispatcherPayloadInput>. Monta {"ContactDetails": [ … ]} (sempre um elemento) sob DispatcherType.retailUpdate. Contrato completo em Account Contact Upload.Implements DispatcherPayloadBuilder<AccountContactUploadDispatcherPayloadInput>. Assembles {"ContactDetails": [ … ]} (always one element) under DispatcherType.retailUpdate. Full contract in Account Contact Upload.Implementa DispatcherPayloadBuilder<AccountContactUploadDispatcherPayloadInput>. Arma {"ContactDetails": [ … ]} (siempre un elemento) bajo DispatcherType.retailUpdate. Contrato completo en Account Contact Upload.
_splitName({name})(String, String)Quebra o nome em contactname + ContactName_LName (primeiro token / resto).Splits the name into contactname + ContactName_LName (first token / rest).Divide el nombre en contactname + ContactName_LName (primer token / resto).
_formatBirthdate({birthdate})StringDOB no formato slashYearMonthDay; "" quando nulo.DOB in slashYearMonthDay format; "" when null.DOB en formato slashYearMonthDay; "" cuando es nulo.
_b2bStatus({contact, action, deactivatesOnDelete})StringNormalmente o .wireValue; força "Inactive" quando o mercado desativa o B2B na exclusão de um contato ativo.Normally the .wireValue; forces "Inactive" when the market deactivates B2B on deleting an active contact.Normalmente el .wireValue; fuerza "Inactive" cuando el mercado desactiva el B2B al eliminar un contacto activo.

Input AccountContactUploadDispatcherPayloadInput (Freezed, 6 campos obrigatórios): contact, account, staffConfig, action, mainContactCpid, submittedAtentities cruas, toda construção wire mora no build() (§36).Input AccountContactUploadDispatcherPayloadInput (Freezed, 6 required fields): contact, account, staffConfig, action, mainContactCpid, submittedAtraw entities, all wire construction lives in build() (§36).Input AccountContactUploadDispatcherPayloadInput (Freezed, 6 campos obligatorios): contact, account, staffConfig, action, mainContactCpid, submittedAtentities crudas, toda construcción wire vive en el build() (§36).

BuildClerkConectaVoceUploadDispatcherPayloadUseCase 1 · ContactConectaVoce
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
build({input})DispatcherEnvelopePayload plano (12 chaves) sob DispatcherType.contactClerkUpdate: sapParent, parentName, sapName, role (programLayer.wireRole), email, celNumber, birthday (isoDate), programLayerCode, user, taxId, accountCode (os três iguais ao documento sem pontuação) e editMode.Flat payload (12 keys) under DispatcherType.contactClerkUpdate: sapParent, parentName, sapName, role (programLayer.wireRole), email, celNumber, birthday (isoDate), programLayerCode, user, taxId, accountCode (the last three all equal to the unpunctuated document) and editMode.Payload plano (12 claves) bajo DispatcherType.contactClerkUpdate: sapParent, parentName, sapName, role (programLayer.wireRole), email, celNumber, birthday (isoDate), programLayerCode, user, taxId, accountCode (los tres iguales al documento sin puntuación) y editMode.
_strippedTaxId({taxId})StringRemove . e - do documento.Strips . and - from the document.Elimina . y - del documento.
_formatBirthday({birthdate})StringData no formato isoDate (yyyy-MM-dd) — diferente do DOB do contato.Date in isoDate format (yyyy-MM-dd) — different from the contact's DOB.Fecha en formato isoDate (yyyy-MM-dd) — diferente del DOB del contacto.
BuildClerkConectaVoceStatusDispatcherPayloadUseCase 1 · ContactUpdateStatus
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
build({input})DispatcherEnvelopePayload de 6 chaves sob DispatcherType.contactClerkStatus: id, accountCode, user (os três = documento sem pontuação), action (ClerkStatusAction.wireValue), e reason/cancelDescription fixos em "outros". O transactionReference é o próprio documento (não o sfid do varejo).A 6-key payload under DispatcherType.contactClerkStatus: id, accountCode, user (all three = the unpunctuated document), action (ClerkStatusAction.wireValue), and reason/cancelDescription fixed at "outros". The transactionReference is the document itself (not the retail sfid).Payload de 6 claves bajo DispatcherType.contactClerkStatus: id, accountCode, user (los tres = el documento sin puntuación), action (ClerkStatusAction.wireValue), y reason/cancelDescription fijos en "outros". El transactionReference es el propio documento (no el sfid del punto de venta).
Submit*UseCase 3 · delegação ao orquestradordelegation to the orchestratordelegación al orquestador
MétodoMethodMétodoRetornaReturnsDevuelveUsoUseUso
SubmitAccountContactUploadUseCase.submit({envelope})Result<DispatcherAck, Failure>Delega ao DispatcherOrchestrator.dispatch. Sem lógica própria.Delegates to DispatcherOrchestrator.dispatch. No logic of its own.Delega al DispatcherOrchestrator.dispatch. Sin lógica propia.
SubmitClerkConectaVoceUploadUseCase.submit({envelope})Result<DispatcherAck, Failure>Idem, para o cadastro Conecta Você.Ditto, for the Conecta Você record.Ídem, para el registro Conecta Você.
SubmitClerkConectaVoceStatusUseCase.submit({envelope})Result<DispatcherAck, Failure>Idem, para a mudança de situação.Ditto, for the state change.Ídem, para el cambio de situación.

O DispatcherOrchestrator.dispatch só enfileira offline as transações de visita — os três envelopes desta feature vão direto ao processamento e falham com NetworkFailure sem rede. Cada tentativa é gravada no histórico via DispatchHistoryUseCase.save, o que alimenta os contadores da Central de dados.DispatcherOrchestrator.dispatch only queues visit transactions offline — this feature's three envelopes go straight to processing and fail with NetworkFailure without network. Every attempt is recorded in the history via DispatchHistoryUseCase.save, which feeds the Data Center counters.El DispatcherOrchestrator.dispatch solo encola offline las transacciones de visita — los tres envelopes de esta feature van directo al procesamiento y fallan con NetworkFailure sin red. Cada intento se graba en el historial vía DispatchHistoryUseCase.save, lo que alimenta los contadores de la Central de datos.

12

Notifier & State

São dois Notifiers independentes, ambos famílias @riverpod chaveadas por parâmetros de rota (§17: a page recebe só identificadores). O ManageStaffNotifier (with AsyncGuard<ManageStaffState>) é o cérebro da lista — leitura pura, com refresh() canônico. O StaffDetailsNotifier (sem AsyncGuard) é o cérebro do formulário: mantém o rascunho, valida por campo, calcula "sujo" a cada tecla e dispara as quatro escritas. Cada State Freezed é a fonte única de verdade da sua page.There are two independent Notifiers, both @riverpod families keyed by route parameters (§17: the page receives identifiers only). ManageStaffNotifier (with AsyncGuard<ManageStaffState>) is the list's brain — pure read, with the canonical refresh(). StaffDetailsNotifier (without AsyncGuard) is the form's brain: it holds the draft, validates per field, recomputes "dirty" on every keystroke and fires the four writes. Each Freezed State is its page's single source of truth.Son dos Notifiers independientes, ambos familias @riverpod indexadas por parámetros de ruta (§17: la page recibe solo identificadores). El ManageStaffNotifier (with AsyncGuard<ManageStaffState>) es el cerebro de la lista — lectura pura, con refresh() canónico. El StaffDetailsNotifier (sin AsyncGuard) es el cerebro del formulario: mantiene el borrador, valida por campo, calcula "sucio" en cada tecla y dispara las cuatro escrituras. Cada State Freezed es la fuente única de verdad de su page.

Métodos ·Methods ·Métodos · ManageStaffNotifier

manageStaffProvider(visitSfid:)build({required String visitSfid}) observa getVisitsUseCaseProvider e getStaffsByVisitUseCaseProvider e retorna guardedBuild(body: () => _load(...)).watches getVisitsUseCaseProvider and getStaffsByVisitUseCaseProvider and returns guardedBuild(body: () => _load(...)).observa getVisitsUseCaseProvider y getStaffsByVisitUseCaseProvider y retorna guardedBuild(body: () => _load(...)).

_load({visitSfid, source = local}) private

RetornoReturnRetorno Future<ManageStaffState>

Dono único da montagem do State: lê o marketConfigurationProvider (para hasConectaVoceStaff), obtém o lastSyncAt do container e busca em paralelo a visita (getCachedBySfid) e a equipe (GetStaffsByVisitUseCase). Visita ausente ⇒ lança BusinessFailure(genericError) (a page vira erro); equipe ausente ⇒ cai para StaffEntity() vazio (não é fatal). Monta showsConectaVoce = hasConectaVoceStaff && staff.isRegisteredInConectaVoce.Sole owner of building the State: reads marketConfigurationProvider (for hasConectaVoceStaff), takes lastSyncAt from the container and fetches the visit (getCachedBySfid) and the team (GetStaffsByVisitUseCase) in parallel. Missing visit ⇒ it throws BusinessFailure(genericError) (the page turns into an error); missing team ⇒ falls back to an empty StaffEntity() (not fatal). Assembles showsConectaVoce = hasConectaVoceStaff && staff.isRegisteredInConectaVoce.Dueño único del armado del State: lee el marketConfigurationProvider (para hasConectaVoceStaff), obtiene el lastSyncAt del container y busca en paralelo la visita (getCachedBySfid) y el equipo (GetStaffsByVisitUseCase). Visita ausente ⇒ lanza BusinessFailure(genericError) (la page pasa a error); equipo ausente ⇒ cae a StaffEntity() vacío (no es fatal). Arma showsConectaVoce = hasConectaVoceStaff && staff.isRegisteredInConectaVoce.

refresh() pull-to-refresh

RetornoReturnRetorno Future<void>

Null-guard em state.value, depois runGuarded(body: () => _load(visitSfid: current.visitSfid, source: DataSourceType.remote)) — ou seja, refaz o fetch remoto da Visit inteira. Não seta AsyncValue.loading (o pull-to-refresh tem indicador próprio); em falha, o AsyncGuard coloca a page em AsyncValue.error. Não há estado de cliente a preservar (a aba ativa vive no State do widget, não no Notifier).Null-guard on state.value, then runGuarded(body: () => _load(visitSfid: current.visitSfid, source: DataSourceType.remote)) — i.e. it re-fetches the whole Visit remotely. It doesn't set AsyncValue.loading (pull-to-refresh has its own indicator); on failure AsyncGuard puts the page into AsyncValue.error. There is no client state to preserve (the active tab lives in the widget's State, not in the Notifier).Null-guard en state.value, luego runGuarded(body: () => _load(visitSfid: current.visitSfid, source: DataSourceType.remote)) — es decir, rehace el fetch remoto de la Visit entera. No setea AsyncValue.loading (el pull-to-refresh tiene indicador propio); en fallo, el AsyncGuard pone la page en AsyncValue.error. No hay estado de cliente que preservar (la pestaña activa vive en el State del widget, no en el Notifier).

State disponível para a Page ·State available to the Page ·State disponible para la Page · ManageStaffState

ManageStaffState 6 campos + 3 gettersfields + 3 getterscampos + 3 getters
campotipodefault
visitSfidStringrequired
accountAccountDataEntityrequired
contactsList<ContactEntity>[]
clerksList<ClerkEntity>[]
showsConectaVoceboolfalse
lastSyncAtDateTime?null

Getters: hasContacts, hasClerks (as listas não estão vazias) e isAccountB2B (account.isB2B ?? false, decide a etiqueta B2B nos cards). Não há busca, ordenação nem paginação — as listas saem na ordem do cache.Getters: hasContacts, hasClerks (the lists aren't empty) and isAccountB2B (account.isB2B ?? false, decides the B2B tag on cards). There is no search, sort or pagination — the lists come out in cache order.Getters: hasContacts, hasClerks (las listas no están vacías) e isAccountB2B (account.isB2B ?? false, decide la etiqueta B2B en las tarjetas). No hay búsqueda, orden ni paginación — las listas salen en el orden del caché.

Métodos ·Methods ·Métodos · StaffDetailsNotifier

staffDetailsProvider(visitSfid:, source:, staffId:)build({required String visitSfid, required StaffSource source, String? staffId}) observa os 3 UseCases + o marketConfigurationProvider, busca os dados de referência (falha tolerada) e chama _load(...). Não há refresh(): a recuperação de erro é ref.invalidate pelo FailureStateView.watches the 3 UseCases + marketConfigurationProvider, fetches reference data (failure tolerated) and calls _load(...). There is no refresh(): error recovery is ref.invalidate from FailureStateView.observa los 3 UseCases + el marketConfigurationProvider, busca los datos de referencia (fallo tolerado) y llama _load(...). No hay refresh(): la recuperación de error es ref.invalidate por el FailureStateView.

_load({visitSfid, source, staffId, staffConfig, contactRoles, languagePreferences}) private

RetornoReturnRetorno Future<StaffDetailsState>

Traduz a entity em rascunho de formulário: busca a visita (ausente ⇒ lança BusinessFailure), acha o contato ou o clerk pelo staffId, quebra o nome em nome/sobrenome, normaliza telefones com PhoneUtils.nationalDigits e copia os demais campos. No fim grava a baseline — e a baseline é a entity recomposta (_composeContact/_composeClerk), não a entity crua, para que normalizações de ida-e-volta não marquem o formulário como sujo sem o usuário digitar nada.Translates the entity into a form draft: fetches the visit (missing ⇒ throws BusinessFailure), finds the contact or the clerk by staffId, splits the name into first/last, normalizes phones with PhoneUtils.nationalDigits and copies the remaining fields. At the end it stores the baseline — and the baseline is the recomposed entity (_composeContact/_composeClerk), not the raw one, so round-trip normalizations don't mark the form dirty without the user typing anything.Traduce la entity en un borrador de formulario: busca la visita (ausente ⇒ lanza BusinessFailure), encuentra el contacto o el clerk por el staffId, divide el nombre en nombre/apellido, normaliza teléfonos con PhoneUtils.nationalDigits y copia los demás campos. Al final graba la baseline — y la baseline es la entity recompuesta (_composeContact/_composeClerk), no la cruda, para que las normalizaciones de ida y vuelta no marquen el formulario como sucio sin que el usuario escriba nada.

update…() 18 · campos do rascunhodraft fieldscampos del borrador

RetornoReturnRetorno void (todos)(all of them)(todos)

Contato (12): updateFullName, updateFirstName, updateLastName, updateContactDesignation, updateLanguagePreference, updatePreferredMethodOfContact, updateRole, updateMainContact, updateCellPhone, updateEmail, updateB2bStatus, updateDateOfBirth. Conecta Você (6): updateClerkName, updateClerkCpf, updateClerkProgramLayer, updateClerkPhone, updateClerkEmail, updateClerkBirthday. Todos delegam ao _patch; os de texto limpam o próprio erro na mesma chamada. O updateB2bStatus existe mas nunca é chamado (o campo é somente leitura).Contact (12): updateFullName, updateFirstName, updateLastName, updateContactDesignation, updateLanguagePreference, updatePreferredMethodOfContact, updateRole, updateMainContact, updateCellPhone, updateEmail, updateB2bStatus, updateDateOfBirth. Conecta Você (6): updateClerkName, updateClerkCpf, updateClerkProgramLayer, updateClerkPhone, updateClerkEmail, updateClerkBirthday. All delegate to _patch; the text ones clear their own error in the same call. updateB2bStatus exists but is never called (the field is read-only).Contacto (12): updateFullName, updateFirstName, updateLastName, updateContactDesignation, updateLanguagePreference, updatePreferredMethodOfContact, updateRole, updateMainContact, updateCellPhone, updateEmail, updateB2bStatus, updateDateOfBirth. Conecta Você (6): updateClerkName, updateClerkCpf, updateClerkProgramLayer, updateClerkPhone, updateClerkEmail, updateClerkBirthday. Todos delegan al _patch; los de texto limpian su propio error en la misma llamada. El updateB2bStatus existe pero nunca se llama (el campo es de solo lectura).

_patch(update) · _isFormDirty({state}) private

RetornoReturnRetorno void · bool

O _patch é o único caminho de mutação do rascunho: aplica a função e recalcula isFormDirty a cada chamada. O _isFormDirty compara a entity recomposta com a baseline por igualdade de valor Freezed — é isso que habilita/desabilita o botão Salvar._patch is the draft's only mutation path: it applies the function and recomputes isFormDirty on every call. _isFormDirty compares the recomposed entity with the baseline by Freezed value equality — that is what enables/disables the Save button.El _patch es el único camino de mutación del borrador: aplica la función y recalcula isFormDirty en cada llamada. El _isFormDirty compara la entity recompuesta con la baseline por igualdad de valor Freezed — eso es lo que habilita/deshabilita el botón Guardar.

validateContact() · validateClerk() validação por campo visívelper-visible-field validationvalidación por campo visible

RetornoReturnRetorno bool

Validam só o que o mercado tornou visível, escrevem as chaves de erro no State e retornam true apenas se todas ficarem nulas. validateContact cobre 5 campos (nome completo / nome / sobrenome obrigatórios, celular por PhoneUtils, e-mail por EmailUtils); validateClerk cobre 4 (nome, documento por DocumentUtils.isValidIndividual, celular, e-mail). Não validam: cargo, designação, idioma, método preferido, data de nascimento, status B2B nem camada do programa.They validate only what the market made visible, write the error keys into the State and return true only if all of them end up null. validateContact covers 5 fields (full name / first / last required, cell phone via PhoneUtils, e-mail via EmailUtils); validateClerk covers 4 (name, document via DocumentUtils.isValidIndividual, cell phone, e-mail). They do not validate: role, designation, language, preferred method, date of birth, B2B status or programme layer.Validan solo lo que el mercado hizo visible, escriben las claves de error en el State y devuelven true solo si todas quedan nulas. validateContact cubre 5 campos (nombre completo / nombre / apellido obligatorios, móvil por PhoneUtils, correo por EmailUtils); validateClerk cubre 4 (nombre, documento por DocumentUtils.isValidIndividual, móvil, correo). No validan: cargo, designación, idioma, método preferido, fecha de nacimiento, estado B2B ni capa del programa.

Helpers privados: _requiredError (staffDetailsFieldRequired), _phoneError (vazio ⇒ obrigatório; inválido ⇒ phoneFormatError), _emailError (inválido ⇒ newRetailEmailFormatError) e _documentError (inválido ⇒ staffDetailsDocumentFormatError).Private helpers: _requiredError (staffDetailsFieldRequired), _phoneError (empty ⇒ required; invalid ⇒ phoneFormatError), _emailError (invalid ⇒ newRetailEmailFormatError) and _documentError (invalid ⇒ staffDetailsDocumentFormatError).Helpers privados: _requiredError (staffDetailsFieldRequired), _phoneError (vacío ⇒ obligatorio; inválido ⇒ phoneFormatError), _emailError (inválido ⇒ newRetailEmailFormatError) y _documentError (inválido ⇒ staffDetailsDocumentFormatError).

conflictingMainContact()

RetornoReturnRetorno Future<ContactEntity?>

Devolve null se o rascunho não marca contato principal. Senão varre visit.accountData.staff.contacts e devolve o primeiro que já é principal e não é o contato em edição. É o gatilho do modal de troca.Returns null if the draft is not flagged as main contact. Otherwise it scans visit.accountData.staff.contacts and returns the first one already flagged main that is not the contact being edited. It is the trigger for the switch modal.Devuelve null si el borrador no marca contacto principal. Si no, recorre visit.accountData.staff.contacts y devuelve el primero que ya es principal y no es el contacto en edición. Es el disparador del modal de cambio.

saveContact({demoteContact?}) escritawriteescritura

RetornoReturnRetorno Future<bool>

Com demoteContact, primeiro despacha o rebaixamento do principal antigo (copyWith(isMainContact: false), isCreation: false) e aborta em erro. Depois recompõe o contato do rascunho e chama orchestrator.saveContact(isCreation: !hasContact). Devolve true/falsenunca coloca o provider em erro; a page traduz o booleano em aviso verde ou vermelho.With demoteContact, it first dispatches the demotion of the old main contact (copyWith(isMainContact: false), isCreation: false) and aborts on error. Then it recomposes the contact from the draft and calls orchestrator.saveContact(isCreation: !hasContact). It returns true/false — it never puts the provider into error; the page turns the boolean into a green or red notice.Con demoteContact, primero despacha la degradación del principal anterior (copyWith(isMainContact: false), isCreation: false) y aborta en error. Luego recompone el contacto del borrador y llama orchestrator.saveContact(isCreation: !hasContact). Devuelve true/falsenunca pone el provider en error; la page traduce el booleano en aviso verde o rojo.

deleteContact() escritawriteescritura

RetornoReturnRetorno Future<bool>

Acha o contato no cache pelo staffId (ausente ⇒ false) e chama orchestrator.deleteContact. Usa a entity do cache, não o rascunho — o formulário aberto não influencia a exclusão.Finds the contact in cache by staffId (missing ⇒ false) and calls orchestrator.deleteContact. It uses the cached entity, not the draft — the open form does not influence the deletion.Encuentra el contacto en el caché por el staffId (ausente ⇒ false) y llama orchestrator.deleteContact. Usa la entity del caché, no el borrador — el formulario abierto no influye en la eliminación.

saveClerk() escrita · Conecta Vocêwrite · Conecta Vocêescritura · Conecta Você

RetornoReturnRetorno Future<bool>

Recompõe o clerk do rascunho e chama orchestrator.saveClerk(isCreation: !hasClerk), que envia cadastro + situação. A situação enviada é activate por padrão.Recomposes the clerk from the draft and calls orchestrator.saveClerk(isCreation: !hasClerk), which sends record + state. The state sent is activate by default.Recompone el clerk del borrador y llama orchestrator.saveClerk(isCreation: !hasClerk), que envía registro + situación. La situación enviada es activate por defecto.

setClerkStatus({action}) escrita · situaçãowrite · stateescritura · situación

RetornoReturnRetorno Future<bool>

Caminho normal: só o envio de situação. Exceção: ao inativar com o rascunho diferente do cache, ele valida e chama saveClerk(statusAction: inactivate) — salva e inativa numa só operação. Clerk ausente no cache ⇒ false.Normal path: only the state send. Exception: when inactivating with the draft differing from cache, it validates and calls saveClerk(statusAction: inactivate) — saving and inactivating in one operation. Clerk missing in cache ⇒ false.Camino normal: solo el envío de situación. Excepción: al inactivar con el borrador diferente del caché, valida y llama saveClerk(statusAction: inactivate) — guarda e inactiva en una sola operación. Clerk ausente en el caché ⇒ false.

_getVisit · _findContact · _findClerk · _composeContact · _composeClerk · _splitName private

RetornoReturnRetorno Future<VisitEntity?> · ContactEntity? · ClerkEntity? · ContactEntity · ClerkEntity · (String, String)

O _getVisit engole o erro (devolve null) porque as escritas já reportam pelo booleano. Os _find* são busca linear por id. Os _compose* são o coração do formulário: transformam rascunho + entity atual numa entity nova — nome montado conforme o mercado (nome completo ou nome+sobrenome), telefone só dígitos, e os campos que a tela não edita (status, taxId, isRewardNominated, isEngaged) preservados do cache (status cai para "active" na criação)._getVisit swallows the error (returns null) because the writes already report via the boolean. The _find* ones are linear searches by id. The _compose* ones are the form's heart: they turn draft + current entity into a new entity — name assembled per market (full name or first+last), phone digits-only, and the fields the screen doesn't edit (status, taxId, isRewardNominated, isEngaged) preserved from cache (status falls back to "active" on create).El _getVisit se traga el error (devuelve null) porque las escrituras ya reportan por el booleano. Los _find* son búsqueda lineal por id. Los _compose* son el corazón del formulario: convierten borrador + entity actual en una entity nueva — nombre armado según el mercado (nombre completo o nombre+apellido), teléfono solo dígitos, y los campos que la pantalla no edita (status, taxId, isRewardNominated, isEngaged) preservados del caché (status cae a "active" al crear).

State disponível para a Page ·State available to the Page ·State disponible para la Page · StaffDetailsState

StaffDetailsState 43 campos + 7 gettersfields + 7 getterscampos + 7 getters
campotipodefault
visitSfidStringrequired
sourceStaffSourcerequired
staffIdString?null
hasContact / hasClerkboolfalse
fullName / firstName / lastNameString""
contactDesignationContactDesignation?null
languagePreferenceLanguagePreference?null
preferredMethodOfContactPreferredContactMethod?null
roleContactRole?null
isMainContactboolfalse
cellPhone / emailString""
b2bStatusB2bPortalStatus?null
dateOfBirthDateTime?null
clerkName / clerkCpf / clerkPhone / clerkEmailString""
clerkProgramLayerClerkProgramLayerunknown
clerkBirthdayDateTime?null
clerkStatusClerkStatus?null
clerkIsEngagedbool?null
staffConfigStaffConfigStaffConfig()
contactRolesList<ContactRole>[]
languagePreferencesList<LanguagePreference>[]
lastSyncAtDateTime?null
isSavingContact / isSavingClerkboolfalse
baselineContactContactEntity?null
baselineClerkClerkEntity?null
isFormDirtyboolfalse
fullNameError / firstNameError / lastNameErrorTranslationConstants?null
cellPhoneError / emailErrorTranslationConstants?null
clerkNameError / clerkCpfErrorTranslationConstants?null
clerkPhoneError / clerkEmailErrorTranslationConstants?null

Getters/métodos: fieldVisible(field) e fieldEditable(field) (roteiam para contactFields ou clerkFields conforme o source — são eles que desenham o formulário), displayB2bStatusKey (o .value do enum como chave i18n; null para unknown), e isCreateMode, isEditMode, isContactCreation, isClerkCreation — os quatro declarados e não usados pela UI, que decide por hasContact/hasClerk.Getters/methods: fieldVisible(field) and fieldEditable(field) (they route to contactFields or clerkFields per source — these are what draw the form), displayB2bStatusKey (the enum's .value as an i18n key; null for unknown), plus isCreateMode, isEditMode, isContactCreation, isClerkCreation — all four declared and unused by the UI, which decides via hasContact/hasClerk.Getters/métodos: fieldVisible(field) y fieldEditable(field) (rutean a contactFields o clerkFields según el source — son ellos los que dibujan el formulario), displayB2bStatusKey (el .value del enum como clave i18n; null para unknown), y isCreateMode, isEditMode, isContactCreation, isClerkCreation — los cuatro declarados y no usados por la UI, que decide por hasContact/hasClerk.

13

Page e widgetsPage & widgetsPage y widgets

Duas pages, ambas ConsumerWidget, ambas com AppPageShell(displayBackButton: true) e sem backgroundColor próprio (§19). Loading e erro são globais (stateAsync.when); o conteúdo existe só no ramo data. Os modais aparecem aninhados sob o widget que os abre.Two pages, both ConsumerWidget, both with AppPageShell(displayBackButton: true) and no backgroundColor of their own (§19). Loading and error are global (stateAsync.when); content exists only in the data branch. The modals appear nested under the widget that opens them.Dos pages, ambas ConsumerWidget, ambas con AppPageShell(displayBackButton: true) y sin backgroundColor propio (§19). Loading y error son globales (stateAsync.when); el contenido existe solo en la rama data. Los modales aparecen anidados bajo el widget que los abre.

Lista ·List ·Lista · ManageStaffPage

  • ManageStaffPage visitSfid · watch manageStaffProvider
    • AppPageShell displayBackButton · drawer
      • CustomLoadingIndicator loading
      • FailureStateView error → ref.invalidate(manageStaffProvider)
      • CustomPullToRefresh data → refresh()
        • DataLoadInfo state.lastSyncAt · "-" quando null
        • AccountHeaderCard nome · SAP · isOverdue
        • ManageStaffBodyWidget StatefulWidget · _selectedIndex
          • CustomText título "Equipe do Varejo"
          • CustomTabBar só se showsConectaVoce · Cadastro / Conecta Você → setState
          • ManageStaffContactsSectionWidget aba 0 (ou única)
            • ManageStaffEmptyWidget CustomEmptyState (lista vazia)
            • ManageStaffContactCardWidget por contato → goToStaffDetails(source: contact, staffId)
              • ManageStaffCardWidget shell · Wrap de pills + nome/cargo + caret
                • CustomTag B2B (se isAccountB2B) · B2bPortalStatus.pillVariant
            • ManageStaffAddButtonWidget → goToStaffDetails(source: contact, sem staffId)
          • ManageStaffClerksSectionWidget aba 1 · só com showsConectaVoce
            • ManageStaffEmptyWidget CustomEmptyState (lista vazia)
            • ManageStaffClerkCardWidget por clerk → goToStaffDetails(source: clerk, staffId)
              • ManageStaffCardWidget shell reusado
                • CustomTag ClerkStatus.pillVariant · "Não engajado" se isEngaged == false
            • ManageStaffAddButtonWidget → goToStaffDetails(source: clerk, sem staffId)

A lista não tem modais próprios e nunca chama AppRouter.backWithResult. A aba ativa é estado local do ManageStaffBodyWidget (um setState), não do Notifier — logo o pull-to-refresh não a reseta. O modal "iniciar visita" que aparece antes desta tela pertence ao VisitStartGuard, no detalhe do varejo.The list has no modals of its own and never calls AppRouter.backWithResult. The active tab is local state of ManageStaffBodyWidget (a setState), not of the Notifier — so pull-to-refresh doesn't reset it. The "start visit" modal that appears before this screen belongs to VisitStartGuard, in the retail detail.La lista no tiene modales propios y nunca llama AppRouter.backWithResult. La pestaña activa es estado local del ManageStaffBodyWidget (un setState), no del Notifier — por eso el pull-to-refresh no la resetea. El modal "iniciar visita" que aparece antes de esta pantalla pertenece al VisitStartGuard, en el detalle del punto de venta.

Detalhe ·Detail ·Detalle · StaffDetailsPage

  • StaffDetailsPage visitSfid · source · staffId? · watch staffDetailsProvider + currentMarketProvider
    • AppPageShell displayBackButton
      • CustomLoadingIndicator loading
      • FailureStateView error → ref.invalidate(staffDetailsProvider)
      • SingleChildScrollView data · sem pull-to-refresh
        • DataLoadInfo state.lastSyncAt
        • _contactFields source == contact · cada campo sob fieldVisible(...)
          • StaffDetailsTextFieldWidget fullName · firstName · lastName · cellPhone · email · b2bStatus (readOnly)
          • CustomDropdown<ContactDesignation> opções de staffConfig.contactDesignations
          • CustomDropdown<LanguagePreference> opções de state.languagePreferences
          • CustomDropdown<PreferredContactMethod> opções de staffConfig.preferredContactMethods
          • StaffDetailsRoleAndMainContactRow ConsumerWidget · sob fieldVisible(role)
            • CustomDropdown<ContactRole> opções de state.contactRoles
            • StaffDetailsMainContactSwitchWidget CustomSwitch → updateMainContact
          • StaffDetailsDateFieldWidget dateOfBirth → updateDateOfBirth
            • CustomCalendarModalContent modal · ConectaModal.show<DateTime> · lastDate = idade mínima 18
          • StaffDetailsActionsRowWidget Salvar (canSave = isFormDirty) · Excluir (showDelete = hasContact) · só callbacks; os modais são abertos pela Page
        • _clerkFields source == clerk
          • StaffDetailsClerkFormWidget state · notifier · market
            • CustomTag _statusRow · ClerkStatus + "Não engajado" (só se hasClerk)
            • StaffDetailsTextFieldWidget clerkName · clerkCpf (DocumentUtils) · clerkPhone · clerkEmail
            • CustomDropdown<ClerkProgramLayer> opções fixas: attendant · manager
            • StaffDetailsDateFieldWidget clerkBirthday
              • CustomCalendarModalContent modal · ConectaModal.show<DateTime>
          • CustomButton Ativar/Inativar (só se hasClerk && clerkStatus != null) + Salvar (enable = isFormDirty) · modal aberto pela Page
    • StaffDetailsMainContactSwitchModalContent modal · _confirmMainContactSwitch (Page) · conflito de principal → backWithResult<bool>
    • StaffDetailsActionConfirmModalContent modal · _confirm (Page) · create / edit / delete / activate / inactivate → backWithResult<bool>

Fluxo de ação (§39): o widget só orquestra UI — validate*() → modal de confirmação → método de escrita do Notifier → _notifyResult, que em sucesso mostra ConectaNotice.success e faz AppRouter.back, e em falha mostra ConectaNotice.error mantendo a tela. Todo await é seguido de guarda context.mounted. Widgets auxiliares: StaffDetailsFieldLabelWidget (o rótulo bold12 reusado por todos os campos) e StaffDetailsTextFieldWidget, que é StatefulWidget porque mantém o TextEditingController e reaplica a máscara (o State guarda o valor cru; a máscara é só apresentação).Action flow (§39): the widget only orchestrates UI — validate*() → confirmation modal → the Notifier's write method → _notifyResult, which on success shows ConectaNotice.success and calls AppRouter.back, and on failure shows ConectaNotice.error keeping the screen. Every await is followed by a context.mounted guard. Helper widgets: StaffDetailsFieldLabelWidget (the bold12 label reused by every field) and StaffDetailsTextFieldWidget, which is a StatefulWidget because it holds the TextEditingController and re-applies the mask (the State keeps the raw value; the mask is presentation only).Flujo de acción (§39): el widget solo orquesta UI — validate*() → modal de confirmación → método de escritura del Notifier → _notifyResult, que en éxito muestra ConectaNotice.success y hace AppRouter.back, y en fallo muestra ConectaNotice.error manteniendo la pantalla. Todo await va seguido de una guarda context.mounted. Widgets auxiliares: StaffDetailsFieldLabelWidget (la etiqueta bold12 reutilizada por todos los campos) y StaffDetailsTextFieldWidget, que es StatefulWidget porque mantiene el TextEditingController y reaplica la máscara (el State guarda el valor crudo; la máscara es solo presentación).

Notas por mercadoMarket notesNotas por mercado

Gerenciar equipe é inteiramente dirigido por configuração de mercado (End Market Configuration) — não há um único if por país no código da feature. Duas chaves decidem tudo: o módulo retail_employees (em accountEditionConfig.modules) faz aparecer o botão de entrada, e o bloco accountEditionConfig.staffConfig desenha o formulário. Está habilitado em três mercados:Manage staff is entirely driven by market configuration (End Market Configuration) — there is not a single per-country if in the feature's code. Two keys decide everything: the retail_employees module (in accountEditionConfig.modules) makes the entry button appear, and the accountEditionConfig.staffConfig block draws the form. It's enabled in three markets:Gestionar equipo está enteramente dirigido por configuración de mercado (End Market Configuration) — no hay un solo if por país en el código de la feature. Dos claves lo deciden todo: el módulo retail_employees (en accountEditionConfig.modules) hace aparecer el botón de entrada, y el bloque accountEditionConfig.staffConfig dibuja el formulario. Está habilitado en tres mercados:

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

Gate de entrada ·Entry gate ·Gate de entrada · accountEditionConfig

ChaveKeyClave BR CL ZA AR PY PE
accountEditionConfigxxx
modules[retail_employees].isVisibletruetruetrue
modules[retail_employees].isEditablefalsefalsefalse
modules[retail_employees].editModenonenonenone
staffConfigxxx

Chave ausente e isVisible: false têm o mesmo efeito (o bloco não é renderizado), porque a lista de módulos já chega pré-filtrada por isVisible. O isEditable: false aqui só significa que o número de colaboradores não é editável no detalhe do varejo — não afeta o botão nem esta tela.An absent key and isVisible: false have the same effect (the block isn't rendered), because the module list already arrives pre-filtered by isVisible. The isEditable: false here only means the number of employees is not editable in the retail detail — it does not affect the button or this screen.Clave ausente e isVisible: false tienen el mismo efecto (el bloque no se renderiza), porque la lista de módulos ya llega prefiltrada por isVisible. El isEditable: false aquí solo significa que el número de colaboradores no es editable en el detalle del punto de venta — no afecta al botón ni a esta pantalla.

Matriz doMatrix ofMatriz de staffConfig

ChaveKeyClave BR CL ZA AR PY PE
contactDesignations[][]3
preferredContactMethods444
hasConectaVoceStafftruefalsefalse
contactRecordTypeId""0120Y000000BOrEQAW0120Y000000BOrEQAW
contactPreferredLanguagePortugueseSpanishEnglish
generatesMainContactCpidfalsetruetrue
sendsPreferredContactMethodfalsefalsetrue
sendsContactDesignationAndLanguagePreferencefalsefalsetrue
deactivatesB2bStatusOnDeletefalsefalsetrue
contactFields7711
clerkFields6[][]

Valores das listas: preferredContactMethods = mobile, phone, email, sms nos três mercados; contactDesignations (ZA) = collection_incharge, orders_incharge, delivery_returns_incharge.List values: preferredContactMethods = mobile, phone, email, sms in all three markets; contactDesignations (ZA) = collection_incharge, orders_incharge, delivery_returns_incharge.Valores de las listas: preferredContactMethods = mobile, phone, email, sms en los tres mercados; contactDesignations (ZA) = collection_incharge, orders_incharge, delivery_returns_incharge.

Campos do formulário de Contato ·Contact form fields ·Campos del formulario de Contacto · contactFields

StaffFieldType BR CL ZA AR PY PE
full_namexx
first_namex
last_namex
contact_designationx
language_preferencex
preferred_method_of_contactx
rolexxx
main_contactxxx
cell_phonexxx
emailxxx
b2b_status (somente leitura)(read-only)(solo lectura)xxx
date_of_birthxxx
cpf
unknown

Todos os campos presentes são isVisible: true, isEditable: true, exceto b2b_status, que é isVisible: true, isEditable: false nos três mercados.Every present field is isVisible: true, isEditable: true, except b2b_status, which is isVisible: true, isEditable: false in all three markets.Todos los campos presentes son isVisible: true, isEditable: true, excepto b2b_status, que es isVisible: true, isEditable: false en los tres mercados.

Campos do formulário Conecta Você ·Conecta Você form fields ·Campos del formulario Conecta Você · clerkFields

StaffFieldType BR CL ZA AR PY PE
full_namex
first_name
last_name
contact_designation
language_preference
preferred_method_of_contact
rolex
main_contact
cell_phonex
emailx
b2b_status
date_of_birthx
cpfx
unknown

Validação do documento por mercadoDocument validation per marketValidación del documento por mercado

MercadoMarketMercadoRegraRuleReglaMáscara de digitaçãoInput maskMáscara de escrituraCampo renderizado?Field rendered?¿Campo renderizado?
BR11 dígitos (CPF)11 digits (CPF)11 dígitos (CPF)CpfInputFormatterx
CL8 a 9 caracteres (RUT, com dígito verificador)8 to 9 characters (RUT, with check digit)8 a 9 caracteres (RUT, con dígito verificador)digitsOnly
ZA13 dígitos (ID Number)13 digits (ID Number)13 dígitos (ID Number)digitsOnly
AR13 dígitos13 digits13 dígitosdigitsOnly
PY13 dígitos13 digits13 dígitosdigitsOnly
PE13 dígitos13 digits13 dígitosdigitsOnly

A regra vem de DocumentUtils.isValidIndividual, que cobre os seis mercados. Na prática, o único campo de documento desta feature é o do Conecta Você, e ele só é renderizado no Brasil — as demais linhas descrevem o comportamento que valeria se o mercado habilitasse cpf em clerkFields ou contactFields.The rule comes from DocumentUtils.isValidIndividual, which covers all six markets. In practice this feature's only document field is the Conecta Você one, and it renders only in Brazil — the other rows describe the behavior that would apply if the market enabled cpf in clerkFields or contactFields.La regla viene de DocumentUtils.isValidIndividual, que cubre los seis mercados. En la práctica el único campo de documento de esta feature es el de Conecta Você, y solo se renderiza en Brasil — las demás filas describen el comportamiento que valdría si el mercado habilitara cpf en clerkFields o contactFields.

Transações disparadas por mercadoTransactions fired per marketTransacciones disparadas por mercado

serviceName BR CL ZA AR PY PE
RetailerUploadAPI (contato)(contact)(contacto)xxx
ContactConectaVoce (cadastro)(record)(registro)x
ContactUpdateStatus (situação)(state)(situación)x
BR

Único com Conecta VocêThe only one with Conecta VocêEl único con Conecta Você É o único mercado com hasConectaVoceStaff: true e clerkFields populado — logo o único onde as abas aparecem (e só se o varejo estiver inscrito). Também é o único que dispara ContactConectaVoce e ContactUpdateStatus. Em troca, é o mercado com o formulário de contato mais enxuto (nome completo em um campo) e o único que não envia CPID (generatesMainContactCpid: false). Os wireValue de ClerkStatus são em português ("Ativo"/"Inativo") porque o programa é brasileiro. Detalhe do programa em Conecta Você. It is the only market with hasConectaVoceStaff: true and a populated clerkFields — hence the only one where the tabs appear (and only if the retail is enrolled). It is also the only one firing ContactConectaVoce and ContactUpdateStatus. In exchange, it has the leanest contact form (full name in one field) and is the only one that does not send CPID (generatesMainContactCpid: false). ClerkStatus's wireValues are in Portuguese ("Ativo"/"Inativo") because the programme is Brazilian. Programme detail in Conecta Você. Es el único mercado con hasConectaVoceStaff: true y clerkFields poblado — por eso el único donde aparecen las pestañas (y solo si el punto de venta está inscrito). También es el único que dispara ContactConectaVoce y ContactUpdateStatus. En cambio, tiene el formulario de contacto más escueto (nombre completo en un campo) y es el único que no envía CPID (generatesMainContactCpid: false). Los wireValue de ClerkStatus están en portugués ("Ativo"/"Inativo") porque el programa es brasileño. Detalle del programa en Conecta Você.

CL

Formulário igual ao BRSame form as BRFormulario igual al BR O formulário de contato é idêntico ao brasileiro (mesmos 7 campos) — não há campo exclusivo do Chile. As diferenças são de wire: envia CPID (generatesMainContactCpid: true), tem um contactRecordTypeId real e manda PreferedLanguage: "Spanish". Sem Conecta Você: a aba nunca aparece e a validação de RUT (8–9 caracteres) fica sem uso nesta tela. The contact form is identical to Brazil's (same 7 fields) — there is no Chile-exclusive field. The differences are on the wire: it sends CPID (generatesMainContactCpid: true), has a real contactRecordTypeId and sends PreferedLanguage: "Spanish". No Conecta Você: the tab never appears and the RUT validation (8–9 characters) goes unused on this screen. El formulario de contacto es idéntico al brasileño (los mismos 7 campos) — no hay campo exclusivo de Chile. Las diferencias son de wire: envía CPID (generatesMainContactCpid: true), tiene un contactRecordTypeId real y manda PreferedLanguage: "Spanish". Sin Conecta Você: la pestaña nunca aparece y la validación de RUT (8–9 caracteres) queda sin uso en esta pantalla.

ZA

Formulário mais ricoRichest formFormulario más rico O único com 11 campos: troca "nome completo" por first_name + last_name e acrescenta os três exclusivos — contact_designation, language_preference e preferred_method_of_contact. É também o único com todas as três flags de wire ligadas: envia PreferedCntMethod, envia contactDesignation + languagePreference e força B2BStatus: "Inactive" ao excluir um contato ativo (deactivatesB2bStatusOnDelete: true). As designações disponíveis são collection_incharge, orders_incharge e delivery_returns_incharge. The only one with 11 fields: it swaps "full name" for first_name + last_name and adds the three exclusive ones — contact_designation, language_preference and preferred_method_of_contact. It is also the only one with all three wire flags on: it sends PreferedCntMethod, sends contactDesignation + languagePreference and forces B2BStatus: "Inactive" on deleting an active contact (deactivatesB2bStatusOnDelete: true). The available designations are collection_incharge, orders_incharge and delivery_returns_incharge. El único con 11 campos: cambia "nombre completo" por first_name + last_name y agrega los tres exclusivos — contact_designation, language_preference y preferred_method_of_contact. Es también el único con las tres flags de wire encendidas: envía PreferedCntMethod, envía contactDesignation + languagePreference y fuerza B2BStatus: "Inactive" al eliminar un contacto activo (deactivatesB2bStatusOnDelete: true). Las designaciones disponibles son collection_incharge, orders_incharge y delivery_returns_incharge.

ARPYPE

InalcançávelUnreachableInalcanzable Existem como mercados do app (flavour PANGEA), mas não têm accountEditionConfig — nem os módulos, nem o staffConfig. Consequência em cadeia: a lista de módulos chega vazia, o bloco de colaboradores não é renderizado, o botão "Gerenciar Equipe" não existe e a tela é inalcançável. Mesmo por deep link o formulário sairia sem nenhum campo, porque StaffConfig() default esconde tudo. As traduções, curiosamente, já existem nos três. Para habilitar bastam duas adições de configuração (o módulo retail_employees e o bloco staffConfig), sem mudança de código. They exist as app markets (PANGEA flavour), but have no accountEditionConfig — neither the modules nor staffConfig. The chain consequence: the module list arrives empty, the employees block isn't rendered, the "Manage staff" button doesn't exist and the screen is unreachable. Even via deep link the form would come out with no fields at all, because the default StaffConfig() hides everything. The translations, curiously, already exist in all three. Enabling it takes two configuration additions (the retail_employees module and the staffConfig block), with no code change. Existen como mercados de la app (flavour PANGEA), pero no tienen accountEditionConfig — ni los módulos ni el staffConfig. Consecuencia en cadena: la lista de módulos llega vacía, el bloque de colaboradores no se renderiza, el botón "Gestionar equipo" no existe y la pantalla es inalcanzable. Incluso por deep link el formulario saldría sin ningún campo, porque el StaffConfig() por defecto esconde todo. Las traducciones, curiosamente, ya existen en los tres. Para habilitarlo bastan dos adiciones de configuración (el módulo retail_employees y el bloque staffConfig), sin cambio de código.

Pendências / roadmapPending / roadmapPendencias / roadmap

  • Escrita não tem fila offline. O DispatcherOrchestrator.dispatch só enfileira transações de DispatcherType.visit, e o flush() só reenvia visitas. Sem conexão, as três transações desta feature falham na hora e só voltam por reenvio manual na Central de dados.The write has no offline queue. DispatcherOrchestrator.dispatch only queues DispatcherType.visit transactions, and flush() only re-sends visits. With no connection this feature's three transactions fail immediately and only come back via manual re-send in the Data Center.La escritura no tiene cola offline. El DispatcherOrchestrator.dispatch solo encola transacciones de DispatcherType.visit, y el flush() solo reenvía visitas. Sin conexión, las tres transacciones de esta feature fallan al instante y solo vuelven por reenvío manual en la Central de datos.
  • A lista não se atualiza ao voltar do detalhe. O goToStaffDetails não propaga resultado e o manageStaffProvider não é invalidado — a mudança só aparece após pull-to-refresh.The list doesn't refresh on returning from the detail. goToStaffDetails propagates no result and manageStaffProvider isn't invalidated — the change only shows after pull-to-refresh.La lista no se actualiza al volver del detalle. El goToStaffDetails no propaga resultado y el manageStaffProvider no se invalida — el cambio solo aparece tras pull-to-refresh.
  • Sem indicador de "salvando". Os campos isSavingContact e isSavingClerk existem no StaffDetailsState mas nunca são lidos nem escritos — durante o envio a tela não dá retorno visual e o botão não bloqueia.No "saving" indicator. The isSavingContact and isSavingClerk fields exist on StaffDetailsState but are never read or written — during submission the screen gives no visual feedback and the button isn't blocked.Sin indicador de "guardando". Los campos isSavingContact e isSavingClerk existen en el StaffDetailsState pero nunca se leen ni escriben — durante el envío la pantalla no da retorno visual y el botón no se bloquea.
  • main_contact não é consultado. A chave existe em StaffFieldType e nos três mercados, mas a chave liga/desliga é exibida sob fieldVisible(role) — configurar main_contact como invisível não a esconde.main_contact is not consulted. The key exists in StaffFieldType and in all three markets, but the toggle is rendered under fieldVisible(role) — configuring main_contact as invisible does not hide it.main_contact no se consulta. La clave existe en StaffFieldType y en los tres mercados, pero el interruptor se renderiza bajo fieldVisible(role) — configurar main_contact como invisible no lo oculta.
  • Camada do programa é hardcoded. O dropdown do Conecta Você oferece uma lista fixa (attendant, manager) em vez de vir da configuração, como os outros dropdowns.The programme layer is hardcoded. The Conecta Você dropdown offers a fixed list (attendant, manager) instead of coming from configuration, unlike the other dropdowns.La capa del programa está hardcoded. El dropdown de Conecta Você ofrece una lista fija (attendant, manager) en vez de venir de la configuración, a diferencia de los otros dropdowns.
  • Ícone do estado vazio é provisório. O ConectaIcons.manageStaffEmpty aponta para assets/icons/global/close.svg — o ícone de fechar compartilhado, não um ícone próprio de "sem colaboradores".The empty-state icon is provisional. ConectaIcons.manageStaffEmpty points at assets/icons/global/close.svg — the shared close icon, not a dedicated "no staff" icon.El icono del estado vacío es provisional. El ConectaIcons.manageStaffEmpty apunta a assets/icons/global/close.svg — el icono de cerrar compartido, no un icono propio de "sin colaboradores".
  • Ack do Conecta Você é mais estrito. O saveClerk exige ack.status == 0, enquanto as outras escritas aceitam 0 ou 5 — um ack 5 num cadastro do programa é tratado como falha e nada é persistido.The Conecta Você ack is stricter. saveClerk requires ack.status == 0, while the other writes accept 0 or 5 — a 5 ack on a programme record is treated as a failure and nothing is persisted.El ack de Conecta Você es más estricto. El saveClerk exige ack.status == 0, mientras las otras escrituras aceptan 0 o 5 — un ack 5 en un registro del programa se trata como fallo y nada se persiste.
  • enabledMarkets é só declarativo. Nenhum código em runtime lê DispatcherType.enabledMarkets; a proteção real por mercado é a tela ser inalcançável fora de BR/CL/ZA.enabledMarkets is declarative only. No runtime code reads DispatcherType.enabledMarkets; the real per-market protection is that the screen is unreachable outside BR/CL/ZA.enabledMarkets es solo declarativo. Ningún código en runtime lee DispatcherType.enabledMarkets; la protección real por mercado es que la pantalla es inalcanzable fuera de BR/CL/ZA.
  • O EMC anotado está defasado. O end_market_configuration.detailed.jsonc não documenta cinco chaves que o JSON real já tem (contactPreferredLanguage, generatesMainContactCpid, sendsPreferredContactMethod, sendsContactDesignationAndLanguagePreference, deactivatesB2bStatusOnDelete).The annotated EMC is out of date. end_market_configuration.detailed.jsonc does not document five keys the real JSON already has (contactPreferredLanguage, generatesMainContactCpid, sendsPreferredContactMethod, sendsContactDesignationAndLanguagePreference, deactivatesB2bStatusOnDelete).El EMC anotado está desfasado. El end_market_configuration.detailed.jsonc no documenta cinco claves que el JSON real ya tiene (contactPreferredLanguage, generatesMainContactCpid, sendsPreferredContactMethod, sendsContactDesignationAndLanguagePreference, deactivatesB2bStatusOnDelete).