Pular para o conteúdo principal

Pedido Sugerido por IA

Última atualização: 23 de setembro de 2025

Visão Geral

Sistema inteligente de Pedido Sugerido por IA que utiliza algoritmos para analisar padrões de compra, histórico de vendas e perfil do cliente para sugerir produtos relevantes. A solução oferece uma experiência completa de assistente virtual com análise de alertas, configuração de parâmetros personalizados e filtragem inteligente de produtos.

Interface do Assistente Virtual

O sistema apresenta uma interface moderna e intuitiva, com a assistente virtual Sofia que guia o usuário através do processo:

Tela Inicial - Apresentação da SofiaSistema de Alertas
Assistente Virtual Sofia - ApresentaçãoSistema de Alertas
Apresentação da assistente virtual SofiaExibição de alertas e validações
Configuração de ParâmetrosFiltros por Categoria
Configuração de ParâmetrosFiltros por Categoria
Interface de configuração de parâmetrosSistema de filtros por categoria

Funcionalidades Principais

FuncionalidadeDescrição
Sugestão Inteligente de ProdutosAlgoritmo que analisa histórico e perfil do cliente
Sistema de AlertasValidações automáticas de estoque, preços e descontos
Parâmetros ConfiguráveisAjuste de critérios de diferença de preço e desconto
Filtros por CategoriaOrganização de produtos por tipo (promoções, comprados, etc.)
Modo PreloadAnálise de produtos já selecionados no carrinho

Estrutura do Componente

Arquivos Principais

ArquivoResponsabilidade
SuggestedSaleModal.tsxComponente principal do modal
SuggestedSaleModalController.tsLógica de negócio e integração com API
SuggestedSalePreloadModalController.tsController para modo preload (produtos existentes)
components/SuggestedSaleModalLoading.tsxTela de carregamento com animações
components/SuggestedSaleModalAlert.tsxExibição e gerenciamento de alertas
components/SuggestedSaleModalParameters.tsxConfiguração de parâmetros de sugestão
components/SuggestedSaleModalFilters.tsxFiltros de categorização de produtos
components/AlertDetailsModal.tsxModal detalhado para visualizar alertas
index.tsArquivo de exportação dos componentes

Fluxo de Navegação

Fluxograma do Modal

EtapaAbaFuncionalidade
Loading-Carregamento de dados da API
AlertasAlertasExibição de validações e inconsistências
ParâmetrosParâmetrosConfiguração de critérios de sugestão
FiltrosFiltrosSeleção de categorias de produtos

Integração com API

Endpoint de Sugestão

POST /mobile/suggested-sale

// Request Body
{
"CODPARC": number,
"CODVEND": number,
"ITEMS": ApiItemData[]
}

// Response
{
"success": boolean,
"data": {
"TPEMISNFE": number,
"CODPARC": number,
"ITEMS": ApiItemData[]
// ... outros campos do pedido
}
}

Estrutura de Dados da API

ApiItemData Interface

interface ApiItemData {
VLRSUBST: number; // Valor de substituição
QTDPENDENTE: number; // Quantidade pendente
VLRDESC: number; // Valor do desconto
VLRTOTWITHIMP: number; // Valor total com impostos
VLRTOT: number; // Valor total
VLRUNITWITHIMP: number; // Valor unitário com impostos
VLRUNIT: number; // Valor unitário
CODLOCALORIG: number; // Código do local origem
CODVOL: string; // Código do volume
VLRDESCTOT: number; // Valor total do desconto
CODPROD: number; // Código do produto
PRECOBASE: number; // Preço base
PERCDESC: number; // Percentual de desconto
QTDCORTE: number; // Quantidade de corte
PESOBRUTO: number; // Peso bruto
QTDFAT: number; // Quantidade faturada
PESOLIQ: number; // Peso líquido
AD_CODVOLEMB: string; // Código volume embalagem
SEQUENCIA: number; // Sequência
PERCDESCTGFDES: number; // Percentual desconto TGF
QTDENTREGUE: number; // Quantidade entregue
VLRIPI: number; // Valor IPI
DESCRLOCAL: string; // Descrição do local
CONTROLE: string; // Controle
QTDNEG: number; // Quantidade negociada
DESCRPROD: string; // Descrição do produto
NUTAB: number; // Número da tabela
}

Sistema de Alertas

Tipos de Alertas Suportados

TipoNívelDescriçãoAção
Produtos com DescontowarningProdutos que possuem desconto aplicadoRevisar descontos aplicados
Produtos sem EstoqueerrorProdutos indisponíveis em estoqueRemover ou substituir produtos
Desconto ExcedidoerrorDesconto acima do limite permitidoAjustar percentual de desconto
Produtos InativoswarningProdutos marcados como inativosVerificar status dos produtos

Algoritmo de Validação de Alertas

// Exemplo: Verificação de estoque
const validateStock = async (items: ApiItemData[]) => {
const itemsWithoutStock = [];

for (const item of items) {
const productStock = realm
.objects("ProductStock")
.filtered("CODPROD == $0", item.CODPROD);

if (productStock.isEmpty() || productStock[0].ESTOQUE <= 0) {
itemsWithoutStock.push(item);
}
}

return itemsWithoutStock;
};

Sistema de Parâmetros

Configurações Disponíveis

Seção 1: Diferença de Preço

OpçãoDescriçãoComportamento
Preço da TabelaManter preço original da tabelaUsa VLRUNIT original
Preço do ClienteAplicar preço específico do clienteBusca preço na tabela do cliente

Sistema de Filtros

Categorias de Produtos

CategoriaCorFonte de DadosDescrição
Promoções#FF6B35ProductData.PROMOCOESProdutos em promoção ativa
Último Pedido#4CAF50ClientLastProductsPurchasedProdutos do último pedido do cliente
Comprados#2196F3ClientProductsPurchasedHistórico de produtos comprados
Destaques#FFC107AsyncStorage: featuredProductsProdutos em destaque da empresa
Perfil#9C27B0AsyncStorage: profileProductsProdutos do perfil do tipo de cliente
Sugerido#607D8BProdutos não categorizadosSugestões baseadas em IA

Algoritmo de Categorização

const categorizeProducts = async (items: ApiItemData[]) => {
const categories = await getFilters();
const filters: FilterItem[] = [];

// Promoções
const promotionsProducts = items
.filter((item) => categories.promotions.includes(item.CODPROD))
.map((item) => item.CODPROD);

if (promotionsProducts.length > 0) {
filters.push({
id: "promotions",
label: "Promoções",
count: promotionsProducts.length,
isSelected: true,
color: "#FF6B35",
type: "promotions",
products: promotionsProducts,
});
}

// ... outras categorias

return filters;
};

Modo Preload

Funcionalidade

O modo preload permite analisar produtos já selecionados no carrinho de compras, oferecendo validações e categorizações sem fazer nova chamada à API de sugestão.

Transformação de Dados

const transformProductToApiItemData = (product: Product): ApiItemData => {
return {
VLRSUBST: 0,
QTDPENDENTE: 0,
VLRDESC: parseFloat(product.VLRDESC || "0"),
VLRTOTWITHIMP: parseFloat(product.VLRTOT || "0"),
VLRTOT: parseFloat(product.VLRTOT || "0"),
VLRUNITWITHIMP: parseFloat(product.VLRUNIT || "0"),
VLRUNIT: parseFloat(product.VLRUNIT || "0"),
CODLOCALORIG: parseInt(product.CODLOCALORIG || "0", 10),
CODVOL: product.CODVOL || product.selectedCodVol || "",
// ... outros campos
CODPROD: product.CODPROD,
QTDNEG: parseFloat(product.QTDNEG || "0"),
DESCRPROD: product.DESCRPROD || "",
};
};

Seletor Redux para Preload

export const getItensPreloadFromProducts = (ids: string[]) =>
createSelector(
(state: RootState) => state.orderCreate?.products.entities,
(products) => {
const selectedProducts = ids
.map((id) => products?.[id])
.filter((product): product is Product => product !== undefined);
return transformProductsToApiItemData(selectedProducts);
}
);

Interfaces TypeScript

Props Principais

export interface SuggestedSaleModalProps {
visible: boolean;
onClose: () => void;
codParc: number | string;
itensPreload?: string[];
}

export interface AlertItem {
id: string;
type: "warning" | "error" | "info";
title: string;
description: string;
count?: number;
products?: any[];
}

export interface FilterItem {
id: string;
label: string;
count: number;
isSelected: boolean;
color: string;
type:
| "promotions"
| "ultimasVendas"
| "comprados"
| "destaques"
| "perfil"
| "ruptura"
| "sugerido"
| "corte"
| "notCategorized";
products: number[];
}

export interface ParameterSection {
id: string;
title: string;
description: string;
options: ParameterOption[];
}

export interface ParameterOption {
id: string;
label: string;
isSelected: boolean;
}

Hooks Personalizados

useSuggestedSaleModalController

Responsabilidade: Gerenciar estado e lógica para modo API completo

export const useSuggestedSaleModalController = (
codParc: number | string,
props: SuggestedSaleModalProps
) => {
// Estados
const [isLoading, setIsLoading] = useState(false);
const [alerts, setAlerts] = useState<AlertItem[]>([]);
const [parameters, setParameters] = useState<ParameterSection[]>([]);
const [filters, setFilters] = useState<FilterItem[]>([]);

// Funções
const fetchSuggestedSale = async () => {
/* ... */
};
const handleParameterChange = useCallback(() => {
/* ... */
}, []);
const handleFilterToggle = useCallback(() => {
/* ... */
}, []);
const handleConfirm = async () => {
/* ... */
};

return {
isLoading,
isDataReady,
alerts,
parameters,
filters,
error,
fetchSuggestedSale,
handleParameterChange,
handleFilterToggle,
handleConfirm,
resetState,
loadingSend,
};
};

useSuggestedSalePreloadModalController

Responsabilidade: Gerenciar estado para modo preload (produtos existentes)

export const useSuggestedSalePreloadModalController = (
codParc: number | string,
props: SuggestedSalePreloadModalProps,
ids: string[]
) => {
// Estados simplificados para modo preload
const [alerts, setAlerts] = useState<AlertItem[]>([]);
const [filters, setFilters] = useState<FilterItem[]>([]);

// Processamento automático de dados preload
const processPreloadedData = async () => {
/* ... */
};

return {
alerts,
filters,
filteredProducts,
isDataReady,
loadingSend,
handleFilterToggle,
handleConfirm,
resetState,
};
};

Integração com Redux

Actions e Selectors Utilizados

// Store Actions
import {
editOrderWithDynamicHeader,
resetOrderEditStatus,
} from "../../store/order-create.slice";

// Selectors
const orderEditStatus = useSelector(
(state: RootState) => state.orderCreate.orderEditStatus
);

const client = useSelector(
(state: RootState) => state?.clients?.entities?.[codParc]
);

const user = useSelector((state: RootState) => state.auth.user);

Fluxo de Confirmação do Pedido

const handleConfirm = async () => {
try {
setLoadingSend(true);

// Filtrar produtos selecionados
const selectedProducts = filters
.filter((f) => f.isSelected)
.map((f) => f.products)
.flat();

// Formatar dados para criação/edição do pedido
const orderData = formatterOrderContent(suggestedSaleData);

// Disparar ação Redux
dispatch(editOrderWithDynamicHeader(orderData));
} catch (error) {
console.error("Erro ao confirmar pedido:", error);
} finally {
setLoadingSend(false);
}
};

Animações e UX

Componente de Loading

// SuggestedSaleModalLoading.tsx
const SuggestedSaleModalLoading = ({ onClose }) => {
// Animações sofisticadas
const fadeInAnim = useRef(new Animated.Value(0)).current;
const slideUpAnim = useRef(new Animated.Value(50)).current;
const pulseAnim = useRef(new Animated.Value(1)).current;
const shimmerAnim = useRef(new Animated.Value(0)).current;

useEffect(() => {
// Animação de entrada (fade in + slide up)
Animated.parallel([
Animated.timing(fadeInAnim, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(slideUpAnim, {
toValue: 0,
duration: 800,
useNativeDriver: true,
}),
]).start();
}, []);

// ... renderização com animações
};

Estados de Loading

EstadoDuraçãoAnimação
Fade In800msOpacidade 0 → 1
Slide Up800msTranslate Y: 50px → 0
Pulse2000msScale: 1 → 1.05 → 1 (loop)
Shimmer1500msTranslate X: -100% → 100%

Configuração e Habilitação

Setting de Controle

// Verificação de habilitação do recurso
const settings = useSelector((state: RootState) => state?.settings);

{
settings?.VENDASUGERIDAIA === "S" && (
<AiActionsButton onPress={() => setSuggestedSaleModal(true)} />
);
}

Configuração no Sankhya

CampoTabelaDescrição
VENDASUGERIDAIATSIPARHabilita/desabilita o recurso ('S'/'N')

Pontos de Integração

Telas que Utilizam o Componente

TelaLocalizaçãoModo de Uso
Ações Rápidas (Home)cs-home-quick-actions.tsxAPI Mode (novo pedido)
Cabeçalho do Carrinhoselected-product-header.tsxPreload Mode (produtos existentes)
Card do Clientecs-client.tsxAPI Mode (novo pedido)

Exemplo de Uso - Ações Rápidas

// cs-home-quick-actions.tsx
const [suggestedSaleModalVisible, setSuggestedSaleModalVisible] =
useState(false);
const [suggestedSaleModalCodParc, setSuggestedSaleModalCodParc] = useState<
number | null
>(null);

{
!!suggestedSaleModalVisible && !!suggestedSaleModalCodParc && (
<SuggestedSaleModal
visible={suggestedSaleModalVisible}
codParc={suggestedSaleModalCodParc?.toString?.()}
onClose={() => setSuggestedSaleModalVisible(false)}
/>
);
}

Exemplo de Uso - Carrinho Existente

// selected-product-header.tsx
const [suggestedSaleModal, setSuggestedSaleModal] = useState(false);

{
suggestedSaleModal && (
<SuggestedSaleModal
codParc={codParc}
visible={true}
onClose={() => setSuggestedSaleModal(false)}
itensPreload={props?.productIDs ?? []}
/>
);
}

Tratamento de Erros

Cenários de Erro Tratados

ErroTratamentoExperiência do Usuário
Falha na APIAlert com mensagem de erro + fechamentoModal fecha automaticamente
Dados InválidosLog de erro + fallbackExibe tela vazia ou mensagem
Timeout de RequisiçãoRetry automático ou manualBotão de tentar novamente
Produtos sem EstoqueAlert específico na tela de alertasOrientação para remover produtos

Implementação de Error Handling

useEffect(() => {
if (
visible &&
!isLoading &&
currentStep === "loading" &&
!props?.itensPreload
) {
if (error) {
console.error("API Error:", error);
Alert.alert("Erro", error, [
{
text: "OK",
onPress: handleClose,
},
]);
} else if (isDataReady) {
setCurrentStep("alert");
}
}
}, [visible, isLoading, isDataReady, currentStep, error, handleClose]);

Performance e Otimizações

Estratégias de Performance

EstratégiaImplementaçãoBenefício
Lazy LoadingCarregamento sob demanda de componentesReduz bundle inicial
useCallbackMemoização de funções de callbackEvita re-renders desnecessários
useState Batch UpdatesAgrupamento de atualizações de estadoMelhora responsividade
Selective Re-rendersuseEffect com dependências específicasOtimiza ciclo de renderização

Memoização de Funções

const handleParameterChange = useCallback(
(sectionId: string, optionId: string) => {
setParameters((prev) =>
prev.map((section) =>
section.id === sectionId
? {
...section,
options: section.options.map((option) => ({
...option,
isSelected: option.id === optionId,
})),
}
: section
)
);
},
[]
);

const handleFilterToggle = useCallback(
(filterId: string) => {
setFilters((prev) =>
prev.map((filter) =>
filter.id === filterId
? { ...filter, isSelected: !filter.isSelected }
: filter
)
);
},
[filters, suggestedSaleData]
);

Dependências

Bibliotecas Principais

{
"react": "^18.0.0",
"react-native": "^0.72.0",
"react-native-vector-icons": "^10.0.0",
"react-native-responsive-fontsize": "^0.8.1",
"@reduxjs/toolkit": "^1.9.0",
"react-redux": "^8.0.0",
"axios": "^1.4.0",
"dayjs": "^1.11.0",
"@react-native-async-storage/async-storage": "^1.19.0",
"react-native-reanimated": "^3.0.0"
}

Recursos Externos

  • Realm Database - Armazenamento local de dados
  • API Sankhya - Integração com ERP
  • Redux Store - Gerenciamento de estado global
  • React Navigation - Navegação entre telas