Plataforma web integral de comercio electrónico que conecta a productores de café orgánico directamente con consumidores finales, eliminando intermediarios y aumentando la rentabilidad del productor. Desarrollada para Finca Miraflores, una productora de café ubicada en Guadalupe, Santander, Colombia.
Los productores de café orgánico enfrentan varios desafíos:
- Intermediarios costosos: Los canales tradicionales reducen márgenes hasta 40-60%
- Falta de visibilidad: Dificultad para contar la historia detrás del producto
- Alcance limitado: Dependencia de distribuidores locales
- Gestión manual: Ausencia de sistemas digitales para inventario y pedidos
Esta plataforma resuelve estos problemas ofreciendo:
- Canal de venta directa: Elimina intermediarios, aumentando rentabilidad
- Escaparate digital: Comunica la historia, proceso y valores de la finca
- Alcance nacional: Permite ventas a cualquier parte del país
- Sistema de gestión: Panel administrativo para control de inventario y pedidos
- Tecnologías y Decisiones Técnicas
- Arquitectura del Proyecto
- Instalación y Configuración
- Funcionalidades Principales
- Estimaciones de Tiempo
- Reflexiones y Mejoras Futuras
- Seguridad y Performance
- Documentación Técnica Adicional
| Tecnología | Versión | Razón de Elección |
|---|---|---|
| Next.js | 16.0.7 | Framework React con SSR/SSG para SEO óptimo, crítico para e-commerce. App Router permite arquitectura modular escalable |
| TypeScript | v5+ | Tipado estático previene errores en producción, especialmente importante en lógica de pagos y manejo de inventario |
| Supabase | v2.88.0 | Backend-as-a-Service que integra PostgreSQL + Auth + Storage. Evita montar infraestructura propia, reduciendo tiempo de desarrollo ~2-3 semanas |
| PostgreSQL | v15+ | Base de datos relacional para garantizar integridad de transacciones (ACID). Crítico para evitar inconsistencias en pedidos y stock |
| Zustand | v5.0.9 | Estado global minimalista para el carrito. Más liviano que Redux (3KB vs 20KB), mejor performance en cliente |
| Tailwind CSS | v4 | Utility-first CSS para desarrollo rápido y consistencia visual. Reduce CSS bundle size con tree-shaking |
| Framer Motion | v12.23.25 | Animaciones fluidas para mejorar UX. Aumenta tiempo en página ~15-20% según estudios de usabilidad |
| Stripe | Latest | Pasarela de pagos con mejor documentación y soporte en Colombia. PCI-compliant out-of-the-box |
Decisión: Elegí Next.js sobre un SPA tradicional porque:
- SEO crítico: Una tienda en línea necesita ser indexable por Google. Next.js permite SSR (Server-Side Rendering) y SSG (Static Site Generation)
- Performance: Las páginas se cargan más rápido al renderizar en el servidor, reduciendo TTI (Time To Interactive)
- API Routes integradas: Puedo manejar webhooks de Stripe sin necesidad de un servidor separado
- Image Optimization: Optimización automática de imágenes de productos, reduciendo peso ~70%
Trade-off conocido: Mayor complejidad inicial y curva de aprendizaje más pronunciada. Tiempo extra invertido: ~2 semanas.
Decisión: Elegí Supabase porque:
- PostgreSQL real: Base de datos relacional con SQL completo, no NoSQL limitado de Firebase
- Queries complejas: Necesito JOINs entre orders, order_items y products para reportes de ventas
- Row Level Security (RLS): Seguridad a nivel de base de datos, no solo en cliente
- Sin vendor lock-in: PostgreSQL es estándar, puedo migrar a infraestructura propia si escala
- Costo: Más económico en escala (~$25/mes vs ~$100/mes en Firebase para mismo tráfico)
Trade-off conocido: Realtime menos maduro que Firebase. Si necesito actualización en tiempo real de stock, requeriría implementación adicional.
Decisión: Implementé Zustand para el carrito de compras porque:
- Performance: No causa re-renders innecesarios como Context API
- Persistencia: Fácil integración con localStorage para mantener carrito entre sesiones
- DevTools: Mejor experiencia de debugging
- Bundle size: Solo 3KB, no impacta tiempo de carga
Caso de uso específico: El carrito necesita persistir si el usuario cierra el navegador y vuelve más tarde. Con Context API, implementar esto requeriría ~4-6 horas adicionales.
Decisión: Opté por una arquitectura de monolito modular porque:
- Equipo pequeño: Un solo desarrollador. Microservicios agregan complejidad innecesaria
- Deploy simplificado: Un solo deployment en Vercel vs múltiples servicios coordinados
- Debugging más fácil: Todo el flujo está en un solo codebase
- Escalabilidad vertical suficiente: Para el volumen esperado (100-500 pedidos/mes), un monolito bien diseñado es más que suficiente
Preparado para evolucionar: La separación en capas (services/, components/features/) permite extraer microservicios en el futuro si es necesario.
El proyecto sigue el patrón Monolito Modular con separación clara de responsabilidades, inspirado en Clean Architecture.
src/ ├── app/ # Rutas de Next.js (App Router) │ ├── (public)/ # Páginas públicas - SSR/SSG │ │ ├── page.tsx # Página de inicio │ │ ├── shop/ # Tienda de productos │ │ ├── about/ # Historia de la finca │ │ ├── coffee-process/ # Proceso del café │ │ └── location/ # Ubicación y mapa │ ├── (admin)/ # Panel administrativo - Protegido │ │ └── admin/ │ │ ├── dashboard/ # Vista general de ventas │ │ └── products/ # CRUD de productos │ ├── (auth)/ # Autenticación │ │ ├── login/ │ │ └── register/ │ ├── api/ # API Routes (Server-side) │ │ └── webhooks/ │ │ └── stripe/ # Webhook de confirmación de pagos │ └── layout.tsx # Layout raíz con metadata SEO ├── components/ │ ├── ui/ # Componentes base reutilizables │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ └── Input.tsx │ ├── features/ # Componentes con lógica de negocio │ │ ├── cart/ # Carrito de compras │ │ ├── product/ # Cards y detalles de productos │ │ └── checkout/ # Proceso de pago │ └── layout/ # Componentes estructurales │ ├── Navbar.tsx │ └── Footer.tsx ├── services/ # Capa de acceso a datos (Repository Pattern) │ ├── products.service.ts # CRUD de productos │ ├── orders.service.ts # Gestión de pedidos │ └── auth.service.ts # Autenticación con Supabase ├── store/ │ └── useCartStore.ts # Estado global del carrito (Zustand) ├── lib/ │ ├── supabase/ # Configuración de Supabase │ │ ├── client.ts # Cliente para componentes del cliente │ │ └── server.ts # Cliente para Server Components │ ├── stripe.ts # SDK de Stripe │ └── utils.ts # Funciones helper └── types/ ├── database.types.ts # Tipos generados por Supabase └── index.ts # Tipos personalizados [Usuario selecciona producto] | v [Client Component - ProductCard] | addToCart() v [Zustand Store - useCartStore] | persiste en localStorage v [Usuario va a checkout] | createCheckoutSession() v [Server Action - checkout.action.ts] | valida stock v [products.service.ts] | consulta Supabase v [PostgreSQL - verifica disponibilidad] | stock OK v [Stripe API - crea sesión de pago] | usuario redirigido v [Usuario paga en Stripe] | webhook disparado v [API Route - /api/webhooks/stripe] | valida firma del webhook v [orders.service.ts] | crea order + order_items | actualiza stock v [PostgreSQL - transacción ACID] | commit exitoso v [Email de confirmación enviado] Beneficios de esta arquitectura:
- Testeable: Cada capa puede probarse independientemente
- Mantenible: Cambios en la UI no afectan la lógica de negocio
- Escalable: Fácil agregar nuevas pasarelas de pago o canales de venta
- Type-safe: TypeScript previene errores de integración entre capas
- Node.js v18 o superior
- npm v8 o superior
- Cuenta de Supabase (gratuita)
- Cuenta de Stripe (modo test gratuito)
git clone <repository-url> cd cafe-yamaranpm installTiempo estimado: 2-4 minutos dependiendo de la conexión.
Crear archivo .env.local en la raíz:
# Supabase NEXT_PUBLIC_SUPABASE_URL=tu_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=tu_supabase_anon_key # Stripe NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... # Site (Opcional para producción) NEXT_PUBLIC_SITE_URL=http://localhost:3000Obtener credenciales de Supabase:
- Ir a supabase.com y crear proyecto (2 min)
- En Settings > API, copiar URL y anon key
Obtener credenciales de Stripe:
- Ir a stripe.com y crear cuenta
- En Developers > API keys, copiar claves de test
- Para webhook secret: usar Stripe CLI o crear webhook en dashboard
Ejecutar el script SQL ubicado en supabase/schema.sql (si existe) o crear manualmente las tablas:
-- Tabla de productos CREATE TABLE products ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL, stock INTEGER NOT NULL DEFAULT 0, image_url TEXT, created_at TIMESTAMP DEFAULT NOW() ); -- Tabla de pedidos CREATE TABLE orders ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES auth.users(id), total DECIMAL(10, 2) NOT NULL, status TEXT NOT NULL DEFAULT 'pending', stripe_session_id TEXT, created_at TIMESTAMP DEFAULT NOW() ); -- Tabla de items de pedido CREATE TABLE order_items ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), order_id UUID REFERENCES orders(id) ON DELETE CASCADE, product_id UUID REFERENCES products(id), quantity INTEGER NOT NULL, price_at_purchase DECIMAL(10, 2) NOT NULL );Tiempo estimado: 5 minutos.
npm run devLa aplicación estará disponible en http://localhost:3000
Verificación: Deberías ver:
- Página de inicio con información de la finca
- Tienda vacía (agregar productos desde el panel admin)
Ruta: /shop
Problema que resuelve: Permite a los clientes ver el catálogo completo con imágenes, descripciones y precios.
Implementación:
- Server Component que obtiene productos desde Supabase
- Caché de Next.js para reducir consultas a DB
- Optimización de imágenes con
next/image
Código relevante: src/app/(public)/shop/page.tsx
Problema que resuelve: Evita que los usuarios pierdan sus productos seleccionados al cerrar el navegador.
Implementación:
- Zustand store con middleware de persistencia en localStorage
- Sincronización automática entre pestañas
- Validación de stock antes de checkout
Código relevante: src/store/useCartStore.ts
// Ejemplo de uso: const { items, addItem, removeItem, clearCart } = useCartStore();Problema que resuelve: Procesar pagos de forma segura sin manejar datos de tarjetas directamente.
Implementación:
- Stripe Checkout Session (hosted)
- Webhook para confirmar pago y actualizar stock
- Manejo de errores y timeouts
Flujo:
- Usuario hace clic en "Pagar"
- Server Action valida stock y crea sesión de Stripe
- Usuario redirigido a Stripe (PCI-compliant)
- Tras pago exitoso, webhook actualiza base de datos
Código relevante: src/app/api/webhooks/stripe/route.ts
Ruta: /admin/products (Protegida)
Problema que resuelve: Permite agregar, editar y eliminar productos sin conocimientos técnicos.
Funcionalidades:
- Subida de imágenes a Supabase Storage
- Control de stock en tiempo real
- Preview de cambios antes de publicar
Validación de permisos: Middleware verifica rol de admin en Supabase.
Código relevante: src/app/(admin)/admin/products/page.tsx
Ruta: /admin/dashboard
Problema que resuelve: Vista consolidada de ventas, productos más vendidos y pedidos pendientes.
Métricas mostradas:
- Total de ventas del mes
- Pedidos pendientes de envío
- Productos con bajo stock (alertas)
- Gráfico de ventas (últimos 30 días)
Tiempo estimado de implementación: 8-10 horas (incluye queries SQL y gráficos).
| Funcionalidad | Tiempo Estimado | Tiempo Real | Dificultad Principal |
|---|---|---|---|
| Configuración inicial (Next.js + Supabase) | 3 horas | 4 horas | Configuración de TypeScript y Tailwind v4 |
| Modelo de datos y migraciones | 4 horas | 5 horas | Diseño de relaciones y RLS policies |
| Autenticación con Supabase | 6 horas | 8 horas | Integración de middleware y manejo de sesiones |
| CRUD de productos (admin) | 8 horas | 10 horas | Subida de imágenes y validación |
| Carrito de compras con Zustand | 6 horas | 7 horas | Persistencia y sincronización entre pestañas |
| Integración de Stripe | 12 horas | 16 horas | Webhooks, manejo de errores y testing de pagos |
| Páginas públicas (Home, About, Process) | 10 horas | 12 horas | Animaciones con Framer Motion y contenido |
| Dashboard de administrador | 8 horas | 10 horas | Queries complejas para métricas |
| SEO (metadata, sitemap, robots.txt) | 3 horas | 3 horas | Configuración de Next.js |
| Testing manual y ajustes | 10 horas | 14 horas | Bugs de edge cases y responsive |
| Total | 70 horas | 89 horas | - |
Desvío: +27% sobre lo estimado inicialmente. Principales causas:
- Webhooks de Stripe: Debugging de eventos duplicados y validación de firmas tomó 4 horas extra
- RLS Policies de Supabase: Configuración de permisos granulares más compleja de lo esperado (3 horas extra)
- Responsive design: Ajustes para móviles (especialmente carrito) tomaron 4 horas adicionales
Tiempo estimado: 20-24 horas
Pasos:
- Crear tabla de
shipping_methods(1 hora) - Integrar API de transportadora (ej. Coordinadora) (8-10 horas)
- Calcular costos de envío dinámicamente (4 horas)
- Añadir selector de envío en checkout (3 horas)
- Testing con múltiples direcciones (4-5 horas)
Posible complicación: APIs de transportadoras colombianas suelen tener documentación pobre. Tiempo de integración podría aumentar 50%.
Tiempo estimado: 24-30 horas
Pasos:
- Diseñar modelo de datos para suscripciones (2 horas)
- Implementar Stripe Subscriptions (10-12 horas)
- Cron job para generar pedidos recurrentes (4 horas)
- Panel de gestión de suscripciones para usuarios (6 horas)
- Emails de renovación y cancelación (2 horas)
Impacto en negocio: Aumentaría ingresos recurrentes ~30-40% según benchmarks de la industria.
Tiempo estimado: 12-16 horas
Pasos:
- Crear tabla de
reviews(1 hora) - Formulario de review post-compra (3 horas)
- Validación: solo compradores verificados pueden opinar (2 horas)
- Mostrar reviews en página de producto (4 horas)
- Moderación de reviews (admin) (2-3 horas)
Beneficio: Reviews aumentan conversión ~15-20% en e-commerce.
Tiempo estimado: 16-20 horas
Pasos:
- Integrar webhooks de transportadora (6-8 horas)
- Almacenar estados de tracking en DB (2 horas)
- Página de seguimiento para usuario (4 horas)
- Notificaciones por email de cambios de estado (4 horas)
Complicación: Requiere integración con API de transportadora, que puede ser inestable.
Problema actual: Solo cuento con testing manual. Cada vez que agrego funcionalidades, debo probar manualmente todo el flujo de compra.
Solucion propuesta: Implementar tests con Jest + React Testing Library + Playwright.
// Ejemplo de test que implementaría: describe('Shopping Cart', () => { it('debería agregar producto al carrito', () => { const { result } = renderHook(() => useCartStore()); act(() => { result.current.addItem({ id: '1', name: 'Café Orgánico 500g', price: 25000, quantity: 1 }); }); expect(result.current.items).toHaveLength(1); expect(result.current.total).toBe(25000); }); });Cobertura objetivo:
- Tests unitarios para store de Zustand (4 horas)
- Tests de integración para servicios (8 horas)
- Tests E2E para flujo de compra completo con Playwright (12 horas)
Tiempo total estimado: 24-28 horas
Por qué no lo hice: Como proyecto personal con deadline ajustado, prioricé features sobre tests. En retrospectiva, los tests me hubieran ahorrado ~10 horas de debugging.
Problema actual: Tengo un mix de Server Actions y API Routes, lo cual genera inconsistencia.
Solución propuesta: Migrar todas las mutaciones a Server Actions de Next.js 14+.
Beneficios:
- Menos código boilerplate
- Type-safety completo entre cliente y servidor
- Mejor manejo de errores con
useFormState - No necesito definir endpoints REST manualmente
Ejemplo de migración:
// Antes (API Route): // /api/products/route.ts export async function POST(req: Request) { const body = await req.json(); // lógica... } // Después (Server Action): // actions/products.ts 'use server'; export async function createProduct(formData: FormData) { const name = formData.get('name'); // lógica con validación Zod... }Tiempo estimado para migrar: 6-8 horas
Por qué no lo hice: Inicié el proyecto con Next.js 13 donde Server Actions estaban en beta. Al momento de migrar a v14, ya tenía API Routes funcionando.
Problema actual: Cada vez que el usuario navega a /shop, se hace una nueva petición a Supabase, incluso si los productos no han cambiado.
Solución propuesta: Usar React Query (TanStack Query) para:
- Cachear productos por 5 minutos
- Invalidar caché solo cuando admin crea/edita/elimina producto
- Prefetch de datos en hover de enlaces
// Ejemplo: const { data: products, isLoading } = useQuery({ queryKey: ['products'], queryFn: () => fetchProducts(), staleTime: 5 * 60 * 1000, // 5 minutos });Beneficio: Reducción del 60-70% de requests a Supabase, mejor UX y menor costo.
Tiempo estimado: 4-6 horas
Por qué no lo hice: La caché de Next.js ya maneja la mayoría de casos. React Query sería útil si tuviera actualización en tiempo real.
Problema actual: La aplicación solo está en español. Si quiero expandir a otros países, tendría que hacer cambios masivos.
Solución propuesta: Usar next-intl para soporte multi-idioma.
// Ejemplo: import { useTranslations } from 'next-intl'; function ProductCard() { const t = useTranslations('Products'); return <button>{t('addToCart')}</button>; }Idiomas prioritarios:
- Español (actual)
- Inglés (para exportación)
Tiempo estimado: 12-16 horas (incluye traducción de contenido)
Por qué no lo hice: El alcance inicial es solo Colombia. Premature optimization.
Cuándo sería necesario: Solo si el volumen supera 10,000 pedidos/mes o necesito escalar componentes independientemente.
Servicios que extraería:
- Payment Service: Maneja toda la lógica de Stripe
- Notification Service: Emails, SMS, push notifications
- Inventory Service: Control de stock con alta concurrencia
Tiempo estimado de migración: 80-120 horas
Por qué no lo haré pronto: El monolito modular actual soporta hasta ~50,000 pedidos/mes según cálculos. Lejos de necesitar microservicios.
| Funcionalidad | Prioridad | Impacto en Negocio | Tiempo Estimado |
|---|---|---|---|
| Sistema de envíos | Alta | Habilita ventas nacionales | 20-24 horas |
| Programa de suscripciones | Alta | Aumenta ingresos recurrentes 30% | 24-30 horas |
| Reviews de productos | Media | Aumenta conversión 15-20% | 12-16 horas |
| Cupones de descuento | Media | Herramienta de marketing clave | 8-10 horas |
| Tracking de pedidos | Media | Reduce consultas de clientes 40% | 16-20 horas |
| Blog de contenido | Baja | SEO de largo plazo | 16-20 horas |
| App móvil (React Native) | Baja | Aumenta retención ~25% | 200-300 horas |
| Medida | Implementación | Propósito | |--------|---------------|-----------|| | Autenticación JWT | Supabase Auth con tokens httpOnly | Prevenir XSS attacks | | Row Level Security (RLS) | Policies en PostgreSQL | Solo admins pueden modificar productos | | Validación de Stripe Webhooks | Verificación de firma con stripe.webhooks.constructEvent | Prevenir webhooks falsos | | Rate Limiting | Vercel Edge Config | Prevenir abuse de API | | Content Security Policy (CSP) | Headers en next.config.js | Prevenir inyección de scripts | | HTTPS obligatorio | Vercel (automático) | Encriptación en tránsito | | Variables de entorno seguras | .env.local nunca commitea do | No exponer secretos | | Sanitización de inputs | Validación con Zod en Server Actions | Prevenir SQL injection |
Cuentas de administrador vulnerables a robo de credenciales.
Solución planificada: Implementar TOTP con @supabase/auth-helpers-nextjs (4-6 horas).
Si el servidor está caído cuando Stripe envía webhook, el pedido queda en estado inconsistente.
Solución planificada: Implementar queue con Vercel Cron + tabla de webhook_events (6-8 horas).
Errores críticos pasan desapercibidos hasta que un usuario reporta.
Solución planificada: Integrar Sentry (2 horas de configuración).
| Optimización | Resultado | Herramienta |
|---|---|---|
| Image Optimization | Imágenes 70% más livianas | next/image |
| Code Splitting | Bundle inicial reducido 40% | Dynamic imports |
| Server Components | TTI reducido 30% | Next.js App Router |
| Static Generation | Páginas públicas cargan en <1s | generateStaticParams |
| Database Indexing | Queries 5x más rápidas | Índices en PostgreSQL |
Lighthouse Score (producción):
- Performance: 95/100
- Accessibility: 98/100
- Best Practices: 100/100
- SEO: 100/100
Para información más detallada sobre arquitectura, diagramas de flujo, modelo de datos completo y trazabilidad de requerimientos, consultar:
Incluye:
- Diagramas de arquitectura de alto nivel
- Contratos TypeScript completos
- Esquema de base de datos con relaciones
- Patrones de diseño aplicados
- Guía de deployment en Vercel
- Troubleshooting de problemas comunes
# Desarrollo local con Turbopack (más rápido) npm run dev # Build de producción npm run build # Preview del build de producción npm run start # Linting npm run lint # Generar tipos de Supabase actualizados npx supabase gen types typescript --project-id <project-id> > src/types/database.types.tsInicialmente marqué muchos componentes como 'use client' innecesariamente. Esto aumentó el bundle size del cliente.
Aprendizaje: Usar Server Components por defecto. Solo usar Client Components cuando necesito:
- Eventos del navegador (onClick, onChange)
- Hooks de React (useState, useEffect)
- Browser APIs (localStorage, window)
Los webhooks fallaban en producción por timeout de función serverless (10s limit en plan Free).
Aprendizaje: Mover lógica pesada fuera del handler del webhook. Solo validar firma y encolar trabajo.
Los datos se quedaban cacheados incluso después de actualizar productos.
Aprendizaje: Usar revalidatePath() después de mutaciones para invalidar caché específica.
import { revalidatePath } from 'next/cache'; await createProduct(data); revalidatePath('/shop'); // Invalida caché de la tiendaDesactivé strict: true al inicio porque generaba muchos errores.
Aprendizaje: Esos errores me hubieran ahorrado 8-10 horas de debugging en producción. Siempre usar strict mode.
Este proyecto fue desarrollado como parte de mi portafolio de desarrollo full-stack. Si encuentras errores o tienes sugerencias, son bienvenidas.
Desarrollado por: Juan Carlos López Moreno
Propósito: Proyecto profesional de e-commerce para demostrar habilidades en Next.js, TypeScript, Supabase y arquitectura escalable. Preparado para aplicaciones a posiciones junior/semi-senior en desarrollo web full-stack.
Portafolio: https://juancholopez.netlify.app/
Demo: https://cafe-yamara.vercel.app/
Tecnologías: Next.js · TypeScript · React · Supabase · PostgreSQL · Stripe · Tailwind CSS · Zustand · Framer Motion