Saltar a contenido

Arquitectura general

Gestión Civis sigue una arquitectura cliente-servidor desacoplada:

  • Backend: aplicación Flask construida con el patrón app factory (create_app()), organizada en un blueprint por módulo.
  • Frontend: SPA en React + Vite que consume la API REST bajo /api/.
  • Persistencia: PostgreSQL (con caída a SQLite en desarrollo si no hay DATABASE_URL).
flowchart TB
    subgraph Frontend["Frontend (React + Vite)"]
        AX["api.js (Axios + interceptores JWT)"]
    end
    subgraph Backend["Backend (Flask)"]
        AF["create_app() — app factory"]
        BP["Blueprints por módulo"]
        DEC["Decoradores<br/>@token_required / @permiso_required"]
        MOS["MOS (core/mos)"]
    end
    DB[("PostgreSQL")]
    FS["Storage (local/GCS)"]

    AX -- "Authorization: Bearer <jwt>" --> AF
    AF --> BP
    BP --> DEC
    BP --> MOS
    BP --> DB
    BP --> FS

Punto de entrada y app factory

El arranque ocurre en run.py, que invoca create_app() de app/__init__.py. La factoría realiza, en orden:

  1. Carga de .env mediante python-dotenv (app/__init__.py:13).
  2. Configuración desde la clase Config (app.config.from_object).
  3. Feature flags (_configure_feature_flags): banderas de presupuesto y nivel de log.
  4. Logger de presupuesto rotativo (logs/presupuesto.log).
  5. Proveedor de almacenamiento (_configure_storage_provider): instancia y registra LocalStorageProvider en app.extensions["storage"].
  6. CORS restringido a localhost:5173 / 127.0.0.1:5173.
  7. Inicialización de extensiones: db, bcrypt, limiter, migrate.
  8. Importación de todos los modelos dentro del app_context (necesario para que SQLAlchemy y Alembic los conozcan).
  9. Registro de blueprints (todos los módulos).
  10. Hooks before_request / after_request: contexto MOS, cabeceras de seguridad ENS y CORS manual.
def create_app(config_class=Config):
    app = Flask(__name__, static_folder=...)
    app.config.from_object(config_class)
    _configure_feature_flags(app)
    _configure_presupuesto_logger(app)
    _configure_storage_provider(app)
    CORS(app, resources={r"/api/*": {...}}, supports_credentials=True)
    db.init_app(app); bcrypt.init_app(app); limiter.init_app(app)
    with app.app_context():
        from .models import (Entidad, Rol, Usuario, ...)   # todos los modelos
    migrate.init_app(app, db)
    # ... registro de blueprints ...
    return app

Extensiones

Definidas en app/extensions.py e inicializadas en la factoría:

Instancia Tipo Propósito
db SQLAlchemy ORM y sesión de base de datos.
bcrypt Flask-Bcrypt Hash y verificación de contraseñas.
migrate Flask-Migrate Migraciones Alembic.
limiter Flask-Limiter Rate limiting por IP (200/minuto global, memory://).

Rate limiting en memoria

limiter usa storage_uri="memory://": el contador no es compartido entre procesos. En despliegues multi-worker conviene un backend externo (Redis).

Estructura de carpetas (backend)

app/
├── __init__.py          # App factory; registra todos los blueprints
├── models.py            # Modelos SQLAlchemy núcleo (Usuario, Rol, Expediente…)
├── decorators.py        # @token_required, @permiso_required, @rol_funcional_required
├── extensions.py        # db, bcrypt, migrate, limiter
├── config.py            # Clase Config (lee variables de entorno)
├── storage_providers.py # Local / GCS
├── core/
│   ├── auth.py          # Login, refresh, /me, MFA
│   ├── admin.py         # Usuarios, roles, permisos
│   ├── exceptions.py    # AppException, AppError, ErrorSource
│   └── mos/             # Motor de automatización y auditoría
├── modules/             # Módulos de negocio (un blueprint cada uno)
│   ├── documental/
│   ├── contabilidad/
│   ├── presupuesto/
│   ├── tesoreria/
│   ├── recaudacion/
│   ├── calendario/
│   ├── registro/
│   ├── padron_habitantes/
│   ├── firmas/          # Firma genérica por rol funcional
│   ├── codigos_postales/
│   ├── scanner/         # Civis Scanner (agente local)
│   ├── publico/         # Verificación CSV pública
│   └── admin/           # Parámetros de control
├── servicios/           # Servicios transversales (tareas, notificaciones)
└── ia/                  # FAISS / RAG

Modelo de blueprints

Cada funcionalidad registra un Blueprint en create_app() con su prefijo de URL. Ejemplos representativos:

Blueprint Prefijo Módulo
auth_bp /api/auth Autenticación
admin_bp /api/admin Administración
documental_bp /api Documental
contabilidad_bp /api Contabilidad
presupuesto_bp /api/presupuesto Presupuesto
tesoreria_bp /api/tesoreria Tesorería
recaudacion_bp /api/recaudacion Recaudación
registro_bp /api/registro Registro
padron_habitantes_bp /api/padron-habitantes Padrón de Habitantes
mos_bp /api/core MOS (auditoría, reglas, crons)
firmas_bp /api Firma genérica por rol funcional

Módulos con muchos sub-blueprints

Algunos módulos grandes (documental, contabilidad, presupuesto) registran varios blueprints o módulos auxiliares (compromisos, contratos, facturas_emitidas, sprint2_routes…). El registro completo está en app/__init__.py.

Ciclo de una petición

sequenceDiagram
    participant C as Cliente (Axios)
    participant F as Flask
    participant MOS as Contexto MOS
    participant D as Decoradores
    participant H as Handler
    participant DB as PostgreSQL

    C->>F: GET /api/registro/registros (Bearer JWT)
    F->>MOS: before_request → init_mos_context() (request_id, user_id)
    F->>D: @token_required
    D->>DB: Usuario.query.get(user_id)
    D->>D: @permiso_required("registro:entrada:listar")
    D->>H: handler(current_user, entidad_id, ...)
    H->>DB: consulta/escritura
    H-->>F: jsonify(...)
    F->>F: after_request → cabeceras seguridad + CORS
    F-->>C: respuesta JSON

Cabeceras de seguridad (ENS)

El hook after_request añade cabeceras de seguridad conformes al Esquema Nacional de Seguridad:

  • X-Content-Type-Options: nosniff
  • X-XSS-Protection: 1; mode=block
  • Referrer-Policy: strict-origin-when-cross-origin
  • Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
  • X-Frame-Options: DENY y Cache-Control: no-store… (salvo descargas de ficheros)
  • Strict-Transport-Security (HSTS) cuando la conexión es HTTPS

Frontend

gestion-civis-frontend/src/
├── main.jsx              # Punto de entrada
├── App.jsx               # Rutas y proveedores
├── api.js                # Axios + interceptores (refresh en 401)
├── themes.js             # Temas MUI
├── context/AuthContext.jsx  # Estado global de autenticación
├── components/
│   ├── core/             # Login, MainLayout, PrivateRoute, paneles admin
│   └── modules/          # Componentes por módulo
├── services/             # Bus de errores
├── helpers/              # Utilidades
└── hooks/                # usePaleta, etc.
  • Estado: AuthContext (identidad y tokens en localStorage), React Query (estado de servidor) y useState (UI local).
  • HTTP: api.js añade Authorization: Bearer <token> en cada petición y gestiona la renovación automática ante un 401 llamando a /api/auth/refresh.
  • Routing: React Router v6; rutas protegidas envueltas en <PrivateRoute>.

Ver detalles de autenticación en Autenticación y autorización.