El módulo de Productos es el núcleo funcional del panel de administración. Permite gestionar el catálogo completo: crear, editar y eliminar registros, filtrar y paginar el listado, asociar imágenes a cada producto mediante un proceso con transacciones de base de datos, y exportar el catálogo filtrado a un documento PDF generado con la librería mPDF.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/jpbarbatic/webapp/llms.txt
Use this file to discover all available pages before exploring further.
Modelo de datos
La tablaproductos almacena todos los campos del catálogo. La clave foránea id_categoria se define con ON DELETE SET NULL, de modo que al borrar una categoría los productos asociados quedan sin categoría en lugar de eliminarse.
| Columna | Tipo | Notas |
|---|---|---|
id | int AUTO_INCREMENT | Clave primaria |
nombre | varchar(30) | Requerido |
descripcion | varchar(500) | Requerido |
precio | decimal(10,2) | Puede ser NULL |
stock | int | Puede ser NULL |
id_categoria | int | FK → categorias.id · ON DELETE SET NULL |
num_fotos | int | Por defecto 0; se actualiza con transacción |
Rutas del módulo
| URL | Método | Archivo | Descripción |
|---|---|---|---|
productos/ | GET | public/productos/index.php | Listado con filtros, paginación y ordenación |
productos/nuevo.php | GET | public/productos/nuevo.php | Formulario de alta de un nuevo producto |
productos/guardar.php | POST | public/productos/guardar.php | Crea o actualiza el registro (insert/update) |
productos/editar.php?id=N | GET | public/productos/editar.php | Carga el formulario con los datos del producto |
productos/borrar.php?id=N | GET | public/productos/borrar.php | Elimina el registro y redirige al listado |
productos/subir_fotos.php | POST | public/productos/subir_fotos.php | Sube imágenes con transacción atómica |
productos/exportar.php | GET | public/productos/exportar.php | Genera y descarga el catálogo en PDF |
Listado con filtros
El controladorpublic/productos/index.php construye dinámicamente la consulta SQL en función de los parámetros de filtro recibidos por GET. Primero invoca paginacion() (de includes/utilidades.php) para obtener la página actual, el $offset, el campo de ordenación y su dirección, y después añade cláusulas WHERE opcionales:
db_select recibe la consulta base, los parámetros vinculados, el tamaño de página, el desplazamiento y los criterios de ordenación. Devuelve un array asociativo con las claves datos (registros de la página actual) y total (total de filas sin paginar), que se extraen con extract($res).
La vista de filtro (html/productos/filtro.html.php) se muestra como un panel colapsable de Bootstrap. Cuando el usuario ya ha aplicado un filtro ($_GET['filtro'] existe), el panel aparece expandido automáticamente:
html/paginacion.html.php, que calcula el número total de páginas con ceil($total / $items_pagina) y genera los enlaces de navegación utilizando la variable $vista para construir las URLs correctas.
Crear / Editar producto
El flujo de alta y edición comparte el mismo formulario HTML y el mismo controlador de guardado.Abrir el formulario de alta
nuevo.php carga las categorías disponibles con db_query y pasa $titulo = 'Nuevo producto' y $vista = 'productos/nuevo' a la plantilla. Como $producto no está definido, el formulario aparece vacío.Renderizar el formulario compartido
La vista
html/productos/formulario.html.php se usa tanto para alta como para edición. Los campos leen $producto si existe, o muestran valores por defecto en caso contrario. El <select> de categoría usa html_opciones() para pre-seleccionar la categoría actual:Abrir el formulario de edición
editar.php recupera el registro existente con db_get_by_id y lo pasa a la misma plantilla de formulario:Persistir los datos con guardar.php
guardar.php decide entre insertar o actualizar según la presencia del campo id en la petición. Tras guardar, redirige siempre a la vista de edición del registro resultante:Subida de fotos
El endpointpublic/productos/subir_fotos.php acepta múltiples archivos a través del campo fotos[]. Cada imagen se procesa de forma atómica: primero se abre una transacción de base de datos, se incrementa num_fotos en el registro del producto y solo si la copia física del archivo a disco tiene éxito se confirma la transacción; en caso contrario se hace rollback.
- Directorio por producto: las imágenes se guardan en
public/imagenes/productos/{id}/, creando el directorio si no existe. - Nombre de archivo secuencial: cada foto recibe como nombre el valor actualizado de
num_fotos(p. ej.1.jpg,2.jpg, …). - Transacción atómica: el contador
num_fotosy el archivo en disco se actualizan de forma consistente. Si la copia falla, el contador no queda incrementado en la base de datos.
Solo se procesan los archivos cuyo campo
error valga 0 (es decir, carga sin errores según PHP). Los archivos con cualquier otro código de error (UPLOAD_ERR_SIZE, UPLOAD_ERR_NO_FILE, etc.) se omiten silenciosamente. En la versión actual del código la redirección al finalizar está comentada; el script termina sin redirigir al navegador.Exportación PDF
El controladorpublic/productos/exportar.php construye la misma consulta SQL que el listado (con los mismos filtros de nombre por LIKE y categoría recibidos por GET) y la pasa directamente a la función generar_pdf_db() de includes/pdf.php, sin realizar paginación. A diferencia del listado, el exportador no implementa el atajo id:N; el filtro de nombre siempre aplica LIKE.
generar_pdf_db de includes/pdf.php instancia mPDF en modo UTF-8, evalúa la plantilla listado.pdf.php capturando su salida con ob_start()/ob_get_clean() y entrega el PDF al navegador con $mpdf->Output():
html/productos/listado.pdf.php itera los productos en lotes de 1000 registros usando llamadas sucesivas a db_select hasta agotar el total, generando una tabla HTML con estilos inline. El pie de página con numeración se define mediante la regla CSS @page { @bottom-center { content: "Página " counter(page) " de " counter(pages); } }, compatible con mPDF:
QUERY_STRING actual al exportador, de modo que el PDF respeta los filtros activos en ese momento:
Borrado
El controladorpublic/productos/borrar.php recibe el id por GET, llama a db_delete_by_id y redirige al listado con un mensaje de confirmación en sesión:
#deleteModal) pasando el id del registro como atributo data-id:
html/confirmacion.borrado.html.php) contiene un formulario POST hacia productos/borrar.php con campos ocultos para el id y un campo csrf_token cuyo valor se lee de $_SESSION['csrf_token']. El servidor (borrar.php) no valida ese token en la versión actual del código; la protección CSRF es sólo del lado cliente (el campo se incluye en el formulario pero no se comprueba en el controlador).