ChamadosCasesCasos
A lista de chamados (atendimentos / SAC) dos varejos sob a hierarquia do representante de vendas, com status, prioridade e drill-down ao detalhe. É quase só leitura: a única escrita é encerrar um chamado — e só no Brasil. The list of cases (service / support tickets) from the retails under the sales rep's hierarchy, with status, priority and drill-down to detail. It's almost read-only: the single write is closing a case — and only in Brazil. La lista de casos (atenciones / SAC) de los puntos de venta bajo la jerarquía del representante de ventas, con estado, prioridad y acceso al detalle. Es casi solo lectura: la única escritura es cerrar un caso — y solo en Brasil.
O que é e para que serveWhat it is and what it's forQué es y para qué sirve
A tela de Chamados reúne os atendimentos (tickets de suporte / SAC do consumidor) abertos para os varejos sob a hierarquia do representante de vendas. Responde três perguntas: The Cases screen gathers the service tickets (support / consumer SAC) opened for the retails under the sales rep's hierarchy. It answers three questions: La pantalla de Casos reúne las atenciones (tickets de soporte / SAC del consumidor) abiertas para los puntos de venta bajo la jerarquía del representante de ventas. Responde tres preguntas:
Quais chamados existem?Which cases exist?¿Qué casos hay?
Um card por chamado, com número, varejo, status, prioridade e data de abertura.One card per case, with number, retail, status, priority and open date.Una tarjeta por caso, con número, punto de venta, estado, prioridad y fecha de apertura.
Em que pé está?Where does it stand?¿En qué punto está?
O detalhe mostra contato, dados do caso, manifestação e o bloco SAC do consumidor.The detail shows contact, case data, manifestation and the consumer SAC block.El detalle muestra contacto, datos del caso, manifestación y el bloque SAC del consumidor.
O que fazer com um?What to do with one?¿Qué hacer con uno?
Se ainda estiver Novo, o chamado pode ser encerrado com uma justificativa.If still New, the case can be closed with a justification.Si aún está Nuevo, el caso puede ser cerrado con una justificación.
Só BrasilBrazil onlySolo Brasil Chamados é habilitado apenas no Brasil (pela End Market Configuration). Nos demais mercados a ação nem aparece na Home. A lista é leitura; a única escrita — encerrar — também é exclusiva do Brasil. Cases is enabled only in Brazil (via End Market Configuration). In the other markets the action doesn't even show on Home. The list is read-only; the single write — close — is also Brazil-only. Casos está habilitado solo en Brasil (vía End Market Configuration). En los demás mercados la acción ni aparece en el Home. La lista es de lectura; la única escritura — cerrar — también es exclusiva de Brasil.
Como acessarHow to openCómo acceder
- Pela grade de ações da HomeFrom the Home actions gridDesde la grilla de acciones del HomeNo módulo Ações do representante da Home, toque no atalho Chamados (mostra a contagem de pendentes).In the Home Rep actions module, tap the Cases tile (it shows the pending count).En el módulo Acciones del representante del Home, toque el atajo Casos (muestra el conteo de pendientes).
- A lista abreThe list opensLa lista abreMostra os chamados em cards. Puxe para baixo para atualizar; role para carregar mais.It shows the cases as cards. Pull down to refresh; scroll to load more.Muestra los casos en tarjetas. Deslice hacia abajo para actualizar; desplace para cargar más.
- Toque num cardTap a cardToque una tarjetaAbre o detalhe do chamado, com todos os dados e o botão de encerrar (quando cabível).Opens the case detail, with all data and the close button (when applicable).Abre el detalle del caso, con todos los datos y el botón de cerrar (cuando corresponde).
Estrutura da telaScreen structureEstructura de la pantalla
ListaListLista
- CabeçalhoHeaderEncabezado
- Ícone + título "Chamados" e a data da última sincronização.Icon + "Cases" title and the last sync date.Ícono + título "Casos" y la fecha de última sincronización.
- CardsCardsTarjetas
- Um por chamado: número do chamado em destaque e quatro campos — varejo, status, data de abertura e prioridade.One per case: the case number highlighted and four fields — retail, status, open date and priority.Una por caso: el número del caso destacado y cuatro campos — punto de venta, estado, fecha de apertura y prioridad.
- Carregar maisLoad moreCargar más
- A lista mostra 15 por vez e carrega mais ao rolar até o fim (rolagem infinita, sem busca/filtro).The list shows 15 at a time and loads more as you scroll to the end (infinite scroll, no search/filter).La lista muestra 15 por vez y carga más al desplazar hasta el final (scroll infinito, sin búsqueda/filtro).
- Lista vaziaEmpty listLista vacía
- Sem chamados, mostra um estado vazio com ícone e mensagem.With no cases, it shows an empty state with icon and message.Sin casos, muestra un estado vacío con ícono y mensaje.
DetalheDetailDetalle
- NúmeroNumberNúmero
- O número do chamado em destaque no topo.The case number highlighted at the top.El número del caso destacado arriba.
- Varejo & contatoRetail & contactPunto de venta y contacto
- Card com varejo, cargo, responsável, telefone, e-mail, SAP ID e CNPJ/CPF (formatado por mercado).Card with retail, role, responsible, phone, e-mail, SAP ID and tax ID (formatted per market).Tarjeta con punto de venta, cargo, responsable, teléfono, correo, SAP ID y ID fiscal (formateado por mercado).
- Dados do chamadoCase dataDatos del caso
- Card com status, prioridade, responsável, solicitante, assunto, descrição e datas (atualização, encerramento, abertura).Card with status, priority, owner, requestor, subject, description and dates (updated, closing, open).Tarjeta con estado, prioridad, responsable, solicitante, asunto, descripción y fechas (actualización, cierre, apertura).
- AdicionaisAdditionalAdicionales
- Card com empresa, linha, produto/assunto, variedade e a manifestação (grupo, tipo, procedimento).Card with company, line, product subject, variety and the manifestation (group, type, procedure).Tarjeta con empresa, línea, producto/asunto, variedad y la manifestación (grupo, tipo, procedimiento).
- Botão encerrarClose buttonBotón cerrar
- Aparece só quando o status é Novo.Shows only when the status is New.Aparece solo cuando el estado es Nuevo.
Status e prioridadeStatus & priorityEstado y prioridad
O chamado tem um status e uma prioridade. Diferente do status de pedido, aqui os rótulos são traduzidos (i18n) — o valor de wire (inglês) é mapeado para o idioma do app. Valores possíveis:A case has a status and a priority. Unlike the order status, here the labels are translated (i18n) — the wire value (English) is mapped to the app language. Possible values:Un caso tiene un estado y una prioridad. A diferencia del estado del pedido, aquí las etiquetas se traducen (i18n) — el valor de wire (inglés) se mapea al idioma de la app. Valores posibles:
- StatusStatusEstado
New·Working·Escalated·On Hold·Closed- PrioridadePriorityPrioridad
High·Medium·Low
EncerramentoClosingCierre
O botão Encerrar só existe enquanto o chamado está New. Depois de encerrado (ou em qualquer outro status), não há ação — a tela é consulta.
The Close button only exists while the case is New. Once closed (or in any other status), there's no action — the screen is read-only.
El botón Cerrar solo existe mientras el caso está New. Una vez cerrado (o en cualquier otro estado), no hay acción — la pantalla es de consulta.
Ação: encerrar chamadoAction: close caseAcción: cerrar caso
A única escrita da feature. Encerra o chamado, exigindo uma justificativa. É enviada primeiro ao backend (via Dispatcher) e, só se der certo, o chamado passa a Closed localmente.The feature's only write. It closes the case, requiring a justification. It's sent to the backend first (via the Dispatcher) and, only on success, the case becomes Closed locally.La única escritura de la feature. Cierra el caso, exigiendo una justificación. Se envía primero al backend (vía Dispatcher) y, solo si tiene éxito, el caso pasa a Closed localmente.
- Toque em "Encerrar chamado"Tap "Close case"Toque "Cerrar caso"Abre um modal com um campo de texto para a justificativa.A modal opens with a text field for the justification.Abre un modal con un campo de texto para la justificación.
- Escreva a justificativaWrite the justificationEscriba la justificaciónMínimo de 50 caracteres; um contador mostra o progresso e o botão de confirmar só habilita ao atingir o mínimo.Minimum 50 characters; a counter shows progress and the confirm button only enables once the minimum is reached.Mínimo 50 caracteres; un contador muestra el progreso y el botón de confirmar solo se habilita al alcanzar el mínimo.
- ConfirmeConfirmConfirmeO envio segue; em sucesso, aparece um aviso verde, a lista é atualizada e a tela volta. Em erro, um aviso vermelho e o chamado permanece aberto.The submit runs; on success, a green notice appears, the list refreshes and the screen goes back. On error, a red notice shows and the case stays open.El envío corre; en éxito, aparece un aviso verde, la lista se actualiza y la pantalla vuelve. En error, un aviso rojo y el caso permanece abierto.
Arquitetura e fluxo de dadosArchitecture & data flowArquitectura y flujo de datos
Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. Há dois fluxos distintos: a leitura (lista e detalhe) e a escrita (encerrar, via Dispatcher). O dado de leitura atravessa quatro representações ligadas por mappers, com cache write-through.Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. There are two distinct flows: the read (list and detail) and the write (close, via the Dispatcher). Read data crosses four representations linked by mappers, with cache write-through.Clean Architecture + Riverpod + Freezed + gRPC + ObjectBox. Hay dos flujos distintos: la lectura (lista y detalle) y la escritura (cerrar, vía Dispatcher). El dato de lectura atraviesa cuatro representaciones unidas por mappers, con cache write-through.
Leitura · lista (cache + remoto no refresh)Read · list (cache + remote on refresh)Lectura · lista (caché + remoto en refresh)
Um único RPC (getCases) traz todos os chamados. O build() da lista lê do cache (fonte local); o pull-to-refresh força a fonte remote, que grava de volta no ObjectBox:A single RPC (getCases) returns every case. The list's build() reads from cache (local source); pull-to-refresh forces the remote source, which writes back to ObjectBox:Un único RPC (getCases) trae todos los casos. El build() de la lista lee del caché (fuente local); el pull-to-refresh fuerza la fuente remote, que graba de vuelta en ObjectBox:
- CaseManagementReplygRPC proto
- toCasesDTOCasesDTODTO · Freezed
- toDomainCasesEntitydomain
- toModelCasesModelObjectBox
- toDomainCasesEntitydomain · cache
- watchCasesNotifier + State
- → UICasesPage
- watchCasesNotifier + State
- toDomainCasesEntitydomain · cache
- toModelCasesModelObjectBox
- toDomainCasesEntitydomain
- toCasesDTOCasesDTODTO · Freezed
Leitura · detalhe (só cache)Read · detail (cache-only)Lectura · detalle (solo caché)
O detalhe não chama RPC: lê um chamado do cache pelo sfid (getCachedBySfid, §28 cat. A):Detail calls no RPC: it reads one case from cache by sfid (getCachedBySfid, §28 cat. A):El detalle no llama RPC: lee un caso del caché por sfid (getCachedBySfid, §28 cat. A):
- CaseModelObjectBox · cache
- getCases → firstWhereOrNullCaseManagementLocalDataSource
- toDomainCaseEntitydomain
- getCachedBySfidGetCasesUseCase
- build (cache-only)CaseDetailNotifier + State
- → UICaseDetailPage
- build (cache-only)CaseDetailNotifier + State
- getCachedBySfidGetCasesUseCase
- toDomainCaseEntitydomain
- getCases → firstWhereOrNullCaseManagementLocalDataSource
Escrita · encerrar via DispatcherWrite · close via DispatcherEscritura · cerrar vía Dispatcher
Remote-first (§36): o notifier monta o envelope CaseUploadAPI, despacha pelo Dispatcher e, só em sucesso, marca o chamado como Closed no cache local. Não passa pelo CaseManagementRepository de leitura:Remote-first (§36): the notifier builds the CaseUploadAPI envelope, dispatches through the Dispatcher and, only on success, marks the case as Closed in the local cache. It does not go through the read CaseManagementRepository:Remote-first (§36): el notifier arma el sobre CaseUploadAPI, despacha por el Dispatcher y, solo en éxito, marca el caso como Closed en el caché local. No pasa por el CaseManagementRepository de lectura:
- CaseDetailCloseButtonWidgetUI · modal
- closeCase(comment)CaseDetailNotifier
- build(input)BuildCaseUploadDispatcherPayloadUseCase→ DispatcherEnvelope
- submit(envelope)SubmitCaseUploadUseCase
- dispatchDispatcherOrchestratorgRPC dispatcher
- on success → markCaseClosedSaveCaseClosureUseCasecache local
- dispatchDispatcherOrchestratorgRPC dispatcher
- submit(envelope)SubmitCaseUploadUseCase
- build(input)BuildCaseUploadDispatcherPayloadUseCase→ DispatcherEnvelope
- closeCase(comment)CaseDetailNotifier
Pendências / roadmapPending / roadmapPendientes / roadmap
- Sem busca, ordenação ou filtros na lista (só rolagem infinita de 15 em 15) — diferente de Pedidos.No search, sort or filters on the list (only 15-at-a-time infinite scroll) — unlike Orders.Sin búsqueda, orden ni filtros en la lista (solo scroll infinito de 15 en 15) — a diferencia de Pedidos.
lastModifiedDateexiste no proto/datasource mas não é usado — não há sync incremental hoje.lastModifiedDateexists in the proto/datasource but is not used — no incremental sync today.lastModifiedDateexiste en el proto/datasource pero no se usa — no hay sync incremental hoy.- Encerrar é a única escrita, e só de
New→Closed; não há reabrir nem editar. A transação CaseUploadAPI está documentada à parte.Close is the only write, and onlyNew→Closed; no reopen or edit. The CaseUploadAPI transaction is documented separately.Cerrar es la única escritura, y soloNew→Closed; no hay reabrir ni editar. La transacción CaseUploadAPI está documentada aparte.
Modelo de dadosData modelModelo de datos
O mesmo chamado 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 se mantêm; muda muito pouco. O fetch é write-through: todo retorno é gravado no ObjectBox e a UI lê do cache.The same case 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. Names stay the same; very little changes. Fetch is write-through: every response is written to ObjectBox and the UI reads from cache.El mismo caso 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; cambia muy poco. El fetch es write-through: toda respuesta se graba en ObjectBox y la UI lee del caché.
A lista chega num container CasesEntity (lastSyncAt gerado no mapper + items[]); cada item é um Case de 27 campos, com um bloco aninhado CaseSac (SAC do consumidor). Aqui há uma particularidade: só o campo role vira enum tipado (ContactRole?) na Entity — status e priority ficam String em todas as camadas (o enum é resolvido só na UI). A seguir: o proto, as estruturas campo-a-campo, e os mappers.The list arrives in a CasesEntity container (lastSyncAt generated in the mapper + items[]); each item is a 27-field Case with a nested CaseSac block (consumer SAC). There's one quirk here: only the role field becomes a typed enum (ContactRole?) in the Entity — status and priority stay String in every layer (the enum is resolved only in the UI). Next: the proto, the field-by-field structures, and the mappers.La lista llega en un container CasesEntity (lastSyncAt generado en el mapper + items[]); cada ítem es un Case de 27 campos, con un bloque anidado CaseSac (SAC del consumidor). Hay una particularidad: solo el campo role se vuelve enum tipado (ContactRole?) en la Entity — status y priority quedan String en todas las capas (el enum se resuelve solo en la UI). A continuación: el proto, las estructuras campo a campo, y los mappers.
Proto
CaseManagementConectaRep.proto · proto3 · package mn.bat.conectarep.streambridge. Um serviço (CaseManagementConectaRepService), um método unário:One service (CaseManagementConectaRepService), a single unary method:Un servicio (CaseManagementConectaRepService), un método unario:
getCasesunaryrpc getCases(CaseManagementRequest) returns (CaseManagementReply)
path /mn.bat.conectarep.streambridge.CaseManagementConectaRepService/getCases
CaseManagementRequestlocationHierarchySfidstring· #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)lastModifiedDatestring· #2 · optional (não usado hoje)optional (not used today)optional (no usado hoy)
CaseManagementReplyrepeated CaseInfo cases — a lista de chamados. Os 27 campos de CaseInfo estão detalhados nas Estruturas de dados abaixo.the list of cases. CaseInfo's 27 fields are detailed in Data structures below.la lista de casos. Los 27 campos de CaseInfo están detallados en Estructuras de datos abajo.
Estruturas de dadosData structuresEstructuras de datos
Um dropdown por estrutura, aninhados pela hierarquia. Cada tabela tem uma coluna por camada — Proto · DTO · Model · Entity; as células com borda marcam onde o tipo/nome primeiro muda (enum na Entity, ToOne no Model, rename no Model). ¹ = optional no proto.One dropdown per structure, nested by hierarchy. Each table has one column per layer — Proto · DTO · Model · Entity; bordered cells mark where the type/name first changes (enum in the Entity, ToOne in the Model, rename in the Model). ¹ = optional in the proto.Un dropdown por estructura, anidados por jerarquía. Cada tabla tiene una columna por capa — Proto · DTO · Model · Entity; las celdas con borde marcan dónde primero cambia el tipo/nombre (enum en la Entity, ToOne en el Model, rename en el Model). ¹ = optional en el proto.
Case raiz · CaseInfo 27 camposfieldscampos
Campo Proto DTO Model Entity idstring String caseSfidString caseNumberstring String String String statusstring String? String? String? prioritystring String? String? String? openDatestring String? String? String? closingDatestring¹ String? String? String? lastUpdatedstring String? String? String? ownerstring String? String? String? requestorstring String? String? String? subjectstring String? String? String? descriptionstring String? String? String? storeNamestring String? String? String? contactNamestring String? String? String? rolestring String? String? ContactRole?phonestring String? String? String? emailstring String? String? String? taxIdstring String? String? String? sapCustomerIdstring String? String? String? companystring String? String? String? linestring String? String? String? productSubjectstring String? String? String? varietystring String? String? String? manifestationstring String? String? String? manifestationTypestring String? String? String? manifestationGroupstring String? String? String? procedurestring String? String? String? sacCaseSac¹ …DTO? ToOne<…Model>…Entity? CaseSac Case.sac 13 camposfieldscampos
Campo Proto DTO Model Entity consumerNamestring String? String? String? taxNumberstring String? String? String? statusstring String? String? String? lotNumberstring String? String? String? productionDatestring String? String? String? birthDatestring String? String? String? phonestring String? String? String? emailstring String? String? String? zipCodestring String? String? String? statestring String? String? String? citystring String? String? String? neighborhoodstring String? String? String? addressstring String? String? String?
O container Cases (CasesEntity/CasesModel) tem 2 campos: lastSyncAt (DateTime) e items — List<Case> na Entity / ToMany<CaseModel> no Model.The Cases container (CasesEntity/CasesModel) has 2 fields: lastSyncAt (DateTime) and items — List<Case> in the Entity / ToMany<CaseModel> in the Model.El container Cases (CasesEntity/CasesModel) tiene 2 campos: lastSyncAt (DateTime) e items — List<Case> en la Entity / ToMany<CaseModel> en el Model.
Mappers
As conversões entre as camadas, todas como extension (5 direções por tipo):The conversions between layers, all as extensions (5 directions per type):Las conversiones entre capas, todas como extension (5 direcciones por tipo):
| DireçãoDirectionDirección | MétodoMethodMétodo |
|---|---|
| JSON → DTO | static fromMap(Map) (container + CaseDTOMapper/CaseSacDTOMapper)(container + CaseDTOMapper/CaseSacDTOMapper)(container + CaseDTOMapper/CaseSacDTOMapper) |
| Proto → DTO | toCasesDTO() / toDTO() (guarda hasClosingDate()/hasSac())(guards hasClosingDate()/hasSac())(guarda hasClosingDate()/hasSac()) |
| DTO → Entity | toDomain() (resolve role: ContactRole.fromLabel)(resolves role: ContactRole.fromLabel)(resuelve role: ContactRole.fromLabel) |
| Entity → Model | toModel() (role → .label; id → caseSfid; popula ToOne/ToMany)(role → .label; id → caseSfid; fills ToOne/ToMany)(role → .label; id → caseSfid; llena ToOne/ToMany) |
| Model → Entity | toDomain() |
Os únicos deltasThe only deltasLos únicos deltas
roleé o único enum tipado, e só na Entity (ContactRole?);Stringnas outras camadas.roleis the only typed enum, and only in the Entity (ContactRole?);Stringin the other layers.rolees el único enum tipado, y solo en la Entity (ContactRole?);Stringen las demás capas.statusepriorityficamStringem todas as camadas — o enum (CaseStatus/CasePriority) é resolvido só na UI viafromValue.statusandprioritystayStringin all layers — the enum (CaseStatus/CasePriority) is resolved only in the UI viafromValue.statusypriorityquedanStringen todas las capas — el enum (CaseStatus/CasePriority) se resuelve solo en la UI víafromValue.- rename
id→caseSfidno Model (o@Id intdo ObjectBox é separado).renameid→caseSfidin the Model (ObjectBox's@Id intis separate).renameid→caseSfiden el Model (el@Id intde ObjectBox es aparte). Case.sacviraToOne<CaseSacModel>eCases.itemsviraToMany<CaseModel>no Model.Case.sacbecomesToOne<CaseSacModel>andCases.itemsbecomesToMany<CaseModel>in the Model.Case.sacpasa aToOne<CaseSacModel>yCases.itemspasa aToMany<CaseModel>en el Model.lastSyncAtgerado no mapper comDateTimeUtils.now()(o backend não envia o campo).generated in the mapper withDateTimeUtils.now()(the backend doesn't send it).generado en el mapper conDateTimeUtils.now()(el backend no envía el campo).
Repository
CaseManagementRepositoryImpl implementaimplementsimplementa CaseManagementRepositoryInterface e injeta os 3 datasources (mock/local/remote) + ConnectivityService + a flag useMockData + Ref. Método a método:and injects the 3 datasources (mock/local/remote) + ConnectivityService + the useMockData flag + Ref. Method by method:e inyecta los 3 datasources (mock/local/remote) + ConnectivityService + la flag useMockData + Ref. Método a método:
getCases({source}) mock / local / remote
RetornaReturnsDevuelve Result<CasesEntity, Failure>
Ponto de entrada da lista: decide a fonte pela source + flags, mapeia e grava no cache (write-through). Chamado pelo CasesNotifier.List entry point: picks the source from source + flags, maps and writes to cache (write-through). Called by CasesNotifier.Punto de entrada de la lista: elige la fuente por source + flags, mapea y graba en caché (write-through). Llamado por CasesNotifier.
Árvore de decisão de fonteSource decision treeÁrbol de decisión de fuente
useMockData== true ouorosource == mock→_fetchFromMock(): lê o mock, mapeia, grava no cache.→_fetchFromMock(): reads the mock, maps, writes to cache.→_fetchFromMock(): lee el mock, mapea, graba en caché.source == localou offlineor offlineu offline→_fetchFromCacheOrFail(): cache; se vazio,Error(NetworkFailure). É o caminho dobuild()da lista.→_fetchFromCacheOrFail(): cache; if empty,Error(NetworkFailure). It's the list'sbuild()path.→_fetchFromCacheOrFail(): caché; si vacío,Error(NetworkFailure). Es el camino delbuild()de la lista.- senão (remoto + conectado)otherwise (remote + connected)si no (remoto + conectado)→
_fetchFromRemoteWithFallback(): lêcurrentResourceProvider; senullcai pro cache; senão chama o remoto comlocationHierarchyId(§25), mapeia, grava no cache; em erro, fallback pro cache. É o caminho do pull-to-refresh.→_fetchFromRemoteWithFallback(): readscurrentResourceProvider; ifnullfalls back to cache; else calls remote withlocationHierarchyId(§25), maps, writes to cache; on error, falls back to cache. It's the pull-to-refresh path.→_fetchFromRemoteWithFallback(): leecurrentResourceProvider; sinullcae al caché; si no llama al remoto conlocationHierarchyId(§25), mapea, graba en caché; en error, fallback al caché. Es el camino del pull-to-refresh.
getCachedCases() local
RetornaReturnsDevuelve Result<CasesEntity?, Failure>
Só cache. null vira Success(null), não erro.Cache only. null becomes Success(null), not an error.Solo caché. null es Success(null), no error.
getCachedCasesLastSyncAt() local
RetornaReturnsDevuelve DateTime? (sem Result)(no Result)(sin Result)
Timestamp da última sincronização do container, para o DataLoadInfo.The container's last-sync timestamp, for DataLoadInfo.Timestamp de última sincronización del container, para DataLoadInfo.
getCachedCaseBySfid({caseSfid}) local
RetornaReturnsDevuelve Result<CaseEntity?, Failure>
Lê getCachedCases() e faz firstWhereOrNull(id == caseSfid). Alimenta o detalhe (§28 cat. A) — nunca dispara remoto.Reads getCachedCases() and does firstWhereOrNull(id == caseSfid). Feeds the detail (§28 cat. A) — never triggers remote.Lee getCachedCases() y hace firstWhereOrNull(id == caseSfid). Alimenta el detalle (§28 cat. A) — nunca dispara remoto.
saveCases({cases}) local
RetornaReturnsDevuelve Result<void, Failure>
Destrutivo: clearCases() + regrava tudo (cascata das boxes filhas). Cache-writer chamado após cada fetch bem-sucedido.Destructive: clearCases() + rewrites everything (child boxes cascade). Cache-writer called after each successful fetch.Destructivo: clearCases() + regraba todo (cascada de boxes hijas). Cache-writer llamado tras cada fetch exitoso.
markCaseClosed({caseSfid}) local · escritawriteescritura
RetornaReturnsDevuelve Result<void, Failure>
Marca só no cache local o CaseModel (status = CaseStatus.closed.wireValue). A escrita remota é do Dispatcher (não deste repository) — o notifier chama este método só depois do dispatch dar certo (remote-first, §36).Marks the CaseModel in the local cache only (status = CaseStatus.closed.wireValue). The remote write is the Dispatcher's (not this repository's) — the notifier calls this method only after the dispatch succeeds (remote-first, §36).Marca solo en el caché local el CaseModel (status = CaseStatus.closed.wireValue). La escritura remota es del Dispatcher (no de este repository) — el notifier llama a este método solo después de que el dispatch tenga éxito (remote-first, §36).
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 CaseManagementRemoteDataSource gRPC
getCases({locationHierarchySfid, lastModifiedDate?})
- EnvioSendsEnvío
- monta
CaseManagementRequeste chama_client.getCases(request)no clientCaseManagementConectaRepServiceClient(viacaseManagementServiceClientProvider).lastModifiedDatesó é setado se não-null (hoje o repository não passa).buildsCaseManagementRequestand calls_client.getCases(request)onCaseManagementConectaRepServiceClient(viacaseManagementServiceClientProvider).lastModifiedDateis set only when non-null (today the repository doesn't pass it).armaCaseManagementRequesty llama_client.getCases(request)enCaseManagementConectaRepServiceClient(víacaseManagementServiceClientProvider).lastModifiedDatesolo se setea si no es null (hoy el repository no lo pasa). - RetornoReturnRetorno
CasesDTO(viaresponse.toCasesDTO())(viaresponse.toCasesDTO())(víaresponse.toCasesDTO())- Fluxo de usoUsage flowFlujo de uso
- chamado pelo caminho remoto do repository (
_fetchFromRemoteWithFallback), no pull-to-refresh; o resultado é gravado no cache.called by the repository's remote path (_fetchFromRemoteWithFallback), on pull-to-refresh; the result is written to cache.llamado por el camino remoto del repository (_fetchFromRemoteWithFallback), en pull-to-refresh; el resultado se graba en caché. - Tratamento de erroError handlingManejo de errores
GrpcError→GrpcExceptionHandler; outros →ServerException. Em erro, o repository faz fallback pro cache.GrpcError→GrpcExceptionHandler; others →ServerException. On error, the repository falls back to cache.GrpcError→GrpcExceptionHandler; otros →ServerException. En error, el repository hace fallback al caché.
Local CaseManagementLocalDataSource ObjectBox
Envio / fluxo: persistência local via ObjectBox (ObjectBoxDatabase), boxes CasesModel, CaseModel e CaseSacModel — sem rede. Erro: falhas de persistência propagam como CacheException (não engolidas).Sends / flow: local persistence via ObjectBox (ObjectBoxDatabase), CasesModel, CaseModel and CaseSacModel boxes — no network. Error: persistence failures propagate as CacheException (not swallowed).Envío / flujo: persistencia local vía ObjectBox (ObjectBoxDatabase), boxes CasesModel, CaseModel y CaseSacModel — sin red. Error: fallos de persistencia propagan como CacheException (no tragados).
getCases()
- RetornoReturnRetorno
CasesEntity?- ComportamentoBehaviorComportamiento
models.first.toDomain()— o agregado único, ounullse o cache está vazio.models.first.toDomain()— the single aggregate, ornullif the cache is empty.models.first.toDomain()— el agregado único, onullsi el caché está vacío.
getCasesLastSyncAt()
- RetornoReturnRetorno
DateTime?- ComportamentoBehaviorComportamiento
models.first.lastSyncAt
saveCases({entity})
- RetornoReturnRetorno
void- ComportamentoBehaviorComportamiento
- destrutivo:
clearCases()+_box.put(entity.toModel())(grava boxes filhas em cascata).destructive:clearCases()+_box.put(entity.toModel())(writes child boxes in cascade).destructivo:clearCases()+_box.put(entity.toModel())(graba boxes hijas en cascada).
markCaseClosed({caseSfid})
- RetornoReturnRetorno
void- ComportamentoBehaviorComportamiento
- busca o
CaseModelporcaseSfidemgetAll(), setastatus = CaseStatus.closed.wireValuee fazput.finds theCaseModelbycaseSfidingetAll(), setsstatus = CaseStatus.closed.wireValueandputs it.busca elCaseModelporcaseSfidengetAll(), seteastatus = CaseStatus.closed.wireValuey haceput.
clearCases()
- RetornoReturnRetorno
void- ComportamentoBehaviorComportamiento
- limpa as boxes na ordem filhas→raiz (
CaseSacModel→CaseModel→CasesModel).clears the boxes children→root (CaseSacModel→CaseModel→CasesModel).limpia las boxes hijas→raíz (CaseSacModel→CaseModel→CasesModel).
Mock CaseManagementMockDataSource JSON
getCases()
- EnvioSendsEnvío
- carrega o asset JSON
case_management/cases(por mercado, real vs sintético viauseRealMockData) — sem rede.loads the JSON assetcase_management/cases(per market, real vs synthetic viauseRealMockData) — no network.carga el asset JSONcase_management/cases(por mercado, real vs sintético víauseRealMockData) — sin red. - RetornoReturnRetorno
CasesDTO(viaCasesDTOJsonMapper.fromMap)(viaCasesDTOJsonMapper.fromMap)(víaCasesDTOJsonMapper.fromMap)- Fluxo de usoUsage flowFlujo de uso
- usado quando
useMockDataestá ligado ousource == mock; grava no cache como um fetch normal.used whenuseMockDatais on orsource == mock; writes to cache like a normal fetch.usado cuandouseMockDataestá activo osource == mock; graba en caché como un fetch normal. - Tratamento de erroError handlingManejo de errores
- asset ausente ou JSON inválido propaga como
CacheException.missing asset or invalid JSON propagates asCacheException.asset ausente o JSON inválido propaga comoCacheException.
Enums e labelsEnums & labelsEnums y labels
status/priority/role trafegam como String nas camadas de dado; só role é tipado na Entity. status/priority são resolvidos por enum apenas na UI (via fromValue), com label traduzido (i18n). Lista completa:status/priority/role travel as String in the data layers; only role is typed in the Entity. status/priority are resolved to enums only in the UI (via fromValue), with a translated label (i18n). Full list:status/priority/role viajan como String en las capas de dato; solo role es tipado en la Entity. status/priority se resuelven a enums solo en la UI (vía fromValue), con label traducido (i18n). Lista completa:
CaseStatus 6 · wireValue + i18n
| case | wireValue | i18n key |
|---|---|---|
newCase | "New" | caseStatusNew |
working | "Working" | caseStatusWorking |
escalated | "Escalated" | caseStatusEscalated |
onHold | "On Hold" | caseStatusOnHold |
closed | "Closed" | caseStatusClosed |
unknown | "" | rawFallbackrawFallbackrawFallback |
CasePriority 4 · wireValue + i18n
| case | wireValue | i18n key |
|---|---|---|
high | "High" | casePriorityHigh |
medium | "Medium" | casePriorityMedium |
low | "Low" | casePriorityLow |
unknown | "" | rawFallbackrawFallbackrawFallback |
ContactRole 8 · compartilhado · labelshared · labelcompartido · label
De reference_data; o único enum tipado na CaseEntity (campo role, via fromLabel).From reference_data; the only enum typed in CaseEntity (the role field, via fromLabel).De reference_data; el único enum tipado en CaseEntity (el campo role, vía fromLabel).
| case | label |
|---|---|
clerk | "Clerk" |
supervisor | "Supervisor" |
manager | "Manager" |
attendant | "Attendant" |
owner | "Owner" |
primaryContact | "Primary Contact" |
staff | "Staff" |
unknown | "" |
DispatcherType · caseUpload 1 · serviceName + mercadosmarketsmercados
| case | serviceName | enabledMarkets |
|---|---|---|
caseUpload | "CaseUploadAPI" | [BR] |
UseCases
Um dropdown por UseCase, com tabela Método · Retorna · Uso.One dropdown per UseCase, with a Method · Returns · Use table.Un dropdown por UseCase, con tabla Método · Devuelve · Uso.
GetCasesUseCase 4 métodosmethodsmétodos
| MétodoMethodMétodo | RetornaReturnsDevuelve | UsoUseUso |
|---|---|---|
execute({source = local}) | Result<CasesEntity, Failure> | lista; build() usa local, refresh usa remote.list; build() uses local, refresh uses remote.lista; build() usa local, refresh usa remote. |
getCached() | Result<CasesEntity?, Failure> | cache-only do container.cache-only of the container.cache-only del container. |
getCachedLastSyncAt() | DateTime? | DataLoadInfo |
getCachedBySfid({caseSfid}) | Result<CaseEntity?, Failure> | alimenta o detalhe (§28 cat. A).feeds the detail (§28 cat. A).alimenta el detalle (§28 cat. A). |
SaveCaseClosureUseCase 1 · escrita locallocal writeescritura local
| MétodoMethodMétodo | RetornaReturnsDevuelve | UsoUseUso |
|---|---|---|
execute({caseSfid}) | Result<void, Failure> | delega markCaseClosed (cache local). Chamado pelo notifier só após o dispatch dar certo.delegates markCaseClosed (local cache). Called by the notifier only after the dispatch succeeds.delega markCaseClosed (caché local). Llamado por el notifier solo tras el dispatch exitoso. |
BuildCaseUploadDispatcherPayloadUseCase 1 · builder
| MétodoMethodMétodo | RetornaReturnsDevuelve | UsoUseUso |
|---|---|---|
build({input: CaseUploadDispatcherPayloadInput}) | DispatcherEnvelope | implements DispatcherPayloadBuilder (§36). Monta o JSON CaseUploadInfo (Status: "Closed", CReason: comment.trim(), CaseID: id, UpdById: resource.name) e o envelope (DispatcherType.caseUpload, DispatchAccountEntity do varejo, transactionReference = id). Input com entities cruas.implements DispatcherPayloadBuilder (§36). Builds the CaseUploadInfo JSON (Status: "Closed", CReason: comment.trim(), CaseID: id, UpdById: resource.name) and the envelope (DispatcherType.caseUpload, retail DispatchAccountEntity, transactionReference = id). Input with raw entities.implements DispatcherPayloadBuilder (§36). Arma el JSON CaseUploadInfo (Status: "Closed", CReason: comment.trim(), CaseID: id, UpdById: resource.name) y el sobre (DispatcherType.caseUpload, DispatchAccountEntity del punto de venta, transactionReference = id). Input con entities crudas. |
SubmitCaseUploadUseCase 1 · transportetransporttransporte
| MétodoMethodMétodo | RetornaReturnsDevuelve | UsoUseUso |
|---|---|---|
submit({envelope}) | Result<DispatcherAck, Failure> | delegate fino: encaminha ao DispatcherOrchestrator.dispatch. Não decide DispatcherType (é do builder).thin delegate: forwards to DispatcherOrchestrator.dispatch. Doesn't decide DispatcherType (the builder does).delegate fino: reenvía a DispatcherOrchestrator.dispatch. No decide DispatcherType (es del builder). |
Notifier & State
Dois notifiers: CasesNotifier (lista) e CaseDetailNotifier (detalhe, family por caseSfid). O State Freezed é a fonte única de verdade da page.Two notifiers: CasesNotifier (list) and CaseDetailNotifier (detail, family by caseSfid). The Freezed State is the page's single source of truth.Dos notifiers: CasesNotifier (lista) y CaseDetailNotifier (detalle, family por caseSfid). El State Freezed es la fuente única de verdad de la page.
Métodos · listaMethods · listMétodos · lista
CasesNotifier.build()
Watch de getCasesUseCaseProvider e guardedBuild(() => _load()) (mixin AsyncGuard). _load({source = local}) monta o State a partir de execute(source:) (getOrThrow).Watches getCasesUseCaseProvider and guardedBuild(() => _load()) (AsyncGuard mixin). _load({source = local}) builds the State from execute(source:) (getOrThrow).Observa getCasesUseCaseProvider y guardedBuild(() => _load()) (mixin AsyncGuard). _load({source = local}) arma el State desde execute(source:) (getOrThrow).
loadMore()
Só client-state: incrementa visibleCount em kCasesPageSize (15), com clamp em totalCases. Não busca dado.Client-state only: bumps visibleCount by kCasesPageSize (15), clamped to totalCases. No data fetch.Solo client-state: incrementa visibleCount en kCasesPageSize (15), con clamp en totalCases. No busca dato.
refresh()
runGuarded(() => _load(source: remote)) — pull-to-refresh; força o remoto (sem AsyncValue.loading). Nome canônico refresh (§37).runGuarded(() => _load(source: remote)) — pull-to-refresh; forces remote (no AsyncValue.loading). Canonical name refresh (§37).runGuarded(() => _load(source: remote)) — pull-to-refresh; fuerza el remoto (sin AsyncValue.loading). Nombre canónico refresh (§37).
Métodos · detalheMethods · detailMétodos · detalle
CaseDetailNotifier.build({caseSfid})
Watch dos 4 useCases (get + build + submit + saveClosure) e _load cache-only via getCachedBySfid; ausente → BusinessFailure.Watches the 4 useCases (get + build + submit + saveClosure) and _load cache-only via getCachedBySfid; missing → BusinessFailure.Observa los 4 useCases (get + build + submit + saveClosure) y _load cache-only vía getCachedBySfid; ausente → BusinessFailure.
closeCase({comment}) → Future<Failure?>
Remote-first (§36/§39). Lê currentResourceProvider; seta isClosing: true; monta o DispatcherEnvelope (input com caseEntity, resource, comment, submittedAt: DateTimeUtils.now()); despacha via SubmitCaseUploadUseCase.Remote-first (§36/§39). Reads currentResourceProvider; sets isClosing: true; builds the DispatcherEnvelope (input with caseEntity, resource, comment, submittedAt: DateTimeUtils.now()); dispatches via SubmitCaseUploadUseCase.Remote-first (§36/§39). Lee currentResourceProvider; setea isClosing: true; arma el DispatcherEnvelope (input con caseEntity, resource, comment, submittedAt: DateTimeUtils.now()); despacha vía SubmitCaseUploadUseCase.
Sucesso (ack.isSuccess) → SaveCaseClosureUseCase.execute (marca cache local) + State com status = Closed e isClosing: false; retorna null. Falha → reverte isClosing e retorna a Failure (o widget mostra o ConectaNotice.error).Success (ack.isSuccess) → SaveCaseClosureUseCase.execute (marks local cache) + State with status = Closed and isClosing: false; returns null. Failure → reverts isClosing and returns the Failure (the widget shows ConectaNotice.error).Éxito (ack.isSuccess) → SaveCaseClosureUseCase.execute (marca caché local) + State con status = Closed e isClosing: false; devuelve null. Falla → revierte isClosing y devuelve la Failure (el widget muestra ConectaNotice.error).
State disponível para a PageState available to the PageState disponible para la Page
CasesState 2 campos + 5 gettersfields + 5 getterscampos + 5 getters
| Campo / getterField / getterCampo / getter | Tipo / o que dáType / what it givesTipo / qué da |
|---|---|
cases | AsyncValue<CasesEntity> |
visibleCount | int (default 15)(default 15)(default 15) |
lastSyncAt | DateTime? |
allCases | List<CaseEntity> |
visibleCases | allCases.take(visibleCount) |
totalCases | int |
hasMoreToLoad | visibleCount < totalCases |
CaseDetailState 2 campos + 1 getterfields + 1 gettercampos + 1 getter
| Campo / getterField / getterCampo / getter | Tipo / o que dáType / what it givesTipo / qué da |
|---|---|
caseEntity | CaseEntity |
isClosing | bool (default false)(default false)(default false) |
canClose | CaseStatus.fromValue(status) == newCase |
Page e widgetsPage & widgetsPage y widgets
ListaListLista
- CasesPage ConsumerWidget · AppPageShell
- estadosstatesestados:
CustomLoadingIndicator·FailureStateView(onRetry → invalidate)- _CasesBody ConsumerStatefulWidget · scroll infinito
- CustomPullToRefresh onRefresh → notifier.refresh()
- DataLoadInfo state.lastSyncAt
- CasesHeaderWidget ícone + títuloicon + titleícono + título
- CasesListWidget
- CustomEmptyState quando vaziawhen emptycuando vacía
- CaseCardWidget um por chamado · tap → goToCaseDetailone per case · tap → goToCaseDetailuno por caso · tap → goToCaseDetail
- CaseFieldWidget label + valor ("-" se vazio)label + value ("-" if empty)label + valor ("-" si vacío)
- _CasesBody ConsumerStatefulWidget · scroll infinito
- estadosstatesestados:
DetalheDetailDetalle
- CaseDetailPage ConsumerWidget · family(caseSfid) · AppPageShell
- estadosstatesestados:
CustomLoadingIndicator·FailureStateView- _CaseDetailBody SingleChildScrollView
- CasesHeaderWidget
- número do chamadocase numbernúmero del caso CustomText
- CaseDetailSectionCardWidget varejo & contatoretail & contactpunto de venta y contacto
- CaseDetailSectionCardWidget dados do chamadocase datadatos del caso
- CaseDetailSectionCardWidget adicionaisadditionaladicionales
- CaseDetailCloseButtonWidget self-check:
SizedBox.shrink()se!canCloseself-check:SizedBox.shrink()if!canCloseself-check:SizedBox.shrink()si!canClose- CaseClosureModalContent modal · textarea min 50 chars · backWithResult<String>modal · textarea min 50 chars · backWithResult<String>modal · textarea mín 50 chars · backWithResult<String>
- _CaseDetailBody SingleChildScrollView
- estadosstatesestados:
Fluxo do encerrar: o botão abre o ConectaModal com o CaseClosureModalContent; o comentário (≥ 50 chars) volta por backWithResult; o widget chama notifier.closeCase(comment); em sucesso, invalidate(casesProvider) + ConectaNotice.success + AppRouter.back.Close flow: the button opens ConectaModal with CaseClosureModalContent; the comment (≥ 50 chars) returns via backWithResult; the widget calls notifier.closeCase(comment); on success, invalidate(casesProvider) + ConectaNotice.success + AppRouter.back.Flujo de cierre: el botón abre ConectaModal con CaseClosureModalContent; el comentario (≥ 50 chars) vuelve vía backWithResult; el widget llama notifier.closeCase(comment); en éxito, invalidate(casesProvider) + ConectaNotice.success + AppRouter.back.
Notas por mercadoMarket notesNotas por mercado
Chamados é dirigido pela End Market Configuration: o atalho cases vive dentro do módulo rep_actions da Home. Só o Brasil declara esse detalhe (isVisible: true); em CL/ZA o módulo existe mas o detalhe cases está ausente; em AR/PY/PE o próprio rep_actions não existe. A escrita (CaseUploadAPI) também é BR-only (enabledMarkets: [BR]).Cases is driven by End Market Configuration: the cases shortcut lives inside Home's rep_actions module. Only Brazil declares that detail (isVisible: true); in CL/ZA the module exists but the cases detail is absent; in AR/PY/PE rep_actions itself doesn't exist. The write (CaseUploadAPI) is also BR-only (enabledMarkets: [BR]).Casos está dirigido por la End Market Configuration: el atajo cases vive dentro del módulo rep_actions del Home. Solo Brasil declara ese detalle (isVisible: true); en CL/ZA el módulo existe pero el detalle cases está ausente; en AR/PY/PE el propio rep_actions no existe. La escritura (CaseUploadAPI) también es BR-only (enabledMarkets: [BR]).
| Chave EMCEMC keyClave EMC | BR | CL | ZA | AR | PY | PE |
|---|---|---|---|---|---|---|
rep_actions (módulo)(module)(módulo) |
x | x | x | — | — | — |
rep_actions › cases (detalhe)(detail)(detalle) |
x | — | — | — | — | — |
DispatcherType.caseUpload (escrita)(write)(escritura) |
x | — | — | — | — | — |
Único mercadoOnly marketÚnico mercado
Único com Chamados habilitado. O bloco CaseSac (SAC do consumidor: nome, CPF, lote, datas, endereço) é tipicamente brasileiro. O CNPJ/CPF do taxId é formatado por mercado via DocumentUtils.formatCompany.
The only one with Cases enabled. The CaseSac block (consumer SAC: name, tax number, lot, dates, address) is typically Brazilian. The taxId is formatted per market via DocumentUtils.formatCompany.
El único con Casos habilitado. El bloque CaseSac (SAC del consumidor: nombre, ID fiscal, lote, fechas, dirección) es típicamente brasileño. El taxId se formatea por mercado vía DocumentUtils.formatCompany.
CL · ZA · AR · PY · PE O atalho de Chamados não aparece na Home e não há rota de entrada. As camadas de dado (proto, DTO, model, mappers, mock por mercado) existem no código, mas sem a chave EMC a feature fica inacessível. The Cases shortcut doesn't appear on Home and there's no entry route. The data layers (proto, DTO, model, mappers, per-market mock) exist in the code, but without the EMC key the feature is unreachable. El atajo de Casos no aparece en el Home y no hay ruta de entrada. Las capas de dato (proto, DTO, model, mappers, mock por mercado) existen en el código, pero sin la clave EMC la feature queda inaccesible.