109 lines
4.2 KiB
TypeScript
109 lines
4.2 KiB
TypeScript
import { GoogleGenAI } from '@google/genai';
|
|
import { ExtractedProductData } from '@/types';
|
|
import { calculateRetailPrice } from './pricing-engine';
|
|
|
|
export async function parseSupplierMessage(text: string): Promise<ExtractedProductData> {
|
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
|
|
const fallbackExtract = (): ExtractedProductData => {
|
|
let price = 90000;
|
|
const priceMatch = text.match(/(?:precio|valor|mayorista|costo|\$)\s*[:=]?\s*(\d{2,3}(?:\.\d{3})*|\d{2,3}k?)/i);
|
|
if (priceMatch) {
|
|
const raw = priceMatch[1].toLowerCase().replace(/\./g, '');
|
|
if (raw.endsWith('k')) {
|
|
price = parseInt(raw.replace('k', ''), 10) * 1000;
|
|
} else {
|
|
const num = parseInt(raw, 10);
|
|
price = num < 1000 ? num * 1000 : num;
|
|
}
|
|
}
|
|
|
|
const sizes: string[] = [];
|
|
const sizeRangeMatch = text.match(/(\d{2})\s*(?:a|al|-)\s*(\d{2})/i);
|
|
if (sizeRangeMatch) {
|
|
const start = parseInt(sizeRangeMatch[1], 10);
|
|
const end = parseInt(sizeRangeMatch[2], 10);
|
|
if (start >= 34 && end <= 46 && start <= end) {
|
|
for (let s = start; s <= end; s++) sizes.push(s.toString());
|
|
}
|
|
} else {
|
|
const individualSizes = text.match(/\b(3[4-9]|4[0-6])\b/g);
|
|
if (individualSizes) {
|
|
sizes.push(...Array.from(new Set(individualSizes)));
|
|
}
|
|
}
|
|
if (sizes.length === 0) {
|
|
sizes.push('37', '38', '39', '40', '41', '42', '43');
|
|
}
|
|
|
|
let brand = 'Nike';
|
|
if (/jordan/i.test(text)) brand = 'Jordan';
|
|
else if (/adidas|bad bunny|campus|forum/i.test(text)) brand = 'Adidas';
|
|
else if (/new balance|nb/i.test(text)) brand = 'New Balance';
|
|
else if (/puma/i.test(text)) brand = 'Puma';
|
|
else if (/asics/i.test(text)) brand = 'Asics';
|
|
else if (/vans/i.test(text)) brand = 'Vans';
|
|
|
|
const cleanLines = text.split('\n').filter((l) => l.trim().length > 0 && !/🔥|🔴|llegando|promo/i.test(l));
|
|
const name = cleanLines[0] || `${brand} Sneakers Urban Edition`;
|
|
|
|
return {
|
|
name: name.replace(/tallas?:.*|precio:.*|\$/gi, '').trim() || `${brand} Zapatillas`,
|
|
brand,
|
|
sizes,
|
|
supplierPrice: price,
|
|
suggestedRetailPrice: calculateRetailPrice(price, 35, 30000),
|
|
category: 'Calzado / Sneakers',
|
|
confidence: 0.85,
|
|
};
|
|
};
|
|
|
|
if (!apiKey) {
|
|
return fallbackExtract();
|
|
}
|
|
|
|
try {
|
|
const ai = new GoogleGenAI({ apiKey });
|
|
const prompt = `Eres un asistente experto en e-commerce y calzado deportivo en Colombia.
|
|
Analiza el siguiente mensaje extraído de un grupo mayorista de WhatsApp.
|
|
Extrae los siguientes campos en formato JSON estricto:
|
|
- "name": Nombre comercial limpio del modelo (ej: "Nike Dunk Low Retro Panda", "Air Jordan 4 Military Black").
|
|
- "brand": Marca ("Nike", "Jordan", "Adidas", "New Balance", "Puma", "Asics", etc.).
|
|
- "sizes": Array de tallas en horma colombiana/europea (ej: ["38", "39", "40", "41", "42", "43"]). Si dice "38 al 43", expande todas las tallas intermedias.
|
|
- "supplierPrice": Precio de compra mayorista como número entero en COP (ej: 90000, 95000). Si dice "90mil" o "90", es 90000.
|
|
- "category": Categoría ("Sneakers / Colección", "Urbano / Casual", "Running / Deportivo", etc.).
|
|
|
|
Mensaje del grupo:
|
|
"""
|
|
${text}
|
|
"""
|
|
|
|
Responde ÚNICAMENTE con el objeto JSON sin bloques de código markdown ni texto adicional.`;
|
|
|
|
const response = await ai.models.generateContent({
|
|
model: 'gemini-2.5-flash',
|
|
contents: prompt,
|
|
});
|
|
|
|
const outputText = response.text || '';
|
|
const cleanJson = outputText.replace(/```json|```/gi, '').trim();
|
|
const parsed = JSON.parse(cleanJson);
|
|
|
|
const supplierPrice = typeof parsed.supplierPrice === 'number' ? parsed.supplierPrice : 90000;
|
|
const sizes = Array.isArray(parsed.sizes) && parsed.sizes.length > 0 ? parsed.sizes.map(String) : ['37', '38', '39', '40', '41', '42', '43'];
|
|
|
|
return {
|
|
name: parsed.name || 'Zapatilla Urbana',
|
|
brand: parsed.brand || 'Nike',
|
|
sizes,
|
|
supplierPrice,
|
|
suggestedRetailPrice: calculateRetailPrice(supplierPrice, 35, 30000),
|
|
category: parsed.category || 'Sneakers',
|
|
confidence: 0.98,
|
|
};
|
|
} catch (err) {
|
|
console.warn('Fallo llamada Gemini, utilizando extractor regex fallback:', err);
|
|
return fallbackExtract();
|
|
}
|
|
}
|