feat(ecommerce): release Ari Shopping Frontend v2.1 real catalog
Build and Push Frontend / build (push) Successful in 2m30s

This commit is contained in:
2026-07-19 13:41:37 -05:00
parent 6700166821
commit 1c74220c88
33 changed files with 547 additions and 261 deletions
+25 -32
View File
@@ -1,36 +1,29 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse,csv,hashlib,io,json,re,sys,urllib.request
from datetime import datetime,timezone
import csv, json
from datetime import datetime, timezone
from pathlib import Path
try:
from PIL import Image,ImageOps
except ImportError:
Image=ImageOps=None
ROOT=Path(__file__).resolve().parents[1];OUT=ROOT/'data/products.json';IMG=ROOT/'assets/img/products'
def b(v,d=False): return str(v or '').strip().lower() in {'1','true','yes','si',''} if str(v or '').strip() else d
def n(v,d=0):
try:return int(float(str(v or '').strip()))
except:return d
def slug(v):return re.sub(r'[^a-zA-Z0-9_-]+','-',v.strip()).strip('-').lower() or hashlib.sha256(v.encode()).hexdigest()[:12]
def download(url):
req=urllib.request.Request(url,headers={'User-Agent':'AriShoppingCatalogSync/2.0'});data=urllib.request.urlopen(req,timeout=25).read(12*1024*1024+1)
if len(data)>12*1024*1024:raise ValueError('Imagen demasiado grande')
return data
def image(data,dst):
if Image is None:raise RuntimeError('Instala Pillow')
im=ImageOps.exif_transpose(Image.open(io.BytesIO(data))).convert('RGB');canvas=Image.new('RGB',(1200,1200),'white');im.thumbnail((1080,1080),Image.Resampling.LANCZOS);canvas.paste(im,((1200-im.width)//2,(1200-im.height)//2));canvas.save(dst,'WEBP',quality=86,method=6)
def main():
ap=argparse.ArgumentParser();ap.add_argument('--source',type=Path,default=ROOT/'catalog-source.csv');ap.add_argument('--download-images',action='store_true');a=ap.parse_args();items=[]
with a.source.open(encoding='utf-8-sig') as f:
for row in csv.DictReader(f):
pid=slug(row.get('id') or row['name']);imgs=[];urls=[u.strip() for u in row.get('image_urls','').split('|') if u.strip()]
if a.download_images:
for i,u in enumerate(urls,1):
try:dst=IMG/f'{pid}-{i}.webp';image(download(u),dst);imgs.append(str(dst.relative_to(ROOT)).replace('\\','/'))
except Exception as e:print(f'AVISO {pid}: {e}',file=sys.stderr)
if not imgs:
local=IMG/f'{pid}.svg';imgs=[str(local.relative_to(ROOT)).replace('\\','/')] if local.exists() else urls
items.append({'id':pid,'name':row['name'].strip(),'category':row.get('category','Otros').strip(),'price':n(row.get('price')),'compareAtPrice':n(row.get('compare_at_price')),'featured':b(row.get('featured')),'new':b(row.get('new')),'description':row.get('description','').strip(),'tags':[t.strip() for t in row.get('tags','').split('|') if t.strip()],'images':imgs,'stock':n(row.get('stock')),'available':b(row.get('available'),True),'source':{'type':'authorized-feed','name':row.get('source_name','Fuente autorizada'),'url':row.get('source_url','')}})
OUT.write_text(json.dumps({'version':'2.0.0','updatedAt':datetime.now(timezone.utc).isoformat(),'currency':'COP','products':items},ensure_ascii=False,indent=2)+'\n');print(f'OK: {len(items)} productos')
ROOT=Path(__file__).resolve().parents[1]
SOURCE=ROOT/'catalog-source.csv'
OUTPUT=ROOT/'data/products.json'
def boolean(v:str)->bool:return str(v).strip().lower() in {'1','true','yes','si',''}
def main()->int:
products=[]
with SOURCE.open(newline='',encoding='utf-8-sig') as handle:
for row in csv.DictReader(handle):
raw=(row.get('price') or '').strip()
price=int(float(raw)) if raw else None
products.append({
'id':row['id'].strip(),'name':row['name'].strip(),
'category':row['category'].strip(),'collection':row['collection'].strip(),
'description':row['description'].strip(),'dimensions':row['dimensions'].strip(),
'price':price,'priceText':'Consultar disponibilidad' if price is None else '',
'available':boolean(row['available']),'featured':boolean(row['featured']),
'new':boolean(row['new']),'stockText':'Disponible para confirmar',
'images':[row['image'].strip()],
'source':{'type':'instagram-export','account':'ari_shopping1','fbid':row['source_fbid'].strip()}
})
OUTPUT.write_text(json.dumps({'version':'2.1.0','updatedAt':datetime.now(timezone.utc).isoformat(),'currency':'COP','pricingMode':'consult','products':products},ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print(f'OK: {len(products)} productos escritos en {OUTPUT}')
return 0
if __name__=='__main__':raise SystemExit(main())