Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/obando1998/Proyecto_UCP/llms.txt

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

Overview

DevolutionSync implements a clean MVC (Model-View-Controller) architecture that separates business logic, data access, and presentation layers. This pattern ensures maintainability, testability, and scalability.

MVC Pattern Diagram

┌──────────────────────────────────────────────────┐
│                   USER REQUEST                    │
│            (HTTP via Browser)                     │
└────────────────────┬─────────────────────────────┘


┌────────────────────────────────────────────────────┐
│              FRONT CONTROLLER                      │
│                 (index.php)                        │
│  • Parse URL                                       │
│  • Load Controller                                 │
│  • Execute Method                                  │
└────────────────────┬───────────────────────────────┘


┌────────────────────────────────────────────────────┐
│                  CONTROLLER                        │
│        (AuthController, AdminController)           │
│  • Receive Request                                 │
│  • Validate Input                                  │
│  • Call Model Methods                              │
│  • Prepare Data                                    │
│  • Load View                                       │
└──────────┬────────────────────┬────────────────────┘
           │                    │
           ▼                    ▼
┌──────────────────┐  ┌──────────────────────────┐
│      MODEL       │  │         VIEW             │
│ (DevolucionModel)│  │  (panel_administrador)   │
│  • Database      │  │  • HTML Template         │
│  • Business Logic│  │  • Display Data          │
│  • Data Access   │  │  • User Interface        │
└──────────┬───────┘  └──────────────────────────┘


┌──────────────────────────────────────────────────┐
│               DATABASE (MySQL)                    │
│         devoluciones, usuarios, etc.              │
└───────────────────────────────────────────────────┘

Directory Structure

DevolutionSync/
├── index.php                    # Front Controller
├── Controllers/
   ├── AuthController.php       # Authentication
   ├── AdminController.php      # Administrator panel
   ├── PanelController.php      # User dashboard
   ├── ConsultaController.php   # Query/search
   ├── HomeController.php       # Home page
   └── UsuarioController.php    # User management
├── Models/
   ├── AuthModel.php            # Authentication logic
   ├── DevolucionModel.php      # Deviation management
   ├── ProductoModel.php        # Product catalog
   ├── ConsultaModel.php        # Query operations
   └── UsuarioModel.php         # User operations
├── Views/
   ├── auth/
   └── login.php            # Login page
   ├── admin/
   ├── panel_administrador.php
   └── estadisticas.php
   ├── panel/
   └── dashboard.php
   └── consulta/
       └── historial.php
└── Config/
    └── Conexion.php             # Database connection

Controllers Layer

Purpose

Controllers handle HTTP requests, coordinate between models and views, and contain application flow logic.

Controller Base Structure

<?php
require_once 'Models/AuthModel.php';

class AuthController {
    private $model;

    public function __construct() {
        if (session_status() === PHP_SESSION_NONE) session_start();
        $this->model = new AuthModel();
    }

    public function index() {
        if (isset($_SESSION['logged_in'])) {
            $this->redirigirSegunGrado($_SESSION['grado']);
            return;
        }
        require_once 'Views/auth/login.php';
    }

    public function login() {
        header('Content-Type: application/json');
        
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $username = trim($_POST['username'] ?? '');
            $password = $_POST['password'] ?? '';

            $user = $this->model->buscarUsuario($username);

            if ($user && $password === $user['PAS']) {
                $_SESSION['user'] = $user['USR'];
                $_SESSION['nombre'] = $user['NOMBRE'];
                $_SESSION['grado'] = $user['GRADO'];
                $_SESSION['logged_in'] = true;
                $_SESSION['last_activity'] = time();
                
                session_regenerate_id(true);

                echo json_encode([
                    'success' => true,
                    'redirect' => $this->getRedirectUrl($user['GRADO'])
                ]);
            } else {
                echo json_encode([
                    'success' => false, 
                    'message' => 'Credenciales incorrectas'
                ]);
            }
        }
    }

    public function logout() {
        session_unset();
        session_destroy();
        header('Location: index.php?url=auth/index');
        exit;
    }

    private function getRedirectUrl($grado) {
        switch ($grado) {
            case 1: 
                return 'index.php?url=home/index'; 
            case 2: 
                return 'index.php?url=devolucion/crear'; 
            case 3: 
                return 'index.php?url=consulta/index';
            default: 
                return 'index.php?url=auth/index';
        }
    }

    private function redirigirSegunGrado($grado) {
        header('Location: ' . $this->getRedirectUrl($grado));
        exit;
    }
}

Controller Responsibilities

Request Handling

  • Receive HTTP requests
  • Parse GET/POST parameters
  • Validate input data
  • Handle file uploads

Business Logic

  • Call model methods
  • Process data
  • Apply business rules
  • Handle transactions

Security

  • Session management
  • Authentication checks
  • Authorization validation
  • CSRF protection

Response

  • Load views
  • Return JSON
  • Handle redirects
  • Set headers

Controller Naming Convention

Pattern: {Entity}Controller.php
  • Class name: {Entity}Controller
  • File name: {Entity}Controller.php
  • Location: Controllers/
  • Example: AdminController.php contains class AdminController

Models Layer

Purpose

Models handle data access, database operations, and contain business logic related to data manipulation.

Model Base Structure

<?php
require_once 'Config/Conexion.php';

class DevolucionModel {
    private $db;

    public function __construct() {
        $this->db = Conexion::Conectar();
    }

    // ========================================
    // CREATE - Insert new deviation
    // ========================================
    
    public function guardar($datos) {
        $sql = "INSERT INTO devoluciones (
                    nit, nombre_cliente, direccion, item_producto, 
                    descripcion_producto, unidad, kg, motivo, 
                    cantidad_und, cantidad_kg, observacion, 
                    usuario_creador, estado, fecha_creacion, evidencia
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'Pendiente', NOW(), ?)";
        
        $stmt = $this->db->prepare($sql);
        
        return $stmt->execute([
            $datos['nit'],
            $datos['nombre_cliente'],
            $datos['direccion'],
            $datos['item_producto'],
            $datos['descripcion_producto'],
            $datos['unidad'],
            $datos['kg'],
            $datos['motivo'],
            $datos['cantidad_und'],
            $datos['cantidad_kg'],
            $datos['observacion'],
            $datos['usuario_creador'],
            $datos['evidencia'] ?? null
        ]);
    }

    // ========================================
    // READ - Get pending deviations
    // ========================================

    public function obtenerPendientes() {
        $stmt = $this->db->prepare(
            "SELECT * FROM devoluciones 
             WHERE estado = 'Pendiente' 
             ORDER BY fecha_creacion ASC"
        );
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    // ========================================
    // UPDATE - Process admin review
    // ========================================

    public function procesarRevision($id, $accion, $codigo, $obs, $revisor) {
        try {
            $this->db->beginTransaction();

            $sql = "UPDATE devoluciones 
                    SET estado = ?, 
                        codigo_admin = ?, 
                        observacion_admin = ?, 
                        usuario_revisor = ?, 
                        fecha_revision = NOW() 
                    WHERE id = ?";
            
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$accion, $codigo, $obs, $revisor, $id]);

            $this->db->commit();
            return true;
        } catch (Exception $e) {
            $this->db->rollBack();
            return false;
        }
    }

    // ========================================
    // STATISTICS - Dashboard data
    // ========================================
    
    public function obtenerEstadisticas($fecha = null) {
        $where = $fecha ? "WHERE DATE(fecha_creacion) = :fecha" : "";
        
        $sql = "SELECT 
                    COUNT(*) as total,
                    COALESCE(SUM(cantidad_kg), 0) as total_kg,
                    COALESCE(SUM(cantidad_und), 0) as total_und,
                    
                    COUNT(CASE WHEN estado = 'Pendiente' THEN 1 END) as pendientes,
                    COUNT(CASE WHEN estado = 'Aprobado' THEN 1 END) as aprobados,
                    COUNT(CASE WHEN estado = 'Rechazado' THEN 1 END) as rechazados,
                    
                    COUNT(CASE WHEN motivo = 'Devolucion' THEN 1 END) as motivo_dev,
                    COUNT(CASE WHEN motivo = 'Faltante' THEN 1 END) as motivo_fal,
                    COUNT(CASE WHEN motivo = 'Sobrante' THEN 1 END) as motivo_sob
                FROM devoluciones 
                $where";

        $stmt = $this->db->prepare($sql);
        if ($fecha) $stmt->bindValue(':fecha', $fecha);
        $stmt->execute();
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    public function obtenerFechas() {
        $stmt = $this->db->query(
            "SELECT DISTINCT DATE(fecha_creacion) as fecha 
             FROM devoluciones 
             ORDER BY fecha DESC"
        );
        return $stmt->fetchAll(PDO::FETCH_COLUMN);
    }
}

Model Responsibilities

  • Execute SQL queries
  • Use prepared statements
  • Handle database connections
  • Return result sets
  • Create: Insert new records
  • Read: Query and fetch data
  • Update: Modify existing records
  • Delete: Remove records (soft/hard)
  • Data validation
  • Calculations and aggregations
  • Transaction management
  • Data transformations
  • Hide SQL complexity
  • Provide clean interfaces
  • Return formatted data
  • Handle database errors

PDO Connection Pattern

All models use the Conexion class for database access:
private $db;

public function __construct() {
    $this->db = Conexion::Conectar();
}
The Conexion::Conectar() method returns a configured PDO instance with:
  • Exception error mode
  • Associative array fetch mode
  • Real prepared statements (not emulated)

Prepared Statements

$sql = "SELECT * FROM devoluciones WHERE id = ? AND estado = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([123, 'pendiente']);

Transaction Management

DevolucionModel.php (excerpt)
public function procesarRevision($id, $accion, $codigo, $obs, $revisor) {
    try {
        $this->db->beginTransaction();

        // Multiple database operations
        $stmt = $this->db->prepare($sql);
        $stmt->execute([...]);
        
        // More operations...

        $this->db->commit();
        return true;
    } catch (Exception $e) {
        $this->db->rollBack();
        return false;
    }
}
Transaction Best Practices:
  • Always use try-catch with transactions
  • Roll back on any error
  • Keep transactions short
  • Avoid user input during transactions

Views Layer

Purpose

Views handle presentation logic and render HTML templates with data provided by controllers.

View Structure Example

Views/admin/panel_administrador.php (excerpt)
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title><?= htmlspecialchars($titulo) ?></title>
    <link rel="stylesheet" href="assets/css/admin.css">
</head>
<body>
    <header>
        <h1>Panel de Administración</h1>
        <p>Usuario: <?= htmlspecialchars($_SESSION['nombre']) ?></p>
    </header>
    
    <main>
        <section class="pendientes">
            <h2>Devoluciones Pendientes (<?= count($pendientes) ?>)</h2>
            
            <?php if (empty($pendientes)): ?>
                <p class="no-data">No hay devoluciones pendientes</p>
            <?php else: ?>
                <table>
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Cliente</th>
                            <th>Producto</th>
                            <th>Motivo</th>
                            <th>Fecha</th>
                            <th>Acciones</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($pendientes as $dev): ?>
                            <tr>
                                <td><?= htmlspecialchars($dev['id']) ?></td>
                                <td><?= htmlspecialchars($dev['nombre_cliente']) ?></td>
                                <td><?= htmlspecialchars($dev['descripcion_producto']) ?></td>
                                <td><?= htmlspecialchars($dev['motivo']) ?></td>
                                <td><?= htmlspecialchars($dev['fecha_creacion']) ?></td>
                                <td>
                                    <button onclick="revisar(<?= $dev['id'] ?>)">
                                        Revisar
                                    </button>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            <?php endif; ?>
        </section>
    </main>
    
    <script src="assets/js/admin.js"></script>
</body>
</html>

View Responsibilities

Presentation

  • Render HTML
  • Display data
  • Format output
  • Apply styling

User Interface

  • Forms
  • Tables
  • Buttons
  • Navigation

Data Display

  • Loop through arrays
  • Conditional rendering
  • Format dates/numbers
  • Escape output

Client-Side

  • Include JavaScript
  • Include CSS
  • AJAX calls
  • Event handlers

XSS Prevention

Always escape user data in views:
<!-- GOOD: Escaped output -->
<p>Cliente: <?= htmlspecialchars($cliente['nombre']) ?></p>

<!-- BAD: Direct output (XSS vulnerability) -->
<p>Cliente: <?= $cliente['nombre'] ?></p>

<!-- GOOD: Escaped attribute -->
<input type="text" value="<?= htmlspecialchars($producto['nombre']) ?>">

<!-- GOOD: Loop with escaping -->
<?php foreach ($items as $item): ?>
    <li><?= htmlspecialchars($item['descripcion']) ?></li>
<?php endforeach; ?>

View Loading Pattern

Controllers load views using require_once:
public function index() {
    $titulo = "Panel Administrador";
    $pendientes = $this->model->obtenerPendientes();
    $historial = $this->consultaModel->obtenerHistorial(50);
    
    // Variables are available in the view
    require_once 'Views/admin/panel_administrador.php';
}
Variables defined in the controller method are automatically available in the required view file.

Data Flow Example

Complete Request-Response Cycle

1

User Submits Form

User clicks “Aprobar” button on admin panel
fetch('index.php?url=admin/revisar', {
    method: 'POST',
    body: formData
})
2

Front Controller Routes

index.php parses URL and loads AdminController
$controllerName = 'AdminController';
$method = 'revisar';
$controller->revisar();
3

Controller Validates

AdminController::revisar() validates input
$id = intval($_POST['id_devolucion'] ?? 0);
$accion = trim($_POST['accion'] ?? '');
if (!in_array($accion, ['aprobado', 'rechazado'])) {
    throw new Exception('Invalid action');
}
4

Model Updates Database

DevolucionModel::procesarRevision() executes SQL
$this->db->beginTransaction();
$stmt->execute([$accion, $codigo, $obs, $revisor, $id]);
$this->db->commit();
5

Controller Responds

Redirect to admin panel with success message
header('Location: index.php?url=admin/index&msg=success');
6

View Displays Result

Admin panel shows updated data
$pendientes = $this->model->obtenerPendientes();
require_once 'Views/admin/panel_administrador.php';

Best Practices

Controller Best Practices

Controllers should orchestrate, not implement business logic:
// ❌ BAD: Too much logic in controller
public function aprobar() {
    $sql = "UPDATE devoluciones SET estado = 'aprobado' WHERE id = ?";
    $stmt = $db->prepare($sql);
    $stmt->execute([$id]);
}

// ✅ GOOD: Delegate to model
public function aprobar() {
    $resultado = $this->model->aprobarDevolucion($id);
    if ($resultado) {
        header('Location: index.php?url=admin/index&msg=success');
    }
}
// ✅ GOOD: Thorough validation
$id = intval($_POST['id_devolucion'] ?? 0);
if ($id <= 0) {
    throw new Exception('Invalid ID');
}

$accion = trim($_POST['accion'] ?? '');
if (!in_array($accion, ['aprobado', 'rechazado'])) {
    throw new Exception('Invalid action');
}
public function __construct() {
    if (session_status() === PHP_SESSION_NONE) session_start();
    
    // Check authentication first
    if (!isset($_SESSION['logged_in']) || $_SESSION['grado'] != 1) {
        header('Location: index.php?url=auth/index');
        exit;
    }
}

Model Best Practices

// ✅ GOOD: Prepared statement
$stmt = $this->db->prepare("SELECT * FROM devoluciones WHERE id = ?");
$stmt->execute([$id]);

// ❌ BAD: String interpolation (SQL injection!)
$sql = "SELECT * FROM devoluciones WHERE id = $id";
$this->db->query($sql);
public function complexOperation() {
    try {
        $this->db->beginTransaction();
        
        // Multiple operations
        $this->updateDevolucion($id);
        $this->createNotificacion($userId);
        
        $this->db->commit();
        return true;
    } catch (Exception $e) {
        $this->db->rollBack();
        error_log($e->getMessage());
        return false;
    }
}
// ✅ GOOD: Consistent return type
public function obtenerPorId($id) {
    $stmt = $this->db->prepare("SELECT * FROM devoluciones WHERE id = ?");
    $stmt->execute([$id]);
    $result = $stmt->fetch();
    return $result ?: null; // Always return array or null
}

View Best Practices

<!-- GOOD: Escaped -->
<p><?= htmlspecialchars($data['nombre']) ?></p>

<!-- BAD: Not escaped (XSS vulnerability) -->
<p><?= $data['nombre'] ?></p>
<!-- BAD: Database query in view -->
<?php
$stmt = $db->query("SELECT * FROM devoluciones");
$data = $stmt->fetchAll();
?>

<!-- GOOD: Data passed from controller -->
<?php foreach ($pendientes as $item): ?>
    <li><?= htmlspecialchars($item['nombre']) ?></li>
<?php endforeach; ?>
<!-- GOOD: Reusable header -->
<?php require_once 'Views/partials/header.php'; ?>

<main>
    <!-- Page content -->
</main>

<?php require_once 'Views/partials/footer.php'; ?>

Testing Considerations

Unit Testing Models

class DevolucionModelTest extends PHPUnit\Framework\TestCase {
    private $model;
    
    public function setUp(): void {
        $this->model = new DevolucionModel();
    }
    
    public function testObtenerPendientes() {
        $result = $this->model->obtenerPendientes();
        $this->assertIsArray($result);
    }
    
    public function testGuardarDevolucion() {
        $datos = [
            'nit' => '900123456',
            'nombre_cliente' => 'Test Cliente',
            // ... more fields
        ];
        $result = $this->model->guardar($datos);
        $this->assertTrue($result);
    }
}

Next Steps

Architecture Overview

Understand the complete system architecture

Database Schema

Explore the database structure

API Reference

View all controller and model methods

Deployment

Deploy with Docker

Build docs developers (and LLMs) love