Overview
Pro Stock Tool follows consistent coding patterns across frontend JavaScript, backend PHP, and HTML templates. This guide documents the established conventions observed in the codebase.
JavaScript Conventions
File Structure Pattern
All JavaScript controllers follow a consistent organizational structure:
Standard Controller Template
// 1. API Configuration
const API_URL = 'http://localhost/Pro-Stock-Tool/database/{module}.php' ;
// 2. Global Variables
let items = [];
let editingItem = null ;
let itemToDelete = null ;
// 3. DOM Element References
const modalOverlay = document . getElementById ( 'modalOverlay' );
const formElement = document . getElementById ( 'formElement' );
const listContainer = document . getElementById ( 'listContainer' );
// 4. Event Listeners
document . addEventListener ( 'DOMContentLoaded' , () => {
loadData ();
initializeEventListeners ();
});
// 5. API Functions
async function loadData () { /* ... */ }
async function createItem ( data ) { /* ... */ }
async function updateItem ( id , data ) { /* ... */ }
async function deleteItem () { /* ... */ }
// 6. UI Functions
function renderItems ( items ) { /* ... */ }
function openModal () { /* ... */ }
function closeModal () { /* ... */ }
// 7. Helper Functions
function showAlert ( message , type ) { /* ... */ }
function validateInput ( data ) { /* ... */ }
View Complete Example: bodega.js
// Configuration
const API_URL = 'http://localhost/Pro-Stock-Tool/database/bodega.php' ;
// Global state
let bodegas = [];
let bodegaEditando = null ;
let bodegaParaEliminar = null ;
// DOM elements
const modalOverlay = document . getElementById ( 'modalOverlay' );
const formBodega = document . getElementById ( 'formBodega' );
const listaBodegas = document . getElementById ( 'listaBodegas' );
// Initialize on page load
document . addEventListener ( 'DOMContentLoaded' , () => {
cargarBodegas ();
inicializarEventListeners ();
});
function inicializarEventListeners () {
btnNuevaBodega . addEventListener ( 'click' , abrirModalNuevo );
btnCancelar . addEventListener ( 'click' , cerrarModal );
formBodega . addEventListener ( 'submit' , guardarBodega );
buscarBodega . addEventListener ( 'input' , filtrarBodegas );
// Event delegation for dynamic buttons
listaBodegas . addEventListener ( 'click' , ( e ) => {
const btn = e . target . closest ( '[data-action]' );
if ( ! btn ) return ;
const id = Number ( btn . getAttribute ( 'data-id' ));
const action = btn . getAttribute ( 'data-action' );
if ( action === 'edit' ) editarBodega ( id );
else if ( action === 'delete' ) abrirModalEliminar ( id );
});
}
Naming Conventions
Variables camelCase for all variableslet bodegaEditando ;
const listaBodegas ;
Functions camelCase with descriptive verbsfunction cargarBodegas ()
function abrirModalNuevo ()
function guardarBodega ()
Constants UPPER_SNAKE_CASE for true constants
DOM Elements Descriptive names matching IDs const modalOverlay =
document . getElementById ( 'modalOverlay' );
Async/Await Pattern
All API calls use modern async/await syntax with try-catch error handling:
Standard API Call Pattern
async function cargarBodegas () {
try {
const url = ` ${ API_URL } ?_= ${ Date . now () } ` ; // Cache busting
const response = await fetch ( url , { cache: 'no-store' });
const data = await response . json ();
if ( data . success ) {
bodegas = data . data ;
renderizarBodegas ( bodegas );
actualizarEstadisticas ();
} else {
mostrarAlerta ( 'Error al cargar bodegas: ' + data . error , 'error' );
}
} catch ( error ) {
console . error ( 'Error:' , error );
mostrarAlerta ( 'Error de conexión al cargar bodegas' , 'error' );
}
}
Cache Busting : API URLs include ?_=${Date.now()} to prevent browser caching of GET requests.
Event Delegation
Dynamic elements use event delegation on parent containers:
listaBodegas . addEventListener ( 'click' , ( e ) => {
const btn = e . target . closest ( '[data-action]' );
if ( ! btn ) return ;
const id = Number ( btn . getAttribute ( 'data-id' ));
if ( Number . isNaN ( id )) return ;
const action = btn . getAttribute ( 'data-action' );
if ( action === 'edit' ) {
editarBodega ( id );
} else if ( action === 'delete' ) {
abrirModalEliminar ( id );
}
});
function guardarBodega ( e ) {
e . preventDefault ();
const datos = {
nombre: bodegaNombre . value . trim (),
descripcion: bodegaDescripcion . value . trim ()
};
// Client-side validation
if ( ! datos . nombre ) {
mostrarAlerta ( 'El nombre es obligatorio' , 'error' );
bodegaNombre . focus ();
return ;
}
if ( bodegaEditando ) {
actualizarBodega ( bodegaEditando . id , datos );
} else {
crearBodega ( datos );
}
}
PHP Backend Conventions
Endpoint Structure
All PHP endpoints follow a RESTful pattern with HTTP method routing:
Standard PHP Endpoint Structure
<? php
// 1. Headers
header ( 'Content-Type: application/json; charset=utf-8' );
header ( 'Access-Control-Allow-Origin: *' );
header ( 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS' );
header ( 'Access-Control-Allow-Headers: Content-Type' );
header ( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' );
header ( 'Pragma: no-cache' );
// 2. OPTIONS Preflight
if ( $_SERVER [ 'REQUEST_METHOD' ] === 'OPTIONS' ) {
http_response_code ( 200 );
exit ;
}
// 3. Database Connection
require 'conexion.php' ;
// 4. Method Routing
$method = $_SERVER [ 'REQUEST_METHOD' ];
try {
switch ( $method ) {
case 'GET' :
// Retrieve data
break ;
case 'POST' :
// Create data
break ;
case 'PUT' :
// Update data
break ;
case ' DELETE ' :
// Delete data
break ;
default :
echo json_encode ([ 'success' => false , 'error' => 'Método no permitido' ]);
break ;
}
} catch ( Exception $e ) {
echo json_encode ([ 'success' => false , 'error' => 'Error del servidor: ' . $e -> getMessage ()]);
}
$conn -> close ();
?>
Validation & Sanitization
// 1. Parse JSON input
$input = file_get_contents ( 'php://input' );
$data = json_decode ( $input , true );
if ( ! $data ) {
echo json_encode ([ 'success' => false , 'error' => 'Datos inválidos' ]);
exit ;
}
// 2. Extract and trim values
$nombre = isset ( $data [ 'nombre' ]) ? trim ( $data [ 'nombre' ]) : '' ;
$descripcion = isset ( $data [ 'descripcion' ]) ? trim ( $data [ 'descripcion' ]) : '' ;
// 3. Validate required fields
if ( empty ( $nombre )) {
echo json_encode ([ 'success' => false , 'error' => 'El nombre es obligatorio' ]);
exit ;
}
// 4. Validate length constraints
if ( strlen ( $nombre ) > 100 ) {
echo json_encode ([ 'success' => false , 'error' => 'El nombre no puede superar 100 caracteres' ]);
exit ;
}
// 5. Escape for SQL
$nombre = $conn -> real_escape_string ( $nombre );
$descripcion = $conn -> real_escape_string ( $descripcion );
Security Note : Always use real_escape_string() or prepared statements to prevent SQL injection attacks.
All API responses use consistent JSON structure:
echo json_encode ([
'success' => true ,
'data' => $result ,
'message' => 'Operación exitosa'
], JSON_UNESCAPED_UNICODE );
echo json_encode ([
'success' => false ,
'error' => 'Descripción del error'
], JSON_UNESCAPED_UNICODE );
JSON_UNESCAPED_UNICODE ensures Spanish characters (á, é, í, ó, ú, ñ) are properly encoded.
Database Query Patterns
case 'GET' :
$sql = " SELECT id, nombre, descripcion FROM bodegas ORDER BY id DESC " ;
$result = $conn -> query ( $sql );
$bodegas = [];
if ( $result && $result -> num_rows > 0 ) {
while ( $row = $result -> fetch_assoc ()) {
$bodegas [] = $row ;
}
}
echo json_encode ([ 'success' => true , 'data' => $bodegas ]);
break ;
case 'POST' :
// Validate and escape input
$nombre = $conn -> real_escape_string ( $nombre );
$descripcion = $conn -> real_escape_string ( $descripcion );
// Check for duplicates
$checkSql = " SELECT id FROM bodegas WHERE nombre = ' $nombre '" ;
$checkResult = $conn -> query ( $checkSql );
if ( $checkResult && $checkResult -> num_rows > 0 ) {
echo json_encode ([ 'success' => false , 'error' => 'Ya existe una bodega con este nombre' ]);
exit ;
}
// Insert
$sql = " INSERT INTO bodegas (nombre, descripcion, fecha_creacion)
VALUES (' $nombre ', ' $descripcion ', NOW () )" ;
if ( $conn -> query ( $sql )) {
echo json_encode ([
'success' => true ,
'id' => $conn -> insert_id ,
'message' => 'Bodega creada exitosamente'
]);
}
break ;
case 'PUT' :
$id = isset ( $data [ 'id' ]) ? intval ( $data [ 'id' ]) : 0 ;
if ( $id <= 0 ) {
echo json_encode ([ 'success' => false , 'error' => 'ID inválido' ]);
exit ;
}
// Verify record exists
$checkSql = " SELECT id FROM bodegas WHERE id = $id " ;
$checkResult = $conn -> query ( $checkSql );
if ( ! $checkResult || $checkResult -> num_rows === 0 ) {
echo json_encode ([ 'success' => false , 'error' => 'Bodega no encontrada' ]);
exit ;
}
// Update
$sql = " UPDATE bodegas SET
nombre = ' $nombre ',
descripcion = ' $descripcion ',
fecha_actualizacion = NOW ()
WHERE id = $id " ;
if ( $conn -> query ( $sql )) {
echo json_encode ([ 'success' => true , 'message' => 'Bodega actualizada exitosamente' ]);
}
break ;
case ' DELETE ' :
$id = isset ( $data [ 'id' ]) ? intval ( $data [ 'id' ]) : 0 ;
// Verify record exists
$checkSql = " SELECT id FROM bodegas WHERE id = $id " ;
$checkResult = $conn -> query ( $checkSql );
if ( ! $checkResult || $checkResult -> num_rows === 0 ) {
echo json_encode ([ 'success' => false , 'error' => 'Bodega no encontrada' ]);
exit ;
}
// Check for foreign key constraints
$productCheckSql = " SELECT COUNT ( * ) as count FROM productos WHERE bodega_id = $id " ;
$productResult = $conn -> query ( $productCheckSql );
if ( $productResult ) {
$productCount = $productResult -> fetch_assoc ()[ 'count' ];
if ( $productCount > 0 ) {
echo json_encode ([ 'success' => false , 'error' => "No se puede eliminar. Tiene $productCount producto(s) asociado(s)" ]);
exit ;
}
}
// Delete
$sql = " DELETE FROM bodegas WHERE id = $id " ;
if ( $conn -> query ( $sql )) {
echo json_encode ([ 'success' => true , 'message' => 'Bodega eliminada exitosamente' ]);
}
break ;
HTML Conventions
Page Structure
<! DOCTYPE html >
< html lang = "es" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Module Name - Pro Stock Tool </ title >
< link rel = "stylesheet" href = "../styles/header-footer.css" >
< link rel = "stylesheet" href = "../styles/module.css" >
</ head >
< body >
< main >
< section class = "panel-module" >
<!-- Content -->
</ section >
<!-- Modals -->
< div id = "modalOverlay" class = "modal-overlay" aria-hidden = "true" >
<!-- Modal content -->
</ div >
</ main >
<!-- Scripts -->
< script src = "../controllers/header-footer.js" ></ script >
< script src = "../controllers/module.js" ></ script >
</ body >
</ html >
Accessibility
ARIA Attributes < div id = "modal"
aria-hidden = "true"
role = "dialog" >
Labels < label for = "nombre" > Nombre </ label >
< input id = "nombre" type = "text" >
Required Fields < label > Nombre < span class = "required" > * </ span ></ label >
CSS Conventions
Class Naming
/* Block */
.bodega-card { }
/* Element */
.bodega-card__header { }
.bodega-card__title { }
.bodega-card__actions { }
/* Modifier */
.btn-primary { }
.btn-delete { }
.btn-cancel { }
/* State */
.modal-overlay.active { }
.alerta-notificacion.show { }
Color Variables
/* Primary Blue */
#2e6df6
#4a90e2
#1e4db8
/* Semantic Colors */
.success { color : #28a745 ; }
.error { color : #dc3545 ; }
.warning { color : #ffc107 ; }
Section Separators
Inline Documentation
// ==================== FUNCIONES DE API ====================
// ==================== FUNCIONES DE UI ====================
// ==================== MODAL PRINCIPAL ====================
// ==================== SISTEMA DE ALERTAS ====================
Best Practices Summary
Consistent Patterns Follow established patterns for similar functionality across modules
Error Handling Always use try-catch blocks and validate user input
Security First Sanitize inputs, escape SQL queries, use HTTPS in production
User Feedback Show clear success/error messages in Spanish for all operations
Separation of Concerns Keep business logic, presentation, and data access separate
Code Reusability Use shared components like header-footer.js across pages
Next Steps
Project Structure Understand the folder organization
Troubleshooting Common issues and solutions