161 lines
6.9 KiB
Python
161 lines
6.9 KiB
Python
import urllib.request
|
||
import json
|
||
import ssl
|
||
import sys
|
||
import re
|
||
import io
|
||
|
||
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')
|
||
|
||
ctx = ssl.create_default_context()
|
||
ctx.check_hostname = False
|
||
ctx.verify_mode = ssl.CERT_NONE
|
||
|
||
endpoint = 'https://appwrite.almeira.com.co/v1'
|
||
project_id = '69b84df5003bd8957c5c'
|
||
api_key = 'standard_cce3cdb866ea6433888d40cedd24dc45987372308fdc3dd0697de08648c0e5c7e04b02c3bcee271403fd656b00a5820f04c08b51631284a6e8a3582d8aed2aa33234f3c29cc777e9b162eb54810b789251b0d006a9a3b081ebc2da53b9baa94614b124b3508ad849fd9a4187cc6094f506d4b57f409b69d9562a74df594f3ec3'
|
||
db_id = 'sneakers_multistore_db'
|
||
collection_id = 'catalog_products'
|
||
|
||
headers = {
|
||
'X-Appwrite-Project': project_id,
|
||
'X-Appwrite-Key': api_key,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
|
||
def clean_title(title, brand='Sneakers'):
|
||
c = title or ''
|
||
# 0. Normalizar fuentes estilizadas (Unicode Mathematical / Bold fonts)
|
||
import unicodedata
|
||
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*\)|\[\s*\]', ' ', c)
|
||
|
||
# 4. Quitar 'del 36 al 39 eur', 'del 40 al 44 eur'
|
||
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)
|
||
|
||
# 5. Quitar 'euro 36 - 39', 'eur 36/37/38', 'eu 36 al 39', '36 al 39 eu'
|
||
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)
|
||
|
||
# 6. Quitar secuencias de tallas: 32-34-36-38-40, 36/37/38, 36 - 38 - 44, 36 & Hombre 43
|
||
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)
|
||
|
||
# 7. Quitar rangos de tallas: 36 - 39, 40 - 41, 36 a 39, 36 al 39
|
||
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)
|
||
|
||
# 8. Quitar tallas individuales con palabra clave
|
||
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'\b(?:3[2-9]|4[0-8])\s*(?:eur|euro|eu)\b', ' ', c, flags=re.I)
|
||
c = re.sub(r'\btallas?\s+(?:de\s+)?(?:hombre|dama|disponibles?)?(?:\s+mil)?\b', ' ', c, flags=re.I)
|
||
|
||
# 9. Quitar .000 y residuos de precios
|
||
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'\.000\b', ' ', c, flags=re.I)
|
||
|
||
# 10. 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)\b', ' ', c, flags=re.I)
|
||
|
||
# 11. Limpiar símbolos residuales y ampersands aislados
|
||
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()
|
||
|
||
# 12. 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'
|
||
|
||
# 13. 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)
|
||
|
||
def appwrite_get(path):
|
||
req = urllib.request.Request(f'{endpoint}{path}', headers=headers, method='GET')
|
||
with urllib.request.urlopen(req, context=ctx, timeout=30) as resp:
|
||
return json.loads(resp.read().decode())
|
||
|
||
def appwrite_patch(path, data):
|
||
body = json.dumps(data).encode('utf-8')
|
||
req = urllib.request.Request(f'{endpoint}{path}', data=body, headers=headers, method='PATCH')
|
||
with urllib.request.urlopen(req, context=ctx, timeout=30) as resp:
|
||
return json.loads(resp.read().decode())
|
||
|
||
print("📦 Consultando productos desde Appwrite...")
|
||
docs = []
|
||
cursor = None
|
||
while True:
|
||
q = '?limit=100'
|
||
if cursor:
|
||
q += f'&queries[]=cursorAfter("{cursor}")'
|
||
path = f'/databases/{db_id}/collections/{collection_id}/documents{q}'
|
||
res = appwrite_get(path)
|
||
cur_docs = res.get('documents', [])
|
||
if not cur_docs:
|
||
break
|
||
docs.extend(cur_docs)
|
||
cursor = cur_docs[-1]['$id']
|
||
if len(cur_docs) < 100:
|
||
break
|
||
|
||
print(f"Total documentos encontrados en Appwrite: {len(docs)}")
|
||
|
||
updated_count = 0
|
||
for doc in docs:
|
||
doc_id = doc['$id']
|
||
old_name = doc.get('name', '')
|
||
brand = doc.get('brand', 'Sneakers')
|
||
new_name = clean_title(old_name, brand)
|
||
|
||
if old_name != new_name:
|
||
try:
|
||
appwrite_patch(f'/databases/{db_id}/collections/{collection_id}/documents/{doc_id}', {
|
||
'data': {
|
||
'name': new_name,
|
||
'modelName': new_name
|
||
}
|
||
})
|
||
print(f" [FIXED] {doc_id}:\n OLD: {old_name}\n NEW: {new_name}")
|
||
updated_count += 1
|
||
except Exception as e:
|
||
print(f" [ERROR] {doc_id}: {e}")
|
||
|
||
print(f"🎉 Migración completada en Appwrite: {updated_count} productos actualizados.")
|