---
name: imap_mark_seen
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def imap_mark_seen(conn, uid: int, seen: bool = True) -> dict"
description: "Marca o desmarca un mensaje IMAP como leido (bandera del sistema \\Seen) operando por UID sobre una conexion imaplib.IMAP4_SSL ya autenticada y con un mailbox seleccionado. Ejecuta conn.uid('STORE', str(uid), '+FLAGS' si seen else '-FLAGS', '(\\Seen)'): seen=True anade \\Seen (leido), seen=False la quita (no leido). Opera siempre por UID (estable dentro del mailbox mientras no cambie la UIDVALIDITY), nunca por numero de secuencia. No abre la conexion ni resuelve credenciales: el caller pasa conn ya conectado, autenticado y con conn.select() hecho. Nunca lanza: devuelve {status:'ok', uid, seen} o {status:'error', error}; tambien error si STORE responde algo distinto de OK. Parte del grupo email/imap (mutacion de estado de correo por IMAP, tecnologia propia, sin browser). Solo stdlib (imaplib)."
tags: [email, imap, mail, flags, seen, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_py_core"
imports: [imaplib]
params:
  - name: conn
    desc: "objeto imaplib.IMAP4_SSL (o IMAP4) YA conectado, autenticado y con un mailbox seleccionado (conn.select('INBOX')). Normalmente lo produce imap_connect. La funcion no lo abre ni lo cierra."
  - name: uid
    desc: "UID del mensaje dentro del mailbox seleccionado. Es estable mientras no cambie la UIDVALIDITY del mailbox (a diferencia del numero de secuencia, que se desplaza al borrar mensajes). Se obtiene de una busqueda/fetch por UID previa."
  - name: seen
    desc: "True (default) para marcar como leido: STORE +FLAGS (\\Seen). False para marcar como no leido: STORE -FLAGS (\\Seen)."
output: "dict. En exito: {status:'ok', uid:int, seen:bool} reflejando el UID y el estado solicitado. En error (sin lanzar): {status:'error', error:str}, incluyendo el caso en que el servidor responde un typ distinto de OK al comando STORE."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/infra/imap_mark_seen.py"
---

## Ejemplo

```python
import sys
sys.path.insert(0, "python/functions")
from infra.imap_connect import imap_connect
from infra.imap_mark_seen import imap_mark_seen

# conn ya conectado, autenticado y con INBOX seleccionado (lo produce imap_connect).
conn = imap_connect(...)["conn"]  # firma exacta la define imap_connect

# Marcar el mensaje UID 12345 como leido.
print(imap_mark_seen(conn, 12345))
# {'status': 'ok', 'uid': 12345, 'seen': True}

# Volver a marcarlo como NO leido.
print(imap_mark_seen(conn, 12345, seen=False))
# {'status': 'ok', 'uid': 12345, 'seen': False}
```

## Cuando usarla

Cuando ya tienes el UID de un mensaje (por una busqueda/fetch previa) y quieres
cambiar su estado leido/no-leido sin descargar ni reenviar nada: marcar como
visto tras procesarlo automaticamente, o re-marcar como no leido para que el
usuario lo vea pendiente. Es la primitiva de mutacion mas barata del grupo IMAP
(un solo comando STORE). Compone bien tras `imap_search` + `imap_fetch_message`:
lees, decides, y marcas el estado con esta funcion.

## Gotchas

- **Impura**: cambia estado en el servidor de correo de forma persistente.
- **UID, no secuencia**: la funcion usa `conn.uid("STORE", ...)`. El UID es
  estable dentro del mailbox mientras la UIDVALIDITY no cambie; el numero de
  secuencia NO lo es (se desplaza al borrar mensajes), por eso nunca se usa.
- **Mailbox seleccionado**: el UID solo tiene sentido en el mailbox que el caller
  selecciono con `conn.select(...)`. El mismo numero de UID en otro mailbox
  apunta a otro mensaje (o a ninguno).
- **\\Seen es del sistema**: `(\\Seen)` es una bandera estandar IMAP. Marcarla no
  mueve ni borra el mensaje, solo cambia su estado de lectura. En Gmail esto
  equivale a marcar el hilo como leido/no leido.
- **Nunca lanza**: cualquier fallo (conexion caida, mailbox no seleccionado,
  respuesta no-OK del servidor) vuelve como `{status:'error', error:str}`.
