fix(monitoring): lista negra estricta e ignorado permanente de grupos ajenos, eliminacion de productos no calzado y proteccion en webhook
This commit is contained in:
parent
02bac37af0
commit
31130bfbb5
@ -82,7 +82,20 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const isGroupIgnored = (jid: string, name?: string): boolean => {
|
||||
if (!jid) return false;
|
||||
if (jid === '120363400148850031@g.us' || jid === '120363420942364759@g.us') return true;
|
||||
if (name && /black diamonds|team emporio|emporio/i.test(name)) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleToggleSelectJid = (jid: string) => {
|
||||
const grp = liveGroups.find((g) => g.jid === jid);
|
||||
if (grp && isGroupIgnored(grp.jid, grp.name)) {
|
||||
setToast(`⛔ El grupo "${grp.name}" está excluido permanentemente y no puede seleccionarse.`);
|
||||
setTimeout(() => setToast(null), 4000);
|
||||
return;
|
||||
}
|
||||
setSelectedJids((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(jid)) next.delete(jid);
|
||||
@ -94,7 +107,11 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
const handleSelectAllFiltered = (filtered: MonitoredWhatsAppGroup[]) => {
|
||||
setSelectedJids((prev) => {
|
||||
const next = new Set(prev);
|
||||
filtered.forEach((g) => next.add(g.jid));
|
||||
filtered.forEach((g) => {
|
||||
if (!isGroupIgnored(g.jid, g.name)) {
|
||||
next.add(g.jid);
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
@ -108,9 +125,10 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
try {
|
||||
const updatedGroups: MonitoredWhatsAppGroup[] = liveGroups.map((lg) => ({
|
||||
...lg,
|
||||
isMonitored: selectedJids.has(lg.jid),
|
||||
autoPublish: true,
|
||||
isMonitored: !isGroupIgnored(lg.jid, lg.name) && selectedJids.has(lg.jid),
|
||||
autoPublish: !isGroupIgnored(lg.jid, lg.name),
|
||||
profitMarginOverride: 35,
|
||||
notes: isGroupIgnored(lg.jid, lg.name) ? 'IGNORADO EXPLICITAMENTE' : lg.notes,
|
||||
}));
|
||||
|
||||
const res = await fetch('/api/whatsapp-groups', {
|
||||
@ -134,6 +152,12 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
};
|
||||
|
||||
const handleToggleGroup = async (group: MonitoredWhatsAppGroup) => {
|
||||
if (isGroupIgnored(group.jid, group.name)) {
|
||||
setToast(`⛔ El grupo "${group.name}" está en la lista de exclusión (ignorado permanente) y no puede ser monitoreado.`);
|
||||
setTimeout(() => setToast(null), 4500);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextState = !group.isMonitored;
|
||||
const updatedList = groups.map((g) =>
|
||||
g.jid === group.jid || g.id === group.id ? { ...g, isMonitored: nextState } : g
|
||||
@ -329,12 +353,15 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
) : (
|
||||
filteredGroups.map((group) => {
|
||||
const isMonitored = group.isMonitored;
|
||||
const isIgnored = isGroupIgnored(group.jid, group.name);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={group.id || group.jid}
|
||||
className={`p-5 rounded-3xl border transition-all space-y-4 ${
|
||||
isMonitored
|
||||
isIgnored
|
||||
? 'bg-neutral-950/80 border-rose-900/40 opacity-70'
|
||||
: isMonitored
|
||||
? 'bg-gradient-to-br from-neutral-900 to-neutral-900/90 border-purple-500/40 shadow-lg shadow-purple-950/20 ring-1 ring-purple-500/20'
|
||||
: 'bg-neutral-900/60 border-neutral-800 hover:border-neutral-700 opacity-80'
|
||||
}`}
|
||||
@ -342,15 +369,20 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span
|
||||
className={`w-2.5 h-2.5 rounded-full shrink-0 ${
|
||||
isMonitored ? 'bg-purple-400 animate-pulse' : 'bg-neutral-600'
|
||||
isIgnored ? 'bg-rose-500' : isMonitored ? 'bg-purple-400 animate-pulse' : 'bg-neutral-600'
|
||||
}`}
|
||||
/>
|
||||
<h3 className="font-black text-white text-sm leading-snug line-clamp-1">
|
||||
{group.name}
|
||||
</h3>
|
||||
{isIgnored && (
|
||||
<span className="bg-rose-500/20 text-rose-300 border border-rose-500/30 text-[9px] font-black px-2 py-0.5 rounded-full uppercase tracking-wider">
|
||||
⛔ Ignorado
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[11px] font-mono text-neutral-400 pl-4 mt-0.5 truncate">
|
||||
{group.jid}
|
||||
@ -360,13 +392,21 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
{/* Toggle Switch */}
|
||||
<button
|
||||
onClick={() => handleToggleGroup(group)}
|
||||
className={`shrink-0 text-xs font-bold px-3 py-1.5 rounded-xl border flex items-center gap-1.5 transition-all cursor-pointer ${
|
||||
isMonitored
|
||||
? 'bg-purple-600 text-white border-purple-400 shadow-md font-black'
|
||||
: 'bg-neutral-800 text-neutral-400 border-neutral-700 hover:text-white'
|
||||
disabled={isIgnored}
|
||||
className={`shrink-0 text-xs font-bold px-3 py-1.5 rounded-xl border flex items-center gap-1.5 transition-all ${
|
||||
isIgnored
|
||||
? 'bg-rose-950/40 text-rose-400 border-rose-900/50 cursor-not-allowed'
|
||||
: isMonitored
|
||||
? 'bg-purple-600 text-white border-purple-400 shadow-md font-black cursor-pointer'
|
||||
: 'bg-neutral-800 text-neutral-400 border-neutral-700 hover:text-white cursor-pointer'
|
||||
}`}
|
||||
>
|
||||
{isMonitored ? (
|
||||
{isIgnored ? (
|
||||
<>
|
||||
<XCircle className="w-3.5 h-3.5" />
|
||||
<span>Bloqueado</span>
|
||||
</>
|
||||
) : isMonitored ? (
|
||||
<>
|
||||
<CheckCircle2 className="w-3.5 h-3.5 fill-white text-purple-600" />
|
||||
<span>Monitoreando</span>
|
||||
@ -500,22 +540,27 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
</div>
|
||||
) : (
|
||||
filteredPickerGroups.map((group) => {
|
||||
const isSelected = selectedJids.has(group.jid);
|
||||
const isIgnored = isGroupIgnored(group.jid, group.name);
|
||||
const isSelected = !isIgnored && selectedJids.has(group.jid);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={group.jid}
|
||||
onClick={() => handleToggleSelectJid(group.jid)}
|
||||
className={`p-3.5 rounded-2xl border transition-all cursor-pointer flex items-center justify-between gap-3 ${
|
||||
isSelected
|
||||
? 'bg-purple-950/30 border-purple-500/50 shadow-md ring-1 ring-purple-500/20'
|
||||
: 'bg-neutral-950 border-neutral-800 hover:border-neutral-700'
|
||||
onClick={() => !isIgnored && handleToggleSelectJid(group.jid)}
|
||||
className={`p-3.5 rounded-2xl border transition-all flex items-center justify-between gap-3 ${
|
||||
isIgnored
|
||||
? 'bg-neutral-950/60 border-rose-950/60 opacity-60 cursor-not-allowed'
|
||||
: isSelected
|
||||
? 'bg-purple-950/30 border-purple-500/50 shadow-md ring-1 ring-purple-500/20 cursor-pointer'
|
||||
: 'bg-neutral-950 border-neutral-800 hover:border-neutral-700 cursor-pointer'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-3 flex-1 min-w-0">
|
||||
<div
|
||||
className={`w-5 h-5 rounded-lg border flex items-center justify-center shrink-0 transition-all ${
|
||||
isSelected
|
||||
isIgnored
|
||||
? 'bg-neutral-900 border-neutral-800 text-neutral-600'
|
||||
: isSelected
|
||||
? 'bg-purple-600 border-purple-400 text-white'
|
||||
: 'border-neutral-700 bg-neutral-900'
|
||||
}`}
|
||||
@ -524,8 +569,13 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
</div>
|
||||
|
||||
<div className="min-w-0">
|
||||
<h4 className="font-bold text-white text-xs truncate">
|
||||
{group.name}
|
||||
<h4 className="font-bold text-white text-xs truncate flex items-center gap-1.5">
|
||||
<span>{group.name}</span>
|
||||
{isIgnored && (
|
||||
<span className="bg-rose-500/20 text-rose-300 border border-rose-500/30 text-[9px] font-black px-1.5 py-0.2 rounded-full uppercase">
|
||||
⛔ Ignorado
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
<p className="text-[10px] font-mono text-neutral-400 truncate">
|
||||
{group.jid}
|
||||
@ -534,7 +584,11 @@ export default function AdminWhatsAppGroupsPage() {
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 flex items-center gap-2">
|
||||
{isSelected ? (
|
||||
{isIgnored ? (
|
||||
<span className="bg-rose-950/40 text-rose-400 border border-rose-900/50 text-[10px] font-bold px-2 py-0.5 rounded-full">
|
||||
Bloqueado
|
||||
</span>
|
||||
) : isSelected ? (
|
||||
<span className="bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 text-[10px] font-black px-2 py-0.5 rounded-full uppercase">
|
||||
Monitorear
|
||||
</span>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { dataService } from '@/lib/data-service';
|
||||
import { dataService, isGroupIgnoredOrBlacklisted } from '@/lib/data-service';
|
||||
import { parseSupplierMessage, detectCategory } from '@/lib/gemini-parser';
|
||||
import { sanitizePublicationTitle } from '@/lib/sanitizer';
|
||||
import { generateAttractiveProductCopy } from '@/lib/attractive-copy';
|
||||
@ -14,6 +14,7 @@ import {
|
||||
} from '@/lib/evolution-api';
|
||||
import {
|
||||
getMonitoredGroupsFromAppwrite,
|
||||
saveMonitoredGroupToAppwrite,
|
||||
saveProductToAppwrite,
|
||||
saveSupplierPostToAppwrite,
|
||||
updateSupplierPostStatusInAppwrite,
|
||||
@ -238,7 +239,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CASO B: GRUPOS MAYORISTAS DE PROVEEDORES (Publicación Automática Incondicional)
|
||||
// CASO B: GRUPOS MAYORISTAS DE PROVEEDORES (Monitoreo Estricto por Lista Blanca)
|
||||
// =========================================================================
|
||||
// Resolver el nombre del grupo de forma precisa usando Evolution API si no viene en el payload
|
||||
let groupSubject = data.groupInfo?.subject || data.chatName || '';
|
||||
@ -255,6 +256,17 @@ export async function POST(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 0. LISTA NEGRA PERMANENTE DE GRUPOS IGNORADOS (Blacklist incondicional)
|
||||
if (isGroupIgnoredOrBlacklisted(remoteJid, groupSubject)) {
|
||||
console.log(`[DripZone Webhook] Grupo IGNORADO por lista negra: "${groupSubject}" (${remoteJid})`);
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
ignored: 'Grupo en lista negra comercial permanente (no monitorear ni publicar)',
|
||||
chat: groupSubject,
|
||||
remoteJid,
|
||||
});
|
||||
}
|
||||
|
||||
// 1. FILTRAR EXCLUSIVAMENTE GRUPOS DE PRENSA Y GOBIERNO (Reenviar a portal de noticias Almeira)
|
||||
const isNonProductNews = /gob cauca|gobernacion|alcaldia|prensa|medios|noticias|comunicaciones|sedes asociadas|notificaciones almeira|inelpac|meliextra|almeira producciones|publicaciones almeira|quilichao en medios/i.test(
|
||||
groupSubject
|
||||
@ -292,25 +304,68 @@ export async function POST(request: Request) {
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-registrar grupo como monitoreado y activo si no existía previamente
|
||||
try {
|
||||
const currentGroups = dataService.getMonitoredGroups();
|
||||
const existingGrp = currentGroups.find((g) => g.jid === remoteJid);
|
||||
if (!existingGrp) {
|
||||
dataService.saveMonitoredGroups([
|
||||
...currentGroups,
|
||||
{
|
||||
id: `grp-${remoteJid.replace(/\D/g, '').slice(-8)}`,
|
||||
jid: remoteJid,
|
||||
name: groupSubject,
|
||||
isMonitored: true,
|
||||
autoPublish: true,
|
||||
notes: 'Auto-registrado desde Webhook activo de WhatsApp',
|
||||
supplierName: groupSubject,
|
||||
},
|
||||
]);
|
||||
}
|
||||
} catch (_) {}
|
||||
// 1.2 FILTRAR CONTENIDO DE EVENTOS, CONFERENCIAS, REUNIONES VIRTUALES O SPAM
|
||||
const isSpamOrAnnouncement = /conferencia|webinar|zoom\.us|pantalla de zoom|podcast|mañanas millonarias|liderazgo|liderea|orador|taller virtual|evento online|conversaciones m[aá]s dinero|estrategia que funciona|elevax10n|smoothie|strawberry flavored|smartwatch|reloj inteligente|forex|cripto|trading|hoy 🚨|empezamos en \d+ minutos|llamada 911/i.test(
|
||||
`${messageContent} ${groupSubject}`
|
||||
);
|
||||
if (isSpamOrAnnouncement) {
|
||||
console.log(`[DripZone Webhook] Mensaje descartado por ser anuncio/evento/no calzado: "${messageContent.slice(0, 60)}"`);
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
ignored: 'Mensaje es un anuncio, evento o contenido no calzado',
|
||||
chat: groupSubject,
|
||||
remoteJid,
|
||||
});
|
||||
}
|
||||
|
||||
// 1.3 VERIFICAR QUE EL GRUPO ESTÉ EXPRESAMENTE MONITOREADO Y ACTIVO
|
||||
let currentGroups = dataService.getMonitoredGroups();
|
||||
let existingGrp = currentGroups.find(
|
||||
(g) => g.jid === remoteJid || g.name.toLowerCase().replace(/\s+/g, ' ').trim() === groupSubject.toLowerCase().replace(/\s+/g, ' ').trim()
|
||||
);
|
||||
|
||||
if (!existingGrp) {
|
||||
try {
|
||||
const appwriteGroups = await getMonitoredGroupsFromAppwrite();
|
||||
existingGrp = appwriteGroups.find((g) => g.jid === remoteJid);
|
||||
if (existingGrp) {
|
||||
dataService.saveMonitoredGroups([...currentGroups, existingGrp]);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Si el grupo nunca se ha configurado, registrarlo como INACTIVO (pendiente de aprobación en panel)
|
||||
if (!existingGrp) {
|
||||
const newGroupDoc = {
|
||||
id: `grp-${remoteJid.replace(/\D/g, '').slice(-8)}`,
|
||||
jid: remoteJid,
|
||||
name: groupSubject,
|
||||
isMonitored: false,
|
||||
autoPublish: false,
|
||||
notes: 'Detectado en webhook (inactivo por defecto)',
|
||||
supplierName: groupSubject,
|
||||
};
|
||||
dataService.saveMonitoredGroups([...currentGroups, newGroupDoc]);
|
||||
saveMonitoredGroupToAppwrite(newGroupDoc).catch(() => {});
|
||||
console.log(`[DripZone Webhook] Nuevo grupo detectado pero REGISTRADO COMO INACTIVO: "${groupSubject}" (${remoteJid})`);
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
ignored: 'Grupo nuevo no monitoreado (registrado inactivo en panel admin)',
|
||||
chat: groupSubject,
|
||||
remoteJid,
|
||||
});
|
||||
}
|
||||
|
||||
// Si el grupo está desactivado o tiene auto-publicación apagada
|
||||
if (!existingGrp.isMonitored || existingGrp.autoPublish === false) {
|
||||
console.log(`[DripZone Webhook] Mensaje descartado: grupo no monitoreado o autoPublish apagado: "${groupSubject}" (${remoteJid})`);
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
ignored: 'Grupo no monitoreado o auto-publicación apagada',
|
||||
chat: groupSubject,
|
||||
remoteJid,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. VERIFICACIÓN DE DUPLICADOS
|
||||
if (isDuplicateMessage(whatsappMessageId, messageContent)) {
|
||||
@ -397,6 +452,17 @@ export async function POST(request: Request) {
|
||||
};
|
||||
}
|
||||
|
||||
// Validación de coherencia y descarte de spam
|
||||
const invalidTitleCheck = /hoy 🚨|conferencia|evento|zoom|podcast|mañanas millonarias|liderea|sin producto|elevax10n|smoothie|no especificado/i;
|
||||
if (invalidTitleCheck.test(extracted.name || '') || invalidTitleCheck.test(messageContent)) {
|
||||
console.log(`[DripZone Webhook] Publicación cancelada por contenido no comercial/calzado: "${extracted.name}"`);
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
ignored: 'Contenido descartado por no ser calzado o producto legítimo',
|
||||
name: extracted.name,
|
||||
});
|
||||
}
|
||||
|
||||
// Categorización segura
|
||||
if (!extracted.category || extracted.category === 'Calzado / Sneakers' || extracted.category === 'Sneakers / Colección') {
|
||||
extracted.category = detectCategory(`${extracted.name || ''} ${messageContent}`).category;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { dataService } from '@/lib/data-service';
|
||||
import { dataService, isGroupIgnoredOrBlacklisted } from '@/lib/data-service';
|
||||
import { MonitoredWhatsAppGroup } from '@/types';
|
||||
import {
|
||||
getMonitoredGroupsFromAppwrite,
|
||||
@ -22,7 +22,7 @@ export async function GET(request: Request) {
|
||||
const appwriteGroups = await getMonitoredGroupsFromAppwrite();
|
||||
if (appwriteGroups.length > 0) {
|
||||
dataService.saveMonitoredGroups(appwriteGroups);
|
||||
localGroups = appwriteGroups;
|
||||
localGroups = dataService.getMonitoredGroups();
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Error leyendo grupos desde Appwrite:', err);
|
||||
@ -36,24 +36,46 @@ export async function GET(request: Request) {
|
||||
});
|
||||
}
|
||||
|
||||
// Consultar grupos en vivo desde Evolution API usando findChats (base de datos local rápida sin rate-limits)
|
||||
// Consultar grupos en vivo desde Evolution API usando findChats y fetchAllGroups
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${EVOLUTION_API_URL}/chat/findChats/${EVOLUTION_INSTANCE_NAME}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
apikey: EVOLUTION_API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
let liveGroups: any[] = [];
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${EVOLUTION_API_URL}/group/fetchAllGroups/${EVOLUTION_INSTANCE_NAME}?getParticipants=false`,
|
||||
{
|
||||
headers: { apikey: EVOLUTION_API_KEY },
|
||||
signal: AbortSignal.timeout(8000),
|
||||
}
|
||||
);
|
||||
if (response.ok) {
|
||||
const all = await response.json();
|
||||
if (Array.isArray(all) && all.length > 0) {
|
||||
liveGroups = all.map((g: any) => ({
|
||||
remoteJid: g.id || g.jid,
|
||||
name: g.subject || g.name || 'Grupo WhatsApp',
|
||||
size: g.size,
|
||||
}));
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (_) {}
|
||||
|
||||
const allChats = await response.json();
|
||||
const liveGroups = (Array.isArray(allChats) ? allChats : []).filter(
|
||||
(c: any) => (c.remoteJid && c.remoteJid.includes('@g.us')) || (c.id && c.id.includes('@g.us'))
|
||||
);
|
||||
if (liveGroups.length === 0) {
|
||||
const response = await fetch(
|
||||
`${EVOLUTION_API_URL}/chat/findChats/${EVOLUTION_INSTANCE_NAME}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
apikey: EVOLUTION_API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
);
|
||||
const allChats = await response.json();
|
||||
liveGroups = (Array.isArray(allChats) ? allChats : []).filter(
|
||||
(c: any) => (c.remoteJid && c.remoteJid.includes('@g.us')) || (c.id && c.id.includes('@g.us'))
|
||||
);
|
||||
}
|
||||
|
||||
if (liveGroups.length > 0) {
|
||||
const merged: MonitoredWhatsAppGroup[] = await Promise.all(
|
||||
@ -61,49 +83,34 @@ export async function GET(request: Request) {
|
||||
const jid = lg.remoteJid || lg.id || lg.jid;
|
||||
const name = lg.name || lg.pushName || lg.subject || 'Grupo WhatsApp';
|
||||
let isStillMember = true;
|
||||
let participants = undefined;
|
||||
let participants = lg.size || undefined;
|
||||
|
||||
try {
|
||||
const infoRes = await fetch(
|
||||
`${EVOLUTION_API_URL}/group/findGroupInfos/${EVOLUTION_INSTANCE_NAME}?groupJid=${jid}`,
|
||||
{
|
||||
headers: { apikey: EVOLUTION_API_KEY },
|
||||
signal: AbortSignal.timeout(3500),
|
||||
}
|
||||
);
|
||||
if (infoRes.status === 404 || infoRes.status === 401 || infoRes.status === 403) {
|
||||
isStillMember = false;
|
||||
} else if (infoRes.ok) {
|
||||
const infoData = await infoRes.json();
|
||||
if (Array.isArray(infoData.participants)) {
|
||||
participants = infoData.participants.length;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Si falla por timeout, mantener estado previo
|
||||
}
|
||||
const isBlacklisted = isGroupIgnoredOrBlacklisted(jid, name);
|
||||
const existing = localGroups.find(
|
||||
(g) => g.jid === jid || g.name.toLowerCase().replace(/\s+/g, ' ').trim() === name.toLowerCase().replace(/\s+/g, ' ').trim()
|
||||
);
|
||||
|
||||
const existing = localGroups.find((g) => g.jid === jid || g.name.toLowerCase() === name.toLowerCase());
|
||||
|
||||
// Pre-marcar como monitoreados grupos reconocidos de proveedores si no había configuración previa
|
||||
const isKnownSupplierGroup = /exclusive|emporio|jovic|diamonds|mayorista|calzado|sneakers|zapatillas/i.test(name);
|
||||
const isMonitored = existing
|
||||
// Pre-marcar SOLO proveedores exclusivos verificados de calzado si no había configuración previa
|
||||
const isKnownSupplierGroup = !isBlacklisted && /exclusive cc|jovic store/i.test(name);
|
||||
const isMonitored = isBlacklisted ? false : (existing
|
||||
? (existing.isMonitored && isStillMember)
|
||||
: (isKnownSupplierGroup && isStillMember);
|
||||
: (isKnownSupplierGroup && isStillMember));
|
||||
|
||||
return {
|
||||
id: existing?.id || `grp-${jid.replace(/\D/g, '').slice(-6)}`,
|
||||
id: existing?.id || `grp-${jid.replace(/\D/g, '').slice(-8)}`,
|
||||
jid,
|
||||
name,
|
||||
isMonitored: isStillMember ? isMonitored : false,
|
||||
isMonitored: isBlacklisted ? false : (isStillMember ? isMonitored : false),
|
||||
isStillMember,
|
||||
hasLeft: !isStillMember,
|
||||
supplierId: existing?.supplierId,
|
||||
supplierName: existing?.supplierName || name,
|
||||
profitMarginOverride: existing?.profitMarginOverride || 35,
|
||||
autoPublish: existing?.autoPublish ?? true,
|
||||
autoPublish: isBlacklisted ? false : (existing?.autoPublish ?? true),
|
||||
participantsCount: participants ?? lg.unreadCount,
|
||||
notes: isStillMember ? (existing?.notes || '') : 'Saliste de este grupo en WhatsApp',
|
||||
notes: isBlacklisted
|
||||
? 'IGNORADO EXPLICITAMENTE'
|
||||
: isStillMember ? (existing?.notes || '') : 'Saliste de este grupo en WhatsApp',
|
||||
};
|
||||
})
|
||||
);
|
||||
@ -145,7 +152,8 @@ export async function POST(request: Request) {
|
||||
|
||||
if (body.action === 'toggle' || body.action === 'deactivate-group') {
|
||||
const { idOrJid, isMonitored } = body;
|
||||
const targetState = isMonitored !== undefined ? isMonitored : false;
|
||||
const isBlacklisted = isGroupIgnoredOrBlacklisted(idOrJid);
|
||||
const targetState = isBlacklisted ? false : (isMonitored !== undefined ? isMonitored : false);
|
||||
const updated = dataService.toggleGroupMonitoring(idOrJid, targetState);
|
||||
toggleMonitoredGroupInAppwrite(idOrJid, targetState).catch((e) =>
|
||||
console.warn('Error sincronizando toggle con Appwrite:', e)
|
||||
@ -154,8 +162,14 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
if (Array.isArray(body.groups)) {
|
||||
const saved = dataService.saveMonitoredGroups(body.groups);
|
||||
Promise.all(body.groups.map((g: any) => saveMonitoredGroupToAppwrite(g))).catch((e) =>
|
||||
const sanitized = body.groups.map((g: any) => {
|
||||
if (isGroupIgnoredOrBlacklisted(g.jid, g.name)) {
|
||||
return { ...g, isMonitored: false, autoPublish: false, notes: 'IGNORADO EXPLICITAMENTE' };
|
||||
}
|
||||
return g;
|
||||
});
|
||||
const saved = dataService.saveMonitoredGroups(sanitized);
|
||||
Promise.all(sanitized.map((g: any) => saveMonitoredGroupToAppwrite(g))).catch((e) =>
|
||||
console.warn('Error sincronizando grupos con Appwrite:', e)
|
||||
);
|
||||
return NextResponse.json({ success: true, groups: saved });
|
||||
|
||||
@ -227,6 +227,19 @@ export const INITIAL_USERS: User[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const BLACKLISTED_WHATSAPP_GROUPS: string[] = [
|
||||
'120363400148850031@g.us', // SOMOS BLACK DIAMONDS 💎💎💎
|
||||
'120363420942364759@g.us', // 💎TEAM EMPORIO💎🔥👑❤️🔥🤯
|
||||
];
|
||||
|
||||
export function isGroupIgnoredOrBlacklisted(jid: string, name?: string): boolean {
|
||||
if (!jid) return false;
|
||||
const cleanJid = jid.trim();
|
||||
if (BLACKLISTED_WHATSAPP_GROUPS.includes(cleanJid)) return true;
|
||||
if (name && /black diamonds|team emporio|emporio/i.test(name)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export const INITIAL_MONITORED_GROUPS: MonitoredWhatsAppGroup[] = [];
|
||||
|
||||
function getFsModule() {
|
||||
@ -1092,21 +1105,48 @@ class DataService {
|
||||
return user || null;
|
||||
}
|
||||
|
||||
private monitoredGroups: MonitoredWhatsAppGroup[] = [...INITIAL_MONITORED_GROUPS];
|
||||
private monitoredGroups: MonitoredWhatsAppGroup[] = loadPersistedData<MonitoredWhatsAppGroup[]>(
|
||||
'persisted_monitored_groups.json',
|
||||
[...INITIAL_MONITORED_GROUPS]
|
||||
);
|
||||
|
||||
getMonitoredGroups(): MonitoredWhatsAppGroup[] {
|
||||
return this.monitoredGroups;
|
||||
if (!this.monitoredGroups || this.monitoredGroups.length === 0) {
|
||||
this.monitoredGroups = loadPersistedData<MonitoredWhatsAppGroup[]>(
|
||||
'persisted_monitored_groups.json',
|
||||
[...INITIAL_MONITORED_GROUPS]
|
||||
);
|
||||
}
|
||||
return this.monitoredGroups.map((g) => {
|
||||
if (isGroupIgnoredOrBlacklisted(g.jid, g.name)) {
|
||||
return { ...g, isMonitored: false, autoPublish: false, notes: 'IGNORADO EXPLICITAMENTE' };
|
||||
}
|
||||
return g;
|
||||
});
|
||||
}
|
||||
|
||||
saveMonitoredGroups(groups: MonitoredWhatsAppGroup[]): MonitoredWhatsAppGroup[] {
|
||||
this.monitoredGroups = groups;
|
||||
this.monitoredGroups = groups.map((g) => {
|
||||
if (isGroupIgnoredOrBlacklisted(g.jid, g.name)) {
|
||||
return { ...g, isMonitored: false, autoPublish: false, notes: 'IGNORADO EXPLICITAMENTE' };
|
||||
}
|
||||
return g;
|
||||
});
|
||||
savePersistedData('persisted_monitored_groups.json', this.monitoredGroups);
|
||||
return this.monitoredGroups;
|
||||
}
|
||||
|
||||
toggleGroupMonitoring(idOrJid: string, isMonitored: boolean): MonitoredWhatsAppGroup | null {
|
||||
const grp = this.monitoredGroups.find((g) => g.id === idOrJid || g.jid === idOrJid);
|
||||
if (grp) {
|
||||
grp.isMonitored = isMonitored;
|
||||
if (isGroupIgnoredOrBlacklisted(grp.jid, grp.name)) {
|
||||
grp.isMonitored = false;
|
||||
grp.autoPublish = false;
|
||||
grp.notes = 'IGNORADO EXPLICITAMENTE';
|
||||
} else {
|
||||
grp.isMonitored = isMonitored;
|
||||
}
|
||||
savePersistedData('persisted_monitored_groups.json', this.monitoredGroups);
|
||||
return grp;
|
||||
}
|
||||
return null;
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
MessageTemplate,
|
||||
CustomProductRequest,
|
||||
} from '@/types';
|
||||
import { INITIAL_CITIES, INITIAL_PAYMENT_METHODS, INITIAL_SUPPLIERS } from './data-service';
|
||||
import { INITIAL_CITIES, INITIAL_PAYMENT_METHODS, INITIAL_SUPPLIERS, isGroupIgnoredOrBlacklisted } from './data-service';
|
||||
import { DEFAULT_MESSAGE_TEMPLATES } from './templates-service';
|
||||
import { sanitizePublicationTitle, sanitizePublicationText } from './sanitizer';
|
||||
|
||||
@ -717,15 +717,18 @@ export async function getMonitoredGroupsFromAppwrite(): Promise<MonitoredWhatsAp
|
||||
Query.limit(100),
|
||||
]);
|
||||
if (res.documents && res.documents.length > 0) {
|
||||
return res.documents.map((d: any) => ({
|
||||
id: d.$id,
|
||||
jid: d.jid || '',
|
||||
name: d.name || '',
|
||||
isMonitored: Boolean(d.isMonitored),
|
||||
autoPublish: d.autoPublish !== false,
|
||||
notes: d.notes || '',
|
||||
supplierName: d.name || '',
|
||||
}));
|
||||
return res.documents.map((d: any) => {
|
||||
const isBlacklisted = isGroupIgnoredOrBlacklisted(d.jid, d.name);
|
||||
return {
|
||||
id: d.$id,
|
||||
jid: d.jid || '',
|
||||
name: d.name || '',
|
||||
isMonitored: isBlacklisted ? false : Boolean(d.isMonitored),
|
||||
autoPublish: isBlacklisted ? false : d.autoPublish !== false,
|
||||
notes: isBlacklisted ? 'IGNORADO EXPLICITAMENTE' : d.notes || '',
|
||||
supplierName: d.name || '',
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
} catch (e: any) {
|
||||
@ -737,13 +740,14 @@ export async function getMonitoredGroupsFromAppwrite(): Promise<MonitoredWhatsAp
|
||||
export async function saveMonitoredGroupToAppwrite(
|
||||
group: Partial<MonitoredWhatsAppGroup>
|
||||
): Promise<MonitoredWhatsAppGroup> {
|
||||
const isBlacklisted = isGroupIgnoredOrBlacklisted(group.jid || '', group.name);
|
||||
const docId = group.id || `grp-${(group.jid || '').replace(/\D/g, '').slice(-8)}` || `grp-${Date.now()}`;
|
||||
const payload = {
|
||||
jid: group.jid || '',
|
||||
name: group.name || '',
|
||||
isMonitored: Boolean(group.isMonitored),
|
||||
autoPublish: group.autoPublish !== false,
|
||||
notes: group.notes || '',
|
||||
isMonitored: isBlacklisted ? false : Boolean(group.isMonitored),
|
||||
autoPublish: isBlacklisted ? false : group.autoPublish !== false,
|
||||
notes: isBlacklisted ? 'IGNORADO EXPLICITAMENTE' : group.notes || '',
|
||||
};
|
||||
|
||||
try {
|
||||
@ -780,12 +784,24 @@ export async function toggleMonitoredGroupInAppwrite(
|
||||
isMonitored: boolean
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const groups = await getMonitoredGroupsFromAppwrite();
|
||||
const found = groups.find((g) => g.id === idOrJid || g.jid === idOrJid);
|
||||
if (found) {
|
||||
await serverDatabases.updateDocument(DB_ID, 'monitored_groups', found.id, {
|
||||
isMonitored,
|
||||
});
|
||||
const isBlacklisted = isGroupIgnoredOrBlacklisted(idOrJid);
|
||||
const finalState = isBlacklisted ? false : isMonitored;
|
||||
|
||||
const res = await serverDatabases.listDocuments(DB_ID, 'monitored_groups', [Query.limit(100)]);
|
||||
const matchingDocs = res.documents.filter(
|
||||
(d: any) => d.$id === idOrJid || d.jid === idOrJid || d.name === idOrJid
|
||||
);
|
||||
|
||||
if (matchingDocs.length > 0) {
|
||||
await Promise.all(
|
||||
matchingDocs.map((doc: any) =>
|
||||
serverDatabases.updateDocument(DB_ID, 'monitored_groups', doc.$id, {
|
||||
isMonitored: finalState,
|
||||
autoPublish: finalState,
|
||||
notes: isBlacklisted ? 'IGNORADO EXPLICITAMENTE' : doc.notes,
|
||||
})
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
114
scripts/clean_unwanted_supplier_products.js
Normal file
114
scripts/clean_unwanted_supplier_products.js
Normal file
@ -0,0 +1,114 @@
|
||||
const sdk = require('node-appwrite');
|
||||
const client = new sdk.Client()
|
||||
.setEndpoint('https://appwrite.almeira.com.co/v1')
|
||||
.setProject('69b84df5003bd8957c5c')
|
||||
.setKey('standard_cce3cdb866ea6433888d40cedd24dc45987372308fdc3dd0697de08648c0e5c7e04b02c3bcee271403fd656b00a5820f04c08b51631284a6e8a3582d8aed2aa33234f3c29cc777e9b162eb54810b789251b0d006a9a3b081ebc2da53b9baa94614b124b3508ad849fd9a4187cc6094f506d4b57f409b69d9562a74df594f3ec3');
|
||||
const dbs = new sdk.Databases(client);
|
||||
|
||||
const DB_ID = 'sneakers_multistore_db';
|
||||
|
||||
async function main() {
|
||||
console.log('=== [1] ELIMINANDO PRODUCTOS BASURA DE LOS GRUPOS IGNORADOS ===');
|
||||
let offset = 0;
|
||||
const limit = 100;
|
||||
const toDelete = [];
|
||||
|
||||
while (true) {
|
||||
const res = await dbs.listDocuments(DB_ID, 'products', [
|
||||
sdk.Query.limit(limit),
|
||||
sdk.Query.offset(offset)
|
||||
]);
|
||||
if (!res.documents || res.documents.length === 0) break;
|
||||
|
||||
for (const doc of res.documents) {
|
||||
const sup = (doc.supplierName || '').toLowerCase();
|
||||
const name = (doc.name || '').toLowerCase();
|
||||
const raw = JSON.stringify(doc).toLowerCase();
|
||||
|
||||
if (
|
||||
sup.includes('emporio') ||
|
||||
sup.includes('black diamonds') ||
|
||||
raw.includes('120363400148850031') ||
|
||||
raw.includes('120363420942364759') ||
|
||||
name.includes('hoy 🚨') ||
|
||||
name.includes('liderea con')
|
||||
) {
|
||||
// Asegurar que no borramos productos legítimos de Jovic o CC Mío
|
||||
if (sup.includes('jovic') || sup.includes('cc mío') || sup.includes('exclusive')) {
|
||||
continue;
|
||||
}
|
||||
toDelete.push({ id: doc['$id'], name: doc.name, supplier: doc.supplierName });
|
||||
}
|
||||
}
|
||||
offset += limit;
|
||||
if (offset >= res.total) break;
|
||||
}
|
||||
|
||||
console.log(`Encontrados ${toDelete.length} productos para eliminar:`);
|
||||
for (const p of toDelete) {
|
||||
try {
|
||||
await dbs.deleteDocument(DB_ID, 'products', p.id);
|
||||
console.log(` ✓ Eliminado producto: ${p.id} - ${p.name} (${p.supplier})`);
|
||||
} catch (e) {
|
||||
console.error(` ✗ Error eliminando ${p.id}:`, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== [2] SANITIZANDO COLECCIÓN MONITORED_GROUPS EN APPWRITE ===');
|
||||
const groupsRes = await dbs.listDocuments(DB_ID, 'monitored_groups', [sdk.Query.limit(100)]);
|
||||
console.log(`Total grupos en Appwrite: ${groupsRes.total}`);
|
||||
|
||||
const blacklistedJids = [
|
||||
'120363400148850031@g.us', // SOMOS BLACK DIAMONDS
|
||||
'120363420942364759@g.us', // TEAM EMPORIO
|
||||
];
|
||||
|
||||
// Agrupar por JID para eliminar duplicados
|
||||
const groupsByJid = new Map();
|
||||
for (const doc of groupsRes.documents) {
|
||||
const jid = doc.jid;
|
||||
if (!groupsByJid.has(jid)) {
|
||||
groupsByJid.set(jid, []);
|
||||
}
|
||||
groupsByJid.get(jid).push(doc);
|
||||
}
|
||||
|
||||
for (const [jid, docs] of groupsByJid.entries()) {
|
||||
const isBlacklisted = blacklistedJids.includes(jid) ||
|
||||
docs.some(d => /black diamonds|team emporio|emporio/i.test(d.name || ''));
|
||||
|
||||
// Mantener 1 documento canónico y eliminar duplicados
|
||||
const canonicalDoc = docs[0];
|
||||
const duplicates = docs.slice(1);
|
||||
|
||||
for (const dup of duplicates) {
|
||||
try {
|
||||
await dbs.deleteDocument(DB_ID, 'monitored_groups', dup['$id']);
|
||||
console.log(` ✓ Eliminado documento duplicado: ${dup['$id']} (${jid})`);
|
||||
} catch (e) {
|
||||
console.error(` ✗ Error eliminando duplicado ${dup['$id']}:`, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlacklisted) {
|
||||
try {
|
||||
await dbs.updateDocument(DB_ID, 'monitored_groups', canonicalDoc['$id'], {
|
||||
isMonitored: 0,
|
||||
autoPublish: 0,
|
||||
notes: 'IGNORADO EXPLICITAMENTE - NO PUBLICAR NI MONITOREAR',
|
||||
});
|
||||
console.log(` 🛑 Grupo marcado como IGNORADO permanentemente: ${canonicalDoc.name} (${jid})`);
|
||||
} catch (e) {
|
||||
console.error(` ✗ Error actualizando grupo ${canonicalDoc['$id']}:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n=== [3] VERIFICACIÓN FINAL DE GRUPOS EN APPWRITE ===');
|
||||
const finalRes = await dbs.listDocuments(DB_ID, 'monitored_groups', [sdk.Query.limit(100)]);
|
||||
finalRes.documents.forEach((d) => {
|
||||
console.log(`ID: ${d['$id']} | JID: ${d.jid} | isMonitored: ${d.isMonitored} | Name: ${d.name}`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
45
scripts/find_all_unwanted_products.js
Normal file
45
scripts/find_all_unwanted_products.js
Normal file
@ -0,0 +1,45 @@
|
||||
const sdk = require('node-appwrite');
|
||||
const client = new sdk.Client()
|
||||
.setEndpoint('https://appwrite.almeira.com.co/v1')
|
||||
.setProject('69b84df5003bd8957c5c')
|
||||
.setKey('standard_cce3cdb866ea6433888d40cedd24dc45987372308fdc3dd0697de08648c0e5c7e04b02c3bcee271403fd656b00a5820f04c08b51631284a6e8a3582d8aed2aa33234f3c29cc777e9b162eb54810b789251b0d006a9a3b081ebc2da53b9baa94614b124b3508ad849fd9a4187cc6094f506d4b57f409b69d9562a74df594f3ec3');
|
||||
const dbs = new sdk.Databases(client);
|
||||
|
||||
async function inspectAll() {
|
||||
let offset = 0;
|
||||
const limit = 100;
|
||||
const toDelete = [];
|
||||
|
||||
while (true) {
|
||||
const res = await dbs.listDocuments('sneakers_multistore_db', 'products', [
|
||||
sdk.Query.limit(limit),
|
||||
sdk.Query.offset(offset)
|
||||
]);
|
||||
if (!res.documents || res.documents.length === 0) break;
|
||||
|
||||
for (const doc of res.documents) {
|
||||
const sup = (doc.supplierName || '').toLowerCase();
|
||||
const name = (doc.name || '').toLowerCase();
|
||||
const raw = JSON.stringify(doc).toLowerCase();
|
||||
|
||||
if (
|
||||
sup.includes('emporio') ||
|
||||
sup.includes('black diamonds') ||
|
||||
raw.includes('120363400148850031') ||
|
||||
raw.includes('120363420942364759') ||
|
||||
name.includes('hoy 🚨') ||
|
||||
name.includes('liderea con')
|
||||
) {
|
||||
toDelete.push({ id: doc.$id, name: doc.name, supplier: doc.supplierName });
|
||||
}
|
||||
}
|
||||
|
||||
offset += limit;
|
||||
if (offset >= res.total) break;
|
||||
}
|
||||
|
||||
console.log(`Encontrados ${toDelete.length} productos publicados de estos grupos:`);
|
||||
toDelete.forEach(p => console.log(` - ID: ${p.id} | Nombre: ${p.name} | Proveedor: ${p.supplier}`));
|
||||
}
|
||||
|
||||
inspectAll().catch(console.error);
|
||||
24
scripts/inspect_target_products.js
Normal file
24
scripts/inspect_target_products.js
Normal file
@ -0,0 +1,24 @@
|
||||
const sdk = require('node-appwrite');
|
||||
const client = new sdk.Client()
|
||||
.setEndpoint('https://appwrite.almeira.com.co/v1')
|
||||
.setProject('69b84df5003bd8957c5c')
|
||||
.setKey('standard_cce3cdb866ea6433888d40cedd24dc45987372308fdc3dd0697de08648c0e5c7e04b02c3bcee271403fd656b00a5820f04c08b51631284a6e8a3582d8aed2aa33234f3c29cc777e9b162eb54810b789251b0d006a9a3b081ebc2da53b9baa94614b124b3508ad849fd9a4187cc6094f506d4b57f409b69d9562a74df594f3ec3');
|
||||
const dbs = new sdk.Databases(client);
|
||||
|
||||
async function inspect() {
|
||||
const ids = ['snk-1789056531032-5ut8', 'snk-1789057093753-lz2a', 'snk-1789058687729-e6zk'];
|
||||
for (const id of ids) {
|
||||
try {
|
||||
const doc = await dbs.getDocument('sneakers_multistore_db', 'products', id);
|
||||
console.log('--- PRODUCT:', doc['$id']);
|
||||
console.log('Name:', doc.name);
|
||||
console.log('Description:', (doc.description || '').slice(0, 150));
|
||||
console.log('SupplierPostId:', doc.supplierPostId);
|
||||
console.log('SupplierName:', doc.supplierName);
|
||||
console.log('IsActive:', doc.isActive);
|
||||
} catch(e) {
|
||||
console.log('Error', id, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
inspect().catch(console.error);
|
||||
@ -20,9 +20,9 @@ const GROUPS_TO_SEED = [
|
||||
{
|
||||
jid: '120363420942364759@g.us',
|
||||
name: '💎TEAM EMPORIO💎🔥👑❤️🔥🤯',
|
||||
isMonitored: true,
|
||||
autoPublish: true,
|
||||
notes: 'Proveedor Mayorista Team Emporio Sneakers',
|
||||
isMonitored: false,
|
||||
autoPublish: false,
|
||||
notes: 'IGNORADO EXPLICITAMENTE - NO PUBLICAR NI MONITOREAR',
|
||||
},
|
||||
{
|
||||
jid: '120363027254133375@g.us',
|
||||
@ -34,9 +34,9 @@ const GROUPS_TO_SEED = [
|
||||
{
|
||||
jid: '120363400148850031@g.us',
|
||||
name: 'SOMOS BLACK DIAMONDS 💎💎💎',
|
||||
isMonitored: true,
|
||||
autoPublish: true,
|
||||
notes: 'Proveedor Mayorista Black Diamonds Sneakers',
|
||||
isMonitored: false,
|
||||
autoPublish: false,
|
||||
notes: 'IGNORADO EXPLICITAMENTE - NO PUBLICAR NI MONITOREAR',
|
||||
},
|
||||
{
|
||||
jid: '120363317242539420@g.us',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user