"""Genera candidatos de email a partir de nombre, apellidos y dominio. Funcion pura de OSINT pasivo: produce los patrones de direccion de correo mas habituales en organizaciones, sin tocar la red. Util como punto de partida para verificacion posterior (MX, catch-all, validacion SMTP, etc.). """ import unicodedata def _ascii_lower(text: str) -> str: """Normaliza un texto a ASCII en minusculas. Translitera acentos y caracteres latinos (a, e, n, ...) a su forma ASCII, pasa a minusculas y elimina cualquier caracter que no sea alfanumerico. No introduce separadores (a diferencia de un slug). Args: text: texto de entrada (puede contener acentos, ñ, mayusculas). Returns: cadena ASCII en minusculas formada solo por [a-z0-9]. """ # NFKD descompone los caracteres acentuados en base + diacritico. decomposed = unicodedata.normalize("NFKD", text) stripped = "".join(c for c in decomposed if not unicodedata.combining(c)) lowered = stripped.lower() return "".join(c for c in lowered if c.isalnum()) def guess_email_formats(nombre: str, apellidos: str, dominio: str) -> list: """Genera candidatos de email comunes a partir de identidad y dominio. Combina el nombre y los apellidos en los patrones de direccion mas frecuentes (nombre.apellido, inicial+apellido, apellido.nombre, etc.), normalizando acentos/ñ a ASCII y minusculas. Si hay dos apellidos tambien genera variantes que los unen. Deduplica preservando el orden de aparicion. Args: nombre: nombre de pila (puede incluir varios tokens separados por espacio; se usa el primero como nombre principal). apellidos: uno o dos apellidos separados por espacio. dominio: dominio de correo sin arroba (ej. "empresa.com"). Returns: lista de strings "@" con los candidatos, en el orden en que se generan los patrones y sin duplicados. """ n = _ascii_lower(nombre.split()[0]) if nombre.split() else "" apellido_tokens = [_ascii_lower(a) for a in apellidos.split() if _ascii_lower(a)] a1 = apellido_tokens[0] if apellido_tokens else "" a2 = apellido_tokens[1] if len(apellido_tokens) > 1 else "" dom = dominio.strip().lstrip("@").lower() ni = n[0] if n else "" a1i = a1[0] if a1 else "" apellido_full = "".join(apellido_tokens) # apellido1apellido2 unidos locals_raw = [ n, # nombre f"{n}.{a1}" if n and a1 else "", # nombre.apellido f"{n}{a1}" if n and a1 else "", # nombreapellido f"{ni}.{a1}" if ni and a1 else "", # n.apellido f"{ni}{a1}" if ni and a1 else "", # napellido f"{n}_{a1}" if n and a1 else "", # nombre_apellido f"{a1}.{n}" if a1 and n else "", # apellido.nombre f"{n}{a1i}" if n and a1i else "", # nombre+inicial_apellido f"{n}.{apellido_full}" if n and a2 else "", # nombre.apellido1apellido2 f"{n}{apellido_full}" if n and a2 else "", # nombreapellido1apellido2 f"{n}.{a2}" if n and a2 else "", # nombre.apellido2 f"{ni}{apellido_full}" if ni and a2 else "", # n+apellidos unidos ] seen = set() out = [] for local in locals_raw: if not local: continue email = f"{local}@{dom}" if email not in seen: seen.add(email) out.append(email) return out