Módulo Registro
En una frase
Registro de entrada y salida de documentos del ayuntamiento conforme a la Ley 39/2015, con numeración atómica, cadena de integridad (hash-chain), recibo PDF con CSV, asignación multi-destinatario, archivado en expediente-almacén e interconexión SIR / DIR3 / SICRES 3.0.
1. Propósito y alcance
El módulo Registro implementa el Registro General del ayuntamiento:
- Asientos de Entrada (E) y Salida (S) con numeración secuencial por ejercicio y tipo.
- Interesados (personas físicas/jurídicas y representantes) con validación de documento de identidad (NIF/NIE/CIF/Pasaporte).
- Adjuntos con límites SICRES 3.0 y hash SHA-256.
- Recibo PDF con código CSV de verificación y metadatos ENI.
- Asignación a uno o varios destinatarios (usuarios, roles funcionales, departamentos, grupos) y archivado automático en un expediente-almacén por departamento.
- Trazabilidad inalterable mediante hash-chain y auditoría MOS.
- Interconexión registral con otras AAPP vía SIR (formato SICRES 3.0, catálogo DIR3), con sellado de tiempo TSA (RFC 3161).
Marco normativo: Ley 39/2015 (LPAC), Esquema Nacional de Interoperabilidad (ENI), especificación SICRES 3.0 del MINHAP.
2. Estructura de archivos
app/modules/registro/
├── __init__.py
├── models.py # Modelos del módulo (asientos, oficinas, DIR3, SIR…)
├── routes.py # Blueprint registro_bp y endpoints REST
├── seed_registro.py # Permisos y roles del módulo
└── servicios/
├── numerador.py # Numeración atómica
├── hash_chain.py # Cadena de integridad SHA-256
├── errors.py # Catálogo de errores REG-*
├── validador_documentos.py # Validación NIF/NIE/CIF/Pasaporte
├── audit_helper.py # emit_registro_event + snapshots
├── notificaciones_asignacion.py # Expansión de destinatarios + avisos
├── expediente_almacen.py # Expediente-almacén por departamento
├── recibo_pdf.py # Recibo PDF + CSV + ENI
├── dir3_sync.py # Catálogo DIR3 (seed + importar CSV)
├── sicres3.py # Formato XML SICRES 3.0
├── sir_client.py # Cliente SOAP a SIR (mock/production)
├── sir_bandeja_entrada.py # Cron MOS: descargar pendientes
└── sir_bandeja_salida.py # Cron MOS: enviar pendientes
- Blueprint:
registro_bp, registrado con prefijo/api/registro. - Registro en la factoría:
app/__init__.pyimporta los modelos y registra el blueprint, y registra las acciones MOS de las bandejas SIR.
3. Modelos de datos
3.1 Enums
| Enum | Valores |
|---|---|
TipoRegistroEnum |
E, S |
OrigenRegistroEnum |
VENTANILLA, SEDE_CIUDADANA, SIR_ENTRANTE, SIR_SALIENTE |
EstadoRegistroEnum |
BORRADOR, REGISTRADO, ACEPTADO, RECHAZADO, ANULADO |
TipoDocumentoIdentificacionEnum |
NIF, NIE, CIF, PASAPORTE, OTRO |
CanalNotificacionEnum |
POSTAL, ELECTRONICO, AMBOS |
TipoValidezDocumentoEnum |
ORIGINAL_ELECTRONICO, COPIA_AUTENTICA, COPIA |
NivelAdministrativoEnum |
AGE, CCAA, EELL, UNIVERSIDAD, OTROS |
TipoIntercambioSIREnum |
ENVIO, RECEPCION |
EstadoIntercambioSIREnum |
PREPARADO, ENVIANDO, ENVIADO, ACEPTADO, RECHAZADO, ERROR, REINTENTAR, PENDIENTE_ACEPTACION |
3.2 Máquina de estados del asiento
stateDiagram-v2
[*] --> BORRADOR
BORRADOR --> REGISTRADO: registrar (asigna número)
REGISTRADO --> ACEPTADO: aceptar/asignar
REGISTRADO --> RECHAZADO: rechazar (motivo)
REGISTRADO --> ANULADO: anular (motivo)
ACEPTADO --> ANULADO: anular (motivo)
Alta directa en ACEPTADO
En el flujo actual de ventanilla, POST /registros crea el asiento ya en
estado ACEPTADO (con número asignado y archivado), sin pasar
explícitamente por REGISTRADO.
3.3 Tablas
registros — asiento registral (entidad central)
Campos destacados:
| Grupo | Campos |
|---|---|
| Identificación | id, entidad_id, tipo (E/S), ejercicio, numero, referencia (E-2026-00000123), fecha_registro. |
| Contenido | extracto (máx 240, obligatorio), descripcion. |
| Origen | origen, presentado_por_id (Tercero, si SEDE_CIUDADANA). |
| Oficina | oficina_id (obligatorio). |
| Estado/asignación | estado, departamento_destino_id (legacy), usuario_destino_id (legacy), expediente_id, ud_id. |
| SIR | dir3_origen, dir3_destino, sir_intercambio_id, sir_estado. |
| Integridad | hash_anterior, hash_propio (SHA-256). |
| Anulación | motivo_anulacion, fecha_anulacion, anulado_por_id. |
| Rechazo | motivo_rechazo, fecha_rechazo, rechazado_por_id. |
| Aceptación | fecha_aceptacion, aceptado_por_id. |
| Auditoría | creado_por_id, fecha_creacion, fecha_modificacion. |
Índices: único (entidad_id, ejercicio, tipo, numero); búsqueda por
(entidad_id, estado) y (entidad_id, fecha_registro).
Relaciones: oficina, creado_por, interesados (cascade), adjuntos
(cascade), auditoria (cascade), destinatarios (4 relaciones M2M), registros_ud.
registro_oficinas — oficinas físicas de registro
codigo (único por entidad, autogenerado OF-NNN), nombre, dirección,
codigo_dir3 (obligatorio para poder registrar salidas), activa.
registro_contadores — numeración atómica
Clave (entidad_id, ejercicio, tipo) con siguiente_numero. Se accede con
SELECT … FOR UPDATE para garantizar unicidad bajo concurrencia.
registro_interesados
Persona física/jurídica o representante: tipo_documento, numero_documento,
razon_social o nombre/apellidos, domicilio, email/telefono,
canal_notificacion, es_representante, representa_a_id (auto-FK),
tercero_id (enlace CRM opcional).
registro_documentos_anexos
Adjunto del asiento: nombre_fichero, mime_type, tamano_bytes,
hash_sha256, ruta_storage, tipo_validez (ENI), digitalizado_por_id.
Restricciones SICRES 3.0
Máximo 5 adjuntos por asiento, 10 MB por adjunto y 15 MB en total. Tipos MIME admitidos: PDF, JPEG, PNG, TIFF, GIF, DOC(X)/ODT, XLS(X)/ODS, TXT, CSV, XML, ZIP.
registro_estados_auditoria
Histórico inmutable de transiciones: estado_anterior, estado_nuevo, motivo,
comentario, usuario_id, fecha.
registro_destinatario_{usuario,rol,departamento,grupo}
Asignación múltiple (patrón análogo a ExpedienteAcceso). El destinatario por
rol apunta a un rol funcional (cargo), no a un rol RBAC.
registro_ud — relación 1:N asiento → UDs
Un asiento puede generar varias unidades documentales (una por departamento
receptor). El campo legacy registros.ud_id apunta a la UD del primer
departamento.
oficinas_dir3 — catálogo oficial MINHAP (compartido)
Catálogo global (no por entidad) de unidades orgánicas de las AAPP españolas:
codigo (PK, p. ej. E04931801), denominacion, nivel, nif_cif,
provincia, dependencia_de (jerarquía), es_oficina_registro, es_sir,
vigente, origen_datos (seed / minhap_csv / manual).
registro_intercambios_sir
Auditoría de cada intercambio SIR: tipo (ENVIO/RECEPCION), estado,
codigo_intercambio (único), dir3_origen/dir3_destino, xml_payload y
xml_respuesta (binario), num_intentos, proximo_intento (backoff),
tsa_token (RFC 3161), y marcas temporales del ciclo (envío, confirmación,
aceptación).
4. Endpoints REST
Blueprint registro_bp bajo /api/registro.
Oficinas
| Método | Ruta | Permiso | Propósito |
|---|---|---|---|
GET |
/oficinas |
registro:oficina:ver |
Listar oficinas (?solo_activas=true). |
GET |
/oficinas/<id> |
registro:oficina:ver |
Detalle. |
POST |
/oficinas |
registro:oficina:gestionar |
Crear (código OF-NNN autogenerado). |
PUT |
/oficinas/<id> |
registro:oficina:gestionar |
Actualizar. |
Asientos registrales
| Método | Ruta | Permiso | Propósito |
|---|---|---|---|
GET |
/registros |
registro:entrada:listar y/o registro:salida:listar (según ?tipo) |
Listado paginado con filtros. |
GET |
/registros/<id> |
Según tipo | Detalle completo con relaciones. |
POST |
/registros |
registro:entrada:crear o registro:salida:crear |
Alta del asiento (BORRADOR→REGISTRADO→ACEPTADO en un paso). |
POST |
/registros/<id>/anular |
registro:entrada:anular o registro:salida:anular |
Anular (requiere motivo). |
POST |
/registros/<id>/rechazar |
registro:entrada:rechazar o registro:salida:rechazar |
Rechazar (requiere motivo). |
Filtros del listado: tipo, estado, ejercicio, referencia, extracto,
departamento_destino_id, usuario_destino_id, destinatario_usuario_id,
dir3_destino, solo_dir3.
Adjuntos
| Método | Ruta | Permiso | Propósito |
|---|---|---|---|
POST |
/registros/<id>/adjuntos |
registro:entrada:crear / registro:salida:crear |
Subir adjunto (multipart). |
GET |
/registros/<id>/adjuntos |
token | Listar adjuntos. |
GET |
/registros/<id>/adjuntos/<adj_id> |
token | Descargar adjunto. |
DELETE |
/registros/<id>/adjuntos/<adj_id> |
registro:entrada:crear / registro:salida:crear |
Eliminar adjunto. |
Catálogos y DIR3
| Método | Ruta | Permiso | Propósito |
|---|---|---|---|
GET |
/catalogos/roles-funcionales |
registro:registro:listar |
Lista de cargos para asignación. |
GET |
/dir3 |
token | Buscar en catálogo DIR3 (?q, ?nivel, ?solo_sir, ?limit). |
GET |
/dir3/<codigo> |
token | Detalle de un organismo DIR3. |
POST |
/dir3/seed |
registro:dir3:sincronizar |
Cargar seed mínimo DIR3. |
POST |
/dir3/importar-csv |
registro:dir3:sincronizar |
Importar CSV oficial MINHAP. |
Flujo de alta (POST /registros)
flowchart TD
A[Validar tipo, permiso, extracto≤240, oficina activa] --> B{¿Salida?}
B -- sí --> B1[Oficina debe tener codigo_dir3]
B -- no --> C
B1 --> C[Validar dir3_destino contra catálogo si procede]
C --> D[Validar interesados<br/>obligatorios si NO hay dir3_destino]
D --> E[Exigir ≥1 destinatario]
E --> F[Calcular set de departamentos involucrados]
F --> G[Reservar número atómico — proximo_numero]
G --> H[Crear/recuperar expediente-almacén por departamento]
H --> I[Crear asiento en estado ACEPTADO]
I --> J[Crear interesados y destinatarios M2M]
J --> K[Crear una UD por departamento + registro_ud]
K --> L[Calcular hash_anterior + hash_propio]
L --> M[Auditoría de estado + archivar recibo PDF]
M --> N[Notificar a destinatarios efectivos]
N --> O[emit REGISTRO_CREAR]
O --> P[201 con asiento completo]
5. Servicios internos
numerador.py
proximo_numero(entidad_id, ejercicio, tipo) -> (numero, referencia). Formato
"{tipo}-{ejercicio:04d}-{numero:08d}". Atómico vía SELECT … FOR UPDATE. No
hace commit (responsabilidad del caller). Lanza NumeradorError.
hash_chain.py — integridad sin TSA externa
Cada asiento guarda hash_anterior (hash del asiento previo de la misma cadena)
y hash_propio (SHA-256 de su identidad inmutable + adjuntos + hash_anterior).
Funciones:
hash_registro_anterior(entidad_id, ejercicio, tipo)serializar_para_hash(registro, adjuntos_hashes)— JSON canónico de campos inmutables (excluye estado, asignación, SIR, motivos).calcular_hash_propio(registro, adjuntos_hashes)verificar_cadena(entidad_id, ejercicio, tipo)→{total, integros, rotos:[…]}.
Por qué importa
Modificar un asiento intermedio rompe el encadenado a partir de él, lo que permite detectar manipulaciones del libro de registro.
validador_documentos.py
validar_documento(tipo, numero) -> (valido, error, normalizado) para NIF
(letra de control mód-23), NIE (X/Y/Z→0/1/2), CIF (algoritmo oficial),
PASAPORTE (5–20 alfanum.) y OTRO (flexible). Normaliza a mayúsculas sin
separadores.
audit_helper.py
emit_registro_event(...) envuelve a emit_event con metadata funcional del
asiento (referencia, tipo, ejercicio, asunto, oficina, estado, DIR3, hash…),
motivo separado y commit autónomo. Aporta snapshot_estado() (cambios de
estado) y snapshot_completo() (alta).
notificaciones_asignacion.py
notificar_asignacion(registro) expande las 4 relaciones de destinatarios
(usuarios, roles funcionales, departamentos, grupos) a usuarios efectivos y
emite avisos in-app (prioridad alta si origen=SIR_ENTRANTE).
expediente_almacen.py
obtener_o_crear_almacen(departamento_id, entidad_id, usuario_creador_id):
mantiene un expediente ALMACEN_REGISTRO por departamento como cola de
clasificación de los documentos registrados.
recibo_pdf.py
Genera el recibo PDF A4 (WeasyPrint) con datos del asiento, tabla de adjuntos
con hash, código CSV de verificación y metadatos ENI (justificante de
recepción TD21). guardar_recibo_en_almacen(...) lo archiva como adjunto de la UD
(idempotente).
dir3_sync.py
seed_inicial(force=False)— inserta el seed de organismos top (TGSS, INSS, SEPE, AEAT…).importar_csv_minhap(csv_text, origen)— upsert desde el CSV oficial (separador;,windows-1252).buscar(q, nivel, solo_sir, limit).
Interconexión SIR
sicres3.py— genera/parse del XML SICRES 3.0 (adjuntos en base64, namespaces oficiales del ENI).sir_client.py— cliente SOAP a SIR. Modomock(por defecto, auto-acepta) oproduction(zeep + certificado). Variables:SIR_MODE,SIR_WSDL_URL,SIR_USERNAME,SIR_PASSWORD,SIR_CERT_P12_PATH,SIR_CERT_P12_PWD,SIR_TIMEOUT_S.sir_bandeja_salida.py— reenvía asientos condir3_destinoysir_estado=PENDIENTE_REENVIO. Backoff[1, 5, 30, 120, 720, 1440]min, máx 6 intentos.sir_bandeja_entrada.py— descarga intercambios entrantes por DIR3 propio, los persiste comoRECEPCION/PENDIENTE_ACEPTACION(idempotente) y confirma.
6. Permisos y roles
Definidos en seed_registro.py. Códigos principales (registro:*):
| Categoría | Permisos |
|---|---|
| Entrada | entrada:crear, entrada:ver, entrada:listar, entrada:anular, entrada:aceptar, entrada:rechazar. |
| Salida | salida:crear, salida:ver, salida:listar, salida:anular, salida:aceptar, salida:rechazar. |
| Bandejas SIR | bandeja:entrada:ver, bandeja:salida:ver, bandeja:enviar. |
| Oficinas | oficina:ver, oficina:gestionar. |
| Documentos | ud:traspasar, adjunto:descargar. |
| DIR3 | dir3:ver, dir3:sincronizar. |
| Sede ciudadana | sede:presentar, sede:consultar (no asignados a roles internos). |
| Admin / config / auditoría | configuracion:gestionar, admin:gestionar, auditoria:ver. |
Funciones de sembrado
seed_permisos_registro()— crea/actualiza permisos (idempotente).crear_rol_jefe_registro(entidad_id)— rol "Jefe de Registro" con todos losregistro:*salvo los de sede ciudadana.asignar_permisos_a_roles_base(entidad_id)— añade ver/listar (y traspaso) a Jefe de Departamento y ver/listar/descargar a Colaborador.asignar_a_administrador_del_sistema(entidad_id)— asegura todos los permisos.seed_registro(entidad_id=None)— sembrado completo (todas las entidades siNone).
Permisos puros
El acceso se decide por permiso (@permiso_required), nunca nombrando el
rol en el código. La asignación a destinatarios por rol funcional es un
concepto distinto (cargo institucional).
7. Integraciones
MOS — eventos
REGISTRO_OFICINA_CREAR, REGISTRO_OFICINA_ACTUALIZAR, REGISTRO_CREAR
(con metadata funcional exhaustiva), REGISTRO_ANULAR, REGISTRO_RECHAZAR,
REGISTRO_ADJUNTO_SUBIR, DIR3_SEED, DIR3_IMPORTAR_CSV, más eventos por cada
intercambio SIR descargado/enviado.
MOS — crons
| Acción | Periodicidad sugerida |
|---|---|
registro.sir.enviar_pendientes |
*/5 * * * * |
registro.sir.descargar_pendientes |
*/10 * * * * |
Otros módulos
- Documental: crea
Expediente(almacén),UnidadDocumentalyUDAdjunto; usa elProcedimientoestándar "Registro". - Identidad: usa
Departamento,RolFuncional, grupos yTercero. - Firmas / TSA: sello de órgano para firmar SICRES y sellado de tiempo.
Sistemas externos
- SIR (Sistema de Interconexión de Registros, MINHAP) vía SOAP.
- DIR3 (Directorio Común de Unidades Orgánicas).
- TSA RFC 3161 (FNMT) para sellado de tiempo.
Estado de producción
Por defecto el módulo opera en modo MOCK de SIR y con el cron sin
programar. Para producción: programar registro.sir.enviar_pendientes,
fijar SIR_MODE=production, aportar credenciales MINHAP y certificado FNMT.