Asesor de belleza basado en los principios de los ingredientes INCI — Práctica de recomendación de cosméticos personalizados mediante la regla de orden por concentración
La nomenclatura de ingredientes cosméticos (INCI) se enumera, en la mayoría de los países, por orden de concentración. Esta única regulación proporciona una señal poderosa. Los primeros 3 a 5 ingredientes determinan, de hecho, la identidad de ese producto; para concentraciones inferiores al 1%, el orden es libre, pero la presencia de cualquier ingrediente es clara. Este capítulo construye de forma práctica una herramienta de recomendación de cosméticos personalizada utilizando datos abiertos y técnicas de IA, aprovechando este principio regulatorio. El usuario de ejemplo es una persona real con el perfil: "mujer de 35 años, piel grasa, desea mejorar acné y poros, primer uso de retinol, planea un embarazo". Para evitar las regulaciones de privacidad de datos, se excluye la información genética (SNP), y se combinan embeddings de ingredientes, CNN para lesiones faciales, reglas de conflicto y resúmenes mediante LLM.
📚 Recomendación para principiantes (Altamente recomendado)
Este capítulo es una profundización avanzada de IA × Biotecnología. Antes de comenzar, se recomienda encarecidamente haber visto o escuchado los siguientes capítulos de DryBench:
- DryBench ai-native #8 RAG y contexto
- DryBench ai-native #9 Agentes y uso de herramientas
- DryBench ai-native #13 HuggingFace y APIs comerciales
Si se inicia sin los capítulos previos, será difícil seguir el ritmo, ya que procederemos directamente al código práctico sin reexplicar los principios de búsqueda RAG, orquestación de herramientas (tool orchestration), carga de modelos de embedding o segmentación por CNN tratados en este capítulo.
Lo que ya aprendimos en DryBench
En DryBench ai-native #8, aprendimos que RAG es un enfoque que añade conocimiento externo a los parámetros (documentos, bases de datos) al contexto como resultado de una búsqueda; en el #9, que un agente puede llamar autónomamente a múltiples herramientas para procesar tareas comple de complejidad; y en el #13, que HuggingFace proporciona modelos de visión y embeddings químicos mediante APIs estandarizadas.
La belleza es un dominio donde estos tres principios se combinan de forma sorprendentemente natural. La base de datos de ingredientes cosméticos actúa como la fuente de conocimiento de RAG, el LLM actúa como mediador en lenguaje natural entre la piel/preferencias del usuario y la información de los ingredientes, y los embeddings de huellas químicas (chemical fingerprints) permiten la búsqueda KNN de ingredientes similares. Este capítulo es la canalización (pipeline) para convertir esa combinación en una herramienta práctica, abordando incluso un diseño de compromiso que evita riesgos regulatorios y de privacidad sin perder la utilidad.
Definición del problema avanzado
Persona de usuario real
Especificamos los requisitos del pipeline utilizando un ejemplo de persona.
- Edad: Mujer de 35 años.
- Tipo de piel: Grasa (especialmente en la zona T).
- Objetivos: controlar el acné, reducir la apariencia de los poros y tratar los primeros signos de envejecimiento.
- Historial de uso: niacinamida durante 3 meses, con efecto favorable; BHA durante 1 mes, con irritación.
- Producto nuevo considerado: sérum de retinol para principiantes.
- Consideraciones especiales: planificación de embarazo, por lo que deben evitarse los retinoides; alergia al ácido salicílico.
- Presupuesto: se priorizan ingredientes y propiedades, independientemente de la marca.
Incluso con esta única persona, la canalización debe resolver numerosas tareas.
- Enumerar productos candidatos con retinol, retinal o palmitato de retinilo entre los cinco primeros ingredientes INCI.
- Como existe planificación de embarazo, los retinoides deben evitarse; recomendar alternativas vegetales como el bakuchiol.
- La alergia declarada a BHA o ácido salicílico exige excluir inmediatamente los productos que lo contengan.
- Como la persona continúa usando niacinamida, priorizar productos con ingredientes complementarios.
- Para piel grasa y control del acné, evitar ingredientes potencialmente comedogénicos o que puedan favorecer la seborrea, como determinados lípidos y el aceite de coco.
Estructura de información derivada de los principios regulatorios
Principios fundamentales del etiquetado INCI de cosméticos:
- UE (Reglamento CE 1223/2009): los ingredientes se enumeran por cantidad; los presentes en una concentración igual o inferior al 1% pueden aparecer en cualquier orden. Se indican los alérgenos regulados [1].
- EE. UU. (21 CFR 701): orden por cantidad; el orden es libre por debajo del 1%; los colorantes se declaran por separado [2].
- Corea (Ley de Cosméticos y MFDS): orden por cantidad; el orden es libre por debajo del 1%; requisitos de alérgenos armonizados en gran medida con la UE tras la revisión de 2020 [3].
- Japón (normas de etiquetado de cuasifármacos y cosméticos): declaración de todos los ingredientes en orden de cantidad [4].
Idea clave: el orden de los ingredientes contiene información. En particular, los cinco primeros definen en gran medida la identidad del producto. Este principio permite:
- Identificar activos principales: comprobar si aparecen ácido hialurónico, niacinamida u otros activos entre los primeros ingredientes.
- Distinguir vehículo de activos relevantes: si los primeros puestos contienen únicamente agua, glicerina y butilenglicol, predomina la base de la formulación.
- Detectar combinaciones problemáticas: por ejemplo, retinoides con AHA/BHA, que pueden aumentar la irritación, o vitamina C con retinol, cuya compatibilidad depende de la formulación y del régimen de uso.
Alcance práctico de esta unidad
- Recopilar información de 50.000–100.000 cosméticos desde una base de datos abierta como Open Beauty Facts.
- Representar cada ingrediente INCI mediante una huella química con RDKit ECFP.
- Introducir el perfil cutáneo del usuario, los ingredientes preferidos y los que deben evitarse.
- Opcionalmente, segmentar lesiones en una imagen facial mediante una CNN y asociar ingredientes de interés por región.
- El motor de reglas advierte sobre ingredientes conflictivos, alergias e ingredientes a evitar durante el embarazo.
- Claude LLM proporciona una explicación en lenguaje natural para las 3 recomendaciones finales.
Principios de Regulación y Privacidad (Estricto cumplimiento):
- No se utiliza información genética SNP · genotype (evitando datos sensibles según la Ley de Protección de Información Personal de KR · EU GDPR Art.9).
- Exclusión de promoción o desprestigio de marcas específicas. Solo ingredientes, propiedades físicas y etiquetas abiertas.
- Principio de procesamiento local para imágenes faciales (evitando la transmisión a APIs externas). Consentimiento del usuario explícito.
- Especificar que no es asesoramiento médico (es obligatorio incluir la frase de recomendación de consulta con farmacécus, médicos o dermatólogos).
- La información sobre embarazo, alergias o enfermedades específicas es de entrada autónoma del usuario, sin perfilado.
Stack de herramientas y requisitos de infraestructura
| Herramienta | Rol | Licencia |
|---|---|---|
| Open Beauty Facts (Datos abiertos) | Ingredientes de cosméticos · marcas · códigos de barras | ODbL |
| CosIng (Comisión UE) | Diccionario estándar INCI | Público |
| RDKit | Huella química (ECFP · MACCS) | BSD-3-Clause |
| HuggingFace transformers | CNN facial (Segformer, etc.) | Apache 2.0 |
| Claude API | Resumen de recomendaciones en lenguaje natural | Comercial |
| FAISS · scikit-learn | KNN de ingredientes similares · clustering | MIT · BSD |
| FastAPI + Streamlit (Opcional) | Servicialización | MIT · Apache 2.0 |
| PubChem REST | Mapeo CAS → SMILES | Público |
Requisitos de infraestructura:
- GPU de consumo pequeña (inferencia de CNN facial). El fallback a CPU es posible, pero el procesamiento de imágenes es 10x más lento.
- RAM 8GB o superior (índice de embeddings de ingredientes).
- Disco: Subconjunto de Open Beauty Facts aprox. 500MB, modelo CNN aprox. 100MB.
Coste estimado de reproducción para el estudiante: Basado en la tarifa de Claude API [5], entre 5 y 15 centavos por sesión de usuario. Datos abiertos · CosIng son gratuitos.
Implementación práctica del pipeline
Flujo completo:
Paso 1. Extracción y análisis de INCI de Open Beauty Facts
Open Beauty Facts es un conjunto de datos abiertos basado en la contribución de la comunidad [6].
from dataclasses import dataclass, fieldimport gzipimport jsonfrom pathlib import Pathimport re
import requests
OBF_DUMP_URL = "https://static.openbeautyfacts.org/data/openbeautyfacts-products.jsonl.gz"
@dataclassclass Product: barcode: str brand: str name: str ingredients_raw: str inci_list: list[str] # Lista de ingredientes ordenada por concentración categories: list[str] = field(default_factory=list) country: str = "" image_url: str = ""
def download_obf_dump(dest: Path) -> Path: """Descarga el volcado JSON de Open Beauty Facts (unos 500 MB en gzip).""" dest.parent.mkdir(parents=True, exist_ok=True) if dest.exists() and dest.stat().st_size > 100_000_000: return dest print(f"Descargando... {OBF_DUMP_URL}") with requests.get(OBF_DUMP_URL, stream=True, timeout=600) as r: r.raise_for_status() with open(dest, "wb") as f: for chunk in r.iter_content(chunk_size=8192 * 1024): f.write(chunk) return dest
def parse_inci(ingredients_raw: str) -> list[str]: """Analiza el texto INCI sin procesar como una lista ordenada por concentración.
- Separa por comas, punto y coma y puntos. - Los paréntesis contienen alias o números CI (por ejemplo, "WATER" en "AQUA (WATER)"). - Elimina el contenido entre corchetes (por ejemplo, la notación nano). - Conserva las etiquetas con asterisco de ingredientes alergénicos. """ if not ingredients_raw: return [] # Normalización y tratamiento de paréntesis y corchetes parts = re.split(r"[,;·]", ingredients_raw) cleaned = [] for p in parts: # Elimina corchetes (por ejemplo, "[nano]") p = re.sub(r"\[.*?\]", "", p) # Trata el alias entre paréntesis (aquí se elimina del nombre principal) p = re.sub(r"\(.*?\)", "", p) p = p.strip().upper() # Decide si conservar etiquetas especiales como asteriscos (**) o virgulillas (~) if p and len(p) > 1 and len(p) < 100: cleaned.append(p) return cleaned
def iter_cosmetics(dump_path: Path, min_ingredients: int = 3): """Procesa el volcado OBF en streaming y filtra por número mínimo de ingredientes.""" with gzip.open(dump_path, "rt", encoding="utf-8") as f: for line in f: try: d = json.loads(line) except json.JSONDecodeError: continue if not d.get("ingredients_text"): continue inci = parse_inci(d.get("ingredients_text", "")) if len(inci) < min_ingredients: continue yield Product( barcode=d.get("code", ""), brand=(d.get("brands") or "").split(",")[0].strip(), name=d.get("product_name", ""), ingredients_raw=d.get("ingredients_text", ""), inci_list=inci, categories=d.get("categories_tags", []), country=d.get("countries", ""), image_url=d.get("image_url", ""), )Paso 2. Coincidencia con CosIng y normalización de ingredientes
Los nombres INCI tienen muchas variaciones en su forma de escritura. Se utiliza CosIng como diccionario para normalizarlos [7]. Es imprescindible incluir una opción de coincidencia parcial como alternativa.
import csv
@dataclassclass CosingEntry: inci_name: str cas: str einecs: str function: str # e.g. "Skin conditioning · Emollient" restriction: str # Regulación de la UE (por ejemplo, "Concentration limit 0.5%") allergen_flag: bool = False
def load_cosing_dict(cosing_csv: Path) -> dict[str, CosingEntry]: """Convierte el CSV de CosIng en un diccionario {nombre_INCI_en_mayúsculas: CosingEntry}.""" result: dict[str, CosingEntry] = {} with open(cosing_csv, encoding="utf-8", errors="ignore") as f: reader = csv.DictReader(f) for row in reader: key = (row.get("INCI name") or "").upper().strip() if not key: continue result[key] = CosingEntry( inci_name=key, cas=row.get("CAS #", "").strip(), einecs=row.get("EINECS/ELINCS #", "").strip(), function=row.get("Function", "").strip(), restriction=row.get("Restriction", "").strip(), allergen_flag="allergen" in row.get("Restriction", "").lower(), ) return result
def normalize_inci(raw_name: str, cosing_dict: dict[str, CosingEntry]) -> str | None: """Normaliza con el diccionario CosIng y usa coincidencia parcial como alternativa.""" upper = raw_name.upper().strip() if upper in cosing_dict: return upper # Coincidencia parcial (por ejemplo, "AQUA (WATER)" → "AQUA") for key in cosing_dict: if key in upper or upper in key: return key # Alternativa: coincidencia difusa (distancia de Levenshtein) return NonePaso 3. Incrustación de la huella química de los ingredientes (Chemical Fingerprint)
La mayoría de los ingredientes INCI pueden consultarse mediante su estructura química utilizando el número CAS. Se calcula la huella dactilar (fingerprint) con RDKit.
from rdkit import Chem, RDLoggerfrom rdkit.Chem import AllChemimport numpy as np
RDLogger.DisableLog("rdApp.*")
def cas_to_smiles(cas_number: str, cache: dict | None = None) -> str | None: """CAS → SMILES mediante la API REST de PubChem [8]. Se recomienda usar caché para evitar repeticiones.""" if not cas_number: return None if cache and cas_number in cache: return cache[cas_number] url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/{cas_number}/property/CanonicalSMILES/JSON" try: resp = requests.get(url, timeout=15) if resp.status_code != 200: if cache is not None: cache[cas_number] = None return None data = resp.json() smiles = data["PropertyTable"]["Properties"][0]["CanonicalSMILES"] if cache is not None: cache[cas_number] = smiles return smiles except (requests.RequestException, KeyError): if cache is not None: cache[cas_number] = None return None
def compute_ecfp(smiles: str, radius: int = 2, n_bits: int = 2048) -> np.ndarray: """Calcula ECFP (Extended Connectivity Fingerprint), una huella de Morgan.""" mol = Chem.MolFromSmiles(smiles) if mol is None: return np.zeros(n_bits, dtype=np.uint8) fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius, nBits=n_bits) return np.array(fp, dtype=np.uint8)
def build_ingredient_embeddings( cosing_dict: dict[str, CosingEntry], cache_path: Path, sleep_between: float = 0.2, # Cortesía con PubChem: como máximo 5 solicitudes por segundo) -> dict[str, np.ndarray]: """Crea un diccionario ingrediente INCI → embedding ECFP usando la caché de PubChem.""" import time if cache_path.exists(): return np.load(cache_path, allow_pickle=True).item()
embeddings: dict[str, np.ndarray] = {} smiles_cache: dict[str, str | None] = {} for i, (inci_name, info) in enumerate(cosing_dict.items()): if i % 100 == 0: print(f"[{i}/{len(cosing_dict)}] calculando fingerprints") smiles = cas_to_smiles(info.cas, cache=smiles_cache) if smiles: embeddings[inci_name] = compute_ecfp(smiles) time.sleep(sleep_between) # Respeta el límite de solicitudes de PubChem
np.save(cache_path, embeddings, allow_pickle=True) return embeddingsPaso 4. Motor de reglas de incompatibilidad, regulación y alergias
Integra las combinaciones incompatibles conocidas de la dermatología y la química cosmética + los 26 alérgenos de la UE + los ingredientes que deben evitarse en el embarazo.
from typing import Literal, NamedTuple
Severity = Literal["info", "warn", "avoid"]
class Rule(NamedTuple): kind: str # "conflict" · "allergen" · "pregnancy" · "user_avoid" ingredient_a: str ingredient_b: str | None # None si la regla afecta a un solo ingrediente severity: Severity reason: str reference: str context: dict[str, str] = {}
CONFLICT_RULES: list[Rule] = [ Rule( kind="conflict", ingredient_a="RETINOL", ingredient_b="GLYCOLIC ACID", severity="warn", reason="El uso simultáneo de retinoides y AHA puede causar irritación y eritema. Se recomienda separarlos entre la noche y la mañana.", reference="AAD (American Academy of Dermatology) retinoid guidelines", ), Rule( kind="conflict", ingredient_a="RETINOL", ingredient_b="SALICYLIC ACID", severity="warn", reason="El uso simultáneo de retinoides y BHA puede causar irritación y una exfoliación excesiva.", reference="Journal of the American Academy of Dermatology", ), Rule( kind="conflict", ingredient_a="RETINOL", ingredient_b="ASCORBIC ACID", severity="warn", reason="El retinol y la vitamina C pura tienen requisitos de pH incompatibles (retinol 5,5; vitamina C 3,5), con posibles problemas de estabilidad e irritación.", reference="Skin Therapy Letter, vitamin C stability review", ), Rule( kind="conflict", ingredient_a="BENZOYL PEROXIDE", ingredient_b="RETINOL", severity="avoid", reason="El BPO oxida y degrada el retinol, lo que reduce su efecto y puede irritar la piel.", reference="Journal of Drugs in Dermatology", ), Rule( kind="conflict", ingredient_a="NIACINAMIDE", ingredient_b="ASCORBIC ACID", severity="info", reason="Existe la hipótesis de que su uso conjunto a altas concentraciones genera ácido nicotínico; los estudios recientes no suelen observar problemas, aunque sigue siendo controvertido.", reference="Cosmetic Dermatology reviews · 2024 update", ),]
PREGNANCY_AVOID: list[Rule] = [ Rule( kind="pregnancy", ingredient_a=name, ingredient_b=None, severity="avoid", reason=f"Se recomienda evitar {name} durante el embarazo por evidencia de riesgo teratógeno o como medida preventiva.", reference="ACOG (American College of Obstetricians and Gynecologists) guidelines", ) for name in [ "RETINOL", "RETINALDEHYDE", "RETINYL PALMITATE", "TRETINOIN", "ADAPALENE", "TAZAROTENE", # Familia de retinoides "SALICYLIC ACID", # BHA en alta concentración "HYDROQUINONE", # Despigmentante ]]
EU_ALLERGENS_26 = [ "LIMONENE", "LINALOOL", "CITRAL", "GERANIOL", "EUGENOL", "COUMARIN", "CITRONELLOL", "FARNESOL", "BENZYL ALCOHOL", "BENZYL BENZOATE", "BENZYL SALICYLATE", "BENZYL CINNAMATE", "CINNAMAL", "CINNAMYL ALCOHOL", "AMYLCINNAMAL", "AMYLCINNAMYL ALCOHOL", "HEXYL CINNAMAL", "HYDROXYCITRONELLAL", "HYDROXYISOHEXYL 3-CYCLOHEXENE CARBOXALDEHYDE", "ISOEUGENOL", "METHYL 2-OCTYNOATE", "ANISYL ALCOHOL", "ALPHA-ISOMETHYL IONONE", "EVERNIA PRUNASTRI EXTRACT", "EVERNIA FURFURACEA EXTRACT", "3-P-CUMENYL-2-METHYLPROPIONALDEHYDE",]
def check_all_rules( inci_list: list[str], user_profile: dict,) -> list[Rule]: """Compara los ingredientes del producto con las reglas y devuelve las aplicables.""" triggered = [] upper_set = {name.upper() for name in inci_list}
# 1. Combinaciones incompatibles for rule in CONFLICT_RULES: if rule.ingredient_a in upper_set and (rule.ingredient_b or "") in upper_set: triggered.append(rule)
# 2. Planificación del embarazo if user_profile.get("pregnancy_planning", False): for rule in PREGNANCY_AVOID: if rule.ingredient_a in upper_set: triggered.append(rule)
# 3. Alergias e ingredientes que el usuario evita for avoid in user_profile.get("allergies", []) + user_profile.get("avoid_ingredients", []): if avoid.upper() in upper_set: triggered.append(Rule( kind="user_avoid", ingredient_a=avoid.upper(), ingredient_b=None, severity="avoid", reason=f"Indicación del usuario: evitar {avoid} o alergia declarada.", reference="user profile", ))
# 4. Los 26 alérgenos de la UE (nivel informativo) for allergen in EU_ALLERGENS_26: if allergen in upper_set: triggered.append(Rule( kind="allergen", ingredient_a=allergen, ingredient_b=None, severity="info", reason="Incluido en la lista de 26 alérgenos de la UE; su presencia no implica necesariamente una reacción alérgica.", reference="EU Regulation (EC) 1223/2009 Annex III", ))
return triggeredPaso 5. CNN de segmentación de lesiones faciales (opcional)
Cuando el usuario proporciona una imagen facial, se segmentan las lesiones (eritema, acné, zonas secas).
import torchfrom transformers import SegformerImageProcessor, SegformerForSemanticSegmentation
class FacialLesionSegmenter: """Segmentación conceptual de lesiones faciales basada en SegFormer.
En producción se recomienda un modelo ajustado para afecciones cutáneas, por ejemplo con un benchmark dermatológico ISIC. Este ejemplo es una referencia entrenada con imágenes naturales; para un servicio real es imprescindible volver a entrenarlo. """
MODEL_ID = "nvidia/segformer-b0-finetuned-ade-512-512" # Ejemplo; en producción debe ser específico para la piel
def __init__(self, device: str = "cuda"): self.device = device self.processor = SegformerImageProcessor.from_pretrained(self.MODEL_ID) self.model = SegformerForSemanticSegmentation.from_pretrained(self.MODEL_ID).to(device).eval()
@torch.no_grad() def segment(self, image: np.ndarray) -> dict: """Convierte una imagen RGB en un diccionario de máscaras de lesiones.""" inputs = self.processor(images=image, return_tensors="pt").to(self.device) outputs = self.model(**inputs) logits = outputs.logits upsampled = torch.nn.functional.interpolate( logits, size=image.shape[:2], mode="bilinear", align_corners=False ) pred_mask = upsampled.argmax(dim=1).squeeze().cpu().numpy() return { "mask": pred_mask, "class_ratios": { int(cls): float((pred_mask == cls).mean()) for cls in np.unique(pred_mask) }, }
LESION_TO_INGREDIENT_HINTS = { "acne": ["SALICYLIC ACID", "BENZOYL PEROXIDE", "NIACINAMIDE", "ZINC PCA"], "erythema": ["CENTELLA ASIATICA EXTRACT", "MADECASSOSIDE", "PANTHENOL"], "dryness": ["HYALURONIC ACID", "GLYCERIN", "CERAMIDE NP", "SQUALANE"], "hyperpigmentation": ["NIACINAMIDE", "ALPHA-ARBUTIN", "TRANEXAMIC ACID", "AZELAIC ACID"],}Paso 6. Recomendación de lenguaje natural con Claude LLM
Texto regulatorio importante (según se indica en el cuerpo del documento): El modelo CNN anterior es una referencia para el aprendizaje de imágenes naturales. Para el servicio real, es necesario un modelo ajustado específicamente para la dermatología. Además, no es un diagnóstico (para evitar la regulación de dispositivos médicos y se debe indicar al usuario).
import anthropic
RECOMMEND_PROMPT = """Eres un asistente experto en ingredientes cosméticos y regulación.Revisa el perfil del usuario y los productos candidatos y recomienda los tres mejores en lenguaje natural.
Perfil del usuario:- Tipo de piel: {skin_type}- Ingredientes de interés: {preferred}- Ingredientes que evita y alergias: {avoid}- Planificación del embarazo: {pregnancy}- Historial de uso: {history}
Productos candidatos (cinco primeros ingredientes INCI):{products_formatted}
Reglas detectadas (incompatibilidades, alergias y exclusiones durante el embarazo):{rules_formatted}
Principios:1. No favorezcas ni desacredites marcas concretas. Basa la respuesta únicamente en ingredientes, propiedades y reglas.2. Justifica brevemente cada recomendación (3–5 frases): qué ingredientes principales responden al objetivo del usuario y cómo se gestionaron las reglas detectadas.3. Incluye siempre al final: "Esta información es solo de referencia y no constituye asesoramiento médico. Para problemas de piel, consulta a un dermatólogo".4. Si el usuario planea un embarazo y hay productos con retinoides entre los candidatos, recomienda evitarlos y sugiere alternativas como el bakuchiol.
Formato de respuesta: Markdown."""
def format_products(products: list[dict]) -> str: lines = [] for i, p in enumerate(products, 1): top5 = ", ".join(p["inci_list"][:5]) lines.append(f"{i}. {p['brand']} / {p['name']}\n Cinco ingredientes principales: {top5}") return "\n".join(lines)
def format_rules(rules: list[Rule]) -> str: if not rules: return "No se detectaron reglas." lines = [] for r in rules: if r.ingredient_b: pair = f"{r.ingredient_a} × {r.ingredient_b}" else: pair = r.ingredient_a lines.append(f"- [{r.severity}] {pair}: {r.reason} (fuente: {r.reference})") return "\n".join(lines)
def generate_recommendation( user_profile: dict, top_products: list[dict], all_rules: list[Rule], model: str = "claude-sonnet-4-5",) -> str: """Genera con Claude el resumen final de recomendaciones en lenguaje natural.""" client = anthropic.Anthropic() prompt = RECOMMEND_PROMPT.format( skin_type=user_profile.get("skin_type", ""), preferred=", ".join(user_profile.get("preferred", [])), avoid=", ".join(user_profile.get("avoid_ingredients", []) + user_profile.get("allergies", [])), pregnancy="sí" if user_profile.get("pregnancy_planning", False) else "no", history="; ".join(user_profile.get("history", [])), products_formatted=format_products(top_products), rules_formatted=format_rules(all_rules), ) resp = client.messages.create( model=model, max_tokens=2048, messages=[{"role": "user", "content": prompt}], ) return resp.content[0].textPaso 7. Ejemplo de ejecución del pipeline integrado · Persona
def full_advisor_pipeline( user_profile: dict, face_image: np.ndarray | None, product_db: list[Product], ingredient_embeddings: dict[str, np.ndarray], top_k: int = 10,) -> dict: """Perfil del usuario e imagen facial → productos recomendados, explicación y reporte de reglas.""" # 1. Promedio de embeddings de los ingredientes preferidos por el usuario preferred = [p.upper() for p in user_profile.get("preferred", [])] pref_embs = [ingredient_embeddings[p] for p in preferred if p in ingredient_embeddings] user_emb = np.mean(pref_embs, axis=0) if pref_embs else None
# 2. Combina ingredientes evitados, alergias y exclusiones durante el embarazo hard_avoid = {a.upper() for a in user_profile.get("avoid_ingredients", []) + user_profile.get("allergies", [])} if user_profile.get("pregnancy_planning", False): for rule in PREGNANCY_AVOID: hard_avoid.add(rule.ingredient_a)
# 3. Puntuación de candidatos scored = [] for prod in product_db: top5 = {i.upper() for i in prod.inci_list[:10]} # Excluye de inmediato si contiene un ingrediente estrictamente evitado if hard_avoid & top5: continue top5_embs = [ ingredient_embeddings[i] for i in prod.inci_list[:5] if i in ingredient_embeddings ] if not top5_embs: continue prod_emb = np.mean(top5_embs, axis=0) if user_emb is not None: similarity = float(np.dot(prod_emb, user_emb) / ( np.linalg.norm(prod_emb) * np.linalg.norm(user_emb) + 1e-8 )) else: similarity = 0.5 scored.append({ "brand": prod.brand, "name": prod.name, "inci_list": prod.inci_list, "score": similarity, "categories": prod.categories, "image_url": prod.image_url, })
# 4. Ordena los K mejores scored.sort(key=lambda x: -x["score"]) top_products = scored[:top_k]
# 5. Comprueba las reglas all_rules = [] for prod in top_products: all_rules.extend(check_all_rules(prod["inci_list"], user_profile))
# 6. Analiza las lesiones faciales si hay una imagen lesion_info = None if face_image is not None: segmenter = FacialLesionSegmenter() lesion_info = segmenter.segment(face_image)
# 7. Recomendación en lenguaje natural con Claude recommendation = generate_recommendation(user_profile, top_products, all_rules)
return { "recommended_products": top_products, "triggered_rules": [r._asdict() for r in all_rules], "lesion_analysis": lesion_info, "recommendation_text": recommendation, "disclaimer": "Esta información es solo de referencia y no constituye asesoramiento médico. Para problemas de piel, consulta a un dermatólogo.", }
# Ejemplo de ejecución (escenario de persona)# user = {# "skin_type": "oily · acne-prone",# "preferred": ["NIACINAMIDE", "BAKUCHIOL", "ZINC PCA"],# "avoid_ingredients": ["ALCOHOL DENAT"],# "allergies": ["SALICYLIC ACID"],# "pregnancy_planning": True,# "history": ["La niacinamida funcionó durante 3 meses", "El BHA causó irritación durante 1 mes"],# }# result = full_advisor_pipeline(user, face_image=None, product_db=[...], ingredient_embeddings={...})# print(result["recommendation_text"])Rendimiento · Costo · Casos de fallo conocidos
Referencia de rendimiento (citando evidencia pública)
Debido a la naturaleza de los datos abiertos, este informe carece de un "gold standard" para un benchmark completo, pero se presenta la evidencia pública por cada componente:
| Componente | Bench | Métrica | Fuente |
|---|---|---|---|
| Huella dactilar ECFP | Similitud ChEMBL | Correlación de similitud química r ≈ 0.75 | Rogers & Hahn 2010 [9] |
| Segformer (imagen natural) | ADE20K | mIoU 0.65 | Xie et al., NeurIPS 2021 [10] |
| Segformer (ajustado para piel) | Bench ISIC | Dice 0.80~0.85 | Bench de ajuste comunitario [11] |
| Resumen Claude | Sin benchmark | Requiere pruebas A/B de preferencia de usuario | — |
| Consistencia de datos Open Beauty Facts | Verificación propia | Tasa de error en nomenclatura INCI ~15% | Reporte de la comunidad OBF [6] |
Costo estimado de reproducción para el estudiante
- API de Claude: Aproximadamente 5~15 centavos por sesión de usuario.
- CAS → SMILES PubChem API: Gratuito (requiere espera entre solicitudes, menos de 5 req por segundo).
- Inferencia de CNN facial: Basado en GPU local, 100~500ms por imagen.
- Auto-alojamiento del sistema completo: VPS pequeño, 5~20 USD mensuales.
5 Casos de fallo conocidos (recopilados de comunidad y artículos)
-
Fallo de emparejamiento con CosIng por desviación o errores tipográficos en la nomenclatura INCI Síntoma: El INCI de la etiqueta del producto difiere sutilmente del nombre estándar de CosIng ("AQUA" vs "AQUA (WATER)" vs "WATER"). Causa: Diferencias en las prácticas de etiquetado según la marca, anotaciones multilingües, errores tipográficos. Evasión: (a) fallback de emparejamiento parcial (Paso 2 de este informe), (b) emparejamiento difuso (distancia de Levenshtein, librería
rapidfuzz), (c) diccionario de nombres alternativos de la comunidad (uso del diccionario INCI de OBF), (d) normalización mediante LLM (consulta a Claude: "¿A qué ingrediente INCI corresponde esta nomenclatura?"). Fuente: Open Beauty Facts GitHub — Problemas del diccionario INCI [6]. -
Ausencia de mapeo CAS → SMILES (especialmente en ingredientes de origen natural) Síntoma: Los extractos vegetales, productos de fermentación y aceites esenciales premium no tienen un CAS definido de forma única o carecen de SMILES en PubChem. Causa: Las sustancias de origen natural son mezclas de varios compuestos. Un SMILES único es inapropiado. Evitar: (a) separar los ingredientes de origen natural en una categoría distinta, (b) sustituir las claves MACCS por incrustaciones de texto (incrustar las descripciones de los ingredientes con un LLM), (c) utilizar solo el ingrediente activo principal para la huella digital (por ejemplo, Centella Asiatica → madecassoside). Fuente: documentación de PubChem sobre "productos naturales" [8].
-
Advertencias excesivas de las reglas de conflicto vs. ignorar las diferencias individuales Síntoma: se muestra automáticamente una advertencia de conflicto entre retinol y niacinamida, pero investigaciones recientes refutan que esa combinación sea realmente un problema. Esto genera confusión en los usuarios. Causa: la investigación sobre la química de los cosméticos se actualiza rápidamente y existen diferentes opiniones dentro de la comunidad de dermatología. Evitar: (a) indicar la fiabilidad de la regla ("definitiva"/"controvertida"), (b) proporcionar la URL de la referencia, (c) crear una interfaz de usuario que permita a los usuarios descartar la regla, (d) actualizar las reglas periódicamente (por ejemplo, revisarlas trimestralmente), (e) establecer el nivel de gravedad en "información" y, de forma predeterminada, ocultarlo en la interfaz de usuario. Fuente: revisión del debate sobre la niacinamida y la vitamina C en el Journal of Cosmetic Dermatology [12].
-
Cambio de dominio de la CNN facial (iluminación, raza, ángulo) Síntoma: el rendimiento de la CNN entrenada con imágenes naturales disminuye drásticamente cuando se utiliza con selfies de usuarios (iluminación y ángulos diversos de los teléfonos inteligentes). Causa: falta de diversidad en la iluminación y la demografía en los datos de entrenamiento. Sesgo en los conjuntos de datos de determinadas razas (por ejemplo, ISIC está sesgado hacia la piel blanca). Evitar: (a) realizar un ajuste fino con datos diversos de diferentes razas e iluminación, (b) proporcionar una guía de iluminación y ángulo en la interfaz de usuario para los usuarios, (c) indicar la fiabilidad en los resultados, (d) dar menor prioridad (la lógica principal es la incrustación de ingredientes y el motor de reglas). Fuente: Wen et al. "Características de los conjuntos de datos de imágenes de cáncer de piel disponibles públicamente: una revisión sistemática". Lancet Digital Health 2022 [13].
-
Regulaciones de protección de datos personales (Ley de Protección de Datos Personales de Corea del Sur, RGPD, CCPA) Síntoma: existe el riesgo de incumplir las regulaciones al almacenar imágenes faciales, perfiles de alergias e información sobre el estado de embarazo. Causa: las imágenes faciales se consideran datos biométricos según la Ley de Protección de Datos Personales de Corea del Sur y la categoría especial del artículo 9 del RGPD. La información sobre el estado de embarazo se considera información sobre la salud (categoría especial del RGPD). Evitar: (a) procesar las imágenes faciales solo localmente en el navegador (no enviarlas al servidor), (b) almacenar los perfiles de los usuarios solo dentro de la sesión, obtener el consentimiento explícito y cifrar los datos al almacenarlos en la base de datos, (c) indicar explícitamente que no se realizará la creación de perfiles ni se utilizarán los datos con fines publicitarios, (d) proporcionar una interfaz de usuario obligatoria para la política de privacidad y el acuerdo de consentimiento, (e) restringir el uso a menores de 14 años. Fuente: Guía de la Comisión de Protección de Datos Personales para aplicaciones de cosméticos y belleza; GDPR Art. 9, Art. 22.
Ideas para ampliar
- Integración de una aplicación de escaneo de códigos de barras: en la tienda, el usuario escanea un código de barras → se muestra inmediatamente la lista de ingredientes, las coincidencias y las advertencias.
- OCR multilingüe para etiquetas: foto de la etiqueta de un cosmético extranjero → Tesseract OCR → análisis automático de INCI.
- Ampliación del perfil de alergias: el usuario registra los alérgenos → se filtran inmediatamente los productos que contienen esa sustancia + también se advierte sobre ingredientes similares (similitud de huella).
- Filtro de sostenibilidad ambiental: etiquetas para microplásticos, aceite de palma y pruebas en animales.
- Puntuación de densidad de ingredientes en relación con el precio: correlación entre la concentración de ingredientes activos y el precio.
- Análisis de sentimiento de reseñas de la comunidad: extracción de reseñas de OBF y Reddit r/SkincareAddiction (tenga en cuenta las licencias) → análisis de sentimiento con LLM.
- Indicación específica para Corea de los productos sujetos a la revisión de la KFDA: etiquetas para la certificación de cosméticos funcionales (antiedad, blanqueador, protección solar).
Próximos temas
- Episodio 07
drug-target-gnn: Cuantificación de la interacción entre ingredientes y receptores de la piel mediante GNN. - Episodio 09
llm-vendor-benchmark: Evaluación comparativa de las recomendaciones de lenguaje natural de este episodio con Claude, GPT y Gemini. - Episodio 14
bio-mcp-agent: Exposición de esta secuencia de procesamiento como una herramienta MCP → asesor de belleza en forma de chatbot.
Referencias
- Reglamento (CE) n.º 1223/2009 de la UE (Reglamento de productos cosméticos):
https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02009R1223 - Guía de etiquetado de cosméticos de la FDA (21 CFR 701):
https://www.fda.gov/cosmetics/cosmetics-labeling - Ley de cosméticos de Corea del Sur (Ministerio de Seguridad de Alimentos y Medicamentos):
https://www.mfds.go.kr/ - Estándares de etiquetado de productos cosméticos y cuasi farmacéuticos de Japón:
https://www.mhlw.go.jp/ - Precios de la API de Anthropic Claude:
https://www.anthropic.com/pricing - Open Beauty Facts:
https://world.openbeautyfacts.org// GitHub:https://github.com/openfoodfacts/openbeautyfacts-server - CosIng (base de datos de ingredientes cosméticos de la Comisión Europea):
https://ec.europa.eu/growth/tools-databases/cosing/ - Documentación de la API REST de PubChem:
https://pubchem.ncbi.nlm.nih.gov/docs/pug-rest - Rogers D, Hahn M. "Extended-Connectivity Fingerprints". J Chem Inf Model 2010.
- Xie E, Wang W, Yu Z, et al. "SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers". NeurIPS 2021.
https://arxiv.org/abs/2105.15203 - ISIC (International Skin Imaging Collaboration):
https://www.isic-archive.com/ - Journal of Cosmetic Dermatology (revisiones sobre el debate niacinamida + vitamina C): varios artículos.
- Wen D, Khan SM, Xu AJ, et al. "Characteristics of publicly available skin cancer image datasets: a systematic review". Lancet Digital Health 2022.
https://www.thelancet.com/journals/landig/article/PIIS2589-7500(21)00252-1 - Guías de la Academia Estadounidense de Dermatología:
https://www.aad.org/ - Guías del Colegio Estadounidense de Obstetras y Ginecólogos (ACOG):
https://www.acog.org/ - GDPR Art. 9 (categorías especiales):
https://gdpr-info.eu/art-9-gdpr/ - Documentación de RDKit Fingerprints:
https://www.rdkit.org/docs/GettingStartedInPython.html - rapidfuzz (coincidencia difusa):
https://github.com/rapidfuzz/RapidFuzz - INCI Beauty (referencia de la comunidad):
https://incibeauty.com/ - EWG Skin Deep (referencia, metodología controvertida):
https://www.ewg.org/skindeep/