Modelo de datos¶
Custodiam usa PostgreSQL 15 como base de datos principal y SQLModel (ADR-002) como ORM unificado (SQLAlchemy 2.0 + Pydantic en una sola clase). El esquema se evoluciona con Alembic, incluyendo data migrations para pre-poblar catálogos.
Patrón estructural del proyecto¶
Las entidades del dominio que admiten tipos predefinidos pero ampliables (acreditaciones, equipamiento, tipos de servicio, tipos de inventario, tipos de notificación) siguen un patrón único formalizado en ADR-025: catálogo + tabla de instancias + JSONB para campos específicos + enum discriminador.
Esto permite:
- Añadir un tipo nuevo es un INSERT en el catálogo, sin ALTER TABLE.
- Consultas atómicas por familia (
WHERE categoria = 'FORMACION_INTERNA') y mixtas (EXISTScorrelacionado) sobre la misma estructura. - Campos específicos por tipo en
JSONBdocumentados porcampos_schemadel catálogo (schema-on-read). - Catálogos canónicos versionados en Git como data migrations.
Diagrama ER — módulo voluntarios¶
El diagrama siguiente muestra el módulo de voluntarios. Para el esquema completo de la base de datos (las dieciocho tablas de todos los módulos y el mapa de relaciones entre ellos), ver el diagrama ER completo.
Catálogos pre-poblados¶
Los catálogos tipos_acreditacion y tipos_equipamiento se cargan con datos canónicos mediante Alembic data migrations ejecutadas al desplegar. Esto los versiona en Git como parte del esquema, no como configuración volátil.
tipos_acreditacion (extracto)¶
| codigo | nombre | categoría sugerida | campos_schema (ejemplo) |
|---|---|---|---|
CARNET_CONDUCIR |
Carnet de conducir | LICENCIA_OFICIAL |
{"tipo": "B\|B+E\|C\|C+E\|D", "incluye_remolque": "bool"} |
ESS_SANITARIO |
ESS Sanitario | LICENCIA_OFICIAL |
{"nivel": "ESS\|ATS\|enfermería"} |
ADR_MERCANCIAS_PELIGROSAS |
ADR Mercancías Peligrosas | LICENCIA_OFICIAL |
{"clases": ["I","II","III","IV","V","VI","VII","VIII","IX"]} |
MANIPULADOR_ALIMENTOS |
Manipulador de alimentos | LICENCIA_OFICIAL |
{} |
CURSO_DEA |
Curso uso de DEA | FORMACION_INTERNA |
{} |
CURSO_PROTECCION_CIVIL |
Curso PC (genérico) | FORMACION_INTERNA |
{"horas": "int", "nivel": "básico\|intermedio\|avanzado"} |
JORNADA_RESCATE_VEHICULOS |
Jornada rescate vehículos | FORMACION_INTERNA |
{} |
OTRO |
Otra acreditación | OTRO |
(libre) |
tipos_equipamiento (extracto)¶
| codigo | nombre | sistema_tallas |
|---|---|---|
CAMISA |
Camisa de uniforme | XS-XXXL |
POLO |
Polo de uniforme | XS-XXXL |
CHAQUETA |
Chaqueta de uniforme | XS-XXXL |
PANTALON |
Pantalón de uniforme | 36-50 |
BOTAS |
Botas reglamentarias | EU |
CASCO |
Casco | S-XL |
GUANTES |
Guantes | S-XL |
CHALECO |
Chaleco reflectante | XS-XXXL |
Modificaciones futuras del catálogo (añadir un tipo nuevo, marcar uno como activo = false) se gestionan con nuevas data migrations Alembic, revisables por PR como cualquier otro cambio de schema.
Indexación¶
| Tabla | Índices |
|---|---|
voluntarios |
dni UNIQUE, email UNIQUE, keycloak_id UNIQUE+INDEX, estado |
acreditaciones |
voluntario_id, tipo_id, (voluntario_id, tipo_id, numero) UNIQUE, categoria |
tallas_voluntario |
voluntario_id, (voluntario_id, tipo_id) UNIQUE |
contactos_emergencia |
voluntario_id |
El índice en acreditaciones.categoria soporta consultas atómicas por familia ("voluntarios con cualquier formación interna") sin recurrir a JOIN con el catálogo. Las consultas mixtas (EXISTS correlacionado por tipo) aprovechan los índices compuestos.
Validación de datos_especificos¶
En la capa API, FastAPI + Pydantic valida datos_especificos contra el campos_schema declarado en el tipo asociado. Implementación recomendada:
- Librería
jsonschema(validación a partir del schema almacenado). - Alternativa:
model_validatorPydantic con lógica condicional portipo_id.
La validación es opcional al inicio (los catálogos campos_schema pueden ser null) y estricta cuando madure la UI de gestión (panel admin para crear/editar tipos y describir su schema). El cliente puede leer el campos_schema para construir formularios dinámicos sin hardcodearlos.
Aplicabilidad a otros módulos¶
El patrón se aplicará en módulos futuros con la misma estructura:
| Módulo futuro | Catálogo previsto | Instancias |
|---|---|---|
| Inventario (E05) | tipos_material, tipos_vehiculo |
inventario, vehiculos |
| Servicios (E03) | tipos_servicio (preventivo, emergencia, formación, jornada) |
servicios |
| Notificaciones (E06) | tipos_notificacion con canales (FCM, ntfy, email) |
notificaciones |
La elección concreta (catálogo + instancias + JSONB + enum) se documenta como principio de proyecto en ADR-025: para entidades con tipos predefinidos extensibles, no usar columnas planas ni JSONB libre.
Audit log cross-module¶
Todas las operaciones críticas (alta/baja/edición de voluntarios, asignación a servicios, anonimización RGPD) se registran en una tabla audit_log cross-module con un patrón de imports diferidos para evitar dependencias cíclicas entre módulos. Detalle en Audit log.
Referencias¶
- ADR-002 SQLModel — ORM unificado.
- ADR-025 Modelo extensible — patrón formal completo.
- PostgreSQL — JSONB — operadores e indexación.
- SQLModel — Relationships — patrón de relaciones.