Volver a proyectos
Caso de estudioEn desarrollo

Coda

Un diario musical social: registrás lo que escuchás, reseñás álbumes y recibís recomendaciones que explican por qué.

Rol
Fullstack
Año
2026
Stack
  • Next.js 16
  • NestJS 11
  • TypeScript
  • PostgreSQL 17
  • Redis 7 · BullMQ
  • Meilisearch
Ver el código en GitHub

Contexto y problema

Coda es un diario musical social. Registrás lo que escuchás, calificás y reseñás álbumes, armás listas rankeables y seguís la actividad de otras personas.

La idea central es que sea un diario, no un feed algorítmico: el centro es lo que vos escuchaste y opinaste. Las recomendaciones existen, pero siempre explican de dónde salen.

Arquitectura

Los workers corren como procesos separados de la API. Postgres es la única fuente de verdad; Meilisearch se puede reconstruir desde ahí.

Es un monolito modular: una sola API en NestJS organizada por módulos, con el trabajo pesado delegado a workers de BullMQ. Para un equipo chico, eso significa desplegar y depurar una sola cosa y avanzar rápido, sin pagar el costo operativo de microservicios.

El único candidato claro a separarse es un futuro servicio de recomendaciones en Python, y hoy está en el roadmap.

Tres decisiones técnicas

  1. Un pipeline de catálogo que respeta a sus fuentes

    Decisión
    Spotify siembra el catálogo y MusicBrainz lo enriquece; un match se acepta solo con score >= 80. Las importaciones son idempotentes y reanudables: ids de job determinísticos, checkpoint de paginación en Redis y upserts sobre spotifyId único.
    Por qué
    MusicBrainz permite 1 request por segundo, y el límite se aplica dos veces: un limiter de BullMQ para toda la flota (1 job cada 1100 ms) y un gate serializado en el cliente. Los fallos se reintentan hasta 5 veces con backoff exponencial.
    Tradeoff
    Importar es lento a propósito. A cambio, se respeta el límite de MusicBrainz y un proceso caído retoma donde quedó sin duplicar datos.
  2. Búsqueda como proyección reconstruible

    Decisión
    Postgres es la fuente de verdad. Meilisearch es una proyección de lectura que se actualiza a través de la cola search-sync, y reindex:search la reconstruye desde cero.
    Por qué
    La búsqueda tolerante a typos necesita un motor dedicado, pero ese motor no debería ser dueño de ningún dato. Si el índice se corrompe o cambia el esquema, se vuelve a generar.
    Tradeoff
    Consistencia eventual: entre una escritura y su sincronización, la búsqueda puede mostrar datos levemente desactualizados.
  3. Recomendaciones v1: simples y explicables

    Decisión
    Una heurística precalculada en workers: 0.5 género + 0.35 artista + 0.15 popularidad (log). Un prefiltro SQL por los 5 géneros principales del usuario acota a 300 candidatos y se guardan los 50 mejores. Se regeneran con un debounce de 5 minutos y un refresh nocturno.
    Por qué
    Cada recomendación guarda su razón (topGenre, matchedArtist), así la interfaz puede mostrar “Because you like {género}” o “Because you follow this artist” en lugar de una caja negra.
    Tradeoff
    Deliberadamente simple antes de cualquier modelo de ML. Captura menos matices, pero es barata, predecible y fácil de depurar.

Problemas difíciles

  • El techo de la búsqueda de Spotify

    La búsqueda de Spotify corta la paginación en el offset 1000. Eso bloquea la meta de un catálogo de 100k álbumes si solo se siembra por búsqueda.

  • Un job que BullMQ ignoraba en silencio

    Con ids determinísticos, un job que quedaba en estado completado hacía que BullMQ salteara el siguiente con el mismo id, sin error. Se resolvió con removeOnComplete.

  • Ids de job con dos puntos

    BullMQ rechaza : en los ids de job, así que los ids determinísticos tienen que armarse con otro separador.

  • Specs que nunca corrían en CI

    Un error de orden en el pipeline de CI hacía que los specs contra infraestructura real se saltearan siempre. Un CI en verde no prueba nada si los tests importantes no se ejecutan.

Cómo se testea

570+tests en la web

Todo se escribe con TDD estricto sobre Vitest: primero el test que falla, después el código.

En CI, los specs corren contra Postgres, Redis y Meilisearch reales. Así las colas, la búsqueda y las queries se prueban contra la misma infraestructura que usan, no contra mocks.

Hecho y roadmap

Hecho

  • Importación de catálogo
  • Búsqueda
  • Registro de escuchas
  • Reseñas
  • Listas
  • Feed social con paginación por cursor
  • Notificaciones
  • Recomendaciones v1

Roadmap

  • Filtrado colaborativo
  • Embeddings con pgvector
  • Servicio de recomendaciones en Python
  • App mobile
  • Páginas públicas para SEO

Especificado, todavía no implementado.

¿Querés ver el resto?

El código de Coda es público. También podés volver a los demás proyectos.