Arquitectura del sistema

Estructura, componentes y flujo de datos del sistema

Version 1.0 AYUNTAMIENTOSMART Generado: 29/05/2026 22:48

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:

Capa de lógica de negocio

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

Capa de datos

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

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

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:

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:

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:

Patrones de diseño utilizados

El sistema utiliza varios patrones de diseño reconocidos para mantener el código limpio, mantenible y escalable.

Repository pattern

La capa json_db.php actúa como repository abstracto que encapsula toda la lógica de acceso a datos. Los módulos no acceden directamente a los archivos JSON, sino a través de las funciones db_*. Esto permite cambiar la implementación del almacenamiento (por ejemplo, migrar a PostgreSQL) sin tocar el código de los módulos.

Dependency Injection

Las funciones reciben sus dependencias como parámetros en lugar de usar globales o singletons. Por ejemplo:

function crear_denuncia($datos, $usuario, $db_instance) {
    // Validar
    validar_denuncia($datos);
    // Guardar
    $id = $db_instance->insert('denuncias', $datos);
    // Auditar
    log_auditoria('denuncias', 'crear', $id, $usuario);
    return $id;
}

Esto facilita el testing (se pueden inyectar mocks) y reduce el acoplamiento.

Event-driven architecture

El sistema implementa un bus de eventos simple para permitir extensiones sin modificar el código core:

// Disparar evento
event_dispatch('denuncia.creada', ['id' => $id, 'datos' => $datos]);

// Suscribirse a evento (en plugin o módulo externo)
event_subscribe('denuncia.creada', function($payload) {
    // Enviar email al denunciante
    enviar_email_confirmacion($payload['datos']['email'], $payload['id']);
});

Strategy pattern

Para las integraciones externas (DGT, LexNet, VioGén), se usa el patrón Strategy que permite intercambiar implementaciones:

interface IntegracionStrategy {
    public function consultar($parametros);
    public function enviar($datos);
}

class DGTStrategy implements IntegracionStrategy { ... }
class LexNetStrategy implements IntegracionStrategy { ... }

// Uso
$integracion = obtener_integracion('dgt'); // Devuelve instancia de DGTStrategy
$resultado = $integracion->consultar(['matricula' => '1234ABC']);

Rendimiento y optimizaciones

El sistema implementa varias estrategias de optimización para garantizar tiempos de respuesta rápidos incluso con grandes volúmenes de datos.

Hash partitioning

Ya mencionado, el hash partitioning en 256 buckets distribuye los registros uniformemente. Esto evita archivos JSON de cientos de MB. Con hash partitioning, los archivos raramente superan 1 MB, lo que garantiza lecturas/escrituras rápidas.

Caché multinivel

Se implementan tres niveles de caché:

  • Caché de archivos JSON (APCu): Los archivos JSON leídos se cachean en memoria durante 5 minutos. Si el archivo no cambia, se sirve desde RAM.
  • Caché de consultas frecuentes (Redis): Consultas complejas (estadísticas, búsquedas) se cachean durante 1 hora.
  • Caché HTTP de navegador: Recursos estáticos (CSS, JS, imágenes) con cache-control de 1 año.

Lazy loading

Los registros se cargan bajo demanda. Por ejemplo, en el listado de denuncias solo se cargan los campos básicos (id, fecha, denunciante, estado). Los datos completos (hechos, adjuntos, historial) solo se cargan al abrir el detalle. Esto reduce drásticamente el tamaño de las respuestas.

Paginación eficiente

Los listados están paginados con límite de 20-50 registros por página. La paginación se implementa a nivel de bucket: primero se identifican los buckets que contienen registros que cumplen los filtros, luego se cargan solo los buckets necesarios para la página actual.

Índices en memoria

Se mantienen índices ligeros en archivos separados para acelerar búsquedas. Por ejemplo: /data/{tenant}/denuncias/_index_estado.json contiene un mapa estado → [ids] que permite filtrar por estado sin cargar todos los buckets.

Seguridad en profundidad

La arquitectura del sistema incorpora seguridad en múltiples capas siguiendo el principio de defensa en profundidad.

Validación de entrada

Toda entrada de usuario pasa por validación y sanitización en validacion.php antes de procesarse:

  • Validación de tipos (entero, email, fecha, DNI, matrícula)
  • Sanitización de HTML para prevenir XSS
  • Escape de caracteres especiales
  • Longitud máxima de campos
  • Whitelist de valores permitidos cuando aplica

Protección contra path traversal

Todos los paths de archivos se sanitizan para prevenir ataques de path traversal:

function sanitize_path($path) {
    // Eliminar '..' y otros caracteres peligrosos
    $path = str_replace(['..', '~'], '', $path);
    $path = preg_replace('/[^a-z0-9_\/-]/i', '', $path);
    return $path;
}

Cifrado en reposo

Datos sensibles cifrados con AES-256-GCM. La clave de cifrado se almacena fuera del webroot y se carga desde variable de entorno. Cada campo cifrado usa un IV aleatorio único, previniendo ataques de diccionario.

Cifrado en tránsito

TLS 1.3 obligatorio. Configuración del servidor web con ciphers seguros (ECDHE, AES-GCM), HSTS habilitado, certificados de CA reconocidas (Let's Encrypt, Sectigo).

Auditoría inmutable

Los logs de auditoría son inmutables mediante hash encadenado. Cada entrada incluye el hash SHA256 de la entrada anterior, formando una cadena. Si se modifica una entrada histórica, se rompe la cadena y se detecta inmediatamente.

Backup y recuperación

El sistema implementa una estrategia completa de backup para garantizar la recuperabilidad ante desastres.

Backup incremental diario

Cada noche a las 03:00 AM (ejecutado por cron.php), se crea un backup incremental de todos los datos modificados en las últimas 24 horas. Los backups se almacenan en /backups/{tenant}/{fecha}/ comprimidos con gzip. Se conservan: diarios de último mes, semanales de último trimestre, mensuales de último año.

Backup completo semanal

Cada domingo a las 02:00 AM, se crea un backup completo de todos los datos del tenant. Esto permite restauración completa en caso de corrupción total. Los backups completos se conservan durante 3 meses.

Réplica en tiempo real

Opcionalmente (configuración del tenant), se puede activar réplica en tiempo real a servidor secundario. Cada escritura se replica asíncronamente vía rsync/SSH al servidor de backup. El lag típico es <1 minuto.

Procedimiento de restauración

Para restaurar desde backup:

  1. Detener el sistema (poner en modo mantenimiento)
  2. Identificar el backup a restaurar (/backups/{tenant}/{fecha}/)
  3. Extraer el backup: tar -xzf backup.tar.gz -C /data/{tenant}/
  4. Verificar integridad: comprobar hashes de auditoría
  5. Reiniciar el sistema

Tiempo típico de restauración: 5-15 minutos dependiendo del tamaño de datos.

Escalabilidad

Aunque el sistema está diseñado para municipios pequeños-medianos (hasta 50 usuarios concurrentes), incorpora mecanismos de escalabilidad para crecer si es necesario.

Escalado vertical

La arquitectura soporta fácilmente escalado vertical (más CPU, más RAM, más almacenamiento en el mismo servidor). Aumentar la RAM disponible para caché (APCu) mejora linealmente el rendimiento. Discos SSD NVMe reducen significativamente la latencia de I/O.

Escalado horizontal (multi-servidor)

Para cargas muy altas, se puede escalar horizontalmente:

  • Balanceador de carga: Nginx/HAProxy distribuye peticiones entre N servidores web
  • Almacenamiento compartido: /data/ en NFS o GlusterFS compartido entre servidores
  • Caché compartida: Redis centralizado para caché compartida
  • Sesiones compartidas: Sesiones en Redis para que cualquier servidor pueda atenderlas

Separación de tenants

En instalaciones grandes con muchos tenants, se pueden asignar tenants a servidores dedicados. Por ejemplo: madrid.ayuntamientosmart.com → servidor A, barcelona.ayuntamientosmart.com → servidor B. El balanceador rutea según subdomain. Esto proporciona aislamiento total entre tenants grandes.

CDN para estáticos

Los recursos estáticos (CSS, JS, imágenes, documentos PDF) pueden servirse desde CDN (CloudFlare, AWS CloudFront) para reducir carga del servidor y mejorar latencia global. Los assets se suben periódicamente al CDN y se sirven desde edge locations cercanas al usuario.

Historial de cambios

Version 1.0 - 2026-01-10
  • Versión inicial del documento de arquitectura
  • Descripción completa de la arquitectura de tres capas
  • Documentación del motor de base de datos JSON
  • Explicación del hash partitioning
  • Componentes core del sistema
  • Sistema de multi-tenancy
  • Patrones de diseño utilizados
  • Estrategias de rendimiento y optimización
  • Seguridad en profundidad
  • Backup y escalabilidad