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