almeira-web/zapply-app/src/lib/sanitizer.ts

256 lines
9.3 KiB
TypeScript

/**
* 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',
'difusion jovic store',
'difusión jovic store',
'difusion jovic',
'difusión jovic',
'jovic store',
'exclusive cc mío 3b',
'exclusive cc mio 3b',
'cc mío 3b',
'cc mio 3b',
'cc mio',
'cc mío',
'bodega central mayorista',
'bodega central',
'bodega mayorista',
'proveedor mayorista',
'calzado mayorista',
'mayorista cali',
'mayoristas cali',
'bodega cali',
'canal mayorista',
'grupo mayorista',
'grupo whatsapp',
'team emporio',
'medios y prensa gob cauca',
'medios y prensa',
'notificaciones almeira',
'almeira notificaciones',
'san andresito',
'dropshipping colombia',
'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
.replace(/[💎🔥👑❤️‍🔥🤯🚨✨🌟⚡👟📦🏢📍🏷️💵💰💸💲]/g, '')
.replace(/\s+/g, ' ')
.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) => {
if (!item || typeof item !== 'string') return;
const clean = cleanGroupName(item).toLowerCase().trim();
if (clean.length >= 3) {
terms.add(clean);
}
const rawLower = item.toLowerCase().trim();
if (rawLower.length >= 3) {
terms.add(rawLower);
}
});
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)[] = [],
fallbackBrand: string = 'Sneakers'
): string {
if (!rawTitle || typeof rawTitle !== 'string') {
return `${fallbackBrand} Urban Edition`;
}
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();
}
return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
})
.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)[] = [],
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();
if (
lower.startsWith('proveedor:') ||
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('precio al por mayor') ||
lower.includes('precio de compra')
) {
return false;
}
return true;
});
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')
.trim();
return cleaned;
}