almeira-web/zapply-app/src/lib/gemini-parser.ts

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();
}
}