Kingpin corrige una caché que congelaba el nombre de los listados
El panel del fundador mostraba el título vacío porque el índice de propiedad se escribía antes de que existiera el título, y nadie volvía a sincronizarlo.

Un fundador creó un listado en Kingpin apuntando a su perfil de X en lugar de a una web. El selector del panel le devolvía un nombre vacío. El título real sí acabó existiendo en la base de datos, pero el panel no leía de ahí: leía de una copia congelada con cadena vacía que ya no se tocaba nunca.
Dos filas para un mismo dato
Kingpin pide muy poco durante el checkout: solo una URL, sin campo de título. En el momento en que entra el dinero, el listado no tiene nombre; se rellena después y por dos caminos distintos según el destino.
Para una web normal, captureSiteMeta() descarga la página durante la moderación asíncrona y adopta su <title> o su og:title. Para un perfil de X o Instagram eso no sirve: ambas plataformas sirven a un crawler sin sesión una pantalla de login, así que el título de la página dice "X. It's what's happening" y no el nombre de nadie. Para eso existe socialTitle(), que genera @handle, y captureSiteMeta lo prefiere cuando reconoce la URL como perfil social. Esa mitad funciona: la fila canónica (CATEGORY#<cat>/ITEM#<id>) termina con el handle como título.
El problema está en la otra fila. El panel no lee la canónica, sino el índice de propiedad FOUNDER#<id>/LISTING#<cat>#<id>, que existe para que el panel no tenga que recorrer todas las categorías buscando los listados del fundador. Ese índice duplica el título a propósito, para no hacer un join en la lectura. Y se escribe en el sitio equivocado: linkFounderToListing() corre de forma síncrona dentro del manejador de checkout, antes de que la moderación haya generado el título. En ese instante no hay nombre que copiar, así que la fila del índice queda con Title: '' y ya no la toca nadie más. La moderación actualiza la canónica segundos después, ajena a que existe una segunda copia de ese dato.
Arreglo en dos tiempos
El primer cambio evita que las copias se separen. captureSiteMeta() pide a DynamoDB que le devuelva la fila canónica actualizada (ReturnValues: 'ALL_NEW' sobre el UpdateItem que ya hacía) y, si esa fila lleva un FounderId, escribe el mismo título en el índice de propiedad. Es deliberadamente best-effort: si falla, se registra el aviso y se sigue, porque es decoración encima de un pago ya capturado.
Ese arreglo no cubre todo. linkFounderToListing vive en el Lambda de checkout y captureSiteMeta en el de moderación, sin contrato de orden entre ellos. Si la moderación corre antes, el FounderId todavía no está en la fila y la sincronización no hace nada. De ahí la segunda mitad, pensada para las filas ya rotas: founder-listings.mjs, al leer, comprueba si el título está vacío y, en ese caso, hace una lectura puntual de la fila canónica, adopta el TitleSafe y repara la copia. No es un Scan y no toca el camino público de lectura.
El patrón es viejo y sigue apareciendo: un índice es una caché, y una caché que se escribe una sola vez y no se invalida jamás no es una optimización de rendimiento, es un bug de obsolescencia lento con retardo incorporado. Lo que queda por ver es si el self-heal de lectura se queda como red de seguridad permanente o si acaba costando más lecturas puntuales de las que la compañía espera.


