Inicio / Documentacion / Arquitectura del sistema

Arquitectura del sistema

Estructura, componentes y flujo de datos del sistema

Versión 1.0

Capítulo 1: Visión general de la arquitectura

El sistema ayuntamientosmart.com sigue una arquitectura de tres capas sin frameworks, optimizada para simplicidad operativa y bajo acoplamiento:

Capa de presentación

Responsable de la interfaz de usuario y la interacción con el usuario final. Compuesta por:

  • Archivos PHP que generan HTML5 responsive
  • CSS3 puro sin preprocesadores (diseño mobile-first)
  • JavaScript vanilla sin librerías pesadas (solo fetch API nativa)
  • Plantillas HTML reutilizables

Capa de lógica de negocio

Implementa las reglas de negocio, validaciones y orquestación de operaciones. Ubicada en:

  • /inc/ - Componentes core reutilizables
  • /modulos/{modulo}/ - Lógica específica de cada módulo
  • Funciones puras cuando es posible (sin estado)
  • Sistema de eventos para extensibilidad

Capa de datos

Gestiona el almacenamiento, recuperación y persistencia de datos:

  • Motor JSON DB con hash partitioning (256 buckets)
  • Sistema de caché en memoria (APCu/Redis)
  • File locking para concurrencia (flock)
  • Backup automático incremental

Capítulo 2: Estructura de directorios

La estructura de directorios del sistema está organizada de forma lógica y predecible:

/
├── index.php          # Punto de entrada: login y dashboard
├── ajax.php           # Manejador de peticiones AJAX
├── api.php            # Gateway API REST con autenticación JWT
├── cron.php           # Tareas programadas (backups, notificaciones)
├── inc/               # Componentes core del sistema
│   ├── config.php     # Constantes: paths, keys, timeouts
│   ├── core.php       # Utilidades base, sesiones, tenant
│   ├── json_db.php    # Motor de base de datos JSON
│   ├── hash.php       # Hash partitioning (256 buckets)
│   ├── auth.php       # Autenticación Argon2id, 2FA, brute-force
│   ├── permisos.php   # RBAC con 6 roles y wildcards
│   ├── cifrado.php    # AES-256-GCM para campos sensibles
│   ├── log.php        # Auditoría con hash SHA256
│   └── validacion.php # Sanitización de inputs
├── modulos/           # Módulos funcionales (25 módulos)
│   ├── denuncias/
│   │   ├── index.php      # Listado de denuncias
│   │   ├── ver.php        # Detalle de denuncia
│   │   ├── nuevo.php      # Crear denuncia
│   │   ├── editar.php     # Editar denuncia
│   │   ├── ajax.php       # AJAX específico del módulo
│   │   └── exportar.php   # Exportar a PDF/Excel
│   ├── sanciones/
│   ├── atestados/
│   └── .../
├── data/              # Datos persistentes
│   ├── {tenant_hash}/     # Datos por tenant (multi-tenancy)
│   │   ├── _config.json   # Configuración del tenant
│   │   ├── denuncias/     # Módulo denuncias
│   │   │   ├── 2026/          # Año
│   │   │   │   ├── 00.json    # Bucket 00 (hash partition)
│   │   │   │   ├── 01.json    # Bucket 01
│   │   │   │   ├── .../
│   │   │   │   └── ff.json    # Bucket ff (256 buckets)
│   │   ├── sanciones/
│   │   └── .../
├── assets/            # Recursos estáticos
│   ├── css/
│   ├── js/
│   └── img/
└── vendor/            # Librerías externas (Composer)

Capítulo 3: Motor de base de datos JSON

El sistema utiliza almacenamiento basado en archivos JSON en lugar de bases de datos tradicionales (MySQL, PostgreSQL). Esta decisión arquitectónica tiene ventajas importantes:

Ventajas del almacenamiento JSON

  • Simplicidad: No requiere servidor de base de datos separado, instalación ni configuración
  • Portabilidad: Los datos son archivos de texto, fácilmente transportables, versionables con Git
  • Backup sencillo: Copiar archivos, sin dumps complejos
  • Inspección directa: Los datos son legibles por humanos
  • Escalabilidad horizontal: Cada tenant tiene sus propios archivos, fácil distribuir en múltiples servidores

Hash partitioning

Para evitar archivos JSON excesivamente grandes, se utiliza hash partitioning con 256 buckets (00-ff):

function obtener_bucket($id) {
    $hash = md5($id);
    return substr($hash, 0, 2); // Primeros 2 caracteres hex: 00-ff
}

// Ejemplo: ID 'DEN-2026-00123'
// MD5: '5f2a8b...' → Bucket: '5f'
// Archivo: /data/{tenant}/denuncias/2026/5f.json

Con 256 buckets, si tenemos 25.600 registros, cada archivo contiene ~100 registros. Archivos de 100 registros son rápidos de leer/escribir.

File locking para concurrencia

Para evitar corrupción de datos con escrituras concurrentes, se usa flock():

$fp = fopen($archivo, 'c+');
if (flock($fp, LOCK_EX)) { // Lock exclusivo
    $datos = json_decode(fread($fp, filesize($archivo)), true);
    // Modificar $datos
    ftruncate($fp, 0);
    rewind($fp);
    fwrite($fp, json_encode($datos, JSON_PRETTY_PRINT));
    fflush($fp);
    flock($fp, LOCK_UN); // Liberar lock
}
fclose($fp);

Caché en memoria

Para mejorar el rendimiento, se cachean en memoria (APCu o Redis) los archivos JSON accedidos frecuentemente. La caché se invalida automáticamente cuando se modifica el archivo.

Capítulo 4: Componentes core

config.php - Configuración central

Define todas las constantes del sistema:

// Paths
define('ROOT_PATH', __DIR__ . '/..');
define('DATA_PATH', ROOT_PATH . '/data');
define('INC_PATH', ROOT_PATH . '/inc');

// Seguridad
define('ENCRYPTION_KEY', '...'); // AES-256 key (32 bytes)
define('SALT_TENANT', '...');    // Salt para hash de tenant
define('HASH_ALGO', 'sha256');   // Algoritmo hash para auditoría

// Sesiones
define('SESSION_TIMEOUT', 3600); // 1 hora
define('SESSION_NAME', 'AYUNTAMIENTOSMART_SID');

// Intentos de login
define('MAX_LOGIN_ATTEMPTS', 5);
define('LOGIN_LOCKOUT_TIME', 900); // 15 minutos

core.php - Utilidades base

Funciones de utilidad general:

  • get_tenant(): Detecta el tenant desde el subdomain y calcula su hash
  • session_manager(): Gestión de sesiones seguras con regeneración de ID
  • redirect(): Redirecciones con código HTTP correcto
  • json_response(): Genera respuestas JSON estándar para AJAX/API
  • sanitize_filename(): Sanitiza nombres de archivo para prevenir path traversal

json_db.php - Motor de base de datos

API de alto nivel para operaciones CRUD:

// Leer registros
$registros = db_find('denuncias', ['estado' => 'registrada'], 2026);

// Crear registro
$id = db_insert('denuncias', $datos, 2026);

// Actualizar registro
db_update('denuncias', $id, $cambios, 2026);

// Eliminar registro (soft delete)
db_delete('denuncias', $id, 2026);

// Contar registros
$total = db_count('denuncias', ['tipo' => 'hechos'], 2026);

auth.php - Autenticación

Sistema de autenticación robusto:

  • Hash de contraseñas: Argon2id con parámetros configurables (memory: 65536 KB, time: 4, threads: 3)
  • 2FA opcional: TOTP (Google Authenticator, Authy)
  • Brute-force protection: Bloqueo temporal tras X intentos fallidos
  • Sesiones seguras: HttpOnly, Secure, SameSite cookies
  • Logout automático: Por inactividad configurable

permisos.php - Control de acceso RBAC

Sistema de permisos basado en roles con wildcards:

// 6 roles predefinidos
$ROLES = [
    'admin'         => ['*'],  // Acceso total
    'jefe_servicio' => ['denuncias.*', 'sanciones.*', 'estadisticas.*'],
    'concejal'       => ['denuncias.ver', 'denuncias.crear', 'sanciones.*'],
    'tecnico'        => ['denuncias.crear', 'sanciones.crear'],
    'administrativo'=> ['denuncias.ver', 'sanciones.tramitar'],
    'consulta'      => ['*.ver']
];

// Comprobar permiso
if (tiene_permiso($usuario, 'denuncias.crear')) {
    // Permitir operación
}

cifrado.php - Cifrado de datos sensibles

Cifrado AES-256-GCM para campos sensibles (DNI, dirección, teléfono, datos médicos):

// Cifrar
$cifrado = cifrar_campo($dni); // Devuelve base64 del texto cifrado + IV + tag

// Descifrar
$dni = descifrar_campo($cifrado);

// Campos cifrados por módulo (constante)
$CAMPOS_CIFRADOS = [
    'denuncias' => ['denunciante.dni', 'denunciante.direccion', 'denunciante.telefono'],
    'sanciones' => ['conductor.dni', 'conductor.direccion'],
    'atestados' => ['detenido.dni', 'detenido.antecedentes']
];

log.php - Sistema de auditoría

Logging completo de todas las operaciones:

log_auditoria(
    $modulo,      // 'denuncias', 'sanciones', etc.
    $accion,      // 'crear', 'modificar', 'eliminar', 'ver'
    $registro_id, // ID del registro afectado
    $usuario_id,  // Usuario que realiza la acción
    $datos_extra  // Array con datos adicionales
);

// Cada entrada de log incluye:
// - Timestamp exacto (microsegundos)
// - Usuario y rol
// - IP de origen
// - Acción realizada
// - Datos antes/después (para modificaciones)
// - Hash SHA256 de la entrada anterior (cadena inmutable)

Capítulo 5: Multi-tenancy

El sistema soporta múltiples tenants (ayuntamientos) en la misma instalación mediante subdominios:

Detección de tenant

// URL: https://madrid.ayuntamientosmart.com/
$subdomain = 'madrid';
$tenant_hash = substr(hash('sha256', $subdomain . SALT_TENANT), 0, 8);
// tenant_hash: 'a3f5c2d1' (8 caracteres hex)

// Los datos del tenant se almacenan en:
// /data/a3f5c2d1/

Aislamiento de datos

Cada tenant tiene su propio directorio en /data/. No hay forma de que un tenant acceda a datos de otro. El hash del tenant se calcula con un salt secreto, por lo que no es predecible.

Configuración por tenant

Cada tenant tiene su archivo _config.json con configuración específica:

{
    "municipio": "Ayuntamiento de Madrid",
    "cif": "P2807900B",
    "direccion": "Plaza de la Villa, 1",
    "logo": "madrid.png",
    "integraciones": {
        "dgt_testra": true,
        "lexnet": true,
        "viogen": false
    },
    "limites": {
        "usuarios_max": 50,
        "almacenamiento_mb": 5000
    }
}

Capítulo 6: Sistema de módulos

El sistema es altamente modular. Cada módulo (denuncias, sanciones, etc.) es autocontenido en su directorio y sigue la misma estructura:

/modulos/denuncias/
├── index.php      # Listado con filtros y paginación
├── ver.php        # Vista detalle de un registro
├── nuevo.php      # Formulario de creación
├── editar.php     # Formulario de edición
├── ajax.php       # Endpoints AJAX específicos del módulo
└── exportar.php   # Exportación a PDF/Excel

Ventajas de esta arquitectura modular:

  • Cada módulo es independiente y puede desarrollarse/testearse aisladamente
  • Fácil añadir nuevos módulos sin tocar código existente
  • Posibilidad de activar/desactivar módulos por tenant
  • Claridad en la organización del código

En esta página