Skip to main content

Overview

The product management system in Mis Compras allows sellers to create, edit, and manage their product listings. Products are organized by categories and include images, descriptions, pricing, and seller information.

Key Features

Product Creation

Publish new products with images and details

Category Organization

Organize products into categories like Laptops and Smartphones

Image Upload

Upload product images with automatic file management

Seller Attribution

Products are linked to seller accounts

Database Schema

Products Table

CREATE TABLE `productos` (
  `id_producto` int NOT NULL AUTO_INCREMENT,
  `nombre` varchar(100) NOT NULL,
  `descripcion` text,
  `precio` decimal(10,2) NOT NULL,
  `imagen` varchar(255) DEFAULT NULL,
  `id_vendedor` int DEFAULT NULL,
  `id_categoria` int DEFAULT NULL,
  `fecha_publicacion` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id_producto`),
  FOREIGN KEY (`id_vendedor`) REFERENCES `usuarios` (`id_usuario`),
  FOREIGN KEY (`id_categoria`) REFERENCES `categorias` (`id_categoria`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Categories Table

CREATE TABLE `categorias` (
  `id_categoria` int NOT NULL AUTO_INCREMENT,
  `nombre` varchar(100) NOT NULL,
  `descripcion` text,
  PRIMARY KEY (`id_categoria`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Publishing Products

Product Creation Flow

1

Seller authentication

User must be logged in to publish products
2

Form submission

Product details and image are submitted via multipart form
3

Image processing

Image is validated, renamed uniquely, and saved to server
4

Database insertion

Product record is created with seller ID and category

Backend Implementation

The product publishing endpoint (php/vender.php) handles product creation:
php/vender.php
<?php
session_start();
include '../conexion.php';
header("Content-Type: application/json");

$id_vendedor = $_SESSION['id_usuario'] ?? $_POST['id_vendedor'] ?? null;

if (!$id_vendedor) {
  echo json_encode([
    "success" => false, 
    "message" => "No hay sesión activa. Inicia sesión para publicar."
  ]);
  exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $nombre = trim($_POST['nombre'] ?? '');
  $descripcion = trim($_POST['descripcion'] ?? '');
  $precio = $_POST['precio'] ?? '';
  $categoria = $_POST['categoria'] ?? null;

  if (empty($nombre) || empty($descripcion) || $precio === '') {
    echo json_encode([
      "success" => false, 
      "message" => "⚠️ Todos los campos son obligatorios."
    ]);
    exit;
  }

  if (!isset($_FILES['imagen']) || $_FILES['imagen']['error'] !== UPLOAD_ERR_OK) {
    echo json_encode([
      "success" => false, 
      "message" => "⚠️ Debes subir una imagen válida."
    ]);
    exit;
  }

  // Generate unique filename
  $originalName = $_FILES['imagen']['name'];
  $ext = pathinfo($originalName, PATHINFO_EXTENSION);
  $uniqueName = time() . '_' . bin2hex(random_bytes(6)) . '.' . $ext;
  $rutaDestino = "../imagenes/" . $uniqueName;

  if (move_uploaded_file($_FILES['imagen']['tmp_name'], $rutaDestino)) {
    $id_categoria = is_numeric($categoria) && intval($categoria) > 0 ? intval($categoria) : null;

    if ($id_categoria !== null) {
      $sql = "INSERT INTO productos (nombre, descripcion, precio, imagen, id_vendedor, id_categoria, fecha_publicacion)
              VALUES (?, ?, ?, ?, ?, ?, NOW())";
      $stmt = $conn->prepare($sql);
      $stmt->bind_param("ssdsii", $nombre, $descripcion, $precio, $uniqueName, $id_vendedor, $id_categoria);
    } else {
      $sql = "INSERT INTO productos (nombre, descripcion, precio, imagen, id_vendedor, fecha_publicacion)
              VALUES (?, ?, ?, ?, ?, NOW())";
      $stmt = $conn->prepare($sql);
      $stmt->bind_param("ssdsi", $nombre, $descripcion, $precio, $uniqueName, $id_vendedor);
    }

    if ($stmt->execute()) {
      echo json_encode([
        "success" => true, 
        "message" => "✅ Producto publicado correctamente."
      ]);
    } else {
      echo json_encode([
        "success" => false, 
        "message" => "❌ Error al guardar el producto."
      ]);
    }
  }
}
?>
Image files are renamed using a timestamp and random hex string to prevent naming conflicts and ensure uniqueness.

Displaying Products

Loading Products by Category

The frontend loads products organized by category using productos.js:
productos.js
document.addEventListener('DOMContentLoaded', () => {
  const cont = document.getElementById('productos-por-categoria');
  
  async function cargar() {
    try {
      const resp = await fetch('php/obtener_productos_por_categoria.php');
      const data = await resp.json();

      if (!data || !data.exito || !data.categorias) {
        cont.innerHTML = '<p>No se pudieron cargar los productos.</p>';
        return;
      }

      render(data.categorias);
    } catch (err) {
      console.error(err);
      cont.innerHTML = '<p>Error cargando productos.</p>';
    }
  }

  function render(categorias) {
    cont.innerHTML = '';

    categorias.forEach(cat => {
      if (!cat.productos || cat.productos.length === 0) return;

      const section = document.createElement('section');
      section.className = 'categoria-section';

      const h2 = document.createElement('h2');
      h2.className = 'categoria-titulo';
      h2.textContent = cat.categoria;
      section.appendChild(h2);

      const grid = document.createElement('div');
      grid.className = 'grilla-productos categorias';

      cat.productos.forEach(p => {
        const card = document.createElement('div');
        card.className = 'tarjeta-producto';

        const img = document.createElement('img');
        img.src = p.imagen ? `imagenes/${p.imagen}` : 'imagenes/default-product.svg';
        img.alt = p.nombre;

        const nombre = document.createElement('h3');
        nombre.textContent = p.nombre;

        const precio = document.createElement('p');
        precio.className = 'precio-destacado';
        precio.textContent = `$${parseFloat(p.precio).toFixed(2)}`;

        const vendedor = document.createElement('p');
        vendedor.className = 'vendedor-nombre';
        if (p.id_vendedor) {
          vendedor.innerHTML = `Publicado por <a href="vendedor.html?id=${p.id_vendedor}"><strong>${p.vendedor}</strong></a>`;
        }

        const ver = document.createElement('a');
        ver.className = 'boton-pequeno';
        ver.href = `producto.html?id=${p.id_producto}`;
        ver.textContent = 'Ver';

        card.appendChild(img);
        card.appendChild(nombre);
        card.appendChild(precio);
        card.appendChild(vendedor);
        card.appendChild(ver);

        grid.appendChild(card);
      });

      section.appendChild(grid);
      cont.appendChild(section);
    });
  }

  cargar();
});

Product Card Structure

Each product is displayed as a card with:
  • Product image with fallback to default image
  • Product name as heading
  • Price formatted with 2 decimal places
  • Seller information with link to seller profile
  • View button linking to product detail page

Filtering User’s Products

Users can filter to view only their own products:
productos.js
function aplicarFiltroSoloMios(activo) {
  const idUsuario = localStorage.getItem('id_usuario');
  
  if (activo) {
    if (!idUsuario) {
      mensaje.textContent = 'Inicia sesión para ver tus productos';
      filtroCheckbox.checked = false;
      return;
    }

    // Filter by user ID
    const filtradas = allData.map(cat => ({
      categoria: cat.categoria,
      productos: cat.productos.filter(p => 
        p.id_vendedor && String(p.id_vendedor) === String(idUsuario)
      )
    })).filter(c => c.productos.length > 0);

    if (filtradas.length === 0) {
      cont.innerHTML = '<p>No tienes productos publicados.</p>';
      return;
    }

    render(filtradas, true);
  } else {
    render(allData);
  }
}

filtroCheckbox.addEventListener('change', (e) => {
  aplicarFiltroSoloMios(e.target.checked);
});

User’s Product Dashboard

The profile page displays all products published by the logged-in user:
perfil.js
fetch("php/mis_productos.php?id=" + id_usuario)
  .then(res => res.json())
  .then(data => {
    const productos = (data && data.productos) ? data.productos : [];
    const contenedor = document.getElementById("lista-productos");
    contenedor.innerHTML = "";

    if (!productos.length) {
      contenedor.innerHTML = "<p>No tienes productos publicados.</p>";
      return;
    }

    productos.forEach(prod => {
      contenedor.innerHTML += `
        <div class="producto-card">
          <a href="php/producto.php?id=${prod.id_producto}">
            <img 
              src="${prod.imagen}"
              alt="${prod.nombre}"
              class="producto-imagen"
              onerror="this.onerror=null;this.src='../imagenes/default-product.svg'"
            >
          </a>
          <h4>
            <a href="php/producto.php?id=${prod.id_producto}">
              ${prod.nombre}
            </a>
          </h4>
          <p>$${prod.precio}</p>
          <a href="php/producto.php?id=${prod.id_producto}" 
             class="btn-simple btn-ver">
             Ver producto
          </a>
        </div>
      `;
    });
  });

Product Categories

Available Categories

The platform currently supports these product categories:
IDCategoryDescription
2LaptopsPortátiles de diferentes gamas
3SmartphonesTeléfonos móviles de última generación

Loading Categories

Categories can be fetched dynamically for dropdown menus:
fetch('php/obtener_categorias.php')
  .then(res => res.json())
  .then(data => {
    const select = document.getElementById('categoria');
    data.categorias.forEach(cat => {
      const option = document.createElement('option');
      option.value = cat.id_categoria;
      option.textContent = cat.nombre;
      select.appendChild(option);
    });
  });

Image Management

Image Upload Process

1

File validation

Server checks that image was uploaded successfully
2

Filename generation

Unique filename created using timestamp and random bytes
3

File storage

Image moved to imagenes/ directory on server
4

Database reference

Filename stored in database, not full path

Image Path Format

// Stored in database
imagen: "1703612345_a3f7c2e8d1b9.jpg"

// Rendered in HTML
<img src="imagenes/1703612345_a3f7c2e8d1b9.jpg" alt="Product Name">

// With fallback
<img 
  src="imagenes/${p.imagen}" 
  alt="${p.nombre}"
  onerror="this.onerror=null;this.src='imagenes/default-product.svg'"
>
Always include a fallback image using the onerror attribute to handle missing or broken images gracefully.

API Reference

POST /php/vender.php

Publish a new product listing. Content-Type: multipart/form-data Request Body:
  • nombre (string, required): Product name
  • descripcion (string, required): Product description
  • precio (decimal, required): Product price
  • categoria (integer, optional): Category ID
  • imagen (file, required): Product image file
Response:
{
  "success": true,
  "message": "✅ Producto publicado correctamente."
}

GET /php/obtener_productos_por_categoria.php

Retrieve all products organized by category. Response:
{
  "exito": true,
  "categorias": [
    {
      "categoria": "Smartphones",
      "productos": [
        {
          "id_producto": 1,
          "nombre": "iPhone 15 Pro",
          "precio": "999.99",
          "imagen": "iphone15.jpg",
          "id_vendedor": 17,
          "vendedor": "John Doe"
        }
      ]
    }
  ]
}

GET /php/mis_productos.php

Get products published by a specific user. Query Parameters:
  • id (integer, required): User ID
Response:
{
  "productos": [
    {
      "id_producto": 6,
      "nombre": "Samsung Galaxy S23",
      "precio": "899.99",
      "imagen": "imagenes/694ebb9393c45_galaxy.jpg"
    }
  ]
}

GET /php/obtener_producto.php

Get detailed information for a single product. Query Parameters:
  • id (integer, required): Product ID
Response:
{
  "id_producto": 1,
  "nombre": "iPhone 15 Pro",
  "descripcion": "Chip A17 Pro, cámara de 48MP, titanio",
  "precio": "999.99",
  "imagen": "iphone15.jpg",
  "id_vendedor": 17,
  "vendedor": "John Doe",
  "fecha_publicacion": "2025-12-26 12:04:08"
}

Best Practices

Image Optimization

Compress images before upload to improve page load times

Descriptive Names

Use clear, searchable product names for better discoverability

Accurate Pricing

Always use decimal format with 2 places for prices

Category Selection

Choose the most appropriate category for each product

Next Steps

Shopping Cart

Learn how customers add products to cart

Order Processing

Understand the checkout and order flow

Build docs developers (and LLMs) love