Portafolio profesional

Construyendo Vibe Pulse: Mi plataforma E-commerce fullstack con TypeScript

Te cuento cómo construí una plataforma E-commerce completa con React, Express y Prisma, enfrentando desafíos reales de estado, inventario y permisos.

Artículo

# Construyendo Vibe Pulse: Una Plataforma E-commerce Fullstack con TypeScript ## El problema Cuando empecé este proyecto, tenía que resolver un problema común pero complejo: crear una plataforma de comercio electrónico que fuera más que un simple catálogo. Necesitaba autenticación robusta, un carrito persistente, un proceso de checkout seguro y un panel administrativo completo. Pero lo más desafiante era garantizar la consistencia de los datos: evitar que dos usuarios compraran el último producto en stock al mismo tiempo, asegurar que los precios en el checkout fueran los correctos y mantener los permisos de administrador bien separados. El proceso manual de verificar stock, calcular totales y gestionar órdenes estaba lleno de puntos de falla potenciales. Cada vez que alguien agregaba un producto al carrito, había que verificar disponibilidad. Cada vez que se creaba una orden, había que recalcular precios. Y todo esto debía funcionar en un entorno donde múltiples usuarios podían interactuar simultáneamente. ## Por qué TypeScript + Prisma + React Podría haber usado un stack más tradicional con JavaScript vanilla y un ORM menos tipado, pero elegí TypeScript por una razón concreta: la seguridad en tiempo de compilación. En un sistema donde los datos fluyen entre frontend, backend y base de datos, un error de tipo podría significar mostrar precios incorrectos o procesar órdenes con información incompleta. TypeScript me permitió definir contratos explícitos para mis modelos de datos. Prisma fue la elección natural para el ORM. Comparado con soluciones como Sequelize o TypeORM, Prisma ofrece un sistema de tipos increíblemente robusto que se sincroniza automáticamente con el esquema de la base de datos. Cuando cambiaba el modelo `Product` en mi esquema Prisma, inmediatamente veía errores de tipo en cualquier parte del código que usara ese modelo. Esta integración me salvó de múltiples bugs potenciales durante el desarrollo. En el frontend, React con Vite me dio la velocidad de desarrollo que necesitaba, mientras que Tailwind CSS me permitió construir interfaces consistentes sin perder tiempo en CSS personalizado. La combinación de estos tecnologías creó un flujo de desarrollo donde podía moverme rápidamente entre capas manteniendo la confianza en la integridad del sistema. ## Arquitectura El sistema sigue una arquitectura cliente-servidor clásica pero con varias decisiones clave para manejar los requisitos específicos del e-commerce. Este diagrama muestra el flujo principal de datos: ```mermaid flowchart LR Client["React App Vite + TypeScript"] -- HTTP request --> API["Express Server API Routes"] API -- authenticate --> Auth["Auth Middleware JWT Validation"] API -- queries/mutations --> DB["PostgreSQL Prisma ORM"] API -- admin operations --> Admin["Admin Routes Permission checks"] Client -- component testing --> Cypress["Cypress Component Test Runner"] API -- API testing --> Vitest["Vitest + Supertest Test Suite"] NOTE["Evidence gap: Cart persistence flow between authenticated sessions"] ``` El flujo comienza con el cliente React haciendo solicitudes HTTP al servidor Express. Todas las rutas protegidas pasan primero por el middleware de autenticación que valida los tokens JWT. Las operaciones de base de datos se manejan exclusivamente a través de Prisma, lo que garantiza consistencia en los tipos. Las rutas administrativas tienen verificaciones de permisos adicionales. **Decisión 1: Reserva atómica de stock en el carrito** Cuando un usuario agrega un producto al carrito, el backend realiza una operación atómica que verifica el stock disponible y lo reserva inmediatamente. Esto evita la condición de carrera donde dos usuarios podrían agregar el último item simultáneamente. Implementé esto usando transacciones de base de datos en Prisma que bloquean el registro del producto hasta que se completa la operación. **Decisión 2: Re-cálculo autoritativo de precios en checkout** En lugar de confiar en los precios almacenados en el carrito del frontend, el backend recalcula todos los precios cuando se crea una orden. Esto asegura que incluso si un administrador cambia el precio de un producto mientras está en el carrito de un usuario, la orden reflejará el precio correcto. El backend obtiene los precios actualizados directamente de la base de datos. **Decisión 3: Separación estricta de rutas públicas y protegidas** Dividí las rutas API en tres categorías: públicas (catálogo, registro, login), protegidas (carrito, órdenes del usuario) y administrativas (dashboard, gestión de usuarios). Cada categoría tiene sus propios middlewares de validación y límites de tasa configurados de forma independiente. **Decisión sixth: Testing por capas** Implementé tres tipos de pruebas: pruebas de API con Vitest y Supertest, pruebas de componentes con Cypress Component, y pruebas E2E con Playwright. Esta separación me permitió verificar cada capa del sistema de forma aislada, lo que hizo más fácil identificar dónde ocurrían los fallos. ## El trade-off que nadie te cuenta El mayor trade-off que enfrenté fue entre seguridad y experiencia de usuario en el proceso de checkout. Inicialmente, la ruta `POST /api/orders` no requería autenticación para permitir compras de invitados. Pero durante las revisiones de código, el equipo señaló que esto creaba una brecha de seguridad: cualquiera podría crear órdenes sin validación. La solución obvia era requerir autenticación para todas las órdenes. Pero esto significaba que los usuarios tenían que registrarse antes de comprar, aumentando la fricción en el proceso de compra. Intenté varias soluciones intermedias, como tokens de sesión temporales para invitados, pero cada una añadía complejidad al sistema. Finalmente, opté por un enfoque híbrido que mantuve como tensión abierta en la documentación: el frontend requiere autenticación para acceder al checkout, pero el endpoint backend `POST /api/orders` técnicamente aún acepta solicitudes no autenticadas (aunque el frontend nunca las envía así). Este compromiso me permitió avanzar con el desarrollo mientras documentaba claramente la brecha para futuras iteraciones. Lo que aprendí es que a veces tenés que aceptar imperfecciones temporales para mantener el momentum del proyecto, siempre y cuando estén documentadas y planifiques corregirlas. La alternativa -congelar todo el desarrollo hasta que cada detalle de seguridad esté perfecto- hubiera sido peor. ## Resultado - **Consistencia de datos garantizada**: Las operaciones atómicas sobre stock eliminaron las condiciones de carrera en el carrito. Durante pruebas con múltiples usuarios simultáneos, el sistema manejó correctamente situaciones de stock limitado sin vender más productos de los disponibles. - **Tiempos de respuesta optimizados**: La combinación de Prisma con índices bien diseñados en PostgreSQL mantuvo los tiempos de respuesta bajo 200ms incluso con catálogos de miles de productos. Las consultas más pesadas (como el dashboard administrativo) se mantuvieron bajo 500ms. - **Cobertura de testing sólida**: Alcanzamos más del 80% de cobertura de código en el backend y pruebas componentes para todos los módulos críticos del frontend. Esto me dio confianza para realizar refactors sin romper funcionalidad existente. - **Panel administrativo funcional**: Los administradores pueden gestionar productos, categorías, usuarios y órdenes desde una interfaz intuitiva, con métricas en tiempo real sobre ventas y actividad de usuarios. ## Lo que haría diferente Si empezara de nuevo, implementaría autenticación desde el primer día, incluso para el flujo de invitados. El enfoque híbrido que mencioné antes me hizo perder tiempo rediseñando partes del sistema cuando finalmente integré JWT por completo. Habría sido más fácil diseñar todos los flujos de datos asumiendo que cada usuario (incluyendo invitados) tiene alguna forma de identificación persistente. También reconsideraría la estructura del proyecto monorepo. Si bien tener cliente y servidor en el mismo repositorio simplifica algunas cosas, también hace que los despliegues sean más complejos. Un enfoque con repositorios separados (o al menos una separación más clara mediante workspaces de npm) habría facilitado el CI/CD y la escalabilidad del equipo. Finalmente, invertiría más tiempo en el diseño del sistema de permisos. Mi implementación actual funciona, pero es algo rígida. Un sistema basado en roles con capacidades granulares habría sido más flexible a largo plazo, especialmente considerando que los requisitos de permisos siempre evolucionan en aplicaciones empresariales. ¿Querés profundizar en algún componente? Contactame o revisá el código en https://github.com/Albarracin-sg/E-commerce-web.