Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Nieto2020/portalhub/llms.txt

Use this file to discover all available pages before exploring further.

PortalHub is built on a classic three-tier architecture: a static HTML/CSS/JavaScript frontend communicates with a PHP-FPM backend via JSON API calls, and all persistent data lives in a MySQL database. Each layer has a clearly defined responsibility, making the system easy to extend, debug, and containerise with Docker Compose.

Three-Tier Architecture

Frontend

Pure static files (HTML, CSS, Vanilla JS). No server-side rendering — every page fetches data from the backend API and updates the DOM through view controllers in js/views/.

Backend

PHP modules served by PHP-FPM. Each functional domain (auth, citas, documentos, …) lives in its own subdirectory under backend/modules/, with shared config, middleware, and utilities.

Database

MySQL with a single consultoria schema. All tables use the utf8mb4 / utf8mb4_general_ci collation and InnoDB storage engine with enforced foreign-key constraints.

Infrastructure

Docker Compose orchestrates three services: nginx (reverse proxy), php (PHP-FPM application), and mysql (database). Configuration is injected through environment variables consumed by config.php.

Project Directory Structure

The repository root contains a single application directory, consultoria-contable/, with four top-level folders:
consultoria-contable/

├── backend/                    # Server logic and API (PHP)
   ├── config/
   ├── config.php          # Global constants (DB, roles, timeouts)
   └── conexion.php        # PDO connection factory
   ├── middleware/
   └── auth.php            # Session guard: checkAuth(), checkRole()
   ├── modules/                # Functional modules (one folder per domain)
   ├── anuncios/
   ├── asignaciones/
   ├── auth/
   ├── calificaciones/
   ├── citas/
   ├── documentos/
   ├── mensajes/
   ├── notificaciones/
   ├── pagos/
   ├── perfil/
   ├── reportes/
   ├── servicios/
   └── usuarios/
   ├── services/
   └── NotificationService.php
   ├── uploads/
   └── .htaccess           # Blocks direct HTTP access to uploaded files
   └── utils/
       ├── response.php        # sendResponse() — uniform JSON output
       └── validator.php       # validateRequired(), validateEmail()

├── database/
   ├── schema.sql              # Table definitions and indexes
   └── seed.sql                # Initial data (roles, catalogs, default admin)

├── docker/
   └── php/
       └── Dockerfile          # PHP-FPM image with required extensions

└── frontend/                   # Static UI (HTML / JS / CSS)
    ├── css/                    # Stylesheets organised by role
   ├── admin/
   ├── asesor/
   ├── auth/
   └── cliente/
    ├── js/
   ├── api/                # Thin wrappers around backend endpoints
   ├── auth.js
   ├── citas.js
   ├── documentos.js
   └── servicios.js
   ├── helpers/
   └── logoutHelper.js
   ├── utils/
   ├── helpers.js
   └── sessionCheck.js
   └── views/              # DOM controllers, one per page
       ├── citasView.js
       ├── dashboardView.js
       ├── declaracionesView.js
       ├── documentosView.js
       ├── pagosView.js
       └── perfilView.js
    └── pages/                  # HTML files organised by role
        ├── admin/
        ├── asesor/
        ├── auth/
        └── cliente/

Backend Modules

Each subdirectory of backend/modules/ owns a single business domain. Individual PHP scripts within a module handle discrete operations (create, list, update, delete) and are called directly by the Nginx routing rules.
ModuleResponsibility
anunciosPlatform-wide announcements authored by admins
asignacionesClient–advisor assignment lifecycle
authLogin, logout, registration, session check, password update
calificacionesAnonymous 1–5 star ratings from clients to advisors
citasAppointment scheduling and status management
documentosSecure file upload, versioning, CFDI validation, download
mensajesInternal chat and support tickets between users
notificacionesPer-user event alerts and broadcast notifications
pagosInvoice and payment state tracking
perfilProfile data retrieval and update
reportesAccounting reports (Mensual / Trimestral / Anual / Especial)
serviciosAccounting service status tracking per client
usuariosFull user CRUD and admin password reset, restricted to admins

Request Flow

Every API call follows the same path through the stack:
Browser  →  Nginx (reverse proxy)  →  PHP-FPM  →  PDO  →  MySQL

                                    auth middleware
                                    (checkAuth / checkRole)

                                    business logic

                                    sendResponse()

                                    JSON response
  1. Browser — A js/api/*.js helper builds the request and calls fetch().
  2. Nginx — Proxies .php requests to the PHP-FPM socket; serves static assets directly.
  3. PHP-FPM — Loads config.php, starts or resumes the PHP session, then invokes the middleware.
  4. Auth middlewarecheckAuth() validates the session and timeout; checkRole() enforces role restrictions. Any failure short-circuits the request with a JSON error response.
  5. PDO — Prepared statements execute against MySQL; results are returned as associative arrays.
  6. sendResponse() — Formats every API response identically and calls exit().

Standard JSON Response Format

All endpoints use sendResponse() from backend/utils/response.php. Every response — success or error — follows this envelope:
function sendResponse($status, $message, $data = null) {
    header("Content-Type: application/json");
    http_response_code($status);
    echo json_encode([
        "status"  => $status,
        "message" => $message,
        "data"    => $data
    ]);
    exit();
}
A successful login response looks like this:
{
  "status": 200,
  "message": "Inicio de sesión exitoso",
  "data": {
    "id_usuario": 4,
    "id_rol": 3,
    "require_password_change": false
  }
}
An error response uses the same shape with an appropriate HTTP status code:
{
  "status": 401,
  "message": "Credenciales incorrectas",
  "data": null
}
sendResponse() always calls exit() after writing the response body. This ensures no accidental output follows the JSON payload, regardless of the execution path.

Shared Utilities

response.phpsendResponse(status, message, data)

Sets the Content-Type: application/json header, writes the HTTP status code with http_response_code(), serialises the payload with json_encode(), and terminates execution. Required by every module and by the auth middleware itself.

validator.phpvalidateRequired and validateEmail

// Returns false if any listed field is absent or empty after trim
function validateRequired($data, $fields) {
    foreach ($fields as $field) {
        if (!isset($data[$field]) || empty(trim($data[$field]))) {
            return false;
        }
    }
    return true;
}

// Wraps PHP's built-in FILTER_VALIDATE_EMAIL
function validateEmail($email) {
    return filter_var($email, FILTER_VALIDATE_EMAIL);
}
These two helpers are called at the top of every write endpoint before any database interaction takes place, ensuring malformed or incomplete payloads are rejected with a 400 response early in the pipeline.

Explore Further

Database Schema

Full column-level documentation for all 15 tables, including types, nullability, constraints, and foreign keys.

Roles & Permissions

Role definitions, middleware code, the full permissions matrix, and session-security details.

Build docs developers (and LLMs) love