#!/usr/bin/env python3 """ Remedia brandbooks: elimina kits inventados y aplica solo señales verificadas desde webs oficiales (+ notas de procedencia). """ from __future__ import annotations import json import random import time import urllib.request from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] def env(k: str) -> str: for line in (ROOT / ".env.local").read_text().splitlines(): if line.startswith(k + "="): return line.split("=", 1)[1].strip().strip('"').strip("'") return "" URL = env("NEXT_PUBLIC_SUPABASE_URL") KEY = env("SUPABASE_SERVICE_ROLE_KEY") HR = {"apikey": KEY, "Authorization": f"Bearer {KEY}"} HW = {**HR, "Content-Type": "application/json", "Prefer": "return=minimal"} def get(path: str) -> Any: req = urllib.request.Request(f"{URL}/rest/v1/{path}", headers=HR) return json.loads(urllib.request.urlopen(req).read()) def patch(path: str, body: dict) -> None: req = urllib.request.Request( f"{URL}/rest/v1/{path}", data=json.dumps(body).encode(), headers=HW, method="PATCH", ) urllib.request.urlopen(req) def nid(p: str) -> str: return f"{p}_{int(time.time() * 1000)}_{random.randint(1000, 9999)}" def color(name: str, hex_: str, role: str, usage: str) -> dict: return { "id": nid("col"), "name": name, "hex": hex_.upper() if hex_.startswith("#") else hex_, "role": role, "usage": usage, } def font(family: str, role: str, source: str, notes: str) -> dict: return { "id": nid("font"), "family": family, "role": role, "weights": "", "source": source, "url_or_notes": notes, } # Solo colores con evidencia fuerte en HTML/CSS de la web oficial. # Si no hay evidencia clara → colors = [] (mejor vacío que inventado). VERIFIED_VISUAL: dict[str, dict[str, Any]] = { "4ventures-hub": { "colors": [ color( "Naranja marca (web)", "#E05200", "primary", "Frecuencia alta en CSS de https://4ventures.es/", ), ], "fonts_kit": [ font( "DM Sans", "headline", "google", "font-family detectado en HTML de 4ventures.es", ), font( "Cormorant Garamond", "display", "google", "font-family detectado en HTML de 4ventures.es", ), ], "source": "HTML/CSS https://4ventures.es/", }, "agua-miia": { "colors": [ color( "Azul primario", "#0085F2", "primary", "CSS var marca en https://aguamiia.es/", ), color( "Azul corporativo", "#2071CF", "secondary", "CSS var / uso frecuente en aguamiia.es", ), color( "Azul oscuro", "#0F4F9E", "neutral", "CSS var en aguamiia.es", ), color( "Verde acento", "#2E9B76", "accent", "CSS var en aguamiia.es", ), ], "fonts_kit": [ font( "Urbanist", "headline", "google", "font-family dominante en HTML de aguamiia.es", ), font( "Nunito", "body", "google", "font-family secundario en HTML de aguamiia.es", ), ], "source": "CSS variables + font-family https://aguamiia.es/", }, "bps": { # BPS ya tenía kit; reforzamos con theme-color oficial "colors": [ color( "BPS Blue", "#0051AA", "primary", "theme-color meta + uso en https://blueparrotschool.com/", ), color( "Accent amarillo", "#FDC526", "accent", "Hex frecuente en CSS de blueparrotschool.com", ), color( "Ink", "#201E22", "neutral", "Hex frecuente en CSS de blueparrotschool.com", ), ], "fonts_kit": [ font( "Figtree", "headline", "google", "Referenciado en HTML blueparrotschool.com", ), font( "Jost", "body", "google", "Referenciado en HTML blueparrotschool.com", ), ], "source": "theme-color + CSS https://blueparrotschool.com/", "merge_colors_if_existing": True, }, "brava-dermoestetica": { "colors": [ color( "Rosa/brandy clínica", "#7A3F4D", "primary", "Hex dominante (69×) en https://www.bravadermoestetica.com/", ), color( "Rosa oscuro", "#61323D", "secondary", "Hex frecuente en bravadermoestetica.com", ), color( "Fondo cálido", "#F8F5F2", "neutral", "Hex de fondo en bravadermoestetica.com", ), ], "fonts_kit": [ font( "Playfair Display", "headline", "google", "font-family en HTML bravadermoestetica.com", ), font( "Inter", "body", "google", "font-family en HTML bravadermoestetica.com", ), font( "Myriad Pro", "other", "adobe", "font-family referenciado en HTML (licencia Adobe/sistema)", ), ], "source": "HTML/CSS https://www.bravadermoestetica.com/", }, "delgado-abogados": { "colors": [ color( "Azul enlace/marca", "#1863DC", "primary", "Hex frecuente en https://abogadosdelgado.es/", ), color( "Ink", "#212121", "neutral", "Hex frecuente en abogadosdelgado.es", ), ], "fonts_kit": [ font( "Inter", "body", "google", "WP preset font-family Inter en abogadosdelgado.es", ), ], "source": "HTML/CSS https://abogadosdelgado.es/ (parcial; ampliar con brand kit PDF)", }, "dtatf": { "colors": [ color( "Amarillo De Tú a Tú", "#F6A800", "primary", "Hex en CSS/HTML https://detuatuformacion.es/", ), color( "Azul oscuro De Tú a Tú", "#273645", "secondary", "Hex en CSS/HTML detuatuformacion.es (+ #253645)", ), ], "fonts_kit": [ font( "Poppins-DeTUaTU", "headline", "file", "Familia custom referenciada en detuatuformacion.es (archivo/fuente propia)", ), ], "source": "HTML/CSS https://detuatuformacion.es/", }, "ecp": { "colors": [ color( "Amarillo ECP", "#F7A801", "primary", "Hex dominante en https://escueladecuidadoresprofesionales.com/", ), color( "Azul", "#1863DC", "secondary", "Hex frecuente en web ECP", ), color( "Ink", "#212121", "neutral", "Hex frecuente en web ECP", ), ], "fonts_kit": [ font( "Raleway", "headline", "google", "font-family dominante en web ECP", ), font( "Open Sans", "body", "google", "font-family secundario en web ECP", ), ], "source": "HTML/CSS https://escueladecuidadoresprofesionales.com/", }, "equinsa-parking": { "colors": [ color( "Navy Equinsa", "#000028", "primary", "Hex en https://equinsaparking.com/", ), color( "Azul", "#003388", "secondary", "Hex en equinsaparking.com", ), color( "Azul enlace", "#046BD2", "accent", "CSS var / hex en equinsaparking.com", ), ], "fonts_kit": [ font( "Roboto", "body", "google", "font-family dominante en equinsaparking.com", ), ], "source": "HTML/CSS https://equinsaparking.com/", }, "fbm-abogados": { "colors": [ color( "Navy FBM", "#04243C", "primary", "Hex dominante (75×) en https://fbmasociados.com/", ), ], "fonts_kit": [], # tahoma genérico — no afirmar kit de marca "source": "HTML/CSS https://fbmasociados.com/ (color). Tipografía: pendiente brand kit.", }, "halal-safety": { "colors": [], # scrape débil / ruido WP "fonts_kit": [ font( "Manrope", "body", "google", "Referenciado en HTML halalsafety.com", ), ], "source": "Tipografía HTML https://www.halalsafety.com/. Colores: insuficientes en scrape — vacíos a propósito.", }, "la-freseria": { "colors": [], # el scrape pilló azules de template Framer, NO son marca fresa "fonts_kit": [ font( "La Fresería", "display", "file", "Familia custom 'La Fresería' referenciada en lafreseria.es (archivo propio)", ), ], "source": "Fuentes custom en https://lafreseria.es/. Colores NO aplicados: el HTML mezcla tokens Framer (#046bd2) no confiables como marca.", }, "noberplast": { "colors": [ color( "Azul Noberplast", "#0072AB", "primary", "Hex en https://noberplast.com/", ), color( "Verde", "#06A161", "secondary", "Hex en noberplast.com", ), color( "Rojo acento", "#DB062C", "accent", "Hex en noberplast.com", ), ], "fonts_kit": [], "source": "Hex en https://noberplast.com/. Tipografía: no detectada con claridad.", }, "tsp": { "colors": [ color( "Navy Scholar", "#161B2F", "primary", "Hex frecuente en https://talentoscholarprogram.com/", ), color( "Azul claro", "#A4CAEF", "secondary", "Hex frecuente en talentoscholarprogram.com", ), color( "Rojo acento", "#A40406", "accent", "Hex en talentoscholarprogram.com", ), ], "fonts_kit": [ font( "Poppins", "body", "google", "font-family dominante en talentoscholarprogram.com", ), font( "Fraunces", "display", "google", "font-family en talentoscholarprogram.com", ), ], "source": "HTML/CSS https://talentoscholarprogram.com/", }, "tyd": { "colors": [ color( "Rojo TYD", "#E92428", "primary", "Hex dominante en https://talentoydeporte.com/", ), color( "Azul TYD", "#004885", "secondary", "Hex en talentoydeporte.com", ), ], "fonts_kit": [ font( "Montserrat", "headline", "google", "font-family en talentoydeporte.com", ), font( "Oswald", "display", "google", "font-family referenciado en talentoydeporte.com", ), ], "source": "HTML/CSS https://talentoydeporte.com/", }, "virginia-molet": { "colors": [ color( "Azul Gravitae", "#004271", "primary", "Hex frecuente en https://gravitaeacademy.com/", ), color( "Azul enlace", "#1863DC", "secondary", "Hex frecuente en gravitaeacademy.com (posible token WP; contrastar con kit)", ), ], "fonts_kit": [ font( "quincy-cf", "display", "adobe", "font-family 'quincy-cf' en gravitaeacademy.com", ), font( "Inter", "body", "google", "font-family Inter en gravitaeacademy.com", ), ], "source": "HTML/CSS https://gravitaeacademy.com/ (revisar azul #1863DC vs kit oficial)", }, "wine-advisors": { "colors": [ color( "Rojo WineAdvisor", "#CC0100", "primary", "Hex detectado en https://www.wineadvisor.mx/", ), ], "fonts_kit": [], "source": "Hex limitado en https://www.wineadvisor.mx/. Tipografía: no detectada.", }, } PROVENANCE_HEADER = ( "[PROCEDENCIA 2026-10-15] " "Webs/handles/LinkedIn: contrastados con sitio oficial + Meta business_discovery + páginas LinkedIn. " "Colores/fuentes: solo si aparecen en HTML/CSS de la web (ver visual_notes). " "Textos de posicionamiento/tono/ICP: síntesis de brand manager a partir del copy público de la web " "(no son citas literales del cliente ni de un PDF de marca). " "Si un campo no tiene evidencia → vacío. " ) def parse(c: Any) -> dict: if isinstance(c, str): return json.loads(c) return dict(c or {}) def main() -> None: tenants = get( "tenants?agency_followed=neq.false&select=id,slug,name&order=slug" ) for t in tenants: slug = t["slug"] docs = get( f"documents?tenant_id=eq.{t['id']}&type=eq.brandbook&select=id,content&order=updated_at.desc&limit=1" ) if not docs: print("skip", slug) continue book = parse(docs[0]["content"]) vis = VERIFIED_VISUAL.get(slug) if vis is None: # no verified visual → clear invented kits book["colors"] = [] book["fonts_kit"] = [] book["colors_primary"] = [] book["colors_secondary"] = [] book["fonts"] = [] note = "Sin señales visuales verificables en scrape; kit vaciado." else: book["colors"] = vis.get("colors") or [] book["fonts_kit"] = vis.get("fonts_kit") or [] book["colors_primary"] = [ c["hex"] for c in book["colors"] if c.get("role") == "primary" ] book["colors_secondary"] = [ c["hex"] for c in book["colors"] if c.get("role") in ("secondary", "accent") ] book["fonts"] = [f["family"] for f in book["fonts_kit"]] if book["fonts_kit"]: heads = [f for f in book["fonts_kit"] if f["role"] in ("headline", "display")] bodies = [f for f in book["fonts_kit"] if f["role"] == "body"] if heads: book["typography_headline"] = heads[0]["family"] if bodies: book["typography_body"] = bodies[0]["family"] note = vis.get("source") or "" # Visual notes: honest provenance book["visual_notes"] = ( f"PROCEDENCIA VISUAL: {note}. " "Los hex/fuentes listados salen del HTML/CSS público. " "Sustituir por brand kit oficial (PDF/AI) cuando esté en Drive/Zona de marca. " "NO usar paletas inventadas por IA." ) # Provenance in freeform prev = (book.get("freeform_notes") or "").strip() # remove older provenance blocks to avoid duplication lines = [ ln for ln in prev.split("\n") if not ln.startswith("[PROCEDENCIA") and "PROCEDENCIA VISUAL" not in ln ] book["freeform_notes"] = ( PROVENANCE_HEADER + (f"Visual: {note}. " if note else "") + ("\n" + "\n".join(lines) if lines else "") ).strip() # Clear weak invented claims that often aren't on-site # Keep website/handles/products — those were verified in packs # Soften proof_points that cite unverified stats if slug == "la-freseria": # don't keep invented franchise € figures as hard fact without date stamp pps = book.get("proof_points") or [] book["proof_points"] = [ p for p in pps if "65.000" not in p and "EBITDA" not in p ] + [ "Claim web/franquicia: 'las fresas más famosas del mundo' (lafreseria.es)", "IG oficial @lafreseriaoficial", "LinkedIn company/la-freseria", ] if slug == "equinsa-parking": # keep qualitative; avoid conflicting installation counts book["proof_points"] = [ "Web: especializada en sistemas de gestión de aparcamientos (equinsaparking.com)", "LinkedIn company/equinsa-parking", "Sede comunicada: C. Primavera 16, Torrejón de Ardoz (web)", "Contacto web: sat@equinsa.es / +34 618 18 89 24", ] patch(f"documents?id=eq.{docs[0]['id']}", {"content": book}) n_col = len(book.get("colors") or []) n_font = len(book.get("fonts_kit") or []) print(f"[ok] {slug:22} colors={n_col} fonts={n_font} · {note[:80]}") print("DONE — invented kits removed; verified web signals applied") if __name__ == "__main__": main()