Hi Hacker

Qué pasa, crack, soy el responsable de este proyecto. He desarrollado php-framework a partir de un desarrollo paralelo que rescaté y evolucioné como proyecto de fin de máster en la Universidad Nebrija, bajo el nombre de pnkd. php-framework es un framework desarrollado en PHP, diseñado para adaptarse tanto a proyectos pequeños como a aplicaciones de mayor envergadura. Utiliza una arquitectura MMVC (Modelo, Vista y Controlador Modular) , que proporciona una estructura organizada, favorece la separación de responsabilidades y facilita el mantenimiento y la escalabilidad.

El framework ha sido diseñado para ofrecer un entorno ligero, práctico y seguro desde el primer momento, con un tamaño aproximado de 55 KB y una implementación sencilla. Además, incluye una documentación accesible y de fácil consulta mediante el archivo `scheme.txt`. El proyecto incluye un script PHP ejecutable desde consola (CLI) que permite automatizar el proceso de despliegue inicial, generando rápidamente la estructura necesaria para nuevos controladores y modelos. También incorpora un conjunto de librerías bajo el directorio /engine/utils, que proporcionan funcionalidades reutilizables para implementar sistemas de autenticación, gestión de idiomas, correo electrónico, depuración y generación y validación de tokens, entre otras funcionalidades.

Con este proyecto, además de volverme un poco más maniático 😅, he podido profundizar en herramientas y sistemas de software engineers y developers orientados en PHP, aplicando buenas prácticas de arquitectura, seguridad y mantenibilidad. Para cualquier consulta, sugerencia o reporte de una vulnerabilidad de seguridad no documentada, podéis contactar conmigo en [isaacnavajasit@duck.com].

Configuración inicial para el archivo .env

Esta configuración se cargará dinámicamente a partir de los valores definidos en el archivo .env cada vez que se recargue la página.

Genera un vector de inicialización (IV) y una clave (KEY) aleatorios y compatibles con el sistema, listos para copiar en el archivo .env. Estos valores se utilizarón para cifrar los tokens de autenticación y las credenciales de acceso a la base de datos almacenadas en el archivo .env.

- Random key [KEY=]: WCHns!FhR02MsQF6aE#XGjwKWSPKtXE0

- Random IV [IV=]: cAMXW40E34gafmFI

Una vez copiados el vector de inicialización (IV) y la clave (KEY) y almacenados en el archivo .env, se procederá al cifrado de la contraseña definida mediante la variable defaultPassword en el controlador documentationController.php. Una vez generado el valor cifrado, este deberá establecerse como DB_PASS en el archivo .env. De este modo, la contraseña de acceso a la base de datos no se almacena en texto plano, reduciendo el riesgo de acceso no autorizado en caso de exposición o filtración del archivo .env.

- Contraseña encriptada en la base de datos [DB_PASS=]: pBEXW8qMPWvPbzZl7/vUpQ==

# Esquema de comandos::
# Documentación vf1.2.4

======[🚀 KERNEL ]============================================================================================
## 📦 Kernel · requires ($this->utils, $this->modal, $this->public, Url::asset) $:

*******************************************************

🧐 Carga directa de componentes del framework
⚙️ controlador :
-------------------------------------------------------
## 📖
# Para registrar los 'utils'.
$this->utils('url.php');
# Para registrar tanto el controlador como el modelo.
$this->modal('example/exampleModel.php');
# Para registrar la vista.
$this->public('/views/example/users.php');
-------------------------------------------------------

⚙️ vista : 
-------------------------------------------------------
<!-- apunta a /app/public/src -->
Url::asset('php-framework.png'); 
-------------------------------------------------------

*******************************************************

🧐 Estrategias de carga de 'utils' en el controlador
⚙️ controlador :
-------------------------------------------------------
## 📖
# Los 'utils' pueden cargarse de forma global cuando son utilizadas por varios métodos.
# De este modo quedan disponibles desde la inicialización de la clase y se evita repetir su carga en cada método.
...
<?php
require_once('./engine/kernel.php');

class ExampleController extends kernel {
    private $exampleModel;

    public function __construct() {
        parent::__construct();
        $this->modal('example/exampleModel.php');
        $this->exampleModel = new ExampleModel();

        // Carga utils de forma global
        👉🏻 $this->utils('url.php');
        👉🏻 $this->utils('debug.php');
    }
...
## 📖
# También pueden cargarse de forma local dentro del método cuando su uso está limitado a una operación concreta.
# Esta estrategia reduce dependencias y limita la carga de recursos al contexto necesario.
    public function createUser() {
        // Carga utils de forma local
        👉🏻 $this->utils('sanitizer.php');
        👉🏻 $this->utils('password.php');

        $this->exampleModel->createUser(
            Sanitizer::forceString($_POST['nombre']),
            Password::hash($_POST['password']),
            Sanitizer::forceString($_POST['role_id'])
        );

        Url::redirect('/usuarios');
    }
...
-------------------------------------------------------

*******************************************************

🧐 Transferencia de datos desde un controlador hacia una vista
⚙️ controlador :
-------------------------------------------------------
$data = array(
    'titulo' => 'Página de inicio',
    'descripcion' => 'Php-framework está desarrollado en...',
);
$this->public('/views/datesView.php', $data);
-------------------------------------------------------

⚙️ vista : 
-------------------------------------------------------
<title><?php echo $data['titulo']; ?></title>
<p><?php echo $data['descripcion']; ?></p>
-------------------------------------------------------

*******************************************************

🧐 Configuración de variables de entorno
⚙️ .env :
-------------------------------------------------------
## 📖
# La configuración inicial utiliza estas credenciales en base de datos: Gythdruop$11
# db
DB_HOST=localhost
DB_USER=db_u4445
DB_PASS=pBEXW8qMPWvPbzZl7/vUpQ==
DB_NAME=db_u04
KEY=pHtKs!2FNyU5%aNPoTfRcZixVUUahYBu
IV=Vxii6oTg2zDPW7gH

## 📖
# Estas variables controlan la activación de las funcionalidades de depuración proporcionadas por el 'utils' Debug.
# En entornos de producción, se recomienda una configuración de depuración restrictiva, (como en el ejemplo).
# data
VERSION=1.2.0
ERRORS=false
TRACES=false
LOGS=true
-------------------------------------------------------

⚙️ controlador :
-------------------------------------------------------
...
## 📖
# Es posible definir variables de entorno adicionales según las necesidades de la aplicación. 
# Estas variables pueden cargarse directamente desde el controlador.
$this->env();
$key = getenv('KEY');
...
-------------------------------------------------------

*******************************************************

🧐 Configuración y registro de rutas
⚙️ archivo de configuración (/routers/.routes) :
-------------------------------------------------------
<?php
# Definir controlador 404:
require_once('./app/modals/error/errorController.php');

# Definir resto de controladores web:
require_once('./app/modals/example/exampleController.php');
-------------------------------------------------------

⚙️ controlador :
-------------------------------------------------------
## 📖
# En este archivo se registran las rutas disponibles para la aplicación. 
# Los controladores que intervienen en las rutas deben estar previamente incluidos mediante el mecanismo de autoload.
Http::autoload('.routes');

## 📖
# El método error permite establecer el controlador y método responsables de gestionar las respuestas 404.
Http::error(['controller' => 'ErrorController', 'method' => 'notFound']);

## 📖
# Definición de los métodos HTTP, :id indica un parámetro dinámico de la URL.
Http::get('/', ['controller' => 'exampleController', 'method' => 'index']);
Http::post('/actualizar-usuario/:id', ['controller' => 'exampleController', 'method' => 'updateUser']);
Http::delete('/borrar-usuario/:id', ['controller' => 'exampleController', 'method' => 'deleteUser']);
Http::patch('/actualizar-usuario/:id', ['controller' => 'exampleController', 'method' => 'updateUser']);
Http::put('/actualizar-usuario/:id', ['controller' => 'exampleController', 'method' => 'updateUser']);
-------------------------------------------------------

⚙️ vista : 
-------------------------------------------------------
## 📖
# Para usar DELETE, PATCH y PUT en formularios HTML, es necesario recurrir a la técnica de *method spoofing*.
<form action="/borrar-usuario/<?= $user['id'] ?>" method="POST" style="display:inline;">
    <input type="hidden" name="_method" value="DELETE">
...
<form action="/modificar-usuario/<?= $user['id'] ?>" method="POST" style="display:inline;">
    <input type="hidden" name="_method" value="PUT">
... 
<form action="/modificar-usuarios/<?= $user['id'] ?>" method="POST" style="display:inline;">
    <input type="hidden" name="_method" value="PATCH">
-------------------------------------------------------

======[🚀 AVADAKEDAVRA ]============================================================================================
# ⚗️ Avadakedavra es un script de generación de código (*scaffolding*) desarrollado en PHP.
# Permite generar automáticamente los archivos principales de un módulo, como el modelo y el controlador.

# ⚗️ Procedimiento de instalación y ejecución del script
# Instalar PHP en Windows y añadirlo a las variables de entorno del sistema.
-------------------------------------------------------
    # Sitio oficial de descarga: 
    https://www.php.net/downloads.php

    # Descarga e instala el paquete 'VC_redist.x64.exe'
    > Visual Studio Versions - x64

    # Descargar el archivo ZIP correspondiente a la versión de PHP compatible con el sistema.

    # Descomprimir el ZIP, asignar un nombre identificativo —por ejemplo, `php-8.5.4`— y ubicarlo en `Program Files`.
    # Dentro de esta carpeta se encuentra `php.exe`, ejecutable utilizado por el sistema para lanzar PHP.
    C:\Program Files\php-8.5.4

    # Configurar la variable de entorno `PATH` de Windows:
    variables de  entorno > variables de sistema > path > editar > nuevo > C:\Program Files\php-8.5.4 > aceptar
    
    # Verificar la instalación de PHP mediante la terminal
    php -v
-------------------------------------------------------

# ⚗️ ejecuto el script desde la raíz del proyecto
-------------------------------------------------------
👉🏻 php .\avadakedavra.php
-------------------------------------------------------

======[🚀 CRUD ]====================================================================================================
## 📦 db · requires (select, insert, update, delete, query y noQuery) $:

⚙️ modelo - select :
-------------------------------------------------------
...
public function readUser($id = null) {
    # execute query
    # $this->select("columna1, columna2", "tabla", <opcional>: "condicion", [parámetros]);
    if ($id === null) {
        # $this->select("columna1, columna2", "tabla", <opcional>: "opciones");
        return $this->select("*", $this->table);
    }

    return $this->select("*", $this->table, "id = ?", [$id]);
}
...
-------------------------------------------------------

⚙️ modelo - insert :
-------------------------------------------------------
...
## 📖 
## Utilizar cuando quieres controlar cada dato y están definidos explícitamente en el método.
public function createUser($nombre, $password, $roleId) {
    # execute no query
    # $this->insert("tabla", ["columna1" => "valor1", "columna2" => "valor2"]);
    return $this->insert($this->table, [
        'nombre' => $nombre,
        'password' => $password,
        'role_id' => $roleId
    ]);
}
...
## 📖 
## Utilizar cuando las columnas y valores a insertar se reciben dinámicamente en un array con muchos datos.
public function createUserData200(array $data) {
    return $this->insert($this->table, $data);
}
...
-------------------------------------------------------

⚙️ modelo - update :
-------------------------------------------------------
...
public function updateUser($id, $nombre) {
    # execute no query
    # $this->update("tabla", ["columna1" => "nuevo_valor"], <opcional>: "condicion", [parámetros]);
    return $this->update($this->table, ['nombre' => $nombre], 'id = :id', ['id' => $id]);
}
...
-------------------------------------------------------

⚙️ modelo - delete :
-------------------------------------------------------
...
public function deleteUser($id) {
    # execute no query
    # $this->delete("tabla", <opcional>: "condicion", [parámetros]);
    return $this->delete($this->table, 'id = :id', ['id' => $id]);
}
...
-------------------------------------------------------

⚙️ modelo - query :
-------------------------------------------------------
...
## 📖 
## Utilizar para consultas tipo 'select'
public function index() {
    # $this->query("consulta SQL", ["valor1", "valor2"]);
    # array multidimensional => [ ['usuario_id' => '1', 'nombre_usuario' => 'Isaac'...], ['usuario_id' => '2', 'nombre_usuario' => 'Ana'...], ... ]
    return $this->query("
        SELECT
            u.id AS usuario_id,
            u.nombre AS nombre_usuario,
            r.nombre AS nombre_rol
        FROM {$this->table} u
        INNER JOIN roles r
            ON u.role_id = r.id
    ");
}
...
-------------------------------------------------------

⚙️ modelo - noQuery :
-------------------------------------------------------
...
## 📖 
## Utilizar para consultas tipo 'update, insert o delete'
public function createUser($nombre, $password, $roleId){
    # $this->noQuery("consulta SQL", ["valor1", "valor2", "valor3"]);
    # int => 1 (one row affected), 0 (no rows affected)
        $sql = "INSERT INTO {$this->table} (nombre, password, role_id)
        VALUES (?, ?, ?)";
        
    return $this->noQuery($sql, [$nombre, $password, $roleId]);
} 
...
-------------------------------------------------------

# -

======[🚀 UTILS ]==================================================================================
# 📦 Conjunto de 'utils' desarrolladas por Isaac Navajas Pozo.
# ⚠️ El acceso a los 'utils' se realiza mediante operadores de resolución de ámbito.
# Todos los 'utils' pueden inicializarse desde el constructor del controlador cuando vaya a utilizarse de forma recurrente. 
# También pueden inicializarse en el método y consultarse desde la vista sin transferir el diccionario completo como parámetro.

*******************************************************

## 📦 Utils · sessions
⚙️ controlador : 
-------------------------------------------------------
...
    public function sessionValidationTests() {
        ## 📖

        # Comprueba que la sesión esta activa
        # 👉🏻 debug::kill(Session::isStarted());
        ...
        # Guarda valores de sesión
        👉🏻 Session::set('id', 123);
        👉🏻 Session::set('nombre', 'Alice');
        👉🏻 Session::set('rol', 'admin');
        ...
        # Obtiene los valores guardados
        👉🏻 echo "id:" . "Session::get('id')";
        👉🏻 echo "nombre:" . "Session::get('nombre')";
        👉🏻 echo "rol:" . "Session::get('rol')";
        ...
        # Comprueba si la sesión contiene un valor
        👉🏻 echo "¿id existe?: " . "Session::has('id')";
        ...
        # Comprueba todos los valores de la sesión 
        # 👉🏻 debug:all(Session::all());
        ...
        # Regenera la sesión
        # Se utiliza para evitar session fixation o relaccionados con el login como modificación de privilegios en vivo
        👉🏻 $oldId = Session::id();
        👉🏻 $result = Session::regenerate();
        👉🏻 $newId = Session::id(); 
        👉🏻 echo "ID anterior: $oldId | ID nuevo: $newId | ¿Cambió el ID?: " . ($oldId !== $newId);
        ...
        # Elimina una sesión
        👉🏻 Session::delete('rol');
        ...
        # Cierra temporalmente la sesión para guardar los cambios y liberar el bloqueo.
        👉🏻 Session::close();
        # Reabre la sesión para continuar trabajando con sus datos.
        👉🏻 Session::start();
        ...
        # Destruye completamente la sesión actual y todos sus datos (se utiliza principalmente en logout).
        # En la siguiente petición, el Bootstrap creará una nueva sesión vacía.
        👉🏻 Session::destroy();

    }
...
-------------------------------------------------------

*******************************************************

## 📦 Utils · auth
⚙️ controlador : 
-------------------------------------------------------
<?php
require_once('./engine/kernel.php');

class ExampleController extends kernel {
    private $exampleModel;

    public function __construct() {
        parent::__construct();
        $this->modal('example/exampleModel.php');
        $this->exampleModel = new ExampleModel();

        ## 📖
        ## Deben cargarse estos utils antes de utilizar auth y sus funcionalidades asociadas.
        👉🏻 $this->utils('auth.php');
        👉🏻 $this->utils('sessions.php');
        👉🏻 $this->utils('password.php');
        👉🏻 $this->utils('sanitizer.php');
        👉🏻 $this->utils('url.php');

        ## 📖
        ## Asignar el modelo que utilizará Auth para las consultas de autenticación.
        # 1.º parámetro: instancia del modelo que realizará la consulta.
        # 2.º parámetro: nombre del método del modelo que obtiene el usuario a verificar la autenficiación.
        👉🏻 Auth::init($this->exampleModel, 'getUserByUsername');
    }

    ## ~~ L O G I N ~~
    # -
    ## 📖 
    ## Si es necesario hacer pruebas tengo comentado un ejemplo de usuario y contraseña valido.
    ## Usuario de prueba (<usuario>:<contraseña>):
    ## prueba:Tirfreigynder%78
    ## prueba:$2y$10$nhvWTgsvhG9PFXPTGvEDTu7adVJc0rq3fNV3kuu8v3oyU/ERfzn1.
    # -
    ## Inicializo el CSRF y enlazo la vista
    public function login() {
        ## 📖
        ## Genero un token CSRF y se lo añado a la vista
        👉🏻 $csrf = Auth::generateCsrfToken();
        👉🏻 $this->public('/views/example/login.php', ['csrf' => $csrf]);
    }

    ## Lógica para comprobar usuario correcto
    public function authenticate() {
        ## 📖
        ## Recoge los valores enviados por la vista
        👉🏻 $username = Sanitizer::forceString($_POST['username'] ?? '');
        👉🏻 $password = $_POST['password'] ?? '';
        👉🏻 $csrf = $_POST['csrf_token'] ?? '';

        ## 📖
        ## Calcula si el usuario es valido
       👉🏻 if (Auth::authenticate($username, $password, $csrf)) {
            ## Evita session fixation después del login.
            # 👉🏻 Session::regenerate(true);
            // almaceno los valores de sesion
            # Session::set('name', $username);
            # Session::set('csrf', $csrf);
            # debug::kill(Session::all());
            # Url::redirect('/dashboard');

            echo 'Usuario verificado.';
            return;
        }

        echo 'Usuario o contraseña incorrectos.';
    }

    ## 📖
    ## Método para destruir la sesion y hacer logout
    public function logout(){
        Auth::logout();
        Url::redirect('/login');
    }
...
-------------------------------------------------------

⚙️ modelo : 
-------------------------------------------------------
...
private $table = 'users';
...
## ~~ L O G I N ~~
## 📖
## Este método lo utiliza el 'util' auth, con el mismo nombre,
## por lo que es obligatorio que su nombre sea el mismo al declarado en Auth:init.
👉🏻 public function getUserByUsername($username) {
    $query = $this->select('id, username, password', $this->table, 'username = ?', [$username]);

    return $query;
}
...
-------------------------------------------------------

⚙️ vista : 
-------------------------------------------------------
<?php
## 📖
## defino la variable $csrf
👉🏻 $csrf = $data['csrf'] ?? '';
?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Iniciar sesión</title>
</head>

<body>
    <h1>Iniciar sesión</h1>
    <form method="POST" action="/login/authenticate">
        ## 📖
        ## Creo el input del CSRF para heredar su valor
        👉🏻 <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf, ENT_QUOTES, 'UTF-8'); ?>">
        <div>
            <label for="username">Usuario</label>
            <input type="text" id="username" name="username" required autocomplete="username">
        </div>
        <div>
            <label for="password">Contraseña</label>
            <input type="password" id="password" name="password" required autocomplete="current-password">
        </div>
        <button type="submit">Iniciar sesión</button>
    </form>
</body>
</html>
-------------------------------------------------------

*******************************************************

## 📦 Utils · lang
⚙️ controlador : 
-------------------------------------------------------
...
    public function createForm() {
        👉🏻 $this->utils('lang.php');
        👉🏻 Lang::init('es');
        # Lang::setLang('en');

       $this->public('/views/example/create.php');
    }
...
-------------------------------------------------------

⚙️ archivo de configuración (/lang/es.txt) : 
-------------------------------------------------------
👉🏻 welcome=Bienvenido
logout=Cerrar sesión
username=Usuario
password=Contraseña
-------------------------------------------------------

⚙️ vista : 
-------------------------------------------------------
...
    <h1> <?= 👉🏻 Lang::get('welcome'); ?> </h1>
...
-------------------------------------------------------

*******************************************************

## 📦 Utils · debug
⚙️ controlador :
-------------------------------------------------------
...
👉🏻 $this->utils('debug.php');
...
public function createUserData200() {
    $data = $_POST;
        
    ## 📖
    # Método principal: `log($fileName, $logMessage, $maxDays = 7, $maxExpire = 365, $maxSizeMB = 80)`.
    # 1º parámetro: nombre lógico del archivo de registro; la utilidad crea la carpeta y añade la fecha al archivo.
	`./engine/logs/documentation/documentation-2026-06-26.log`.
    # 2º parámetro: mensaje que se desea registrar en el archivo de log.
    # 3º parámetro (opcional): número máximo de días antes de generar un nuevo archivo de log.
    # 4º parámetro (opcional): número máximo de días que se conservará el archivo antes de su eliminación automática.
    # 5º parámetro (opcional): tamaño máximo del archivo. Si se supera, se genera un nuevo archivo con un sufijo numérico.
		
    # Esta limitación evita el crecimiento de los archivos por exceso de eventos o ataques de denegación de servicio.
    👉🏻 Debug::log('documentation.log', 'El usuario ha accedido correctamente a la página de documentación.', 10, 729, 50);

    ## 📖
    # Genera una salida de depuración, y determina automáticamente el formato de salida.
    👉🏻 Debug::kill($data);
    # Muestra el *backtrace*, es decir, la pila de llamadas que condujo hasta el punto de ejecución actual.
    # 👉🏻 Debug::trace($data);
}
-------------------------------------------------------

*******************************************************

## 📦 Utils · sanitizer
⚙️ controlador :
-------------------------------------------------------
...
    public function createUser() {
        // Carga utils de forma local
        👉🏻 $this->utils('sanitizer.php');
        $this->utils('password.php');

        $this->exampleModel->createUser(
            👉🏻 Sanitizer::forceString($_POST['nombre']),
            Password::hash($_POST['password']),
            👉🏻 Sanitizer::forceString($_POST['role_id'])
        );

        Url::redirect('/usuarios');
    }
...
-------------------------------------------------------

⚙️ Referencia rápida de métodos opcionales :
-------------------------------------------------------
# Sanitización individual (🔥 ~ 20% más rápido que de forma masiva).
$resultado = array(
    'nombre'      => Sanitizer::forceString($datosEntrada['nombre']),
    'edad'        => Sanitizer::forceInt($datosEntrada['edad']),
    'altura'      => Sanitizer::forceFloat($datosEntrada['altura']),
    'activo'      => Sanitizer::forceBool($datosEntrada['activo']),
    'datos'       => Sanitizer::forceArray($datosEntrada['datos']),
    'json'        => Sanitizer::forceJson($datosEntrada['json']),

    'email'       => Sanitizer::forceEmail($datosEntrada['email']),
    'url'         => Sanitizer::forceUrl($datosEntrada['url']),
    'ip'          => Sanitizer::forceIp($datosEntrada['ip']),
    'uuid'        => Sanitizer::forceUuid($datosEntrada['uuid']),
    'hex'         => Sanitizer::forceHex($datosEntrada['hex'], 6),
    'slug'        => Sanitizer::forceSlug($datosEntrada['slug']),

    'fecha'       => Sanitizer::forceDateTime($datosEntrada['fecha'])->format('Y-m-d H:i:s'),
    'timestamp'   => Sanitizer::forceTimestamp($datosEntrada['timestamp']),

    'dni'         => Sanitizer::forceDNI($datosEntrada['dni']),
    'alphanum'    => Sanitizer::forceAlphaNum($datosEntrada['alphanum']),
    'precio'      => Sanitizer::forcePrecio($datosEntrada['precio']),
);

# Sanitización masiva de un conjunto de datos.
$sanitizado = Sanitizer::sanitizeArray($datosEntrada, array(
    'nombre' => 'forceString',
    'edad'   => 'forceInt',
    'email'  => 'forceEmail',
    'activo' => 'forceBool',
));
-------------------------------------------------------

*******************************************************

## 📦 Utils · password
# La utilidad 'password' almacena contraseñas de forma segura mediante bcrypt.
⚙️ Benchmark conceptual de algoritmos criptográficos:
+------------------+----------------+---------------+
| ALGORITMO        | SEGURIDAD      | VELOCIDAD     |
+------------------+----------------+---------------+
| MD5              | █░░░░░░░░░░    | ████████░░░   |
| SHA1             | █░░░░░░░░░░    | ████████░░░   |
+------------------+----------------+---------------+
| SHA256 (sin salt)| ████░░░░░░░    | ██████░░░░░   |
| SHA256 (con salt)| ████░░░░░░░    | ██████░░░░░   |
+------------------+----------------+---------------+
| BCRYPT           | ███████░░░░    | ████░░░░░░░   |
+------------------+----------------+---------------+
| ARGON2ID         | █████████░░    | █░░░░░░░░░░   |
+------------------+----------------+---------------+

⚙️ controlador :
-------------------------------------------------------
...
    public function createUser() {
        // Carga utils de forma local
        $this->utils('sanitizer.php');
        👉🏻 $this->utils('password.php');

        $this->exampleModel->createUser(
            Sanitizer::forceString($_POST['nombre']),
            👉🏻 Password::hash($_POST['password']),
            Sanitizer::forceString($_POST['role_id'])
        );

        Url::redirect('/usuarios');
    }
...
    ## 📖
    # para comprobar que la password coincide y es correcta
    # 👉🏻  if (password_verify($_POST['password'], $hash)) {
    #    echo "Login correcto";
    # }
...
    ## 📖
    # autogenera una contraseña segura en texto plano y fácil de recordar como: Pogaxuba!25
    # 👉🏻 $pass = Password::generate();
    # echo Password::hash($pass);
-------------------------------------------------------

*******************************************************

## 📦 Utils · tokenEncryption
⚙️ controlador :
-------------------------------------------------------
...
👉🏻 $this->env();
👉🏻 $this->utils('tokenEncryption.php');
...
    }

    public function index() {
        # Esta variable contiene el valor de ejemplo que se cifrará de forma segura.
        $defaultPassword = 'Gythdruop$11';

        $changeLogPath = './engine/.documentation/changelog.txt';
        $schemePath = './engine/.documentation/scheme.txt';
        $changeLogPath = file_get_contents($changeLogPath);
        $schemePath = file_get_contents($schemePath);

        ## 📖
        # Cifra un valor utilizando la `KEY` y el `IV` definidos previamente en el archivo `.env`.
        # Ambos valores deben estar almacenados previamente en la configuración de entorno.
        👉🏻 $encryptedPassword = tokenEncryption::encrypt($defaultPassword, getenv('KEY'), getenv('IV'));

        ## 📖
        # Genera nuevos valores aleatorios y validos para `KEY` e `IV`.
        👉🏻 $key = tokenEncryption::randomPseudoKEY();
        👉🏻 $iv = tokenEncryption::randomPseudoIV();

        ## 📖
        # Para descifrar posteriormente un valor cifrado:
        # Es imprescindible utilizar la misma `KEY` y el mismo `IV` empleados durante el cifrado original.
        # 👉🏻 $desencryptedPassword = tokenEncryption::decrypt($defaultPassword, getenv('KEY'), getenv('IV')); 

        $data = array(
            'titulo' => 'php-framework: Página de inicio',
            'version' => getenv('VERSION'),
            'changelog' => $changeLogPath,
            'scheme' => $schemePath,
            'iv' => $iv,
            'key' => $key,
            'encryptedPassword' => $encryptedPassword
        );

        Debug::log('documentation.log', 'a entrado a página de documentación.', 1);
        $this->public('/views/documentacion.php', $data);
    }
...
-------------------------------------------------------

*******************************************************

## 📦 Utils · email
⚙️ archivo de configuración :
# ./engine/utils/email.php
-------------------------------------------------------
...
 ## 📖
# El límite de tamaño de los archivos adjuntos se configura directamente en el código del 'util'.
# Este valor es el mismo para todos y establece el tamaño máximo permitido para cada archivo adjunto.
# Además, se ejecuta antes de incorporarlo al mensaje de correo.
👉🏻 private static $maxAttachmentSize = 5242880; // 5 MB
...
-------------------------------------------------------

⚙️ controlador : (ejemplo 1)
# Ejemplo 1 · Envío a un único destinatario
-------------------------------------------------------
...
    public function testEmail() {
        👉🏻 $this->utils('email.php');

        try {
            ## 📖

            # Configuración de los datos del emisor del correo.
            # 1º parámetro: dirección del remitente válida o identidad configurada en el servidor SMTP.
            # 2º parámetro: nombre visible del remitente para el destinatario.
            👉🏻 Email::init('noreply@php-framework.com', 'php-framework');
            
            # Definición del destinatario principal. 
            # Este método está orientado al envío a un único destinatario.
            👉🏻 Email::addRecipient('correo1@gmail.com');

            # Opcionalmente, pueden añadirse destinatarios en copia (`CC`) o copia oculta (`BCC`).
            # 👉🏻  Email::addCc('copia@dominio.com');
            # 👉🏻  Email::addBcc('oculto@dominio.com');

            # Definición del asunto del mensaje.
            👉🏻 Email::setSubject('Prueba de Email');

            # Definición del cuerpo del mensaje. Admite contenido HTML completo.
            👉🏻 Email::setBody('
                <h1>¡Hola!</h1>
                <p>Este correo ha sido enviado desde mi framework.</p>
            ');

            👉🏻 if (Email::send()) {
                echo "Correo enviado correctamente";
            } else {
                echo "Error al enviar el correo";
            }

        } catch (Exception $e) {
            echo $e->getMessage();
        }
    }
...
-------------------------------------------------------

⚙️ controlador : (ejemplo 2)
# Ejemplo 2 · Envío a múltiples destinatarios (con envío independiente)
-------------------------------------------------------
...
	public function testEmail() {
        👉🏻 $this->utils('email.php');

        try {
            ## 📖 

            # Configuración de los datos del emisor del correo.
            # 1º parámetro: dirección del remitente valida o configurada en el servidor SMTP.
            # 2º parámetro: nombre visible del remitente para el destinatario.
            👉🏻 Email::init('noreply@php-framework.com', 'php-framework');

            Lista de destinatarios, el contenido se envía individualmente a cada dirección.
            👉🏻 $emails = [
                'correo1@gmail.com',
                'correo2@gmail.com',
            ];
            
            # Opcionalmente, pueden añadirse destinatarios en copia (`CC`) o copia oculta (`BCC`).
            # 👉🏻  Email::addCc('copia@dominio.com');
            # 👉🏻  Email::addBcc('oculto@dominio.com');

            # Definición del asunto del mensaje.
            👉🏻 Email::setSubject('Prueba de Email Masivo');

            # cuerpo generado directamente (permite HTML completo)
            👉🏻 Email::setBody('
            <h1>¡Hola!</h1>
            <p>Este correo ha sido enviado desde mi framework en modo masivo.</p>
            ');

            # Envío individual por destinatario para mejorar la trazabilidad y entregabilidad.
            👉🏻 Email::sendToMultiple($emails);

            echo "Correos enviados correctamente";

        } catch (Exception $e) {
            echo $e->getMessage();
        }
    }
...
-------------------------------------------------------

⚙️ controlador : (ejemplo 3)
# Ejemplo 3 · Correo HTML dinámico con adjuntos y varios destinatarios (con un único envío) 
-------------------------------------------------------
	public function testEmail() {
        👉🏻 $this->utils('email.php');

        try {
            ## 📖

            # Configuración del remitente.
            👉🏻 Email::init('noreply@php-framework.com', 'php-framework');

            # Configuración de varios destinatarios (un único envío).
            👉🏻 Email::addRecipient('correo1@gmail.com');
            👉🏻 Email::addRecipient('correo2@gmail.com');

            # Destinatarios en copia (`CC`).
            👉🏻 Email::addCc('correo3@gmail.com');
            👉🏻 Email::addCc('correo4@gmail.com');

            # Destinatarios en copia oculta (`BCC`).
            👉🏻 Email::addBcc('correo5@gmail.com');

            # Definición del asunto del mensaje.
            👉🏻 Email::setSubject('Email con vista y adjuntos');

            # Generación del cuerpo del mensaje mediante una vista HTML dinámica.
            # La vista se resuelve siempre desde `./app/public/views`.
            # 1º parámetro: nombre del archivo de la vista.
            # 2º parámetro: array con los valores dinámicos utilizados por la vista.
            # 3º parámetro: define el formato del contenido (true: HTML; false: texto plano). Por defecto, true.
            👉🏻 Email::setBodyFromView(
                'test.php',
                [
                    'nombre' => 'Isaac',
                    'fecha' => date('Y-m-d H:i:s')
                ],
                true
            );

            # Añadir un archivo adjunto (PDF, imagen u otro formato compatible).
            # Los archivos adjuntos se resuelven desde `./app/public/src`.
            # 1º parámetro: nombre del archivo almacenado en el servidor.
            # 2º parámetro: nombre con el que se enviará el archivo al destinatario.
            👉🏻 Email::addAttachment(
                'ejemplo.pdf',
                'Documento_Ejemplo.pdf'
            );

            # Segundo adjunto opcional (por ejemplo, una imagen).
            👉🏻 Email::addAttachment(
                'php-framework.png',
                'logo.png'
            );

            # Ejecución del envío.
            👉🏻 if (Email::send()) {
                echo "Correo enviado correctamente con vista y adjuntos";
            } else {
                echo "Error al enviar el correo";
            }

        } catch (Exception $e) {
            echo $e->getMessage();
        }
    }
...
-------------------------------------------------------

⚙️ vista : (perteneciente al ejemplo 3)
# ./app/public/views/test.php
-------------------------------------------------------
<!DOCTYPE html>
<html lang="es">
<head>
	<meta charset="UTF-8">
	<title>Email de prueba</title>
	<style>
		body {
			font-family: Arial, sans-serif;
			background-color: #f4f6f8;
			margin: 0;
			padding: 0;
		}

		.container {
			max-width: 600px;
			margin: 30px auto;
			background: #ffffff;
			border-radius: 10px;
			padding: 25px;
			box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
		}

		.header {
			background: #2c3e50;
			color: white;
			padding: 15px;
			border-radius: 8px;
			text-align: center;
		}

		.content {
			margin-top: 20px;
			color: #333;
			line-height: 1.6;
		}

		.info-box {
			background: #ecf0f1;
			padding: 15px;
			border-radius: 8px;
			margin-top: 15px;
		}

		.footer {
			margin-top: 30px;
			font-size: 12px;
			color: #888;
			text-align: center;
		}
	</style>
</head>

<body>
	<div class="container">
		<div class="header">
		<h2>📧 Email de Prueba</h2>
		</div>
        <div class="content"><p>Hola <strong><?= htmlspecialchars($nombre) ?></strong>,</p>
		<p>Este correo ha sido generado desde php-framework utilizando una vista HTML dinámica.</p>
		<div class="info-box">
        <p><strong>📅 Fecha de envío:</strong><?= $fecha ?></p>
		</div>
		<p>Este es un ejemplo de cómo puedes construir emails profesionales con HTML, variables dinámicas y adjuntos.</p>
		</div>
		<div class="footer">©<?= date('Y') ?> PHP Framework - Email System</div>
	</div>
</body>
</html>
-------------------------------------------------------

*******************************************************

## 📦 utils · images $:
⚙️ controlador : 
# Conversión de una imagen a Base64.
-------------------------------------------------------
...
    public function testImageBase64() {
        👉🏻 $this->utils('image.php');

        try {
            ## 📖
            # Las imagenes se resuelven desde `./app/public/src/`.
            👉🏻 $base64 = Image::toBase64('php-framework.png');

            👉🏻 if (!$base64) {
                throw new Exception('No se pudo convertir la imagen a Base64');
            }

            echo "<h3>Imagen convertida a Base64</h3>";
            echo "<p>$base64</p>";
            👉🏻 echo "<img src='{$base64}' style='max-width:300px'>";

        } catch (Exception $e) {
            echo $e->getMessage();
        }
    }
...
-------------------------------------------------------

⚙️ controlador : 
# Conversión de Base64 a imagen y almacenamiento en `./app/public/src/`.
-------------------------------------------------------
...
	public function testBase64FromDB() {
        👉🏻 $this->utils('image.php');

        try {
            ## 📖
            # Simulo un valor Base64 obtenido desde una base de datos...
            $image = './app/public/src/php-framework.png';
            $fakeDB = [
                'image_base64' => 'data:image/png;base64,' . base64_encode(file_get_contents($image ))
            ];

            $base64 = $fakeDB['image_base64'];

            # 1º parámetro: valor Base64 recuperado de la fuente de datos.
            # 2º parámetro: nombre del archivo que se generará. La ruta predeterminada es `./app/public/src/`.
            👉🏻 $saved = Image::fromBase64(
                $base64,
                'imagen_generada.png'
            );
            
            if (file_exists($filename)) {
                echo "⚠ La imagen ya existe. Operación cancelada.";
                return;
            }

            if (!$saved) {
                throw new Exception("No se pudo guardar la imagen desde Base64");
            }

            echo "✔ Imagen guardada correctamente desde Base64";

        } catch (Exception $e) {
            echo $e->getMessage();
        }
    }
...
-------------------------------------------------------


======[🚀 utils - WRAPPERS ]=======================================================================
## 📦 Wrapper · dataTableWrapper
# $dt = new DataTableWrapper($selectorTable, $Options = [], array $filterCols = [])
# $dt->render($data, $columns);

# [ejemplo1]
⚙️ controlador :
session_start();
$data = [
    ['Name' => 'Alice', 'Role' => 'Admin', 'Status' => 'Active'],
    ['Name' => 'Bob', 'Role' => 'User', 'Status' => 'Inactive'],
    ...
];
$columns = ['Name', 'Role', 'Status'];
$filters = [1, 2];                  	// índice de columnas con filtros (Role, Status)

⚙️ vista :
$dt = new DataTableWrapper('myTable', [], $filters);
$dt->render($data, $columns);

# [ejemplo2]
⚙️ vista :
$options = ['paging' => false, 'searching' => false,];
$dt = new DataTableWrapper('table2', $options, [0]);
$dt->render($data, ['Name', 'Role', 'Status']);
# Changelog:: 
# 💾 php-framework.com · Desarrollo por Isaac Navajas Pozo

## [vf.1.2.4] - 2025-12-15:
- Desarrollo avanzado del utils sessions.
- Desarrollo avanzado del utils auth.
- Finalizo y publico la primera versión de descarga.

## [vf.1.2.3] - 2025-09-03:
- Consigo el dominio php-framnework.com para el proyecto.
- Diseño y desarrollo la página pricipal de instalación con la documentación del framework.
- Genero IV random, key random y password en página principal, para una configuración inicial agíl.
- Eliminación de utils internalShell (ilegal en servidores 'Shared hosting').
- Agrego el método generate en password, para generar una contraseña automática con buenas prácticas de seguridad.
- Incorporación de IA local (Gemma3:4B) como soporte al proceso de refactorización y mejora continua de software.

## [vf.1.2.2] - 2025-07-04:
- Modifico la interpretación de la lógica view por public.
- Implementación del archivo .htaccess robusto para endurecimiento de seguridad.
- Corrección en rutas con campos dinámicos.
- Agrego archivo robots.txt.
- Desarrollo avanzado del utils sanitizer.
- Desarrollo avanzado del utils internalShell.
- Desarrollo avanzado del utils password.
- Eliminación de utils validate (sustituida y mejorada por sanitizer).

## [vf.1.2.1] - 2025-06-27:
- Modifico el sistema de rutas.
- Agrego el método error en Http, para declarar la página de error de forma dinámica.
- Agrego el método autoload en Http.
- Agrego el método de log en en utils Debug con sus sistemas de carpetas.

## [vf.1.2.0] - 2025-06-13:
- Cambio de MVC a sistema MMVC (modularizado).
- Agrego archivo ".routes" para desacoplar y centralizar la configuración de las rutas.
- Cambio las instancias de objetos a operadores de resolución de ámbito para diferenciar los utils.
- Desarrollo avanzado del utils debug.
- Desarrollo avanzado del utils email.
- Desarrollo avanzado del utils image.
- Desarrollo avanzado del utils lang.
- Desarrollo avanzado del utils securityScanner.
- Desarrollo avanzado del utils validate.
- Desarrollo avanzado del utils tokenEncryption.
- Desarrollo avanzado del utils url.
- Implementación de wrappers dataTableWrapper.

## [vf.1.1.2] - 2025-05-01:
- Implemento el cifrado de la contraseña en la base de datos mediante IV y KEY almacenados en `.env`.
- Mejoras en el rendimiento.
- Actualización de dependencias.
- Desarrollo de rutas.

## [vf.1.1.0] - 2025-03-21:
- Desarrollo de variables de entorno (.env).
- Desarrollo el depurador de tiempo real.
- Desarrollo el kernel.
- Modifico la arquitectura y el sistema de carpetas.

## [vf.1.1.0] - 2025-03-19:
- Versión final de framework pnkd, proyecto de fin de master de la Universidad de Nebrija.
- Adaptación del framework pnkd a una nueva identidad de trabajo.
Descripción Versión Enlace LTS
Primera versión estable de php-framework. vf.1.2.4 Próximamente...