feat(sanitizer): excluir rigurosamente el costo del producto en titulos y textos de publicaciones
This commit is contained in:
parent
3f9de95ce6
commit
d8e020371c
@ -263,6 +263,7 @@ export async function POST(request: Request) {
|
||||
name: prodName,
|
||||
brand: prodBrand,
|
||||
retailPrice,
|
||||
supplierPrice: post.extractedProduct?.supplierPrice || 75000,
|
||||
forbiddenTerms: forbiddenContext,
|
||||
});
|
||||
|
||||
@ -323,6 +324,7 @@ export async function POST(request: Request) {
|
||||
name: prodName,
|
||||
brand: prodBrand,
|
||||
retailPrice,
|
||||
supplierPrice: p.extractedProduct?.supplierPrice || 75000,
|
||||
forbiddenTerms: forbiddenContext,
|
||||
});
|
||||
|
||||
|
||||
@ -45,5 +45,5 @@ export function generateAttractiveProductCopy(product: {
|
||||
|
||||
⚠️ *Disponibilidad Limitada:* Pocas unidades disponibles por talla en inventario nacional. ¡Asegura tu par ahora antes de que se agoten!`;
|
||||
|
||||
return sanitizePublicationText(rawCopy, product.forbiddenTerms);
|
||||
return sanitizePublicationText(rawCopy, product.forbiddenTerms, product.supplierPrice);
|
||||
}
|
||||
|
||||
@ -215,9 +215,11 @@ class DataService {
|
||||
name: cleanName,
|
||||
brand,
|
||||
retailPrice: product.retailPrice,
|
||||
supplierPrice: product.supplierPrice,
|
||||
forbiddenTerms: [product.supplierName],
|
||||
}),
|
||||
[product.supplierName]
|
||||
[product.supplierName],
|
||||
product.supplierPrice
|
||||
);
|
||||
|
||||
product.name = cleanName;
|
||||
|
||||
@ -618,11 +618,12 @@ export async function parseSupplierMessage(
|
||||
const systemPrompt = `Eres un experto mundial en reconocimiento de calzado deportivo y zapatillas urbanas para e-commerce en Colombia.
|
||||
Tu misión es identificar con MÁXIMA PRECISIÓN la marca y el modelo exacto del calzado.
|
||||
|
||||
⚠️ REGLA CRÍTICA DE CERO PROVEEDORES / CERO GRUPOS (ESTRICTO):
|
||||
- NI EN EL TÍTULO ("name") NI EN NINGÚN TEXTO COMERCIAL DE LA PUBLICACIÓN SE DEBE INCLUIR EL NOMBRE DEL GRUPO DE WHATSAPP, CANAL, PROVEEDOR, BODEGA MAYORISTA, DISTRIBUIDOR O REMITENTE.
|
||||
⚠️ REGLA CRÍTICA DE CERO PROVEEDORES / CERO GRUPOS / CERO COSTOS (ESTRICTO):
|
||||
- NI EN EL TÍTULO ("name") NI EN NINGÚN TEXTO COMERCIAL DE LA PUBLICACIÓN SE DEBE INCLUIR EL NOMBRE DEL GRUPO DE WHATSAPP, CANAL, PROVEEDOR, BODEGA MAYORISTA, DISTRIBUIDOR, NI EL COSTO DEL PRODUCTO (PRECIO DE COMPRA / MAYORISTA).
|
||||
- El costo del producto SOLO debe extraerse en el campo numérico interno "supplierPrice" para el cálculo de margen, pero JAMÁS debe figurar en el título ("name") ni en ninguna descripción de la publicación.
|
||||
- El campo "name" debe ser ÚNICAMENTE el modelo limpio y comercial del calzado (ej: "Adidas Samba OG Blanco Negro", "Nike Air Force 1 07 All White", "New Balance 9060 Sea Salt", "On Cloudmonster Triple Black", "Puma Palermo Special Verde").
|
||||
- Si el mensaje contiene nombres de grupos o proveedores (${forbiddenClause} o palabras como "Bodega", "Mayorista", "Distribuidora", "Proveedor", "Jovic", "CC Mío", "Team Emporio", etc.), DEBES OMITIRLOS Y ELIMINARLOS POR COMPLETO DEL TÍTULO Y DE TODO TEXTO.
|
||||
- Si la zapatilla no tiene modelo exacto identificable, asigna un nombre comercial elegante (ej: "Sneakers Urban Edition"), pero JAMÁS inventes ni coloques el nombre de la bodega o del grupo.
|
||||
- Si el mensaje contiene nombres de grupos o proveedores (${forbiddenClause} o palabras como "Bodega", "Mayorista", "Distribuidora", "Proveedor", "Jovic", "CC Mío", "Team Emporio", etc.), o menciones de costo/precios de bodega, DEBES OMITIRLOS Y ELIMINARLOS POR COMPLETO DEL TÍTULO Y DE TODO TEXTO.
|
||||
- Si la zapatilla no tiene modelo exacto identificable, asigna un nombre comercial elegante (ej: "Sneakers Urban Edition"), pero JAMÁS inventes ni coloques el nombre de la bodega, del grupo ni el costo del producto.
|
||||
|
||||
${
|
||||
imagePayload
|
||||
|
||||
@ -50,6 +50,15 @@ const SUPPLIER_PREFIX_PATTERNS = [
|
||||
const GENERIC_FORBIDDEN_WORDS_REGEX =
|
||||
/\b(proveedor(?:es)?|bodega(?:s)?(?:\s+mayorista)?|distribuidor(?:a)?(?:es)?|mayorista(?:s)?|canal(?:\s+mayorista)?|grupo(?:\s+de)?\s+whatsapp)\b/gi;
|
||||
|
||||
// Regex para eliminar menciones de costo, precios mayoristas y precios de compra
|
||||
const COST_AND_PRICE_PATTERNS = [
|
||||
/(?:costo|precio\s*mayorista|precio\s*bodega|precio\s*distribuidor|precio\s*compra|al\s*por\s*mayor|valor\s*mayorista|mayorista)\s*[:\-–—]?\s*(?:\$?\s*[\d.,]+\s*(?:mil|k)?|\d+k|\$\s*\d+)?/gi,
|
||||
/(?:^|\n|\s*)(?:costo|precio\s*costo|precio\s*proveedor|precio\s*bodega)\s*[:\-–—]?\s*[^\n]+/gi,
|
||||
/(?:💸|💰|💵|💲)\s*[:\-–—]?\s*(?:\$?\s*[\d.,]+\s*(?:mil|k)?|\d+k|\$\s*\d+)/gi,
|
||||
/(?:^|\n|\s*)(?:precio|valor)\s*[:\-–—]\s*(?:\$?\s*[\d.,]+\s*(?:mil|k)?|\d+k|\$\s*\d+)/gi,
|
||||
/\b\d{2,3}[\.,]\d{3}\b|\b\d{2,3}k\b|\b\d{2,3}\s*mil\b/gi,
|
||||
];
|
||||
|
||||
/**
|
||||
* Escapa caracteres especiales para usar en expresiones regulares
|
||||
*/
|
||||
@ -95,7 +104,7 @@ function buildForbiddenList(extraForbidden: (string | undefined | null)[] = []):
|
||||
* Sanitiza el TÍTULO / NOMBRE comercial de la publicación
|
||||
* - Elimina nombres de grupos de WhatsApp
|
||||
* - Elimina nombres de proveedores o bodegas
|
||||
* - Elimina menciones como "Bodega", "Mayorista", "Proveedor", etc.
|
||||
* - Elimina costos, precios y menciones a intermediarios
|
||||
* - Asegura que el producto tenga un título comercial atractivo y limpio
|
||||
*/
|
||||
export function sanitizePublicationTitle(
|
||||
@ -124,23 +133,29 @@ export function sanitizePublicationTitle(
|
||||
// 3. Quitar palabras genéricas de intermediarios
|
||||
cleaned = cleaned.replace(GENERIC_FORBIDDEN_WORDS_REGEX, ' ').trim();
|
||||
|
||||
// 4. Limpiar emojis excesivos de inicio, códigos numéricos, precios pegados
|
||||
// 4. Quitar estrictamente costos, cifras de precios y menciones monetarias
|
||||
for (const pattern of COST_AND_PRICE_PATTERNS) {
|
||||
cleaned = cleaned.replace(pattern, ' ').trim();
|
||||
}
|
||||
|
||||
// 5. Limpiar emojis excesivos de inicio, códigos numéricos, precios pegados
|
||||
cleaned = cleaned
|
||||
.replace(/^[#*_\-•—–~:;,\s]+/, '')
|
||||
.replace(/🔥|🔴|‼️|✨|🌸|👑|💎|🛍️|🌟|💸|💰|💵|💲|\*/g, '')
|
||||
.replace(/\b\d{2,3}[\.,]\d{3}\b|\b\d{2,3}k\b|\$\s*\d+/gi, '')
|
||||
.replace(/dama\s*y\s*caballero|caballero|dama|unisex|recarga|nueva\s*mercanc[ií]a|[uú]ltima\s*unidad/gi, '')
|
||||
.replace(/\b(costo|precio|valor)\b/gi, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// 5. Si quedó vacío o demasiado corto o numérico, asignar nombre por defecto limpio
|
||||
// 6. Si quedó vacío o demasiado corto o numérico, asignar nombre por defecto limpio
|
||||
if (cleaned.length < 3 || /^\d+$/.test(cleaned) || /^[-,.:;\s]+$/.test(cleaned)) {
|
||||
return fallbackBrand && fallbackBrand !== 'Urbano / Variado'
|
||||
? `${fallbackBrand} Urban Edition`
|
||||
: 'Zapatillas Urbanas Streetwear';
|
||||
}
|
||||
|
||||
// 6. Formatear en Capital Case limpio
|
||||
// 7. Formatear en Capital Case limpio
|
||||
return cleaned
|
||||
.split(' ')
|
||||
.filter(Boolean)
|
||||
@ -157,12 +172,14 @@ export function sanitizePublicationTitle(
|
||||
/**
|
||||
* Sanitiza el TEXTO / DESCRIPCIÓN de la publicación
|
||||
* - Elimina menciones a grupos de WhatsApp, enlaces a grupos y canales
|
||||
* - Elimina nombres de proveedores, bodegas o precios de compra mayorista
|
||||
* - Elimina nombres de proveedores, bodegas o intermediarios
|
||||
* - Elimina rigurosamente el COSTO del producto, precio mayorista o valor de compra
|
||||
* - Elimina contactos directos del proveedor o advertencias dirigidas a revendedores
|
||||
*/
|
||||
export function sanitizePublicationText(
|
||||
rawText: string,
|
||||
extraForbidden: (string | undefined | null)[] = []
|
||||
extraForbidden: (string | undefined | null)[] = [],
|
||||
knownSupplierPrice?: number
|
||||
): string {
|
||||
if (!rawText || typeof rawText !== 'string') return '';
|
||||
|
||||
@ -181,7 +198,22 @@ export function sanitizePublicationText(
|
||||
cleaned = cleaned.replace(termRegex, ' ');
|
||||
}
|
||||
|
||||
// 4. Quitar líneas que sean exclusivamente identificadores de proveedor o grupo
|
||||
// 4. Si se conoce el precio de costo numérico, eliminar cualquier aparición de dicha cifra
|
||||
if (knownSupplierPrice && knownSupplierPrice > 0) {
|
||||
const costStr = knownSupplierPrice.toString();
|
||||
const costFormatted = knownSupplierPrice.toLocaleString('es-CO');
|
||||
const costInK = `${Math.round(knownSupplierPrice / 1000)}k`;
|
||||
const costInMil = `${Math.round(knownSupplierPrice / 1000)}\\s*mil`;
|
||||
const costRegex = new RegExp(`\\$?\\s*(?:${escapeRegExp(costStr)}|${escapeRegExp(costFormatted)}|${costInK}|${costInMil})\\b`, 'gi');
|
||||
cleaned = cleaned.replace(costRegex, ' ');
|
||||
}
|
||||
|
||||
// 5. Quitar patrones de costos, precios mayoristas y frases de distribuidor
|
||||
for (const pattern of COST_AND_PRICE_PATTERNS) {
|
||||
cleaned = cleaned.replace(pattern, ' ');
|
||||
}
|
||||
|
||||
// 6. Quitar líneas que sean identificadores de proveedor, grupo o costos
|
||||
const lines = cleaned.split('\n');
|
||||
const cleanLines = lines.filter((line) => {
|
||||
const lower = line.toLowerCase().trim();
|
||||
@ -190,11 +222,18 @@ export function sanitizePublicationText(
|
||||
lower.startsWith('bodega:') ||
|
||||
lower.startsWith('canal:') ||
|
||||
lower.startsWith('grupo:') ||
|
||||
lower.startsWith('costo:') ||
|
||||
lower.startsWith('costo') ||
|
||||
lower.startsWith('precio mayorista') ||
|
||||
lower.startsWith('precio distribuidor') ||
|
||||
lower.startsWith('mayorista:') ||
|
||||
lower.includes('grupo mayorista') ||
|
||||
lower.includes('canal de difusión') ||
|
||||
lower.includes('canal de difusion') ||
|
||||
lower.includes('difusion jovic') ||
|
||||
lower.includes('cc mío 3b')
|
||||
lower.includes('cc mío 3b') ||
|
||||
lower.includes('precio al por mayor') ||
|
||||
lower.includes('precio de compra')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -203,10 +242,10 @@ export function sanitizePublicationText(
|
||||
|
||||
cleaned = cleanLines.join('\n');
|
||||
|
||||
// 5. Quitar palabras clave genéricas de intermediarios sueltas
|
||||
// 7. Quitar palabras clave genéricas de intermediarios sueltas
|
||||
cleaned = cleaned.replace(GENERIC_FORBIDDEN_WORDS_REGEX, ' ');
|
||||
|
||||
// 6. Normalizar espaciado
|
||||
// 8. Normalizar espaciado
|
||||
cleaned = cleaned
|
||||
.replace(/[ \t]+/g, ' ')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
|
||||
@ -523,7 +523,7 @@ export async function getProductsFromAppwrite(): Promise<Product[]> {
|
||||
|
||||
const brand = d.brand || 'Sneakers';
|
||||
const cleanName = sanitizePublicationTitle(d.name || 'Zapatillas', [d.supplierName], brand);
|
||||
const cleanDesc = sanitizePublicationText(d.description || '', [d.supplierName]);
|
||||
const cleanDesc = sanitizePublicationText(d.description || '', [d.supplierName], d.supplierPrice);
|
||||
|
||||
return {
|
||||
id: d.$id,
|
||||
@ -555,7 +555,7 @@ export async function getProductsFromAppwrite(): Promise<Product[]> {
|
||||
export async function saveProductToAppwrite(product: Product): Promise<Product> {
|
||||
const brand = product.brand || 'Sneakers';
|
||||
const cleanName = sanitizePublicationTitle(product.name, [product.supplierName], brand);
|
||||
const cleanDesc = sanitizePublicationText(product.description || '', [product.supplierName]);
|
||||
const cleanDesc = sanitizePublicationText(product.description || '', [product.supplierName], product.supplierPrice);
|
||||
|
||||
const payload = {
|
||||
name: cleanName,
|
||||
|
||||
@ -96,11 +96,11 @@ export async function processWhatsAppMessage(
|
||||
Eres un asistente editorial experto y editor gráfico de Almeira Web.
|
||||
Analiza los mensajes y recursos multimedia recibidos desde WhatsApp para estructurarlos como publicaciones del portal.
|
||||
${explicitTypeDirective}
|
||||
REGLA CRÍTICA OBLIGATORIA (CERO NOMBRES DE GRUPO / PROVEEDOR):
|
||||
Ni en el título ('titulo') ni en el texto o contenido ('contenido', 'seo_descripcion') de la publicación se debe incluir el nombre del grupo de WhatsApp ni el proveedor o remitente del producto o servicio. El título y el texto deben enfocarse en el hecho noticioso o producto sin mencionar nombres de grupos, canales de difusión ni proveedores mayoristas.
|
||||
REGLA CRÍTICA OBLIGATORIA (CERO NOMBRES DE GRUPO / PROVEEDOR / COSTOS):
|
||||
Ni en el título ('titulo') ni en el texto o contenido ('contenido', 'seo_descripcion') de la publicación se debe incluir el nombre del grupo de WhatsApp, el proveedor o remitente del producto o servicio, ni el costo del producto (precio mayorista o de compra). El título y el texto deben enfocarse en el hecho noticioso o producto con redacción profesional limpia, sin citar nombres de grupos, canales de difusión, proveedores ni precios de costo.
|
||||
|
||||
Debes estructurar el mensaje en base a lo siguiente:
|
||||
1. titulo: Asigna un título claro, profesional y conciso (máx. 70 caracteres). ¡Cero nombres de grupos o proveedores!
|
||||
1. titulo: Asigna un título claro, profesional y conciso (máx. 70 caracteres). ¡Cero nombres de grupos, proveedores o costos!
|
||||
2. tipo: Determina la categoría exacta ${explicitType ? `(Usa obligatoriamente "${explicitType}")` : ''}:
|
||||
- "noticia": Noticias generales, política, actualidad, comunicados y reportajes (dimensiones recomendadas de imagen: 640x360 px, relación 16:9).
|
||||
- "evento": Invitaciones a actividades, jornadas, ferias, foros, congresos, festivales o convocatorias con fecha (dimensiones de imagen: 640x640 px, relación 1:1).
|
||||
|
||||
@ -45,5 +45,5 @@ export function generateAttractiveProductCopy(product: {
|
||||
|
||||
⚠️ *Disponibilidad Limitada:* Pocas unidades disponibles por talla en inventario nacional. ¡Asegura tu par ahora antes de que se agoten!`;
|
||||
|
||||
return sanitizePublicationText(rawCopy, product.forbiddenTerms);
|
||||
return sanitizePublicationText(rawCopy, product.forbiddenTerms, product.supplierPrice);
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
/**
|
||||
* Módulo de Sanitización para Títulos y Textos de Publicaciones (Zapply)
|
||||
* Módulo de Sanitización para Títulos y Textos de Publicaciones
|
||||
* Garantiza estrictamente que NI en el título NI en el texto de la publicación
|
||||
* se incluya el nombre del grupo de WhatsApp, canal, proveedor o términos mayoristas.
|
||||
*/
|
||||
|
||||
// Términos y palabras clave prohibidas por defecto (grupos, bodegas, proveedores y mayoristas)
|
||||
export const DEFAULT_FORBIDDEN_TERMS = [
|
||||
'difusión jovic store colombia',
|
||||
'difusion jovic store colombia',
|
||||
@ -39,18 +40,35 @@ export const DEFAULT_FORBIDDEN_TERMS = [
|
||||
'bodegas aliadas',
|
||||
];
|
||||
|
||||
// Regex para detectar patrones de prefijos de proveedor / grupo
|
||||
const SUPPLIER_PREFIX_PATTERNS = [
|
||||
/(?:^|\n|\s*)(?:proveedor|bodega|distribuidor[a]?|mayorista[s]?|canal|grupo\s*(?:de\s*whatsapp)?)\s*[:\-–—]\s*([^\n,.]+)/gi,
|
||||
/(?:^|\n|\s*)(?:de|por|remitente|fuente)\s*[:\-–—]\s*([^\n,.]+)/gi,
|
||||
];
|
||||
|
||||
// Regex para eliminar palabras y conceptos de intermediarios en publicaciones comerciales
|
||||
const GENERIC_FORBIDDEN_WORDS_REGEX =
|
||||
/\b(proveedor(?:es)?|bodega(?:s)?(?:\s+mayorista)?|distribuidor(?:a)?(?:es)?|mayorista(?:s)?|canal(?:\s+mayorista)?|grupo(?:\s+de)?\s+whatsapp)\b/gi;
|
||||
|
||||
// Regex para eliminar menciones de costo, precios mayoristas y precios de compra
|
||||
const COST_AND_PRICE_PATTERNS = [
|
||||
/(?:costo|precio\s*mayorista|precio\s*bodega|precio\s*distribuidor|precio\s*compra|al\s*por\s*mayor|valor\s*mayorista|mayorista)\s*[:\-–—]?\s*(?:\$?\s*[\d.,]+\s*(?:mil|k)?|\d+k|\$\s*\d+)?/gi,
|
||||
/(?:^|\n|\s*)(?:costo|precio\s*costo|precio\s*proveedor|precio\s*bodega)\s*[:\-–—]?\s*[^\n]+/gi,
|
||||
/(?:💸|💰|💵|💲)\s*[:\-–—]?\s*(?:\$?\s*[\d.,]+\s*(?:mil|k)?|\d+k|\$\s*\d+)/gi,
|
||||
/(?:^|\n|\s*)(?:precio|valor)\s*[:\-–—]\s*(?:\$?\s*[\d.,]+\s*(?:mil|k)?|\d+k|\$\s*\d+)/gi,
|
||||
/\b\d{2,3}[\.,]\d{3}\b|\b\d{2,3}k\b|\b\d{2,3}\s*mil\b/gi,
|
||||
];
|
||||
|
||||
/**
|
||||
* Escapa caracteres especiales para usar en expresiones regulares
|
||||
*/
|
||||
function escapeRegExp(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia emojis y símbolos decorativos de nombres de grupos
|
||||
*/
|
||||
export function cleanGroupName(raw: string): string {
|
||||
if (!raw) return '';
|
||||
return raw
|
||||
@ -59,8 +77,12 @@ export function cleanGroupName(raw: string): string {
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normaliza y compila lista de términos prohibidos
|
||||
*/
|
||||
function buildForbiddenList(extraForbidden: (string | undefined | null)[] = []): string[] {
|
||||
const terms = new Set<string>();
|
||||
|
||||
DEFAULT_FORBIDDEN_TERMS.forEach((t) => terms.add(t.toLowerCase().trim()));
|
||||
|
||||
extraForbidden.forEach((item) => {
|
||||
@ -78,6 +100,13 @@ function buildForbiddenList(extraForbidden: (string | undefined | null)[] = []):
|
||||
return Array.from(terms).filter((t) => t.length >= 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza el TÍTULO / NOMBRE comercial de la publicación
|
||||
* - Elimina nombres de grupos de WhatsApp
|
||||
* - Elimina nombres de proveedores o bodegas
|
||||
* - Elimina costos, precios y menciones a intermediarios
|
||||
* - Asegura que el producto tenga un título comercial atractivo y limpio
|
||||
*/
|
||||
export function sanitizePublicationTitle(
|
||||
rawTitle: string,
|
||||
extraForbidden: (string | undefined | null)[] = [],
|
||||
@ -89,36 +118,49 @@ export function sanitizePublicationTitle(
|
||||
|
||||
let cleaned = rawTitle.trim();
|
||||
|
||||
// 1. Quitar prefijos de proveedor (ej: "Proveedor: Nike Dunk" -> "Nike Dunk")
|
||||
for (const pattern of SUPPLIER_PREFIX_PATTERNS) {
|
||||
cleaned = cleaned.replace(pattern, '').trim();
|
||||
}
|
||||
|
||||
// 2. Quitar nombres de grupos y proveedores específicos
|
||||
const forbiddenList = buildForbiddenList(extraForbidden);
|
||||
for (const term of forbiddenList) {
|
||||
const termRegex = new RegExp(`(^|[\\s,;:\\-–—(]|\\[)${escapeRegExp(term)}([\\s,;:\\-–—)\\]]|$)`, 'gi');
|
||||
cleaned = cleaned.replace(termRegex, ' ').trim();
|
||||
}
|
||||
|
||||
// 3. Quitar palabras genéricas de intermediarios
|
||||
cleaned = cleaned.replace(GENERIC_FORBIDDEN_WORDS_REGEX, ' ').trim();
|
||||
|
||||
// 4. Quitar estrictamente costos, cifras de precios y menciones monetarias
|
||||
for (const pattern of COST_AND_PRICE_PATTERNS) {
|
||||
cleaned = cleaned.replace(pattern, ' ').trim();
|
||||
}
|
||||
|
||||
// 5. Limpiar emojis excesivos de inicio, códigos numéricos, precios pegados
|
||||
cleaned = cleaned
|
||||
.replace(/^[#*_\-•—–~:;,\s]+/, '')
|
||||
.replace(/🔥|🔴|‼️|✨|🌸|👑|💎|🛍️|🌟|💸|💰|💵|💲|\*/g, '')
|
||||
.replace(/\b\d{2,3}[\.,]\d{3}\b|\b\d{2,3}k\b|\$\s*\d+/gi, '')
|
||||
.replace(/dama\s*y\s*caballero|caballero|dama|unisex|recarga|nueva\s*mercanc[ií]a|[uú]ltima\s*unidad/gi, '')
|
||||
.replace(/\b(costo|precio|valor)\b/gi, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// 6. Si quedó vacío o demasiado corto o numérico, asignar nombre por defecto limpio
|
||||
if (cleaned.length < 3 || /^\d+$/.test(cleaned) || /^[-,.:;\s]+$/.test(cleaned)) {
|
||||
return fallbackBrand && fallbackBrand !== 'Urbano / Variado'
|
||||
? `${fallbackBrand} Urban Edition`
|
||||
: 'Zapatillas Urbanas Streetwear';
|
||||
}
|
||||
|
||||
// 7. Formatear en Capital Case limpio
|
||||
return cleaned
|
||||
.split(' ')
|
||||
.filter(Boolean)
|
||||
.map((w) => {
|
||||
// Conservar siglas reconocidas en mayúsculas completas (OG, SE, SB, TN, CL, RS-X, GT, etc.)
|
||||
if (/^(OG|SE|SB|TN|CL|GT|RS-X|BB|LV|NB|XT-6|9060|550|530|1906R|2002R)$/i.test(w)) {
|
||||
return w.toUpperCase();
|
||||
}
|
||||
@ -127,23 +169,51 @@ export function sanitizePublicationTitle(
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza el TEXTO / DESCRIPCIÓN de la publicación
|
||||
* - Elimina menciones a grupos de WhatsApp, enlaces a grupos y canales
|
||||
* - Elimina nombres de proveedores, bodegas o intermediarios
|
||||
* - Elimina rigurosamente el COSTO del producto, precio mayorista o valor de compra
|
||||
* - Elimina contactos directos del proveedor o advertencias dirigidas a revendedores
|
||||
*/
|
||||
export function sanitizePublicationText(
|
||||
rawText: string,
|
||||
extraForbidden: (string | undefined | null)[] = []
|
||||
extraForbidden: (string | undefined | null)[] = [],
|
||||
knownSupplierPrice?: number
|
||||
): string {
|
||||
if (!rawText || typeof rawText !== 'string') return '';
|
||||
|
||||
let cleaned = rawText;
|
||||
|
||||
// 1. Quitar enlaces de invitaciones a grupos de WhatsApp
|
||||
cleaned = cleaned.replace(/https?:\/\/chat\.whatsapp\.com\/[A-Za-z0-9]+/gi, '');
|
||||
|
||||
// 2. Quitar teléfonos típicos de proveedores mencionados en el texto
|
||||
cleaned = cleaned.replace(/(?:tel[eé]fono|contacto|pedidos\s*proveedor|asesor|bodega)\s*[:\-–—]?\s*(?:\+?57\s*)?3\d{2}[\s.-]?\d{3}[\s.-]?\d{4}/gi, '');
|
||||
|
||||
// 3. Quitar nombres de grupos y proveedores específicos
|
||||
const forbiddenList = buildForbiddenList(extraForbidden);
|
||||
for (const term of forbiddenList) {
|
||||
const termRegex = new RegExp(`(^|[\\s,;:\\-–—(]|\\[)${escapeRegExp(term)}([\\s,;:\\-–—)\\]]|$)`, 'gi');
|
||||
cleaned = cleaned.replace(termRegex, ' ');
|
||||
}
|
||||
|
||||
// 4. Si se conoce el precio de costo numérico, eliminar cualquier aparición de dicha cifra
|
||||
if (knownSupplierPrice && knownSupplierPrice > 0) {
|
||||
const costStr = knownSupplierPrice.toString();
|
||||
const costFormatted = knownSupplierPrice.toLocaleString('es-CO');
|
||||
const costInK = `${Math.round(knownSupplierPrice / 1000)}k`;
|
||||
const costInMil = `${Math.round(knownSupplierPrice / 1000)}\\s*mil`;
|
||||
const costRegex = new RegExp(`\\$?\\s*(?:${escapeRegExp(costStr)}|${escapeRegExp(costFormatted)}|${costInK}|${costInMil})\\b`, 'gi');
|
||||
cleaned = cleaned.replace(costRegex, ' ');
|
||||
}
|
||||
|
||||
// 5. Quitar patrones de costos, precios mayoristas y frases de distribuidor
|
||||
for (const pattern of COST_AND_PRICE_PATTERNS) {
|
||||
cleaned = cleaned.replace(pattern, ' ');
|
||||
}
|
||||
|
||||
// 6. Quitar líneas que sean identificadores de proveedor, grupo o costos
|
||||
const lines = cleaned.split('\n');
|
||||
const cleanLines = lines.filter((line) => {
|
||||
const lower = line.toLowerCase().trim();
|
||||
@ -152,11 +222,18 @@ export function sanitizePublicationText(
|
||||
lower.startsWith('bodega:') ||
|
||||
lower.startsWith('canal:') ||
|
||||
lower.startsWith('grupo:') ||
|
||||
lower.startsWith('costo:') ||
|
||||
lower.startsWith('costo') ||
|
||||
lower.startsWith('precio mayorista') ||
|
||||
lower.startsWith('precio distribuidor') ||
|
||||
lower.startsWith('mayorista:') ||
|
||||
lower.includes('grupo mayorista') ||
|
||||
lower.includes('canal de difusión') ||
|
||||
lower.includes('canal de difusion') ||
|
||||
lower.includes('difusion jovic') ||
|
||||
lower.includes('cc mío 3b')
|
||||
lower.includes('cc mío 3b') ||
|
||||
lower.includes('precio al por mayor') ||
|
||||
lower.includes('precio de compra')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -164,8 +241,11 @@ export function sanitizePublicationText(
|
||||
});
|
||||
|
||||
cleaned = cleanLines.join('\n');
|
||||
|
||||
// 7. Quitar palabras clave genéricas de intermediarios sueltas
|
||||
cleaned = cleaned.replace(GENERIC_FORBIDDEN_WORDS_REGEX, ' ');
|
||||
|
||||
// 8. Normalizar espaciado
|
||||
cleaned = cleaned
|
||||
.replace(/[ \t]+/g, ' ')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user