--- name: imap_search kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def imap_search(conn, criteria: str = 'UNSEEN', mailbox: str = '') -> dict" description: "Busca mensajes en un buzon IMAP por criterio y devuelve sus UIDs. Sobre una conexion imaplib viva (de imap_connect), opcionalmente hace select(mailbox) y luego conn.uid('SEARCH', None, criteria). Usa SIEMPRE UIDs (estables mientras no cambie UIDVALIDITY), no numeros de secuencia (que se renumeran al borrar). criteria es una expresion IMAP cruda RFC 3501 (UNSEEN, ALL, FROM x, SUBJECT y, SINCE 01-Jan-2026, combinaciones). Devuelve {status:'ok', uids:[int], count} o {status:'error', error}. Nunca lanza." tags: [email, imap, infra, search, network] params: - name: conn desc: "Objeto imaplib.IMAP4[_SSL] vivo y autenticado, producido por imap_connect. None devuelve status error." - name: criteria desc: "Expresion de busqueda IMAP cruda (RFC 3501 SEARCH). Ej: 'UNSEEN', 'ALL', 'FROM foo@bar.com', 'SUBJECT factura', 'SINCE 01-Jan-2026', 'UNSEEN SINCE 01-Jun-2026'. Vacio devuelve status error. Default 'UNSEEN'." - name: mailbox desc: "Si no esta vacio, se hace select(mailbox) antes de buscar (ej. '[Gmail]/Sent Mail'). Vacio (default) usa el buzon ya seleccionado." output: "dict de estado. En exito {status:'ok', uids: list[int], count: int}: uids son UIDs (no numeros de secuencia), ordenados como los devuelve el servidor; lista vacia si nada casa (sigue siendo status ok). En fallo (conn None, criteria vacio o mal formado, buzon inexistente) {status:'error', error: str}." uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_py_core" imports: [] tested: false tests: [] test_file_path: "" file_path: "python/functions/infra/imap_search.py" --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from infra import imap_connect, imap_search, imap_fetch_message c = imap_connect("imap.gmail.com", 993, "gutierenmanuel15@gmail.com", "abcd efgh ijkl mnop") conn = c["conn"] # No leidos del INBOX (buzon ya seleccionado por imap_connect) res = imap_search(conn, criteria="UNSEEN") print(res["status"], res["count"]) # "ok" 7 print(res["uids"]) # [1422, 1425, 1431, ...] # Buscar en otra carpeta sin reconectar sent = imap_search(conn, criteria="SINCE 01-Jun-2026", mailbox="[Gmail]/Sent Mail") # Fetch del primer UID encontrado if res["uids"]: msg = imap_fetch_message(conn, res["uids"][0]) print(msg["message"]["subject"]) conn.logout() ``` ## Cuando usarla Usala tras `imap_connect` cuando necesites localizar mensajes por criterio (no leidos, de un remitente, por asunto, por fecha) antes de leerlos con `imap_fetch_message`. Es el paso intermedio del flujo lectura: connect -> search -> fetch. Para barrer una carpeta distinta del INBOX pasa `mailbox` y evita una reconexion. ## Gotchas - Funcion impura: hace red sobre el `conn` vivo. Nunca lanza: comprueba `status`. El `conn` lo provee `imap_connect` (no resuelve credenciales). - Devuelve UIDs, NO numeros de secuencia. Importante: guarda y reutiliza los UIDs; los seq cambian cuando se borran mensajes, los UIDs no (salvo cambio de UIDVALIDITY del buzon, raro). `imap_fetch_message` tambien espera UID. - El `criteria` es sintaxis IMAP cruda, sin validar: un criterio mal formado hace que el servidor responda no-OK y devuelve `status:'error'`. Las fechas van en formato IMAP `DD-Mon-YYYY` (ej. `01-Jan-2026`), no ISO. - `SEARCH` por defecto opera sobre US-ASCII; para acentos en `SUBJECT`/`FROM` algunos servidores requieren `CHARSET UTF-8` (este wrapper pasa `None` como charset, que cubre el caso comun). Si necesitas charset, busca por cabeceras ASCII o filtra el resultado en cliente. - Una busqueda sin coincidencias devuelve `status:'ok'` con `uids:[]` y `count:0` — distingue "sin resultados" mirando `count`, no `status`. - Cierra con `conn.logout()` al terminar (responsabilidad del caller).