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:
- Carga de
.envmediantepython-dotenv(app/__init__.py:13). - Configuración desde la clase
Config(app.config.from_object). - Feature flags (
_configure_feature_flags): banderas de presupuesto y nivel de log. - Logger de presupuesto rotativo (
logs/presupuesto.log). - Proveedor de almacenamiento (
_configure_storage_provider): instancia y registraLocalStorageProviderenapp.extensions["storage"]. - CORS restringido a
localhost:5173/127.0.0.1:5173. - Inicialización de extensiones:
db,bcrypt,limiter,migrate. - Importación de todos los modelos dentro del
app_context(necesario para que SQLAlchemy y Alembic los conozcan). - Registro de blueprints (todos los módulos).
- 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: nosniffX-XSS-Protection: 1; mode=blockReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=(), payment=()X-Frame-Options: DENYyCache-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 enlocalStorage), React Query (estado de servidor) yuseState(UI local). - HTTP:
api.jsañadeAuthorization: Bearer <token>en cada petición y gestiona la renovación automática ante un401llamando a/api/auth/refresh. - Routing: React Router v6; rutas protegidas envueltas en
<PrivateRoute>.
Ver detalles de autenticación en Autenticación y autorización.