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
Seller authentication
User must be logged in to publish products
Form submission
Product details and image are submitted via multipart form
Image processing
Image is validated, renamed uniquely, and saved to server
Database insertion
Product record is created with seller ID and category
Backend Implementation
The product publishing endpoint (php/vender.php) handles product creation:
<? 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:
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:
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:
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:
ID Category Description 2 Laptops Portátiles de diferentes gamas 3 Smartphones Telé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
File validation
Server checks that image was uploaded successfully
Filename generation
Unique filename created using timestamp and random bytes
File storage
Image moved to imagenes/ directory on server
Database reference
Filename stored in database, not full path
// 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