import { GoogleGenAI, Type } from "@google/genai"; import { Product, AuctionLot, BiddingTender } from "../types"; // Vite: variáveis precisam começar com VITE_ const API_KEY = import.meta.env.VITE_GOOGLE_API_KEY as string | undefined; if (!API_KEY) { // Erro explícito pra não “falhar silencioso” throw new Error( "Missing VITE_GOOGLE_API_KEY. Add it to your .env.local (VITE_GOOGLE_API_KEY=...) and restart the dev server." ); } const ai = new GoogleGenAI({ apiKey: API_KEY }); /** * Helper: converte File em Part (inlineData) para o Gemini */ async function fileToPart(file: File) { return new Promise<{ inlineData: { data: string; mimeType: string } }>((resolve, reject) => { const reader = new FileReader(); reader.onerror = () => reject(new Error("Failed to read file")); reader.onloadend = () => { const result = reader.result as string | null; if (!result) return reject(new Error("Empty file result")); const base64Data = result.split(",")[1] || ""; resolve({ inlineData: { data: base64Data, mimeType: file.type || "application/octet-stream", }, }); }; reader.readAsDataURL(file); }); } /** * Sourcing Otimizado: Foco no MENOR PREÇO de compra (PY) * e MENOR PREÇO de venda competitiva (BR - "Mais Vendidos") */ export async function searchProducts( query: string ): Promise<{ products: Product[]; sources: any[] }> { // try { // Removed to propagate errors const prompt = `OBJETIVO: Análise de Arbitragem Profissional. 1. BUSCA REAL OBRIGATÓRIA: Você DEVE usar a tool googleSearch para buscar dados REAIS e ATUAIS no site www.comprasparaguai.com.br. Busque os 20 itens com o MENOR PREÇO ABSOLUTO para "${query}". Foque em lojas de grande renome (Nissei, Cellshop, Atacado Games, Mega Eletronicos). 2. BUSCA BRASIL (REFERÊNCIA): Para cada item, encontre o MENOR PREÇO de venda no Mercado Livre e Amazon Brasil. 3. REGRAS CRÍTICAS (ANTI-ALUCINAÇÃO): - JAMAIS invente produtos ou preços. - JAMAIS retorne itens com "(Exemplo)", "(Example)" ou dados fictícios. - Se não encontrar dados REAIS, retorne lista vazia. - Use a cotação de dólar turismo/paralelo atual (~5.75 BRL/USD). 4. RETORNO: Apenas JSON conforme o schema.`; const response = await ai.models.generateContent({ model: "gemini-3-pro-preview", // Stable fast model contents: prompt, config: { tools: [{ googleSearch: {} }], responseMimeType: "application/json", responseSchema: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { name: { type: Type.STRING, description: "Nome técnico do produto" }, priceUSD: { type: Type.NUMBER, description: "Menor preço em USD no Paraguai" }, priceBRL: { type: Type.NUMBER, description: "Preço USD convertido para BRL" }, store: { type: Type.STRING, description: "Loja do Paraguai (Nissei, Cellshop, etc)" }, url: { type: Type.STRING, description: "Link da oferta no Paraguai" }, marketPriceBRL: { type: Type.NUMBER, description: "Menor preço encontrado entre os MAIS VENDIDOS no Brasil", }, amazonPrice: { type: Type.NUMBER, description: "Menor preço Amazon (Best Seller)" }, amazonUrl: { type: Type.STRING }, mlPrice: { type: Type.NUMBER, description: "Menor preço Mercado Livre (Mais Vendido)" }, mlUrl: { type: Type.STRING }, }, required: ["name", "priceUSD", "priceBRL", "store", "url", "marketPriceBRL"], }, }, }, }); const text = response.text || "[]"; const products = JSON.parse(text); const sources = response.candidates?.[0]?.groundingMetadata?.groundingChunks || []; // Ordena para mostrar primeiro as melhores oportunidades (menor custo em BRL) const sortedProducts = Array.isArray(products) ? products.sort((a: any, b: any) => (a.priceBRL ?? 0) - (b.priceBRL ?? 0)) : []; return { products: sortedProducts as Product[], sources }; // } catch (error) { // console.error("Erro na busca de arbitrage:", error); // return { products: [], sources: [] }; // } } /** * Busca Oportunidades (>25% Margem) por Categoria */ export async function searchOpportunities(category: string, includeOverhead: boolean = true): Promise<{ products: Product[]; sources: any[] }> { // try { const prompt = `OBJETIVO: Encontrar Oportunidades de Arbitragem (>25% Lucro) na categoria "${category}". 1. BUSCA PARAGUAI: Vasculhe as principais lojas do Paraguai (Nissei, Cellshop, Mega, Atacado Games) em busca de produtos da categoria "${category}" que estejam com preços muito atrativos ou em oferta. Identifique pelo menos 20 produtos promissores. 2. CÁLCULO DE MARGEM: Para cada produto, estime o custo total em BRL (Preço PY * 5.75 * ${includeOverhead ? '1.20' : '1.00'} de taxas). Compare com o MENOR preço de venda real no Brasil (Mercado Livre/Amazon - Filtro "Mais Vendidos"). Margem = (Preço Venda BR - Custo Total) / Preço Venda BR. 3. FILTRO RIGOROSO: Retorne APENAS produtos onde a Margem estimada seja SUPERIOR a 20-25%. Se não encontrar nada com 25%, mostre os que tiverem a maior margem possível. 4. RETORNO: Apenas JSON conforme o schema.`; const response = await ai.models.generateContent({ model: "gemini-3-pro-preview", contents: prompt, config: { tools: [{ googleSearch: {} }], responseMimeType: "application/json", responseSchema: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { name: { type: Type.STRING, description: "Nome técnico do produto" }, priceUSD: { type: Type.NUMBER, description: "Menor preço em USD no Paraguai" }, priceBRL: { type: Type.NUMBER, description: "Preço USD convertido para BRL" }, store: { type: Type.STRING, description: "Loja do Paraguai (Nissei, Cellshop, etc)" }, url: { type: Type.STRING, description: "Link da oferta no Paraguai" }, marketPriceBRL: { type: Type.NUMBER, description: "Menor preço encontrado entre os MAIS VENDIDOS no Brasil", }, amazonPrice: { type: Type.NUMBER, description: "Menor preço Amazon (Best Seller)" }, amazonUrl: { type: Type.STRING }, mlPrice: { type: Type.NUMBER, description: "Menor preço Mercado Livre (Mais Vendido)" }, mlUrl: { type: Type.STRING }, }, required: ["name", "priceUSD", "priceBRL", "store", "url", "marketPriceBRL"], }, }, }, }); const text = response.text || "[]"; const products = JSON.parse(text); const sources = response.candidates?.[0]?.groundingMetadata?.groundingChunks || []; // Ordena por maior margem (diferença entre venda BR e custo PY) const sortedProducts = Array.isArray(products) ? products.sort((a: any, b: any) => { const marginA = (a.marketPriceBRL - a.priceBRL) / a.marketPriceBRL; const marginB = (b.marketPriceBRL - b.priceBRL) / b.marketPriceBRL; return marginB - marginA; // Maior margem primeiro }) : []; return { products: sortedProducts as Product[], sources }; // } catch (error) { // console.error("Erro na busca de oportunidades:", error); // return { products: [], sources: [] }; // } } /** * Leilões: Analisa texto, link ou ARQUIVO PDF */ export async function analyzeAuctionData( input: string | File ): Promise<{ lot: AuctionLot | null; sources: any[] }> { try { const promptPrefix = "Analise este lote de leilão da Receita Federal."; const parts: any[] = []; if (input instanceof File) { const filePart = await fileToPart(input); parts.push(filePart); parts.push({ text: `${promptPrefix} Leia o documento PDF anexo e extraia TODOS os itens da tabela de mercadorias. REGRAS: 1. Identifique os itens reais. 2. Descubra o MENOR valor real de mercado no Brasil (baseado nos itens mais vendidos) via Google Search. 3. Use URLs REAIS dos sites de busca.`, }); } else { parts.push({ text: `${promptPrefix} Analise este conteúdo: "${input}". REGRAS: 1. Identifique os itens reais. 2. Descubra o MENOR valor real de mercado no Brasil (baseado nos itens mais vendidos) via Google Search. 3. Use URLs REAIS dos sites de busca.`, }); } const analysisResponse = await ai.models.generateContent({ model: "gemini-3-pro-preview", contents: { parts }, // padronizado config: { tools: [{ googleSearch: {} }] }, }); const analysisText = analysisResponse.text || ""; const sources = analysisResponse.candidates?.[0]?.groundingMetadata?.groundingChunks || []; // Segunda passada: força JSON limpo const extraction = await ai.models.generateContent({ model: "gemini-3-pro-preview", contents: `Transforme em JSON conforme o schema. Texto base: ${analysisText}`, config: { responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { id: { type: Type.STRING }, title: { type: Type.STRING }, location: { type: Type.STRING }, items: { type: Type.ARRAY, items: { type: Type.STRING } }, currentBid: { type: Type.NUMBER }, minBid: { type: Type.NUMBER }, mlMarketPrice: { type: Type.NUMBER }, }, required: ["id", "title", "items", "minBid", "mlMarketPrice"], }, }, }); const lotData = JSON.parse(extraction.text || "{}"); return { lot: { ...lotData, status: "Analyzing" as any, url: typeof input === "string" && input.startsWith("http") ? input : "", }, sources, }; } catch (error) { console.error("Erro ao analisar leilão:", error); return { lot: null, sources: [] }; } } /** * Licitações Otimizadas */ export async function searchBiddings( query: string ): Promise<{ tenders: BiddingTender[]; sources: any[] }> { try { const prompt = `Pesquise licitações ativas para "${query}" no Brasil. Priorize PNCP e Compras.gov.br. Retorne apenas JSON conforme o schema.`; const searchResponse = await ai.models.generateContent({ model: "gemini-3-pro-preview", contents: prompt, config: { tools: [{ googleSearch: {} }], responseMimeType: "application/json", responseSchema: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { id: { type: Type.STRING }, org: { type: Type.STRING }, object: { type: Type.STRING }, estimatedValue: { type: Type.NUMBER }, deadline: { type: Type.STRING }, items: { type: Type.ARRAY, items: { type: Type.STRING } }, requirements: { type: Type.ARRAY, items: { type: Type.STRING } }, marketReferencePrice: { type: Type.NUMBER }, url: { type: Type.STRING }, }, required: ["id", "org", "object", "url"], }, }, }, }); const tenders = JSON.parse(searchResponse.text || "[]"); const sources = searchResponse.candidates?.[0]?.groundingMetadata?.groundingChunks || []; return { tenders: (Array.isArray(tenders) ? tenders : []) as BiddingTender[], sources }; } catch (error) { console.error("Erro na busca de licitações:", error); return { tenders: [], sources: [] }; } }