Saltar a contenido

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__.py importa 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. Modo mock (por defecto, auto-acepta) o production (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 con dir3_destino y sir_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 como RECEPCION/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 los registro:* 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 si None).

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), UnidadDocumental y UDAdjunto; usa el Procedimiento estándar "Registro".
  • Identidad: usa Departamento, RolFuncional, grupos y Tercero.
  • 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.