154 lines
7.3 KiB
Python
154 lines
7.3 KiB
Python
import paramiko
|
||
import json
|
||
import re
|
||
import unicodedata
|
||
import io
|
||
import sys
|
||
|
||
if sys.platform == 'win32':
|
||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||
|
||
HOST = "161.97.93.156"
|
||
USER = "carlos"
|
||
PASSWORD = "Innova1978+*?"
|
||
|
||
def clean_title(title, brand='Sneakers'):
|
||
c = title or ''
|
||
# 0. Normalizar fuentes estilizadas (Unicode Mathematical / Bold fonts)
|
||
c = unicodedata.normalize('NFKD', c).strip()
|
||
|
||
# 1. Quitar emojis y pictogramas de todo tipo
|
||
c = re.sub(r'[\U00010000-\U0010ffff]|[\u2600-\u27bf]|[\u2300-\u23ff]|[\u200d\ufe0f]|\*|⭐|🌹|⚡|💥|👖|👩🏻|👫|👻|☀️|🔝|🙋🏻♀️|😍|🎩|💕|⏰|🌎', ' ', c)
|
||
|
||
# 2. Quitar códigos de proveedor/bodega: Cod: 734-732 Bbo, Cod: 2236, Cod: 668/833 Bbo, etc.
|
||
c = re.sub(r'\bcod(?:igo)?\s*[:\-–—]?\s*[\d\s\-\/]+(?:\s*bbo)?\b', ' ', c, flags=re.I)
|
||
|
||
# 3. Quitar paréntesis o corchetes con tallas o números
|
||
c = re.sub(r'\(\s*(?:tallas?|euro|eur|talla|disponibles?|del)?[^\)]*\b(?:3[2-9]|4[0-9]|5[0-6])\b[^\)]*\)', ' ', c, flags=re.I)
|
||
c = re.sub(r'\([^\)]*\b(?:euro|eur|talla|tallas|mil)\b[^\)]*\)', ' ', c, flags=re.I)
|
||
c = re.sub(r'\(\s*(?:vienen?|viene)?\s*reducid[oa]s?\s*\)', ' ', c, flags=re.I)
|
||
c = re.sub(r'\(\s*\)|\[\s*\]', ' ', c)
|
||
|
||
# 4. Quitar rangos y expresiones de tallas
|
||
c = re.sub(r'\bdel\s+(?:3[0-9]|4[0-9])\s+al\s+(?:3[0-9]|4[0-9])(?:\s*(?:eur|eu|euro))?\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:euro|eur|eu)\s*[:\-–—\/\s]+(?:3[0-9]|4[0-9])(?:\s*(?:-|–|—|a|al|\/)\s*(?:3[0-9]|4[0-9]))*', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:3[0-9]|4[0-9])\s+al\s+(?:3[0-9]|4[0-9])(?:\s*(?:eur|eu|euro))\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:3[2-9]|4[0-9]|5[0-6])\s*(?:&|y|\/|\-)\s*(?:hombre\s+)?(?:3[2-9]|4[0-9]|5[0-6])(?:\s*(?:eur|eu|euro))?\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:3[2-9]|4[0-9]|5[0-6])(?:\s*[-–—\/]\s*(?:3[2-9]|4[0-9]|5[0-6]))+\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:3[4-9]|4[0-8])\s*(?:-|–|—|a|al|\/)\s*(?:3[4-9]|4[0-8])\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:talla|tallas|eur|euro|eu)\s+(?:3[2-9]|4[0-8])\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\bhasta\s+(?:talla\s+)?(?:3[2-9]|4[0-9]|5[0-6])(?:\s*(?:eur|eu|euro))?\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:solo\s+)?tallas?\s*(?:[:\-–—\s]+(?:hasta|desde|de|disponibles?|al\s+\d+))?\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:al|a)\s+(?:3[2-9]|4[0-8])\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(?:hasta|desde|tallas?|talla|disponibles?)\b', ' ', c, flags=re.I)
|
||
|
||
# 5. Quitar .000 residual y precios monetarios
|
||
c = re.sub(r'(?:^|[\s\(\[\.,\-_])(?:[1-9]\d{0,2})?\.000\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b\d{2,3}[\.,]\d{3}\b|\b\d{2,3}k\b|\b\d{2,3}\s*mil\b|\$\s*\d+', ' ', c, flags=re.I)
|
||
c = re.sub(r'\beu\s*\d+\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\.000\b', ' ', c, flags=re.I)
|
||
|
||
# 6. Quitar palabras residuales
|
||
c = re.sub(r'dama\s*y\s*caballero|recarga|nueva\s*mercanc[ií]a|[uú]ltima\s*unidad', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(costo|precio|valor|al\s*por\s*mayor|mayorista)\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(hombre|dama|caballero)\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\b(eur|euro|eu|mil|talla|tallas)\b', ' ', c, flags=re.I)
|
||
|
||
# 7. Quitar secuencias de tallas al final precedidas de guion o slash (ej: Diesel - 41 42 43, Jordan R3 - 42, Puma Tabla / 36)
|
||
if not re.search(r'(?:pegasus\s*40|balance\s*740)', c, re.I):
|
||
c = re.sub(r'[\s\-–—\/:]+(?:t:?\s*)?(?:3[2-9]|4[0-8])(?:\s*[\-–—\/\s]\s*(?:3[2-9]|4[0-8]))*\s*(?:eu\d+)?\s*$', '', c, flags=re.I)
|
||
|
||
# 8. Limpiar barras residuales, preposiciones flotantes al final y símbolos
|
||
c = re.sub(r'\s*\/+\s*(?:\d+)?', ' ', c)
|
||
c = re.sub(r'\s+(?:de|y|al|a|en|con|del|para)\s*$', '', c, flags=re.I)
|
||
c = re.sub(r'\s+&\s*$', '', c)
|
||
c = re.sub(r'^\s*&\s+', '', c)
|
||
c = re.sub(r'^[#*_\-•—–~:;,\s\.\/&]+|[#*_\-•—–~:;,\s\.\/&]+$', '', c)
|
||
c = re.sub(r'\s+', ' ', c).strip()
|
||
|
||
# 8. Fallback si quedó genérico o vacío
|
||
clean_lower = c.lower()
|
||
is_generic = (
|
||
len(c) < 3 or
|
||
re.match(r'^[\d\s\-.,/&]+$', c) or
|
||
clean_lower in ('euro', 'eur', 'zapatillas', 'sneakers', 'calzado', 'estilo', 'moda', 'solo', 'sin producto') or
|
||
clean_lower.startswith('solo ') or
|
||
clean_lower.startswith('euro ') or
|
||
clean_lower.startswith('sin producto')
|
||
)
|
||
if is_generic:
|
||
if brand and brand not in ('Urbano / Variado', 'Desconocida', 'Generica'):
|
||
c = f'{brand} Urban Edition'
|
||
else:
|
||
c = 'Sneakers Urban Classic'
|
||
|
||
# 9. Capital Case limpio conservando acrónimos
|
||
words = c.split()
|
||
formatted_words = []
|
||
for w in words:
|
||
if re.match(r'^(OG|SE|SB|TN|CL|GT|RS-X|BB|LV|NB|XT-6|CR7|V2K|V5K|9060|550|530|1906R|2002R)$', w, re.I):
|
||
formatted_words.append(w.upper())
|
||
elif re.match(r'^[A-Z0-9]+$', w) and len(w) <= 4 and not re.search(r'[aeiou]', w, re.I):
|
||
formatted_words.append(w.upper())
|
||
else:
|
||
formatted_words.append(w.capitalize())
|
||
|
||
return ' '.join(formatted_words)
|
||
|
||
print("[1] Conectando a VPS...")
|
||
client = paramiko.SSHClient()
|
||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||
client.connect(hostname=HOST, username=USER, password=PASSWORD, timeout=30)
|
||
|
||
stdin, stdout, stderr = client.exec_command("docker ps --format '{{.ID}} {{.Names}}'")
|
||
containers = stdout.read().decode('utf-8').splitlines()
|
||
drip_container = None
|
||
for c in containers:
|
||
if 'bfmno' in c.lower() or 'drip' in c.lower():
|
||
drip_container = c.split()[0]
|
||
break
|
||
|
||
if not drip_container:
|
||
print("Error: No se encontró el contenedor de DripZone")
|
||
sys.exit(1)
|
||
|
||
print(f"[2] Extrayendo persisted_products.json del contenedor {drip_container}...")
|
||
stdin, stdout, stderr = client.exec_command(f"docker exec {drip_container} cat /app/data/persisted_products.json")
|
||
raw_json = stdout.read().decode('utf-8')
|
||
products = json.loads(raw_json)
|
||
print(f" -> Total productos cargados: {len(products)}")
|
||
|
||
cleaned_count = 0
|
||
for p in products:
|
||
old_name = p.get('name', '')
|
||
brand = p.get('brand', 'Sneakers')
|
||
new_name = clean_title(old_name, brand)
|
||
if old_name != new_name:
|
||
p['name'] = new_name
|
||
p['modelName'] = new_name
|
||
cleaned_count += 1
|
||
print(f" [CLEANED] \"{old_name}\" -> \"{new_name}\"")
|
||
|
||
print(f"\n[3] Productos corregidos: {cleaned_count}. Guardando en el contenedor...")
|
||
updated_json = json.dumps(products, ensure_ascii=False, indent=2)
|
||
|
||
# Escribir a archivo temporal en VPS y copiar al contenedor
|
||
sftp = client.open_sftp()
|
||
with sftp.open('/home/carlos/persisted_products_clean.json', 'w') as f:
|
||
f.write(updated_json)
|
||
sftp.close()
|
||
|
||
stdin, stdout, stderr = client.exec_command(f"docker cp /home/carlos/persisted_products_clean.json {drip_container}:/app/data/persisted_products.json")
|
||
stdout.channel.recv_exit_status()
|
||
print(" [OK] Archivo copiado al contenedor.")
|
||
|
||
# También reiniciar el contenedor de Coolify para que recargue en RAM
|
||
print("[4] Reiniciando contenedor Coolify para cargar catálogo limpio en RAM...")
|
||
stdin, stdout, stderr = client.exec_command(f"docker restart {drip_container}")
|
||
stdout.channel.recv_exit_status()
|
||
print(" [OK] Contenedor reiniciado con éxito.")
|
||
|
||
client.close()
|
||
print("🎉 Proceso finalizado.")
|