Skip to main content

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.

El archivo includes/db_pdo.php proporciona dos interfaces complementarias sobre la extensión PDO de PHP para trabajar con la base de datos del panel. La primera es un conjunto de funciones procedurales (db_open, db_query, db_select, db_insert, db_update, db_delete_by_id, entre otras) que pueden usarse directamente en cualquier parte del código. La segunda es la clase Singleton DB, que envuelve esas mismas funciones bajo una interfaz orientada a objetos y garantiza que se reutilice una única conexión PDO durante todo el ciclo de vida de la petición HTTP.

Configuración de conexión

db_open() lee los parámetros de conexión desde las constantes definidas en config.php. El archivo config-ejemplo.php incluido en el repositorio muestra la estructura esperada:
// config.php (basado en config-ejemplo.php)
define('DEBUG',   true);
define('DB_TYPE', 'mysql');      // 'mysql' | 'sqlite'
define('DB_HOST', '127.0.0.1');
define('DB_PORT', 3308);
define('DB_USER', 'root');
define('DB_PASS', 'example');
define('DB_NAME', 'empresa');
ConstanteDescripciónValor de ejemplo
DB_TYPEMotor de base de datos (mysql o sqlite)'mysql'
DB_HOSTHost del servidor de base de datos'127.0.0.1'
DB_PORTPuerto de conexión3308
DB_USERUsuario de la base de datos'root'
DB_PASSContraseña del usuario'example'
DB_NAMENombre de la base de datos'empresa'
Para conexiones SQLite se ignoran DB_HOST, DB_PORT, DB_USER, DB_PASS y DB_NAME; en su lugar se usa la clave sqlite_path pasada directamente al array $conf.

Funciones procedurales

db_open

db_open($conf = null): ?PDO
Abre y configura una conexión PDO. Si $conf es null, los parámetros se leen de las constantes de config.php. Si se proporciona un array, sus valores sobreescriben las constantes mediante extract(). La conexión se configura con los atributos:
  • PDO::ATTR_ERRMODEPDO::ERRMODE_EXCEPTION (lanza excepciones en errores)
  • PDO::ATTR_DEFAULT_FETCH_MODEPDO::FETCH_ASSOC (resultados como arrays asociativos)
  • PDO::ATTR_EMULATE_PREPARESfalse (sentencias preparadas nativas)
Claves soportadas en $conf:
ClaveDescripción
db_type'mysql' o 'sqlite'
db_hostHost del servidor
db_portPuerto
db_userUsuario
db_passContraseña
db_nameNombre de la base de datos
sqlite_pathRuta al archivo SQLite (solo cuando db_type = 'sqlite')
Valor de retorno: instancia PDO configurada, o null si la conexión falla.
// Conexión usando constantes de config.php
$pdo = db_open();

// Conexión SQLite explícita
$pdo = db_open(['db_type' => 'sqlite', 'sqlite_path' => '/tmp/app.db']);

// Conexión a otra base de datos sin modificar config.php
$pdo = db_open([
    'db_type' => 'mysql',
    'db_host' => '10.0.0.5',
    'db_port' => 3306,
    'db_user' => 'app_user',
    'db_pass' => 's3cr3t',
    'db_name' => 'staging',
]);

db_query

db_query(PDO $conn, string $query, ?array $params = null): array|false
Ejecuta una consulta SQL preparada y devuelve todos los resultados como array de arrays asociativos. La función realiza la detección automática de tipos PHP → constantes PDO al vincular cada parámetro:
Tipo PHPConstante PDO
nullPDO::PARAM_NULL
boolPDO::PARAM_BOOL
intPDO::PARAM_INT
Cualquier otroPDO::PARAM_STR
PDO usa índice 1-based para marcadores de posición posicionales (?). db_query gestiona esto internamente: si la clave del array $params es entera, le suma 1 automáticamente antes de llamar a bindValue().
Parámetros:
$conn
PDO
required
Conexión PDO activa obtenida con db_open().
$query
string
required
Consulta SQL con marcadores ? para parámetros posicionales o :nombre para parámetros nombrados.
$params
array|null
Array de valores a vincular. Si es null, se ejecuta la consulta sin parámetros.
Valor de retorno: array con todos los resultados (fetchAll(FETCH_ASSOC)) o false en caso de error.
// Consulta sin parámetros
$todos = db_query($db, 'SELECT * FROM categorias');

// Consulta con parámetros posicionales
$rows = db_query($db, 'SELECT * FROM productos WHERE id_categoria = ?', [1]);

// Consulta con múltiples parámetros
$rows = db_query($db,
    'SELECT * FROM usuarios WHERE id_rol = ? AND nombre = ?',
    [1, 'Admin']
);

db_select

db_select(
    PDO $pdo,
    string $sql_base,
    array $params = [],
    int $limit = 0,
    int $offset = 0,
    ?string $order_by = null,
    ?string $order_dir = 'ASC'
): array
Ejecuta una consulta paginada y devuelve simultáneamente el total de registros (sin paginación) y los datos de la página solicitada. Internamente construye dos consultas:
  1. SELECT COUNT(*) FROM (<sql_base>) as t_count — para obtener el total.
  2. <sql_base> ORDER BY ... LIMIT ? OFFSET ? — para obtener los datos paginados.
Parámetros:
$pdo
PDO
required
Conexión PDO activa.
$sql_base
string
required
Consulta SQL base. No debe incluir ORDER BY, LIMIT ni OFFSET ya que la función los añade.
$params
array
Parámetros de la consulta base (para los WHERE o JOIN que ya tenga).
$limit
int
Número de filas por página. 0 desactiva la paginación.
$offset
int
Desplazamiento en número de filas.
$order_by
string|null
Nombre de columna por la que ordenar. null omite el ORDER BY.
$order_dir
string
Dirección de orden: 'ASC' o 'DESC'. Por defecto 'ASC'.
Valor de retorno:
total
int
Número total de registros que cumple los criterios (sin paginación).
datos
array
Array de filas correspondientes a la página solicitada.
// Página 1: primeros 10 productos, ordenados por nombre ascendente
$res = db_select($db, 'SELECT * FROM productos WHERE TRUE', [], 10, 0, 'nombre', 'ASC');
// $res['total'] => 49 (total de productos)
// $res['datos'] => array con 10 productos

// Página 2 (offset = 10)
$res = db_select($db, 'SELECT * FROM productos WHERE TRUE', [], 10, 10, 'precio', 'DESC');

// Filtro combinado
$res = db_select(
    $db,
    'SELECT * FROM productos WHERE id_categoria = ?',
    [1],
    5, 0, 'nombre', 'ASC'
);

db_get_by_id

db_get_by_id(PDO $conn, string $table, mixed $id, string $id_name = 'id'): mixed
Recupera un único registro buscando por el valor de su clave primaria (o cualquier otra columna identificadora). Valida los nombres de tabla y columna con is_valid_identifier() antes de construir la consulta. Valor de retorno: array asociativo con el registro encontrado, o false si no existe o los identificadores son inválidos.
// Buscar producto con id = 1
$producto = db_get_by_id($db, 'productos', 1);
// => ['id' => 1, 'nombre' => 'Laptop Gamer', ...]

// Buscar con clave primaria con nombre personalizado
$fila = db_get_by_id($db, 'roles_permisos', 2, 'id_rol');

db_insert

db_insert(PDO $conn, string $table, array $dto): string|int|false
Inserta un nuevo registro en la tabla especificada. La función elimina automáticamente la clave id del array $dto antes de construir la sentencia INSERT, evitando conflictos con columnas AUTO_INCREMENT. Valida el nombre de tabla y todos los nombres de campo con is_valid_identifier(). Valor de retorno: el ID del nuevo registro (lastInsertId()) como string o int, o false si la inserción falla.
// Insertar un nuevo producto
$id = db_insert($db, 'productos', [
    'nombre'      => 'Auriculares BT',
    'descripcion' => 'Cancelación de ruido activa, autonomía 30h',
    'precio'      => 79.99,
    'stock'       => 50,
    'id_categoria'=> 1,
    'num_fotos'   => 0,
]);
// $id => '50' (string devuelto por lastInsertId)

// El campo 'id' se ignora aunque esté en el array
$id = db_insert($db, 'roles', ['id' => 99, 'nombre' => 'editor', 'descripcion' => 'Editor de contenidos']);

db_update

db_update(PDO $conn, string $table, array $dto, string $id_name = 'id'): bool
Actualiza un registro existente. El array $dto debe incluir el campo identificador (por defecto id) para construir la cláusula WHERE. La función extrae ese valor del array, lo aparta de los campos a actualizar y lo añade al final de los parámetros vinculados. Valor de retorno: true si la actualización se ejecutó correctamente, false en caso contrario.
// Actualizar nombre y precio de un producto
db_update($db, 'productos', [
    'id'     => 1,
    'nombre' => 'Laptop Gamer Pro',
    'precio' => 4999.99,
]);

// Actualizar con nombre de PK personalizado
db_update($db, 'usuarios', [
    'id'       => 5,
    'telefono' => '+34666000001',
], 'id');

db_delete_by_id

db_delete_by_id(PDO $conn, string $table, mixed $id, string $id_name = 'id'): bool
Elimina el registro cuyo campo $id_name coincida con $id. La consulta se construye dinámicamente solo si los identificadores superan la validación de is_valid_identifier(). Valor de retorno: true si el registro fue eliminado, false en caso de error o identificador inválido.
// Eliminar el producto con id = 42
$ok = db_delete_by_id($db, 'productos', 42);

// Eliminar con nombre de columna personalizado
$ok = db_delete_by_id($db, 'usuarios', 'admin@example.com', 'email');

Transacciones: db_begin, db_commit, db_rollback

db_begin(PDO $conn): void
db_commit(PDO $conn): void
db_rollback(PDO $conn): void
Las tres funciones envuelven los métodos nativos de PDO (beginTransaction, commit, rollback). db_commit y db_rollback comprueban $conn->inTransaction() antes de actuar para evitar errores si se llaman fuera de una transacción activa.
db_begin($db);
try {
    $nuevo_id = db_insert($db, 'productos', [
        'nombre'      => 'Producto Bundle',
        'descripcion' => 'Pack promocional',
        'precio'      => 99.00,
        'stock'       => 10,
    ]);
    db_update($db, 'productos', ['id' => $nuevo_id, 'num_fotos' => 1]);
    db_commit($db);
} catch (Exception $e) {
    db_rollback($db);
    // manejar el error...
}

db_close

db_close(&$conn): void
Cierra la conexión PDO asignando null a la variable pasada por referencia. PDO libera la conexión con la base de datos cuando todas las referencias al objeto se destruyen. Parámetros:
$conn
PDO|null
Referencia a la variable que contiene la conexión PDO activa. Tras la llamada, la variable queda en null.
$pdo = db_open();
// ... operaciones con la base de datos ...
db_close($pdo);
// $pdo === null a partir de aquí

Clase Singleton DB

La clase DB es un wrapper orientado a objetos sobre las funciones procedurales de db_pdo.php. Implementa el patrón Singleton: la primera llamada a DB::open() crea la conexión PDO y guarda la instancia en la propiedad estática $instancia; todas las llamadas posteriores durante la misma petición devuelven esa misma instancia sin abrir una nueva conexión.
class DB
{
    private static $instancia = null;
    private $conn = null;

    private function __construct(PDO $conn) { ... }

    public static function open($conf = null): DB|false { ... }

    public function begin() { ... }
    public function commit() { ... }
    public function query($sql, $params = null) { ... }
    public function get_by_id($table, $id) { ... }
    public function filter($table, $filtro, $orden_campo, $orden_dir, $pagina, $items_por_pagina) { ... }
    public function insert($table, $dto) { ... }
    public function update($table, $dto) { ... }
    public function delete_by_id($table, $id) { ... }
}

Método estático DB::open()

DB::open($conf = null): DB|false
Devuelve la instancia Singleton de DB. Si todavía no existe, llama a db_open($conf) internamente para crear la conexión PDO. Si la conexión falla, retorna false.
El método DB::filter() acepta los parámetros ($table, $filtro, $orden_campo, $orden_dir, $pagina, $items_por_pagina) pero internamente los pasa a db_filter en un orden distinto. Para filtrados complejos con control total sobre $campos y $filtro usa directamente la función procedural db_filter().

Métodos de instancia

MétodoDelegación internaDescripción
begin()db_begin($this->conn)Inicia una transacción
commit()db_commit($this->conn)Confirma la transacción activa
query($sql, $params)db_query($this->conn, ...)Ejecuta una consulta preparada
get_by_id($table, $id)db_get_by_id($this->conn, ...)Obtiene un registro por ID
filter($table, $filtro, $orden_campo, $orden_dir, $pagina, $items_por_pagina)db_filter($this->conn, ...)Filtra, ordena y pagina sobre una tabla
insert($table, $dto)db_insert($this->conn, ...)Inserta un registro
update($table, $dto)db_update($this->conn, ...)Actualiza un registro
delete_by_id($table, $id)db_delete_by_id($this->conn, ...)Elimina un registro por ID
La clase DB no expone db_rollback como método propio. Para revertir una transacción usa la función procedural db_rollback() pasándole la conexión PDO obtenida con db_open(), o bien abre la conexión de forma procedural con db_open() en lugar de usar el Singleton cuando necesites control completo de transacciones con rollback.

Ejemplo completo con la clase DB

// Obtener la instancia (crea la conexión la primera vez)
$db = DB::open();

// Misma instancia en llamadas sucesivas — no abre nueva conexión
$db2 = DB::open();
var_dump($db === $db2); // bool(true)

// Consultar todos los roles
$roles = $db->query('SELECT * FROM roles');

// Insertar un nuevo rol
$id = $db->insert('roles', [
    'nombre'      => 'editor',
    'descripcion' => 'Editor de contenidos',
]);

// Obtener el rol recién creado
$rol = $db->get_by_id('roles', $id);

// Actualizar la descripción
$db->update('roles', [
    'id'          => $id,
    'descripcion' => 'Editor de productos y categorías',
]);

// Eliminar el rol
$db->delete_by_id('roles', $id);

// Transacción (nota: DB no expone rollback; usa db_open() procedural si necesitas rollback)
$db->begin();
try {
    $db->insert('productos', ['nombre' => 'Test', 'descripcion' => '', 'precio' => 1.00, 'stock' => 0]);
    $db->commit();
} catch (Exception $e) {
    // Para rollback usa la API procedural con la conexión obtenida por db_open()
}

Seguridad: is_valid_identifier

function is_valid_identifier(string $identifier): bool
{
    return preg_match('/^[a-zA-Z0-9_]+$/', $identifier);
}
La función is_valid_identifier() es una guarda de seguridad interna que se ejecuta antes de incluir cualquier nombre de tabla o columna en una consulta SQL construida dinámicamente. Valida que el identificador contenga únicamente caracteres alfanuméricos y guiones bajos (^[a-zA-Z0-9_]+$). Las funciones que la utilizan son: db_get_by_id, db_insert, db_update y db_delete_by_id. ¿Por qué es necesaria? Los marcadores de posición PDO (?) protegen los valores de los parámetros contra inyección SQL, pero no pueden usarse para nombres de tablas o columnas. Un atacante que controlara esos argumentos podría inyectar SQL arbitrario como:
productos` WHERE 1=1; DROP TABLE usuarios; --
is_valid_identifier() rechaza esa cadena porque contiene caracteres fuera del patrón permitido (\``, espacios, ;), devolviendo falsey haciendo que la función llamante retornefalse` sin ejecutar ninguna consulta.
Si escribes código que llama a db_insert, db_update o db_delete_by_id con nombres de tabla o columna provenientes de la entrada del usuario, asegúrate de validarlos adicionalmente contra una lista blanca de nombres conocidos. is_valid_identifier sólo verifica el formato del identificador, no si la tabla o columna existe realmente en la base de datos.

db_filter

db_filter(
    PDO $db,
    string $tabla,
    array $campos,
    array $filtro,
    string $tipo_filtro = 'or',
    string $orden_campo = 'id',
    string $orden_dir = 'asc',
    int $pagina = 0,
    int $items_por_pagina = 20
): array|false
Función de alto nivel para filtrado, ordenación y paginación sobre una tabla. Genera dinámicamente la cláusula WHERE a partir de un array de criterios estructurado, calcula el número total de páginas y devuelve los datos de la página solicitada.

Parámetros

$db
PDO
required
Conexión PDO activa.
$tabla
string
required
Nombre de la tabla sobre la que se realiza la consulta.
$campos
array
required
Array asociativo ['columna' => 'alias'] que define las columnas a seleccionar. Si el alias es null, se usa el nombre de columna directamente. Un array vacío o null equivale a SELECT *.
$filtro
array
required
Array de criterios de filtro (ver estructura más abajo).
$tipo_filtro
string
Operador lógico entre criterios: 'or' (por defecto) o 'and'.
$orden_campo
string
Nombre de columna por la que ordenar. Por defecto 'id'.
$orden_dir
string
Dirección de orden: 'asc' o 'desc'. Cualquier otro valor hace que la función retorne false.
$pagina
int
Número de página (base 1). El valor 0 recupera todos los registros sin aplicar LIMIT/OFFSET.
$items_por_pagina
int
Registros por página. Por defecto 20.

Estructura del array $filtro

Cada elemento del array $filtro es un array asociativo con las siguientes claves:
ClaveTipoDescripción
campostringNombre de la columna a filtrar
tipostringTipo de filtro: texto, entero, id o intervalo
valormixedValor del filtro (para texto, entero e id)
minmixedLímite inferior exclusivo (solo para intervalo)
maxmixedLímite superior exclusivo (solo para intervalo)
minemixedLímite inferior inclusivo (solo para intervalo)
maxemixedLímite superior inclusivo (solo para intervalo)
Tipos de filtro:
tipoOperador SQL generadoCondición de activación
textoLOWER(campo) LIKE LOWER(?) con %valor%Solo si el valor no es una cadena vacía
enterocampo = ?Solo si el valor es numérico
idcampo = ?Solo si el valor es numérico
intervalocampo > ?, campo < ?, campo >= ?, campo <= ?Según las claves min, max, mine, maxe presentes

Valor de retorno

total
int
Total de registros que cumplen los criterios de filtro.
datos
array
Filas correspondientes a la página solicitada.
Devuelve false si $db es falsy, si $orden_dir no es 'asc' ni 'desc', o si el número de página está fuera de rango.

Ejemplo: filtrado en el módulo de productos

$filtro = [
    // Buscar por texto en nombre
    ['campo' => 'nombre', 'tipo' => 'texto', 'valor' => 'gamer'],
    // Filtrar por categoría exacta
    ['campo' => 'id_categoria', 'tipo' => 'id', 'valor' => 1],
    // Rango de precio: entre 50 y 200 (inclusive)
    ['campo' => 'precio', 'tipo' => 'intervalo', 'mine' => 50, 'maxe' => 200],
];

$campos = [
    'id'           => null,
    'nombre'       => null,
    'precio'       => null,
    'stock'        => null,
    'id_categoria' => 'categoria_id',
];

$resultado = db_filter(
    $db,
    'productos',
    $campos,
    $filtro,
    'and',       // todos los criterios deben cumplirse
    'precio',    // ordenar por precio
    'asc',       // de menor a mayor
    1,           // página 1
    10           // 10 items por página
);

if ($resultado !== false) {
    echo "Total encontrados: " . $resultado['total'] . PHP_EOL;
    foreach ($resultado['datos'] as $prod) {
        echo $prod['nombre'] . ' — ' . $prod['precio'] . PHP_EOL;
    }
}
db_select vs db_filter: usa db_select cuando ya tienes una consulta SQL base personalizada (con JOIN, subconsultas, etc.) y solo necesitas paginación automática. Usa db_filter cuando trabajas directamente sobre una tabla y quieres delegar la construcción del WHERE al motor de filtrado estructurado, pasando un array de criterios en lugar de SQL manual. Ambas devuelven la misma estructura ['total' => int, 'datos' => array].

Build docs developers (and LLMs) love