# Atomenta Mobile App API Documentation ## 🔐 Authentication & Headers The mobile app accesses distinct sets of APIs: 1. **App APIs (`/api/app/*`)**: For general app features like Home Screen and Orders listing. 2. **Customer APIs (`/api/customer/*`)**: For User Registration, Login, and Profile Management. 3. **Store External APIs (`/api/store/*`)**: For store-specific operations like Catalog and Checkout. ### Common Headers All requests should ideally include: ```http Accept: application/json Content-Type: application/json ``` ### Authentication Strategies #### 1. Customer Auth (for App & Customer APIs) Used for everything related to the logged-in user (Profile, Orders, etc). - **Header:** `Authorization: Bearer ` - **Note:** Obtained via `/api/customer/login`. #### 3. Hybrid Store Auth (for Store External APIs) Used when interacting with a specific store (Catalog, Checkout). - **Header 1:** `Atomenta-Token: 550e8400-e29b-41d4-a716-446655440008` (Store Module ID) - **Header 2:** `Authorization: Bearer ` (Required for Checkout/Orders) --- ## 👤 Customer Management ### 1. Register Customer **POST** `/api/customer` **Headers:** - `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a` (Customer Module ID) **Body:** ```json { "name": "Daniel Loverde", "email": "daniel@example.com", "phoneNumber": "+5511999999999", "birthDate": "1990-01-01T00:00:00Z" // Opcional } ``` **Response:** ```json { "error": false, "code": "CUSTOMER_CREATED", "result": { "id": "cust_uuid...", "name": "Daniel Loverde", "email": "daniel@example.com" } } ``` ### 2. Login (Get Token) **POST** `/api/customer/login` **Body (email + phoneNumber are mandatory):** ```json { "email": "daniel@example.com", "phoneNumber": "+5511999999999" } ``` **Fluxo:** - Passo 1: enviar `email` + `phoneNumber` para receber código por email. - Passo 2: enviar `email` + `phoneNumber` e o OTP no header `Authorization: Bearer `. **Response:** ```json { "error": false, "code": "LOGIN_SUCCESS", "result": { "token": "eyJhbGciOi...", // <--- Use as Bearer Token for other requests "customer": { "id": "cust_uuid...", "name": "Daniel Loverde", "email": "daniel@example.com" } } } ``` ### 3. Get Profile **GET** `/api/customer/profile` **Headers:** - `Authorization: Bearer ` - `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a` **Response:** ```json { "error": false, "code": "CUSTOMER_PROFILE_RETRIEVED", "result": { "id": "cust_uuid...", "name": "Daniel Loverde", "email": "daniel@example.com", "phoneNumber": "+5511999999999", "favorites": ["store_abc...", "store_xyz..."], "address_book": [], "verified": true, "biometricsEnabled": false, "createdAt": "2026-01-21T22:37:56.472Z", "updatedAt": "2026-02-13T19:00:00.000Z" } } ``` **Campos internos não retornados para o app:** `keyHash`, `strongHash`, `source`, `isShadow`, `profile_metrics`, `behavioral_stats`, `otpHash`, `otpHashParams`, `otpExpiresAt`. ### 4. Update Profile **POST** `/api/customer/:id` **Headers:** - `Authorization: Bearer ` - `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a` **Body:** ```json { "name": "Daniel A. Loverde", // Fields to update "phoneNumber": "+5511988888888", "biometricsEnabled": true, "address_book": [ { "label": "Casa", "type": "residential", "address": "Avenida das Andorinhas", "number": "477", "neighborhood": "Jardim Andorinhas", "city": "Campinas", "state": "SP", "zipCode": "13101-400", "country": "Brasil", "complement": "Apto 78", "lat_long": [-22.9064, -47.0616] } ] } ``` **Regra de coordenadas (`address_book.lat_long`):** - Se existir `zipCode` válido e houver cache do CEP no Atomenta (`/api/public/cep/:cep`) para o mesmo usuário, a API valida o ponto enviado. - Se não houver cache local, o Atomenta consulta a AwesomeAPI e atualiza o cache antes de validar. - Se `lat_long` estiver ausente/inválido, a API preenche com as coordenadas do CEP cacheado. - Se a distância entre `lat_long` enviado e o ponto do CEP for maior que `1.5 km`, a API substitui `lat_long` pelo ponto do CEP cacheado. - Objetivo: evitar discrepâncias grandes entre endereço e coordenada salva no perfil. ### 5. List Favorite Stores **GET** `/api/customer/favorites` **Headers:** - `Authorization: Bearer ` - `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a` **Notes:** - Retorna as lojas favoritas do customer autenticado. - Apenas lojas públicas/visíveis são retornadas. - A ordem segue a ordem salva em `favorites`. **Response:** ```json { "error": false, "code": "CUSTOMER_FAVORITES_RETRIEVED", "result": [ { "id": "store_1775510226293_yn5ys", "name": "CPS Drinks", "logo": null, "cover": null, "category": "Doces & Bolos", "rating": 4.8, "totalReviews": 12, "isOpen": true, "statusLabel": "Aberto", "nextOpenLabel": null } ] } ``` ### 6. Add Store to Favorites **POST** `/api/customer/favorites/:storeId` **Headers:** - `Authorization: Bearer ` - `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a` **Notes:** - Salva a loja nos favoritos do customer autenticado. - Se a loja já estiver favoritada, a operação continua idempotente. - A loja precisa existir e estar publicamente visível. **Response:** ```json { "error": false, "code": "CUSTOMER_FAVORITE_SAVED", "result": { "favorites": ["store_1775510226293_yn5ys"], "store": { "id": "store_1775510226293_yn5ys", "name": "CPS Drinks", "logo": null, "cover": null, "category": "Doces & Bolos", "rating": 4.8, "totalReviews": 12, "isOpen": true, "statusLabel": "Aberto", "nextOpenLabel": null } } } ``` ### 7. Remove Store from Favorites **DELETE** `/api/customer/favorites/:storeId` **Headers:** - `Authorization: Bearer ` - `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a` **Notes:** - Remove a loja dos favoritos do customer autenticado. - A operação é idempotente: se a loja não estiver favoritada, o array volta sem ela. **Response:** ```json { "error": false, "code": "CUSTOMER_FAVORITE_REMOVED", "result": { "favorites": [] } } ``` --- ## 🏠 App Home Screen ### List Categories **GET** `/api/public/categories` **Headers:** - `Accept: application/json` **Response:** ```json { "error": false, "result": [ { "id": "all", "name": "Todas", "icon": "🏪" }, { "id": "lanches", "name": "Lanches", "icon": "🍔" }, { "id": "pizza", "name": "Pizza", "icon": "🍕" } ] } ``` ### List Stores (Smart Listing) **GET** `/api/app/stores` **Headers:** - `Authorization: Bearer ` (Customer obrigatório) Lists stores based on user location, filtered by distance. **Query Parameters:** - `lat` (Required): User Latitude (e.g., `-23.550520`) - `lng` (Required): User Longitude (e.g., `-46.633308`) - `category` (Optional): Filter by category name (e.g., `Lanches`) - `search` (Optional): Search by store name **Notes:** - Sem login do customer, a listagem é bloqueada. - Se `lat/lng` não forem enviados, a API tenta usar o endereço salvo no perfil do customer (`address_book.lat_long`). - Lojas fora do raio/bairro de entrega não são exibidas. **Response:** ```json { "error": false, "result": [ { "id": "store_123...", "name": "Burger King", "logo": "https://...", "cover": "https://...", "category": "Lanches", "rating": 4.8, "deliveryTime": "30-45 min", "deliveryFee": 5.99, "distance": 1.2, // km "isOpen": true, "statusLabel": "Aberto" } ] } ``` ### List User Orders **GET** `/api/app/orders` **Headers:** - `Authorization: Bearer ` **Response:** ```json { "error": false, "result": [ { "id": "ord_123...", "total": 54.90, "status": "completed", "createdAt": "2024-03-20T10:00:00Z", "items": [...] } ] } ``` --- ## 🏪 Store Integration (External API) **Base URL:** `/api/store/:storeId` ### 1. Store Identity & Status **GET** `/api/store/:storeId/identity` **Headers/Query:** - `x-user-lat` / `lat`: User Latitude - `x-user-lng` / `lng`: User Longitude **Response:** ```json { "error": false, "result": { "fantasyName": "Mc Donalds", "distance": 2.5, "deliveryTime": "30-45 min", "minOrder": 15.00 } } ``` ### 2. Full Store Info **GET** `/api/store/:storeId/info` Retorna versão pública para app (sem dados administrativos/financeiros da loja). **Response:** ```json { "error": false, "code": "STORE_INFO_RETRIEVED", "result": { "isOpen": true, "statusLabel": "Aberto", "fantasyName": "Mc Donalds", "specialty": "Lanches", "minOrder": 15, "deliveryTime": "30-45 min", "logo": "https://...", "cover": "https://...", "paymentMethods": { "paymentOnDelivery": true, "paymentOnPickup": true, "acceptCash": true, "acceptPix": true }, "openingHours": { "...": "..." }, "address": { "street": "Rua X", "neighborhood": "Centro", "city": "Campinas", "state": "SP", "zipcode": "13000-000", "latitude": -22.9, "longitude": -47.0 } } } ``` ### 3. Product Catalog **GET** `/api/store/:storeId/catalog` Retorna categorias com produtos, incluindo diferenciação explícita de pizza. **Response:** ```json { "error": false, "result": [ { "id": "cat_lanches", "name": "Lanches", "isPizzaCategory": false, "products": [ { "id": "prod_xburger", "type": "prepared", "name": "X-Burger", "description": "Pão, carne e queijo", "image": "/uploads/products/xburger.jpg", "price": "25.00", "addons": "yes", "addonGroups": [] } ] }, { "id": "cat_pizzas", "name": "Pizzas", "isPizzaCategory": true, "pizzaConfig": { "sizes": [{ "id": "broto", "name": "Broto", "slices": 4, "maxFlavors": 1 }], "doughs": [{ "id": "tradicional", "name": "Tradicional", "active": true }], "crusts": [{ "id": "catupiry", "name": "Catupiry", "active": true, "priceModifier": "10.00" }] }, "products": [ { "id": "prod_pizza_calabresa", "type": "pizza", "name": "Pizza Calabresa", "description": "Molho, mussarela e calabresa", "image": "/uploads/products/pizza-calabresa.jpg", "price": "0.00", "pizzaPrices": { "broto": "35.00", "grande": "55.00" }, "addons": "yes", "addonGroups": [] } ] } ] } ``` **Como identificar pizza no app:** - Produto pizza: `products[].type === "pizza"`. - Configuração de montagem/preço por tamanho: `category.isPizzaCategory === true` + `category.pizzaConfig` + `product.pizzaPrices`. ### 4. Validate Delivery Address (Checkout) **POST** `/api/store/:storeId/delivery/validate-address` Use este endpoint ao trocar endereço na tela de pagamento para validar cobertura antes de finalizar. **Body:** ```json { "address": { "street": "Avenida das Andorinhas", "number": "477", "neighborhood": "Jardim Andorinhas", "city": "Campinas", "state": "SP", "zip": "13101-400", "lat": -22.918, "lng": -47.01813 } } ``` **Resposta Exemplo:** ```json { "error": false, "code": "DELIVERY_ADDRESS_VALIDATED", "result": { "deliveryAllowed": true, "reasonCode": "DELIVERY_ALLOWED", "reasonMessage": "Endereço válido para entrega", "deliveryMode": "KM", "distance": 1.3, "deliveryFee": 8, "deliveryTime": "35 min", "sameCity": false, "matchedKmRange": { "from": 1, "to": 2, "price": 8, "time": 35 }, "matchedNeighborhood": null } } ``` **Regras de validação:** - `KM`: valida distância até o limite da última faixa + tolerância de `0.5 km`. - `NEIGHBORHOOD`: reprova se cidade do endereço for diferente da cidade da loja. - `NEIGHBORHOOD`: aprova se bairro configurado bater, ou se for mesma cidade (fallback). ### 5. Create Order (Checkout) **POST** `/api/store/:storeId/orders` **Body:** ```json { "customer": { "name": "Daniel Loverde", "phone": "11999999999", "email": "daniel@example.com", "asaasId": "cus_000005165985" // Opcional. }, // Nota: O usuário DEVE estar autenticado via Bearer Token. // Os dados de 'customer' enviados aqui são usados preferencialmente para // registrar os dados de entrega/cobrança deste pedido específico no Asaas, // mas o 'userId' e o histórico são vinculados ao token do usuário logado. "items": [ { "productId": "prod_123", "name": "X-Burger", "qty": 2, "price": 25.00, "addons": [ { "addonId": "addon_egg", "name": "Ovo", "price": 2.50, "qty": 2 } ] } ], "total": 50.00, "paymentMethod": "PIX", // "PIX" | "CREDIT_CARD" | "DEBIT_CARD" | "MONEY" "deliveryType": "DELIVERY", // "DELIVERY" | "PICKUP" "address": { "street": "Rua Exemplo", "number": "123", "neighborhood": "Centro" } } ``` **Regras para `items[].addons`:** - `qty` do adicional é aceito no backend (ex.: `2 ovos`). - Se `qty` não for enviado ou vier inválido, a API assume `1`. - `qty` mínimo efetivo é `1`. **Response:** ```json { "error": false, "code": "ORDER_CREATED", "result": { "id": "ord_987...", "shortId": "1234", "status": "PAYMENT_PENDING", "paymentStatus": "PENDING", "paymentMethod": "PIX", "paymentPayload": { "copyPaste": "000201010212...", "qrCodeImage": "iVBORw0KGgoAAAANSUhEUgAA...", "expirationDate": "2026-02-14T22:30:00.000Z" }, "payment": { "method": "PIX", "status": "PENDING", "pix": { "copyPaste": "000201010212...", "qrCodeImage": "iVBORw0KGgoAAAANSUhEUgAA...", "expirationDate": "2026-02-14T22:30:00.000Z" } } } } ``` **Observação de segurança:** o Atomenta não retorna mais links de checkout/fatura do Asaas no payload de criação do pedido. ### 6. Realtime de Status do Pedido (Socket.IO) Use este canal para acompanhar mudança de status do pedido em tempo real (sem depender de push notification). **Socket Endpoint:** - `ws://:/socket.io/` (em produção, usar `wss://` se houver TLS no balanceador) **Auth no handshake:** - Enviar o JWT do customer em `auth.token` (mesmo token do `Authorization: Bearer ...` das APIs HTTP). **Exemplo de conexão (conceitual):** ```json { "auth": { "token": "Bearer " } } ``` **Eventos recebidos pelo app:** - `order_update`: alteração geral de status do pedido. - `order_cancelled`: cancelamento confirmado, com motivo e metadados de estorno. - `delivery_order_cancelled`: evento dedicado para telas de OTP/entrega interromperem o fluxo imediatamente. **Payload típico de `order_cancelled`:** ```json { "event": "order_cancelled", "emittedAt": "2026-03-05T18:00:00.000Z", "orderId": "ffdc47b4-6a55-4713-aea9-45169e4cf0d7", "shortId": "3778", "storeId": "store_1772117366848_wmqw4", "userId": "cust_uuid_001", "status": "CANCELED", "cancelledBy": "store", "cancelledAt": "2026-03-05T18:00:00.000Z", "cancelReasonCode": "STORE_UNAVAILABLE", "cancelReasonDetail": "Falta de insumo crítico para finalizar o pedido", "refundStatus": "failed", "refundIdempotencyKey": "refund:ffdc47b4-...", "refundProviderRef": null, "refundError": "Falha ao solicitar estorno automático" } ``` ### 7. Fallback oficial (Polling) Se o socket cair, o app deve continuar consultando o status até estado final. **Endpoint:** - `GET /api/public/orders/:orderId` **Sugestão de estratégia:** - primeiros 60s: a cada `3s` - próximos 2min: a cada `5s` - depois: a cada `10s` - parar em status final (`COMPLETED`, `CANCELED`, `REFUNDED`) ou ao sair da tela. ### 8. Cancelamento de Pedido (Loja/Customer) **Cancelamento pela loja (Store API):** - `POST /api/store/:storeId/orders/:orderId/cancel` **Cancelamento pelo customer:** - `POST /api/customer/orders/:orderId/cancel` **Request body (ambos):** ```json { "reasonCode": "CUSTOMER_REQUEST", "reasonDetail": "Cliente solicitou cancelamento antes da entrega" } ``` **Motivos aceitos (`reasonCode`):** - `STORE_UNAVAILABLE` - `CUSTOMER_REQUEST` - `DELIVERY_ISSUE` **Regras atuais de cancelamento:** - Permitido em: `PAYMENT_PENDING`, `PENDING`, `ACCEPTED`, `PREPARING`, `READY`, `DELIVERING`. - Bloqueado em: `COMPLETED` e `CANCELED`. - Motoboy não cancela pedido. - Ao cancelar, o backend invalida `otp`, `confirmOtp` e `customerOtp`. **Concorrência e idempotência (ponta a ponta):** - Cancelamento é serializado por `orderId` (lock por pedido). - Estorno automático usa lock + chave idempotente de estorno para evitar duplicidade. - Em segunda tentativa após cancelamento efetivado, a API retorna `409 ORDER_ALREADY_CANCELED`. **Campos relevantes no retorno:** - `cancelledBy`, `cancelledAt`, `cancelReasonCode`, `cancelReasonDetail` - `refundStatus`, `refundIdempotencyKey`, `refundProviderRef` ### 9. Catálogo Oficial de Tags de Review ### 9.1 Matriz Completa de Endpoints (Reviews + Tags) **Público/App (Customer):** - `GET /api/public/reviews/tags` → catálogo oficial de tags e regras de validação. - `POST /api/public/orders/:orderId/review` → cria/atualiza avaliação do pedido concluído. - `GET /api/public/store/:storeId/reviews` → lista reviews públicos da loja. **Store/Backoffice (analytics e operação):** - `GET /api/store/:storeId/reviews` → listagem detalhada + métricas de reviews da loja. - `POST /api/store/:storeId/reviews/:reviewId/reply` → resposta oficial da loja para avaliação. - `POST /api/store/:storeId/reviews/:reviewId/dispute` → contestação da avaliação. - `GET /api/store/reviews/app/nps` → NPS do app (geral/por filtros). - `GET /api/store/reviews/app/nps/platform` → NPS por plataforma (`ios`, `android`, `web`). - `GET /api/store/reviews/observability/overview` → funil técnico (sucesso/rejeição/erro). - `GET /api/store/reviews/observability/alerts` → alertas operacionais (ex.: pico de reviews negativas). **Observação de base URL:** - No app, consumir sempre via domínio/API oficial do ecossistema (PediFoods/Atomenta), mantendo o Atomenta como orquestrador. O app deve buscar este endpoint para renderizar as tags válidas e enviar somente os `id` retornados. **Endpoint:** - `GET /api/public/reviews/tags` **Headers:** - `Accept: application/json` **Response (resumo):** ```json { "error": false, "result": { "version": "2026-03-01", "order": { "positive": [{ "id": "flavor", "label": "Sabor" }], "improvement": [{ "id": "wrong_items", "label": "Itens errados" }], "rules": { "positiveAllowedWhenRateGte": 5, "improvementAllowedWhenRateLte": 4 } }, "delivery": { "sentiments": [ { "id": "positive", "allowedTags": ["politeness", "on_time"] }, { "id": "negative", "allowedTags": ["delay", "rude"] } ], "positive": [{ "id": "politeness", "label": "Educação" }], "negative": [{ "id": "delay", "label": "Atraso" }] }, "app": { "nps": { "min": 0, "max": 10 }, "platforms": ["ios", "android", "web"] } } } ``` **Regra de integração:** - Não hardcodear tags no app; usar o catálogo do backend. - Enviar no POST de review apenas os `id` retornados em cada bloco. ### 10. Enviar Avaliação do Pedido Permite o customer enviar review da loja a partir de um pedido finalizado. **Endpoint:** - `POST /api/public/orders/:orderId/review` **Headers:** - `Authorization: Bearer ` (obrigatório) - `Accept: application/json` - `Content-Type: application/json` **Body:** ```json { "orderRate": 5, "orderComment": "Pedido chegou certinho e bem embalado.", "orderPositiveTags": ["flavor", "temperature"], "orderImprovementTags": [], "deliverySentiment": "positive", "deliveryPositiveTags": ["on_time", "politeness"], "deliveryNegativeTags": [], "appNps": 10, "platform": "ios", "rate": 5, "message": "Pedido chegou certinho e bem embalado." } ``` **Regras de negócio atuais:** - Só aceita review para pedido com status `COMPLETED`. - O `orderId` deve existir. - `storeId`, `userId`, `clientName` e `orderId` do review são derivados do pedido no backend (não enviar no body). - Campos obrigatórios para integração nova: `orderRate`, `deliverySentiment`, `appNps`, `platform`. - Compatibilidade: `rate/message` ainda são aceitos como fallback. - App deve manter o rascunho local do formulário em caso de erro HTTP para permitir reenvio sem redigitar. **Response (sucesso):** ```json { "error": false, "result": { "id": "rev_abc123", "storeId": "store_1772117366848_wmqw4", "userId": "cust_uuid_001", "clientName": "Customer 002", "rate": 5, "message": "Pedido chegou certinho e bem embalado.", "orderRate": 5, "orderComment": "Pedido chegou certinho e bem embalado.", "deliveryFeedback": "positive", "platform": "ios", "editableUntil": "2026-03-06T17:15:00.000Z", "storeReplyUntil": "2026-03-06T17:15:00.000Z", "reviewWindowExpiresAt": "2026-03-06T16:10:00.000Z", "orderId": "ffdc47b4-6a55-4713-aea9-45169e4cf0d7", "date": "2026-03-01T17:15:00.000Z" } } ``` **Erros esperados:** - `400` quando `orderId` não for enviado. - `400` quando o pedido não existir ou ainda não estiver concluído (`Apenas pedidos concluídos podem ser avaliados`). - `400` `REVIEW_WINDOW_EXPIRED` quando passou da janela inicial de 5 dias para criar review. - `400` `REVIEW_EDIT_WINDOW_EXPIRED` quando tentar alterar review após 5 dias da criação. - `400` `INVALID_REVIEW_PAYLOAD` / `INVALID_REVIEW_TAGS` / `INVALID_DELIVERY_SENTIMENT_TAGS` para payload inválido. - `401` se o token JWT for inválido/ausente. - `500` em falha interna (`Erro ao enviar avaliação`). **Exemplos de validação (cenários):** Exemplo válido (nota alta + entrega positiva): ```json { "orderRate": 5, "orderComment": "Perfeito.", "orderPositiveTags": ["flavor", "temperature"], "orderImprovementTags": [], "deliverySentiment": "positive", "deliveryPositiveTags": ["on_time", "politeness"], "deliveryNegativeTags": [], "appNps": 10, "platform": "android" } ``` Exemplo inválido (nota 5 com `orderImprovementTags`): ```json { "orderRate": 5, "orderImprovementTags": ["temperature"], "deliverySentiment": "positive", "appNps": 8, "platform": "ios" } ``` Retorno esperado: `400 INVALID_REVIEW_TAGS`. Exemplo inválido (entrega positiva com tags negativas): ```json { "orderRate": 4, "orderImprovementTags": ["temperature"], "deliverySentiment": "positive", "deliveryNegativeTags": ["delay"], "appNps": 7, "platform": "web" } ``` Retorno esperado: `400 INVALID_DELIVERY_SENTIMENT_TAGS`. ### 11. Analytics de Reviews (Store/API) Para dashboard da loja e análise de produto, usar: - `GET /api/store/:storeId/reviews` - filtros opcionais: `startDate`, `endDate`, `minRate`, `maxRate`, `deliverySentiment`, `platform` - retorno inclui métricas de estrelas, blocos de pedido/entrega, janelas (`editableUntil`, `storeReplyUntil`) e `appNps` agregado. - `POST /api/store/:storeId/reviews/:reviewId/reply` - body obrigatório: `reply` (texto da resposta da loja). - regra: respeita janela de resposta (`storeReplyUntil`). - `POST /api/store/:storeId/reviews/:reviewId/dispute` - body obrigatório: `reason` (motivo da contestação). - `GET /api/store/reviews/app/nps` - filtros opcionais: `startDate`, `endDate`, `storeId`, `platform` - retorno: volume NPS, média e score. - `GET /api/store/reviews/app/nps/platform` - filtros opcionais: `startDate`, `endDate`, `storeId` - retorno: NPS separado em `ios`, `android`, `web`. - `GET /api/store/reviews/observability/overview` - filtros opcionais: `startDate`, `endDate`, `storeId` - retorno: tentativas, sucesso/rejeição/erro, taxa de sucesso e conversão por pedidos concluídos. - `GET /api/store/reviews/observability/alerts` - filtro opcional: `storeId` - retorno: alertas de pico, ex.: `NEGATIVE_REVIEWS_SPIKE`. **Compatibilidade Store:** - O painel `/store/reviews` segue funcional com os campos legados (`rate`, `message`, `itemFeedback`, `improvementFeedback`, `deliveryFeedback`). - Campos novos coexistem para evolução gradual sem quebrar o fluxo atual.