Appearance
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 JSON2. Slim Framework (Arquitectura Moderna) ✅
Arquitectura en 5 capas documentada en este archivo:
Router → Validation Middleware → Controller → Service → Model → DBEjemplo:
Routes/Compras/ComprobanteRoutes.php
↓ ValidatorMiddleware
controller/Compra/ComprobanteController.php
↓
service/Compra/ComprobanteService.php
↓
models/Compra/Comprobante.phpDecisión: ¿Qué Arquitectura Usar?
| Escenario | Arquitectura | Referencia |
|---|---|---|
| Mantenimiento código existente | ⚠️ Legacy Backend | legacy-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 422Ejemplo 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
AuditableInterfacecuando necesitan auditoría - Usan traits
ConectableyAuditable - 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
Modelbase - Reciben
PDOen 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
DTObase - 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
Validatorbase - 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 JWTPermissionMiddleware: Verifica permisosLoggingMiddleware: Registra requestsCorsMiddleware: 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 opcionalesgetById(int $id): DTO|false- Obtener por IDinsert(RequestDTO $data): ResponseDTO- Crear nuevoupdate(int $id, RequestDTO $data): ResponseDTO- Actualizardelete(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 Request401: Unauthorized403: Forbidden404: Not Found422: Validation Error500: 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.phpUso 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 --runSoft Deletes
Todas las tablas principales tienen deleted_at para soft delete.
Convención:
deleted_at IS NULL= registro activodeleted_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
testprefix:testInsertValidData() - Usar PHPUnit assertions
Integration Tests
Prueban flujos completos incluyendo base de datos.
Security
Best Practices
- SQL Injection: Siempre usar prepared statements
- XSS: Sanitizar output cuando sea necesario
- CSRF: Tokens CSRF en formularios
- Authentication: JWT con expiración
- Authorization: Verificar permisos en cada endpoint
- Input Validation: Validar todos los inputs
- Audit Logging: Registrar operaciones sensibles
Authentication Flow
- Login → Genera JWT
- Request → Incluye JWT en header
Authorization: Bearer {token} - Middleware → Valida JWT
- Controller → Procesa request autenticado
Performance Considerations
Database Optimization
- Índices en campos frecuentemente filtrados
- Índices en foreign keys
- Índices en
deleted_atpara 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.phpReferencias
- Arquitectura Modules/<Module> - Arquitectura DDD para módulos complejos (CRM, Membresía)
- Arquitectura Backend Legacy - Endpoints legacy (deprecado)
- Ejemplo completo: Boniret Resource
- Convenciones de Git
- Arquitectura Frontend
- Arquitectura General
Última actualización: 2025-12-09