LogoLupe

E-commerce con Next.js

Descripción del proyecto

Una tienda en línea completa construida con Next.js y MongoDB. Este proyecto demuestra cómo crear una aplicación e-commerce escalable con características modernas como carrito de compras persistente, pagos en línea y panel de administración.

Características principales

  • Catálogo de productos con filtros y búsqueda
  • Carrito de compras con persistencia
  • Proceso de checkout con Stripe
  • Panel de administración
  • Gestión de inventario
  • Sistema de reseñas y valoraciones
  • Autenticación de usuarios
  • Historial de pedidos

Stack tecnológico

Frontend

  • Next.js 14 con App Router
  • Tailwind CSS para estilos
  • Zustand para gestión de estado
  • React Query para caché y fetching

Backend

  • API Routes de Next.js
  • MongoDB para base de datos
  • Stripe para pagos
  • NextAuth.js para autenticación

Herramientas

  • TypeScript para tipado
  • ESLint y Prettier
  • Jest para testing
  • GitHub Actions para CI/CD

Ejemplos de código

Modelo de Producto

// models/product.js
import mongoose from 'mongoose';

const productSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'El nombre del producto es requerido'],
    trim: true,
    maxLength: [100, 'El nombre no puede exceder 100 caracteres']
  },
  description: {
    type: String,
    required: [true, 'La descripción del producto es requerida'],
    maxLength: [2000, 'La descripción no puede exceder 2000 caracteres']
  },
  price: {
    type: Number,
    required: [true, 'El precio es requerido'],
    maxLength: [8, 'El precio no puede exceder 8 caracteres'],
    default: 0.0
  },
  ratings: {
    type: Number,
    default: 0
  },
  images: [
    {
      public_id: {
        type: String,
        required: true
      },
      url: {
        type: String,
        required: true
      }
    }
  ],
  category: {
    type: String,
    required: [true, 'Seleccione la categoría del producto'],
    enum: {
      values: [
        'Electrónicos',
        'Ropa',
        'Alimentos',
        'Libros',
        'Belleza',
        'Deportes',
        'Hogar'
      ],
      message: 'Seleccione una categoría válida'
    }
  },
  seller: {
    type: String,
    required: [true, 'Ingrese el vendedor del producto']
  },
  stock: {
    type: Number,
    required: [true, 'Ingrese el stock del producto'],
    maxLength: [5, 'El stock no puede exceder 5 caracteres'],
    default: 0
  },
  numOfReviews: {
    type: Number,
    default: 0
  },
  reviews: [
    {
      user: {
        type: mongoose.Schema.ObjectId,
        ref: 'User',
        required: true
      },
      name: {
        type: String,
        required: true
      },
      rating: {
        type: Number,
        required: true
      },
      comment: {
        type: String,
        required: true
      }
    }
  ],
  createdAt: {
    type: Date,
    default: Date.now
  }
});

export default mongoose.models.Product || mongoose.model('Product', productSchema);

Lógica del Carrito

// store/cartSlice.js
import { createSlice } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: {
    items: [],
    totalQuantity: 0,
    totalAmount: 0,
  },
  reducers: {
    addToCart(state, action) {
      const newItem = action.payload;
      const existingItem = state.items.find(item => item.id === newItem.id);
      state.totalQuantity++;
      
      if (!existingItem) {
        state.items.push({
          id: newItem.id,
          price: newItem.price,
          quantity: 1,
          totalPrice: newItem.price,
          name: newItem.name,
          image: newItem.image
        });
      } else {
        existingItem.quantity++;
        existingItem.totalPrice = existingItem.totalPrice + newItem.price;
      }
      
      state.totalAmount = state.items.reduce(
        (total, item) => total + item.price * item.quantity,
        0
      );
    },
    
    removeFromCart(state, action) {
      const id = action.payload;
      const existingItem = state.items.find(item => item.id === id);
      state.totalQuantity--;
      
      if (existingItem.quantity === 1) {
        state.items = state.items.filter(item => item.id !== id);
      } else {
        existingItem.quantity--;
        existingItem.totalPrice = existingItem.totalPrice - existingItem.price;
      }
      
      state.totalAmount = state.items.reduce(
        (total, item) => total + item.price * item.quantity,
        0
      );
    },
    
    clearCart(state) {
      state.items = [];
      state.totalQuantity = 0;
      state.totalAmount = 0;
    }
  }
});

export const cartActions = cartSlice.actions;
export default cartSlice.reducer;

// Uso en un componente
function ProductItem() {
  const dispatch = useDispatch();
  
  const addToCartHandler = () => {
    const productData = {
      id: '123',
      name: 'Producto Ejemplo',
      price: 29.99,
      image: '/images/product.jpg'
    };
    
    dispatch(cartActions.addToCart(productData));
  };
  
  return (
    <div>
      <h2>Producto Ejemplo</h2>
      <p>$29.99</p>
      <button onClick={addToCartHandler}>
        Agregar al carrito
      </button>
    </div>
  );
}

Recursos adicionales

Para comenzar con este proyecto, puedes: