Proyectos

04 / 05Caso de estudio

Network 3.0

Red social full-stack

Una API REST en Django y una aplicación en React y TypeScript: feed por cursor, reposts y citas, hashtags, menciones y notificaciones, desplegadas en Railway con demo pública.

Feed de Network con el editor, las pestañas For you y Following, publicaciones, sugerencias de a quién seguir y hashtags en tendenciaFeed en el teléfono con el editor, las pestañas y la barra de navegación inferior
Mi papel
Único desarrollador; diseño de producto, API, frontend, pruebas y despliegue
Stack
React · TypeScript · Vite · Tailwind CSS · TanStack Query · Django · Django REST Framework · PostgreSQL · Redis · Docker
Estado
Desplegado en Railway · demo pública

Alcance

  • 132pruebas automatizadas, backend y frontend
  • 37operaciones de API documentadas en OpenAPI
  • 8tipos de aviso, cada uno con su destino

El reto

En una red social todo está conectado: un like cambia un contador, un perfil, una lista de personas y el aviso de otra persona, mientras el feed se mueve bajo quien lo lee.

Lo que construí
Reescribí desde cero mi proyecto de CS50W como API REST en Django y aplicación en React y TypeScript, y lo llevé a producción con Docker, integración continua y 132 pruebas.
Decisión técnica clave
Paginación por cursor para que el feed no se repita, contadores con subconsultas en vez de JOIN múltiples y las notificaciones como dominio propio.

Decisiones de diseño

  1. Feed de Network con el editor, las pestañas For you y Following, publicaciones, sugerencias de a quién seguir y hashtags en tendencia
    Problema
    Lo que ves en una red social depende de a quién sigues, y a quién seguir no se descubre solo.
    Decisión
    Publicar, conversar y descubrir personas conviven en una vista de tres columnas, sin salir del feed.
  2. Feed en el teléfono con el editor, las pestañas y la barra de navegación inferior
    Problema
    En el teléfono no caben tres columnas ni seis destinos de navegación.
    Decisión
    Una sola columna y una barra inferior de cuatro destinos; marcadores y ajustes pasan al menú de la cuenta.
  3. Feed con tres tarjetas esqueleto mientras cargan las publicaciones
    Problema
    Un spinner centrado no dice qué viene, y al llegar el contenido la página salta.
    Decisión
    Esqueletos con la geometría exacta de la tarjeta real; el hueco ya tiene su tamaño y nada se mueve.
  4. Feed Following vacío en una cuenta nueva, con una explicación y un enlace para encontrar personas
    Problema
    Una pantalla que sólo dice «no hay nada» parece una función a medio terminar.
    Decisión
    Cada vacío explica qué irá ahí y ofrece la acción que lo llena, como buscar a quién seguir.
  5. Centro de notificaciones con menciones, citas, likes, comentarios, reposts y respuestas, cada una con su icono y su color
    Problema
    Ocho tipos de actividad en una sola lista se leen como un muro indiferenciado.
    Decisión
    Cada tipo lleva su icono, su color y su frase; las no leídas se marcan y cada fila lleva a su origen.
  6. Hilo de comentarios con uno resaltado por un anillo al llegar desde una notificación
    Problema
    Un aviso de comentario que sólo abre la publicación obliga a buscar el comentario en el hilo.
    Decisión
    El enlace lleva al comentario exacto; la página baja hasta él y lo resalta con un anillo.
  7. Diálogo para borrar la cuenta que enumera lo que se elimina y pide la contraseña
    Problema
    «¿Estás seguro?» no dice nada; se confirma sin saber qué se pierde.
    Decisión
    El diálogo nombra lo que se borra y, cuando no hay vuelta atrás, pide la contraseña.
  8. Ajustes en modo oscuro con la opción Dark seleccionada
    Problema
    Un modo oscuro que destella en blanco al cargar, o que ignora el del sistema, se siente roto.
    Decisión
    Claro, oscuro o el del sistema; el tema se aplica antes de que React monte, sin destello.

Sistema

Elige un módulo: se enciende su ruta.Elige un módulo: debajo, su decisión.

Arquitectura27 módulos · 36 conexiones

Tres columnas: publicar, conversar y descubrir personas sin salir del feed, que se carga solo al llegar al final.

React 19 · IntersectionObserver

Respuestas a un solo nivel: basta para conversar sin hilos que se pierdan en profundidad.

React 19 · React Router 7

Pestañas Posts, Media y Likes; seguidores y seguidos son modales con dirección propia.

React 19 · React Router 7

Cada verbo con su icono, su color y su frase, y cada fila lleva a su origen exacto.

React 19 · lucide-react

Una sola caja para personas, texto y hashtags, con atajo /; escribir con # salta a las publicaciones.

React Router 7 · Rebote de 350 ms

Registrarse deja la sesión iniciada, y el acceso ofrece cuentas demo de un clic.

Zustand 5 · React 19

Una columna y una barra inferior de cuatro destinos; marcadores y ajustes, al menú de la cuenta.

Tailwind CSS 4 · Manifiesto instalable

Un like optimista se refleja a la vez en el feed, el detalle y el perfil, escrito en un solo sitio.

TanStack Query 5 · Zustand 5

Una sola promesa de refresco compartida: diez peticiones con el token caducado disparan un refresco, no diez.

Axios 1.20

Versionada bajo /api/v1/: 28 rutas y 37 operaciones, con el esquema OpenAPI generado del propio código.

Django REST Framework 3.17 · django-filter · django-cors-headers

Refresh rotado con lista negra y 10 intentos por minuto y por IP; cambiar la contraseña cierra las demás sesiones.

SimpleJWT 5.5 · Lista negra de tokens

Cursor por fecha e id: publicar algo nuevo no repite la página siguiente; los contadores son subconsultas, no JOIN.

CursorPagination de DRF · Subquery y Exists

Repost y cita son el mismo modelo; una restricción única condicional impide repostear dos veces lo mismo.

Django 6.0 · UniqueConstraint

Toda subida se valida, se rota por EXIF, pierde sus metadatos y se reescribe como WebP.

Pillow 12.3

Seguirse a uno mismo lo prohíbe la base de datos, no sólo la vista.

Django 6.0 · CheckConstraint

App propia con ocho verbos; al deshacer un like, un follow o un repost se borra su aviso.

Django 6.0

Se extraen al guardar; la tendencia cuenta los últimos siete días y se cachea cinco minutos.

Expresiones regulares · Caché de Django

Ocho modelos con índices escritos para las consultas que existen, como el de avisos no leídos.

PostgreSQL 17 · psycopg 3.3 · dj-database-url

Sólo tendencias y sugerencias: si cae, la aplicación se ralentiza, no se rompe.

Redis 8 · django-redis 7.0

Disco local por defecto; un bucket S3 compatible con sólo definir las variables.

django-storages 1.14

Además de lint, tipos y pruebas, valida migraciones, check --deploy y la construcción de las dos imágenes.

GitHub Actions · Ruff · ESLint · Vitest

Dos imágenes multi-stage que respetan $PORT: la misma corre en local y en Railway.

Docker · Docker Compose

Los dos servicios, configurados como código; el health-check sólo falla si cae la base de datos.

Railway · railway.json

Sirve la aplicación, aplica la CSP y las cabeceras de seguridad y devuelve index.html a las rutas del cliente.

nginx · Content-Security-Policy

WSGI: no hay nada en tiempo real que justifique ASGI.

Gunicorn 26 · WhiteNoise 6.12

El esquema sale del código: la documentación no puede quedarse vieja.

drf-spectacular 0.29 · Swagger UI · ReDoc

Integrado pero apagado salvo que exista SENTRY_DSN; no obliga a depender de un servicio externo.

Sentry SDK 2.70

Inspector27 decisiones

Cliente

Feed

Tres columnas: publicar, conversar y descubrir personas sin salir del feed, que se carga solo al llegar al final.

Tecnologías

55 herramientas en 10 áreas. En el sistema, cada módulo dice las suyas.

Lenguajes

  • Python 3.13
  • TypeScript 6.0
  • JavaScript
  • SQL
  • HTML y CSS

Frontend

  • React 19.2
  • Vite 8.0
  • React Router 7.18
  • TanStack Query 5.101
  • Zustand 5.0
  • Axios 1.20

Interfaz

  • Tailwind CSS 4.3
  • lucide-react 1.18
  • Inter (Fontsource) 5.3
  • clsx 2.1

Backend

  • Django 6.0
  • Django REST Framework 3.17
  • django-filter 25.2
  • django-cors-headers 4.9
  • Pillow 12.3
  • python-dotenv 1.2

Autenticación

  • SimpleJWT 5.5
  • Lista negra de tokens
  • Acceso con usuario o email
  • Validadores de contraseña de Django

Contrato de API

  • drf-spectacular 0.29
  • OpenAPI 3.0
  • Swagger UI
  • ReDoc

Datos y caché

  • PostgreSQL 17
  • SQLite 3
  • psycopg 3.3
  • dj-database-url 3.1
  • Redis 8
  • django-redis 7.0
  • django-storages 1.14

Infraestructura

  • Docker
  • Docker Compose
  • nginx
  • Gunicorn 26
  • WhiteNoise 6.12
  • Railway
  • Sentry SDK 2.70

Calidad y CI

  • Test runner de Django
  • Vitest 5.0
  • Testing Library 16.3
  • jsdom 30.1
  • Ruff 0.16
  • ESLint 10.5
  • typescript-eslint 8.61
  • Prettier 3.9
  • GitHub Actions

Herramientas

  • Make
  • EditorConfig
  • Keep a Changelog

Resultados verificables

  • Desplegado en Railway con demo pública; la web y la API corren como servicios separados sobre PostgreSQL y Redis
  • Reescritura completa del proyecto 4 de CS50W, de un monolito con plantillas a una API REST versionada y una aplicación en React
  • Feed paginado por cursor, con contadores resueltos por subconsultas y no por JOIN múltiples
  • JWT con rotación de refresh y lista negra; cambiar la contraseña cierra las demás sesiones
  • Reposts y citas, hashtags con tendencias, menciones, marcadores y notificaciones de ocho tipos
  • 132 pruebas automatizadas y una CI que también valida migraciones, check --deploy y las dos imágenes Docker

Recorrido por módulos

62 pantallas en 9 módulos.

Feed y publicaciones11 pantallas

  • Feed de Network con el editor, las pestañas For you y Following, publicaciones, sugerencias de a quién seguir y hashtags en tendencia

    Publicar, conversar y descubrir personas en una sola vista de tres columnas.

  • Detalle de una publicación con su imagen, sus acciones y el hilo de comentarios

    El detalle reúne la publicación y toda su conversación.

  • Hilo de comentarios con uno resaltado por un anillo al llegar desde una notificación

    El aviso lleva al comentario exacto y lo resalta.

  • Caja de respuesta abierta bajo un comentario del hilo

    Responder ocurre bajo el comentario, sin salir del hilo.

  • Editor de publicación abierto con texto, hashtags y una imagen adjunta en previsualización

    La imagen se ve antes de enviarla; el servidor la reescribe como WebP.

  • Feed con la pestaña Following activa

    Following reúne lo de quien sigues y lo que publicas tú.

  • Feed con tres tarjetas esqueleto mientras cargan las publicaciones

    Mientras carga, el esqueleto ocupa el sitio exacto de la tarjeta.

  • Menú de una publicación propia con copiar enlace, guardar, editar y borrar

    Editar y borrar sólo aparecen sobre lo propio.

  • Modal para citar una publicación, con la original embebida bajo el campo de texto

    Al citar se ve la original completa, imagen incluida.

  • Modal Liked by con tres personas, su titular y su botón de seguimiento

    El contador de likes se abre como lista de personas.

  • Diálogo para borrar una publicación que avisa de que también se van sus respuestas, likes y reposts

    El diálogo nombra lo que se pierde, no sólo la acción.

Perfiles y relaciones7 pantallas

  • Perfil propio con portada, avatar, titular, biografía, ubicación, web, contadores y pestañas Posts, Media y Likes

    El perfil une identidad, relaciones y lo publicado.

  • Perfil de otra persona con el botón Follow y la insignia Follows you

    «Follows you» resuelve la relación de un vistazo.

  • Pestaña Media de un perfil con sus imágenes en una rejilla de tres columnas

    La pestaña Media recorre el perfil por imagen.

  • Pestaña Likes de un perfil con las publicaciones que esa persona marcó

    Lo que alguien marca también dice quién es.

  • Modal para editar el perfil con portada, avatar y campos con contador de caracteres

    Editar el perfil entero sin salir de él, con contador en cada campo.

  • Modal de seguidores con siete personas, su titular, la insignia Follows you y su botón de seguimiento

    Seguidores y seguidos son modales con dirección propia: se pueden enlazar.

  • Modal de personas seguidas con sus titulares y botones de seguimiento

    El grafo se recorre en las dos direcciones desde el perfil.

Notificaciones2 pantallas

  • Centro de notificaciones con menciones, citas, likes, comentarios, reposts y respuestas, cada una con su icono y su color

    Cada tipo de aviso con su icono, su color y su frase.

  • Diálogo para vaciar todas las notificaciones

    Vaciar la lista pide confirmación y dice qué borra.

Búsqueda y marcadores5 pantallas

  • Resultados del hashtag design con las publicaciones etiquetadas

    Escribir con # salta directo a las publicaciones del tema.

  • Búsqueda de personas con ocho resultados, su titular y su botón de seguimiento

    Buscar personas incluye el titular, no sólo el nombre.

  • Búsqueda de publicaciones para el término pagination

    La misma caja busca personas y publicaciones.

  • Búsqueda sin resultados que repite el término buscado y sugiere probar otro

    Sin resultados, la pantalla repite el término y sugiere qué probar.

  • Página de marcadores con las publicaciones guardadas y el aviso de que sólo tú ves la lista

    Los marcadores son privados, y la pantalla lo dice.

Acceso y cuenta6 pantallas

  • Pantalla de acceso con panel de marca y el recuadro de cuentas demo con su contraseña

    La demo pública ofrece cuentas de prueba de un clic.

  • Formulario de acceso con el aviso de credenciales inválidas

    El error aparece en el propio formulario, sin recargar.

  • Formulario de registro con nombre, apellidos, usuario, email y contraseña

    Crear la cuenta deja la sesión iniciada: una pantalla, no dos.

  • Menú de la cuenta abierto con perfil, marcadores, ajustes y cerrar sesión

    El menú de la cuenta reúne lo que no está en la navegación.

  • Ajustes con el perfil, la apariencia clara, oscura o del sistema, y el cambio de contraseña

    Cambiar la contraseña cierra la sesión en los demás dispositivos.

  • Diálogo para borrar la cuenta que enumera lo que se elimina y pide la contraseña

    Borrar la cuenta enumera lo que se pierde y pide la contraseña.

Estados vacíos y errores5 pantallas

  • Feed Following vacío en una cuenta nueva, con una explicación y un enlace para encontrar personas

    El vacío explica qué irá ahí y enlaza a la acción que lo llena.

  • Marcadores vacíos con la explicación de cómo guardar una publicación

    Enseña el gesto exacto que lo resuelve.

  • Notificaciones vacías en una cuenta nueva

    Anticipa qué avisos llegarán a esta pantalla.

  • Perfil recién creado sin publicaciones, con avatar de iniciales

    Un perfil sin contenido sigue siendo presentable.

  • Página 404 con el logotipo y un botón para volver al feed

    El 404 mantiene la marca y ofrece una salida.

Modo oscuro6 pantallas

  • Feed en modo oscuro con publicaciones, sugerencias y tendencias

    En oscuro, los bordes hacen el trabajo de las sombras.

  • Detalle de una publicación en modo oscuro con su hilo de comentarios

    La jerarquía del hilo se mantiene al cambiar de tema.

  • Perfil propio en modo oscuro con portada y contadores

    La portada en degradado también funciona sobre fondo oscuro.

  • Notificaciones en modo oscuro con cada tipo de aviso en su color

    Cada color de aviso tiene su variante oscura.

  • Ajustes en modo oscuro con la opción Dark seleccionada

    Claro, oscuro o el del sistema, aplicado antes de pintar.

  • Pantalla de acceso en modo oscuro con el recuadro de cuentas demo

    El acceso y sus cuentas demo, también en oscuro.

API documentada1 pantalla

  • Swagger UI de la API de Network 3.1.0 con los endpoints de autenticación y de comentarios

    Swagger y ReDoc salen del esquema OpenAPI, que se genera del código.

En el teléfono19 pantallas

  • Feed en el teléfono con el editor, las pestañas y la barra de navegación inferior

    Una columna y una barra inferior con cuatro destinos.

  • Editor de publicación en el teléfono con texto e imagen en previsualización

    Publicar con imagen funciona igual en el teléfono.

  • Detalle de una publicación en el teléfono con su imagen y sus comentarios

    El hilo completo se lee en una columna.

  • Caja de respuesta abierta bajo un comentario en el teléfono

    Responder sigue ocurriendo bajo el comentario.

  • Perfil propio en el teléfono con portada, datos, contadores y pestañas

    El perfil apila portada, datos y pestañas.

  • Pestaña Media de un perfil en el teléfono

    La rejilla de medios pasa de tres columnas a dos.

  • Lista de seguidores en el teléfono, abierta como hoja inferior

    Las listas de personas suben como hoja inferior.

  • Perfil de otra persona en el teléfono con el botón Follow

    El botón de seguir sigue a mano en el teléfono.

  • Notificaciones en el teléfono con cada tipo de aviso en su color

    Cada tipo de aviso sigue distinguiéndose en pantalla estrecha.

  • Marcadores en el teléfono

    Los marcadores se leen igual en una columna.

  • Búsqueda sin consulta en el teléfono con los hashtags en tendencia

    Sin consulta, la búsqueda propone temas.

  • Resultados del hashtag design en el teléfono

    La búsqueda por hashtag, igual en el teléfono.

  • Menú de la cuenta abierto en el teléfono con perfil, marcadores, ajustes y cerrar sesión

    Marcadores y ajustes viven en el menú de la cuenta.

  • Ajustes en el teléfono con perfil, apariencia y contraseña

    Los ajustes se apilan en el mismo orden.

  • Feed Following vacío en el teléfono con un enlace para encontrar personas

    El vacío conserva su explicación en pantalla estrecha.

  • Feed en modo oscuro en el teléfono

    Modo oscuro y teléfono a la vez.

  • Perfil propio en modo oscuro en el teléfono

    El perfil mantiene la jerarquía en oscuro.

  • Notificaciones en modo oscuro en el teléfono

    Los colores de cada aviso aguantan el oscuro y la pantalla estrecha.

  • Pantalla de acceso en el teléfono con las cuentas demo y su contraseña

    Las cuentas demo caben también en el teléfono.

8 min de lectura

El caso completo

Contexto

Network nació como el proyecto 4 de CS50W, el curso de desarrollo web de Harvard: una red social pequeña hecha con plantillas de Django y JavaScript sin framework. Mi primera entrega ya iba más allá de lo que pedía el enunciado —comentarios con respuestas, imágenes, borrado y búsqueda—, pero seguía siendo un monolito de curso.

Network 3.0 es una reescritura completa, no un retoque. Borré el monolito y en su lugar hay dos servicios independientes que sólo comparten un contrato HTTP: una API REST en Django y una aplicación de una sola página en React y TypeScript. La versión actual, la 3.1.0, está desplegada en Railway con una demo pública. Fueron un par de semanas de trabajo, en solitario.

Problema

Lo difícil de una red social pequeña no son las pantallas: es que todo está conectado con todo y todo cambia mientras lo miras. Un like toca el contador de la tarjeta, la pestaña Likes de un perfil y la lista de quién reaccionó, y crea un aviso para otra persona. El feed se mueve mientras alguien lo lee, cada tarjeta necesita cinco datos agregados y cualquier imagen que sube un usuario es, por definición, contenido en el que no se puede confiar.

Mi función

Lo hice solo: el diseño del producto y de la interfaz, la API, el frontend, las pruebas, los contenedores, la integración continua, el despliegue y la documentación.

Lo que pedía CS50W y lo que añadí

El enunciado pedía siete cosas: publicar texto, ver todas las publicaciones, un perfil con seguidores y botón de seguir, la página de las personas seguidas, paginación de diez en diez con botones, editar sin recargar y dar like sin recargar.

La versión 3 añade, encima:

  • Arquitectura. API REST versionada y aplicación de una sola página, desplegables por separado; JWT con rotación y lista negra en lugar de la sesión; paginación por cursor, y un esquema OpenAPI 3 con Swagger y ReDoc.
  • Producto. Reposts y citas, marcadores privados, hashtags con tendencias de la semana, menciones que notifican, notificaciones de ocho tipos, perfiles con portada y pestañas Posts, Media y Likes, sugerencias de a quién seguir, tema claro, oscuro o del sistema, cambio de contraseña y borrado de cuenta.
  • Oficio. 132 pruebas automatizadas donde antes no había ninguna, integración continua, imágenes Docker, procesado de imágenes en el servidor y cabeceras de seguridad.

Restricciones

  • Nada obligatorio para desarrollar. Arranca con SQLite y caché en memoria; PostgreSQL y Redis se activan con variables de entorno. Todo lo que usa la caché tiene que funcionar sin ella.
  • Sin almacenamiento de objetos garantizado. Los archivos subidos van al disco salvo que se configure un bucket compatible con S3.
  • Una sola persona y ningún usuario al que preguntar. Todas las decisiones de producto se tomaron sin datos de uso.

Cómo lo construí

Primero borré el monolito, en su propio commit; después vinieron el backend, el frontend y, al final, los contenedores. Cuando volví para llevarlo a producción, el orden fue este: primero la integración continua, luego las dependencias con avisos de seguridad y sólo después las funciones nuevas, el rediseño y la documentación.

El backend son cuatro apps de Django separadas por dominio y no por capa técnica: core, users, posts y notifications. En el frontend, TanStack Query gobierna todo lo que vive en el servidor y Zustand sólo lo que es del cliente: la sesión, el tema y los avisos. Cada página se carga por separado.

Decisiones de arquitectura

  • Paginación por cursor en los timelines. Ordenada por fecha e id, con el id como desempate: publicar algo nuevo no desplaza la página siguiente ni repite lo ya leído. Las listas acotadas, como personas o comentarios, siguen por número de página. La página sigue siendo de diez, como pedía CS50W, pero se carga sola al llegar al final.
  • Contadores sin JOIN múltiples. Cada tarjeta trae likes, comentarios y reposts como subconsultas independientes, y «¿le di like?», «¿lo reposteé?» y «¿lo guardé?» como EXISTS. La publicación citada recibe las mismas anotaciones.
  • JWT con rotación y lista negra. Cada uso del refresh emite uno nuevo e invalida el anterior, y cambiar la contraseña invalida todos los demás. En el cliente, las peticiones que fallan a la vez comparten un solo refresco.
  • Repost y cita, un solo modelo. Una restricción única condicional impide repostear dos veces lo mismo sin impedir citarlo varias veces, y la base de datos prohíbe seguirse a uno mismo.
  • Las notificaciones, un dominio propio. Una app de Django con sus ocho verbos, sus endpoints y un índice compuesto pensado para las dos consultas que existen: la lista y el contador de no leídas.
  • Redis opcional y no crítico. Sólo guarda las tendencias y las sugerencias, con tiempos de espera cortos: si cae, la aplicación se ralentiza, no se rompe. El health-check informa de la caché, pero sólo marca el servicio como caído si falla la base de datos.

Seguridad e imágenes

  • Cada imagen subida se valida con Pillow, se rota según su EXIF, pierde sus metadatos, se reescala según su uso y se reescribe como WebP, con un tope de 40 megapíxeles contra las bombas de descompresión.
  • El registro, el acceso y el cambio de contraseña tienen su propio límite: diez peticiones por minuto y por IP.
  • En producción, Django pone HSTS, cookies seguras y las cabeceras de seguridad, y nginx pone la política de seguridad de contenidos. La aplicación se niega a arrancar con la clave secreta de desarrollo.

Desafíos

  • Que el feed no se repita al paginar: cursor con desempate por id.
  • Que dos reposts simultáneos no rompan nada: la restricción única en la base de datos y el error de integridad capturado.
  • Que un token caducado no dispare una tormenta de refrescos: una sola promesa compartida en el cliente HTTP.
  • Que el tema no destelle al cargar: un script en index.html aplica el tema antes de que React monte.
  • Que los avisos no se dupliquen ni queden huérfanos: se crean una sola vez y se borran cuando se deshace el like, el follow o el repost.
  • Que los datos de demostración sean reproducibles: generadores con semilla fija.

Lo que enseñó el despliegue

El primer despliegue en Railway sacó a la luz lo que el entorno local no podía enseñar: los permisos del volumen de archivos, el directorio personal del usuario sin privilegios del contenedor, una migración que no coincidía con el modelo y la necesidad de sembrar la demo al arrancar. Los cuatro se resolvieron en un pull request propio, y la integración continua comprueba que no falte ninguna migración.

Diseño y UX

Interfaz propia con Tailwind CSS 4 y la tipografía Inter servida desde el propio sitio, sin peticiones a terceros. Todos los estados están diseñados: los de carga replican la tarjeta real, los vacíos explican qué irá ahí y ofrecen la acción que los llena, y los diálogos destructivos nombran lo que se pierde. El tema oscuro no es un filtro invertido y respeta el del sistema, y en el teléfono la navegación baja a una barra de cuatro destinos.

Lo que no tiene

Para que nadie lo dé por supuesto: no hay tiempo real —ni WebSockets ni sondeo; los datos se refrescan al volver a pedirlos—, no hay cola de tareas y no hay service worker. El manifiesto hace la web instalable, pero no funciona sin conexión.

Sobre las capturas

Las capturas salen de una instancia local sembrada con un elenco inventado —diez personas con avatares de iniciales generados por código— e imágenes abstractas generadas con Pillow. Los nombres y las cifras que aparecen, como likes o seguidores, no son métricas de nada. La demo pública usa otras cuentas de ejemplo, así que los nombres no coinciden. Las pantallas con algún defecto visible se dejaron fuera.

Probar la demo

La demo está en web-production-9475c.up.railway.app. Entra con ada, grace, linus, margaret, alan, katherine, tim o hedy; la contraseña de todas es network123. La pantalla de acceso ofrece cuatro de ellas con un clic, y también puedes crear tu propia cuenta. La documentación de la API está en Swagger.

Aprendizajes

Reescribir fue más limpio que refactorizar: al borrar primero el monolito, la API se diseñó sin heredar la forma de las plantillas. Y el despliegue enseñó lo que el desarrollo no podía: permisos, usuarios del contenedor y migraciones sólo fallan de verdad en producción.

Estado actual

Terminado y en funcionamiento en la versión 3.1.0: desplegado de forma permanente en Railway sobre PostgreSQL y Redis, con el repositorio público bajo licencia GPL-3.0, la integración continua en verde y 132 pruebas automatizadas —98 de backend y 34 de frontend— que pasan. No tiene usuarios reales, así que no publico cifras de uso: es un proyecto para demostrar cómo construyo.