Skip to content

Arquitectura Backend - Sistema Bautista

Esta guía describe la arquitectura del backend del sistema Bautista, sus capas, patrones de diseño y convenciones.

⚠️ Arquitectura Híbrida en Coexistencia

El sistema mantiene dos arquitecturas backend funcionando simultáneamente:

1. Legacy Backend (⚠️ Solo Mantenimiento)

Ver documentación completa: Arquitectura Backend Legacy

  • Location: /backend/**/*.php (archivos individuales)
  • Routing: Basado en paths de archivos físicos
  • HTTP Methods: Switch manual en cada archivo
  • Estructura: Controller/Model legacy o query directa
  • Estado: ⚠️ DEPRECADO - Solo mantenimiento

Ejemplo:

POST /backend/mod-compras/comprobante.php
  ↓ Switch HTTP method
  ↓ Controller (si existe) o query directa
  ↓ Response JSON

2. Slim Framework (Arquitectura Moderna) ✅

Arquitectura en 5 capas documentada en este archivo:

Router → Validation Middleware → Controller → Service → Model → DB

Ejemplo:

Routes/Compras/ComprobanteRoutes.php
  ↓ ValidatorMiddleware
controller/Compra/ComprobanteController.php

service/Compra/ComprobanteService.php

models/Compra/Comprobante.php

Decisión: ¿Qué Arquitectura Usar?

EscenarioArquitecturaReferencia
Mantenimiento código existente⚠️ Legacy Backendlegacy-architecture.md
Endpoint nuevo✅ Slim Framework (5 capas)Este documento
Refactorización de legacy✅ Slim Framework (5 capas)Este documento

IMPORTANTE: SIEMPRE usar Slim Framework para código nuevo.


Stack Tecnológico

  • Lenguaje: PHP 8+
  • Framework: Custom/Slim Framework para routing
  • Base de datos: MySQL/PostgreSQL
  • Autenticación: JWT (JSON Web Tokens)
  • Testing: PHPUnit
  • Validación: rakit/validation
  • ORM: Custom (PDO directo)

Arquitectura en Capas

El backend sigue una arquitectura en capas bien definida:

┌─────────────────────────────────────┐
│   API Layer (Routes + Controllers)  │  ← HTTP Request/Response
├─────────────────────────────────────┤
│   Service Layer (Business Logic)    │  ← Lógica de negocio
├─────────────────────────────────────┤
│   Model Layer (Data Access)         │  ← Acceso a datos
├─────────────────────────────────────┤
│   Database                          │  ← Persistencia
└─────────────────────────────────────┘

1. API Layer (Capa de API)

Responsabilidad: Manejar requests HTTP y devolver responses.

Routes (/Routes/)

Define las rutas de la aplicación y mapea a métodos de controladores.

Estructura:

Routes/
├── Auth/
│   └── AuthRoutes.php
├── Ventas/
│   └── VentasRoutes.php
├── Tesoreria/
│   └── TesoreriaRoutes.php
└── ...

Ejemplo básico:

php
class BoniretRoutes
{
    public function __invoke(RouteCollectorProxy $group): void
    {
        $group->get('', [BoniretController::class, 'get']);
        $group->get('/{id}', [BoniretController::class, 'getById']);
        $group->post('', [BoniretController::class, 'insert']);
        $group->put('/{id}', [BoniretController::class, 'update']);
        $group->delete('/{id}', [BoniretController::class, 'delete']);
    }
}

Validators como Middleware

Los Validators se aplican como middleware en las rutas para validar la estructura de datos antes de que lleguen al Controller.

Flujo de validación:

Request → Route → Validator Middleware → Controller → Service
                      ↓ (si falla)
                   Response 422

Ejemplo con Validators:

php
use App\Middleware\ValidatorMiddleware;
use App\Validators\Tesoreria\CreateBoniretValidator;
use App\Validators\Tesoreria\UpdateBoniretValidator;

class BoniretRoutes
{
    public function __invoke(RouteCollectorProxy $group): void
    {
        // GET - Sin validación (no hay body)
        $group->get('', [BoniretController::class, 'get']);
        $group->get('/{id}', [BoniretController::class, 'getById']);

        // POST - Validar con CreateValidator
        $group->post('', [BoniretController::class, 'insert'])
            ->add(new ValidatorMiddleware(new CreateBoniretValidator()));

        // PUT - Validar con UpdateValidator
        $group->put('/{id}', [BoniretController::class, 'update'])
            ->add(new ValidatorMiddleware(new UpdateBoniretValidator()));

        // DELETE - Sin validación (solo ID en path)
        $group->delete('/{id}', [BoniretController::class, 'delete']);
    }
}

ValidatorMiddleware genérico:

php
namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ValidatorMiddleware implements MiddlewareInterface
{
    public function __construct(private Validator $validator) {}

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $body = $request->getParsedBody();

        // Validar datos
        $validation = $this->validator->validate($body);

        if ($validation->fails()) {
            // Retornar error 422 con mensajes de validación
            return new JsonResponse([
                'error' => 'Datos inválidos',
                'errors' => $validation->errors()->toArray()
            ], 422);
        }

        // Si pasa validación, continuar al controller
        return $handler->handle($request);
    }
}

Ventajas de Validators como Middleware:

  • ✅ Validación estructural antes de llegar al Controller
  • ✅ Controllers más limpios (solo lógica HTTP)
  • ✅ Reutilizable entre múltiples rutas
  • ✅ Respuestas de error consistentes (422)
  • ✅ Separación de responsabilidades clara

Nota: Los Services aún validan lógica de negocio (ej: unicidad de nombres, existencia de relaciones), los Validators middleware solo validan estructura y tipos.

Controllers (/controller/)

Procesan requests, invocan servicios y devuelven responses.

Estructura:

controller/
├── modulo-venta/
├── modulo-tesoreria/
├── modulo-ctacte/
└── ...

Responsabilidades:

  • Extraer datos del request (query params, body, path params)
  • Validar presencia de parámetros requeridos
  • Invocar servicios de negocio
  • Construir y devolver responses JSON
  • Manejar errores HTTP

Ejemplo:

php
class BoniretController
{
    public function __construct(private BoniretService $service) {}

    public function get(
        ServerRequestInterface $request,
        ResponseInterface $response
    ): ResponseInterface {
        $filters = $request->getParsedBody();
        $result = $this->service->getAll($filters);
        return getSuccessResponse(200, $result);
    }
}

2. Service Layer (Capa de Servicios)

Responsabilidad: Implementar la lógica de negocio.

Ubicación: /service/

Estructura:

service/
├── Venta/
├── Tesoreria/
├── CtaCte/
└── ...

Responsabilidades:

  • Implementar reglas de negocio
  • Validar datos de negocio (los Validators de estructura se aplican como middleware en Routes)
  • Coordinar operaciones entre múltiples modelos
  • Gestionar transacciones
  • Registrar auditoría
  • Lanzar excepciones de negocio

Características:

  • Implementan AuditableInterface cuando necesitan auditoría
  • Usan traits Conectable y Auditable
  • Inyectan dependencias via constructor (ConnectionManager, AuditLogger, Validators)

Ejemplo:

php
class BoniretService implements AuditableInterface
{
    use Conectable, Auditable;

    private Boniret $model;
    private CreateBoniretValidator $createValidator;

    public function __construct(
        ConnectionManager $manager,
        ?AuditLogger $auditLogger
    ) {
        $this->setConnectionManager($manager);
        $this->setAuditLogger($auditLogger);
        $this->model = new Boniret($manager->get('oficial'));
        $this->createValidator = new CreateBoniretValidator();
    }

    public function insert(BoniretRequest $data): BoniretDTO
    {
        $this->createValidator->validate($data->toArray());

        $this->connections->beginTransaction('oficial');
        try {
            // Validaciones de negocio
            if ($this->model->getByNombre($data->nombre)) {
                throw new ServerException("El nombre ya existe");
            }

            $result = $this->model->insert($data);

            $this->registrarAuditoria(
                "INSERT",
                "BONIRET",
                $this->model->getTable(),
                $result->id
            );

            $this->connections->commit('oficial');
            return $result;
        } catch (Exception $e) {
            $this->connections->rollback('oficial');
            throw $e;
        }
    }
}

3. Model Layer (Capa de Modelo)

Responsabilidad: Acceso a datos y mapeo objeto-relacional.

Ubicación: /models/

Estructura:

models/
├── modulo-venta/
│   ├── Factura.php
│   └── NotaCredito.php
├── modulo-tesoreria/
│   └── Boniret.php
└── ...

Responsabilidades:

  • Ejecutar queries SQL
  • Mapear resultados a DTOs
  • Implementar CRUD básico
  • Gestionar soft deletes
  • Aplicar filtros

Características:

  • Extienden Model base
  • Reciben PDO en constructor
  • Usan prepared statements
  • Retornan DTOs o arrays de DTOs
  • Implementan soft delete con deleted_at

Ejemplo:

php
class Boniret extends Model
{
    public function __construct(PDO $conn)
    {
        parent::__construct($conn, 'boniret');
    }

    public function getAll(?array $filters = null): array
    {
        $sql = "SELECT * FROM {$this->table} WHERE deleted_at IS NULL";
        $params = [];

        if (isset($filters['tipo'])) {
            $sql .= " AND tipo = :tipo";
            $params['tipo'] = $filters['tipo'];
        }

        $stmt = $this->conn->prepare($sql);
        $stmt->execute($params);
        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

        return array_map(
            fn($r) => BoniretDTO::fromArray($r),
            $results
        );
    }

    public function insert(BoniretRequest $data): BoniretDTO
    {
        $sql = "INSERT INTO {$this->table}
                (nombre, cuenta, valor, tipo, impser, acumula)
                VALUES (:nombre, :cuenta, :valor, :tipo, :impser, :acumula)
                RETURNING id";

        $stmt = $this->conn->prepare($sql);
        $stmt->execute($data->toArray());
        $id = $stmt->fetchColumn();

        return BoniretDTO::fromArray([...$data->toArray(), 'id' => $id]);
    }

    public function delete(int $id): void
    {
        $sql = "UPDATE {$this->table}
                SET deleted_at = :deleted_at
                WHERE id = :id AND deleted_at IS NULL";

        $stmt = $this->conn->prepare($sql);
        $stmt->execute([
            'id' => $id,
            'deleted_at' => date('Y-m-d H:i:s')
        ]);

        if ($stmt->rowCount() === 0) {
            throw new ServerException("Registro no encontrado");
        }
    }
}

4. Supporting Layers (Capas de Soporte)

DTOs (Data Transfer Objects) - /Resources/

Objetos inmutables que transportan datos entre capas.

Tipos:

  • Response DTOs: Datos que se devuelven al cliente
  • Request DTOs: Datos que llegan del cliente

Características:

  • Extienden clase DTO base
  • Propiedades públicas tipadas
  • Método estático fromArray()
  • Validación opcional en constructor
  • Método toArray() para serialización

Ejemplo:

php
// Response DTO
class BoniretDTO extends DTO
{
    public function __construct(
        public ?int $id,
        public string $nombre,
        public int $cuenta,
        public ?float $valor,
        public string $tipo,
        public bool $impser,
        public bool $acumula
    ) {}
}

// Request DTO
class BoniretRequest extends DTO
{
    public function __construct(
        public string $nombre,
        public int $cuenta,
        public string $tipo,
        public bool $impser,
        public bool $acumula,
        public ?float $valor = null
    ) {}
}

Validators - /Validators/

Validan datos de entrada usando reglas declarativas.

Estructura:

Validators/
├── Venta/
├── Tesoreria/
│   ├── CreateBoniretValidator.php
│   └── UpdateBoniretValidator.php
└── ...

Características:

  • Extienden Validator base
  • Definen reglas en método rules()
  • Pueden customizar mensajes en messages()
  • Usan librería rakit/validation

Ejemplo:

php
class CreateBoniretValidator extends Validator
{
    protected function rules(): array
    {
        return [
            'nombre' => 'required|max:15',
            'cuenta' => 'required|integer',
            'valor' => 'nullable|numeric',
            'tipo' => 'required|in:P,F',
            'impser' => 'required|boolean',
            'acumula' => 'required|boolean'
        ];
    }

    protected function messages(): array
    {
        return [
            'nombre.required' => 'El nombre es requerido',
            'tipo.in' => 'El tipo debe ser P o F'
        ];
    }
}

Middleware - /Middleware/

Interceptan requests para autenticación, autorización, logging, etc.

Ejemplos:

  • AuthMiddleware: Verifica JWT
  • PermissionMiddleware: Verifica permisos
  • LoggingMiddleware: Registra requests
  • CorsMiddleware: Maneja CORS

Error Handling - /Errors/

Excepciones personalizadas para diferentes tipos de errores.

Excepciones comunes:

  • BadRequest: Error 400 (datos inválidos)
  • Unauthorized: Error 401 (no autenticado)
  • Forbidden: Error 403 (sin permisos)
  • NotFound: Error 404 (recurso no encontrado)
  • ServerException: Error 500 (error interno)
  • ValidationException: Error 422 (validación fallida)

Patrones de Diseño

1. Dependency Injection (DI)

Todas las dependencias se inyectan via constructor.

Container: Se usa un contenedor DI (probablemente PHP-DI o similar) para resolver dependencias.

2. Repository Pattern (implícito)

Los Models actúan como repositories, encapsulando acceso a datos.

3. Service Layer Pattern

Lógica de negocio separada de controllers y models.

4. DTO Pattern

Transferencia de datos entre capas con objetos tipados.

5. Strategy Pattern (en algunos casos)

Para comportamientos intercambiables (ej: facturación electrónica).

6. Service Decomposition + Orchestrator Pattern

Aplicar cuando: Servicios monolíticos con múltiples responsabilidades (500+ líneas).

Solución: Descomponer en servicios especializados coordinados por un orchestrator:

  • Orchestrator: Coordina flujo, gestiona transacciones
  • Specialized Services: Responsabilidad única (Preparation, Validation, Processing, Audit)

Documentación completa: Service Decomposition Pattern

Convenciones de Código

Naming Conventions

  • Clases: PascalCase (BoniretService, BoniretController)
  • Métodos: camelCase (getAll, getById)
  • Variables: camelCase ($filters, $result)
  • Constantes: UPPER_SNAKE_CASE (MAX_RETRIES)
  • Archivos: PascalCase matching class name

Estructura de métodos CRUD

Los servicios y modelos siguen esta convención:

  • getAll(?array $filters): array - Listar con filtros opcionales
  • getById(int $id): DTO|false - Obtener por ID
  • insert(RequestDTO $data): ResponseDTO - Crear nuevo
  • update(int $id, RequestDTO $data): ResponseDTO - Actualizar
  • delete(int $id): void - Eliminar (soft delete)

Responses API

Success Response:

json
{
  "status": 200,
  "message": "Datos recibidos correctamente.",
  "data": { ... }
}

Error Response:

json
{
  "error": "Mensaje de error descriptivo"
}

Status Codes:

  • 200: OK (GET, PUT)
  • 201: Created (POST)
  • 204: No Content (DELETE)
  • 400: Bad Request
  • 401: Unauthorized
  • 403: Forbidden
  • 404: Not Found
  • 422: Validation Error
  • 500: Server Error

Database Management

Connections

  • ConnectionManager: Gestiona múltiples conexiones
  • Conexión principal: 'oficial'
  • Soporte para tenancy multi-empresa

Migrations

Ubicación: /migrations/ (submódulo Git)

Nota: El directorio migrations/ es un submódulo Git independiente con su propia documentación completa en migrations/README.md. Consultar ese archivo para detalles sobre:

  • Comandos de migración y seeds
  • Sistema multi-tenancy (LEVEL_EMPRESA, LEVEL_SUCURSAL, LEVEL_CAJA)
  • Configuración dinámica de niveles de tablas por empresa
  • Arquitectura y flujo de ejecución

Las migraciones siguen formato:

YYYYMMDDHHMMSS_descripcion.php

Uso básico:

bash
# Ejecutar migraciones
php migrate-db-command.php --migrate

# Ver estado
php migrate-db-command.php --status

# Ejecutar seeds
php seed-db-command.php --run

Soft Deletes

Todas las tablas principales tienen deleted_at para soft delete.

Convención:

  • deleted_at IS NULL = registro activo
  • deleted_at IS NOT NULL = registro eliminado

Auditoría

Audit Log

Sistema de auditoría registra operaciones CRUD.

Información registrada:

  • Operación (INSERT, UPDATE, DELETE)
  • Entidad afectada
  • Tabla
  • ID del registro
  • Usuario
  • Timestamp

Uso:

php
$this->registrarAuditoria(
    "INSERT",
    "BONIRET",
    $this->model->getTable(),
    $result->id
);

Testing

Unit Tests

Ubicación: /Tests/Unit/

Estructura:

Tests/
├── Unit/
│   ├── Domain/
│   ├── Membresia/
│   └── ...
└── Integration/

Convenciones:

  • Un test file por clase: BoniretServiceTest.php
  • Métodos test con test prefix: testInsertValidData()
  • Usar PHPUnit assertions

Integration Tests

Prueban flujos completos incluyendo base de datos.

Security

Best Practices

  1. SQL Injection: Siempre usar prepared statements
  2. XSS: Sanitizar output cuando sea necesario
  3. CSRF: Tokens CSRF en formularios
  4. Authentication: JWT con expiración
  5. Authorization: Verificar permisos en cada endpoint
  6. Input Validation: Validar todos los inputs
  7. Audit Logging: Registrar operaciones sensibles

Authentication Flow

  1. Login → Genera JWT
  2. Request → Incluye JWT en header Authorization: Bearer {token}
  3. Middleware → Valida JWT
  4. Controller → Procesa request autenticado

Performance Considerations

Database Optimization

  • Índices en campos frecuentemente filtrados
  • Índices en foreign keys
  • Índices en deleted_at para soft deletes

Query Optimization

  • Evitar N+1 queries
  • Usar joins cuando sea apropiado
  • Limitar resultados con LIMIT

Caching (futuro)

Preparado para implementar caching en capa de servicio.

Module Organization

Cada módulo sigue la misma estructura:

{modulo}/
├── Routes/
│   └── {Modulo}Routes.php
├── controller/modulo-{nombre}/
│   └── {Recurso}Controller.php
├── service/{Modulo}/
│   └── {Recurso}Service.php
├── models/modulo-{nombre}/
│   └── {Recurso}.php
├── Resources/{Modulo}/
│   ├── {Recurso}DTO.php
│   └── {Recurso}Request.php
└── Validators/{Modulo}/
    ├── Create{Recurso}Validator.php
    └── Update{Recurso}Validator.php

Referencias


Última actualización: 2025-12-09