Módulo Notificaciones (DEHú)
En una frase
Módulo común que entrega notificaciones electrónicas oficiales a la ciudadanía vía DEHú (MINHAP), invocado por los módulos funcionales (Padrón, Registro, …) mediante un helper de un solo punto. Cumple el art. 43 de la Ley 39/2015 (LPAC): ciclo de vida completo, plazo de 10 días con auto-rechazo por silencio y trazabilidad de comparecencia.
1. Propósito y alcance
- Cliente DEHú abstracto con dos modos: MOCK (simulación local para
pruebas) y PRODUCTION (SOAP real contra
dehu.redsara.es— stub hoy, pendiente de implementación cuando hay cert FNMT + alta MINHAP). - Helper único
notificar_ciudadano(**kwargs)que cualquier módulo emisor invoca cuando quiere entregar una comunicación electrónica. - Bandeja unificada: todas las notificaciones de la entidad, sea cual sea el módulo emisor, viven en la misma tabla y comparten UI.
- Configuración multi-tenant: cada entidad activa DEHú por su cuenta
(
dehu_activo=true); con DEHú apagado el helper devuelveNoney los emisores siguen con su canal postal habitual. - Cron de vigilancia diaria: auto-rechazo por silencio art. 43.2 LPAC y refresco de estados desde DEHú.
- Best-effort en los módulos emisores: si DEHú falla, el flujo legal postal continúa sin interrupción.
Marco normativo: Ley 39/2015 (LPAC) art. 43, ENS, RGPD.
2. Estructura de archivos
app/modules/notificaciones/
├── __init__.py
├── models.py
│ NotificacionElectronica # registro de cada notificación
│ NotificacionesConfiguracion # toggle + URL + DIR3 + cert por entidad
│ CanalNotificacionEnum # DEHU (extensible)
│ EstadoNotificacionEnum # 5 estados
├── routes.py # Blueprint notificaciones_bp (10 endpoints)
└── servicios/
├── cliente_dehu.py # MOCK + stub PRODUCTION
├── helper.py # notificar_ciudadano() + cortocircuito
└── vigilancia.py # @register_action cron diario
Integración con módulos emisores:
app/modules/padron_habitantes/servicios/renovaciones.py
notificar(), notificar_recordatorio() → helper.notificar_ciudadano()
app/modules/registro/servicios/notif_dehu.py
notificar_interesados_via_dehu() → helper.notificar_ciudadano()
app/modules/registro/routes.py
crear_registro() invoca notificar_interesados_via_dehu() (best-effort)
3. Modelo de datos
notificacion_electronica
| Columna | Tipo | Propósito |
|---|---|---|
id |
int PK | — |
entidad_id |
FK entidades | Multi-tenant. |
documento_destinatario |
varchar(50) | NIF/NIE/CIF/Pasaporte (denormalizado). |
nombre_destinatario |
varchar(200) | — |
email_destinatario |
varchar(200) | Canal DEHú. |
tercero_id |
FK terceros nullable | Vínculo opcional al maestro. |
asunto |
varchar(500) | — |
cuerpo |
text nullable | Texto adicional. |
pdf_storage_path |
varchar(500) | Ruta del PDF original notificado. |
sha256_pdf |
char(64) nullable | Hash del PDF al momento de notificar. |
tamano_bytes |
int nullable | — |
modulo_origen |
varchar(50) | "padron_habitantes", "registro", … |
referencia_origen |
varchar(200) | p. ej. salida:42:interesado:7. |
canal |
enum notif_canal_enum |
DEHU. |
estado |
enum notif_estado_enum |
Ver más abajo. |
modo |
varchar(20) | "MOCK" / "PRODUCTION". |
identificador_externo |
varchar(200) nullable | ID asignado por DEHú. |
fecha_envio |
datetime nullable | Cuándo se llamó al canal. |
fecha_puesta_disposicion |
datetime nullable | Aceptación del canal. |
fecha_limite_acuse |
datetime nullable | puesta + 10 días naturales. |
fecha_comparecencia |
datetime nullable | Acto del ciudadano. |
fecha_rechazo |
datetime nullable | Rechazo expreso o por silencio. |
mensaje_error |
varchar(500) nullable | Diagnóstico si ERROR / motivo cancelación. |
usuario_creador_id |
FK usuarios nullable | Operador que disparó la emisión. |
creado_en, modificado_en |
datetime | Auditoría. |
Enums (nombrados explícitamente para no chocar con otros módulos):
notif_canal_enum→DEHU.notif_estado_enum→PUESTA_A_DISPOSICION,COMPARECIDA,RECHAZADA,VENCIDA,ERROR.
notificaciones_configuracion
Una fila por entidad (unique). Si no existe se crea con defaults seguros (DEHú apagado + MOCK).
| Columna | Tipo | Default | Propósito |
|---|---|---|---|
dehu_activo |
bool | false |
Toggle principal sí/no. |
dehu_modo |
varchar(20) | "MOCK" |
MOCK o PRODUCTION. |
dehu_url |
varchar(300) | null | URL real del servicio. |
dehu_cert_path |
varchar(300) | null | Ruta del .p12 en el servidor. |
dehu_organismo_dir3 |
varchar(10) | null | Código DIR3 emisor. |
modificado_por_id |
FK usuarios | null | Auditoría. |
Enlaces desde otros módulos
| Tabla | Columna FK | Propósito |
|---|---|---|
padron_hab_renovacion |
notificacion_dehu_id |
Aviso principal entregado vía DEHú. |
padron_hab_renovacion |
recordatorio_notificacion_dehu_id |
Recordatorio entregado vía DEHú. |
registro_interesados |
notificacion_dehu_id |
Interesado de un Registro de Salida entregado vía DEHú. |
Las tres columnas son nullable: si la entidad tiene DEHú apagado o el
canal del destinatario no permite entrega electrónica, quedan en NULL y el
flujo postal sigue su curso.
Migraciones
| Migración | Cambio |
|---|---|
149_notificaciones_dehu |
Tabla notificacion_electronica + enums. |
150_notificaciones_configuracion |
Tabla notificaciones_configuracion. |
151_padron_renovacion_dehu |
FKs en padron_hab_renovacion. |
152_registro_interesado_dehu |
FK en registro_interesados. |
4. API REST
Blueprint notificaciones_bp, prefijo /api/notificaciones. Todos los
endpoints requieren JWT + permiso.
| Método | Ruta | Permiso | Propósito |
|---|---|---|---|
GET |
/notificaciones |
notificaciones:notificacion:ver |
Listado paginado con filtros (estado, módulo, documento, búsqueda libre, rango fechas). |
GET |
/notificaciones/<id> |
notificaciones:notificacion:ver |
Detalle completo. |
POST |
/notificaciones/<id>/consultar-estado |
notificaciones:notificacion:ver |
Pregunta a DEHú y persiste cambios. |
POST |
/notificaciones/<id>/reenviar |
notificaciones:notificacion:reenviar |
Reintenta el envío (solo ERROR). Acepta {"email": "…"} para corregir destinatario. |
POST |
/notificaciones/<id>/cancelar |
notificaciones:notificacion:cancelar |
Marca como RECHAZADA con mensaje_error ("Cancelada por X: motivo"). |
GET |
/notificaciones/<id>/pdf |
notificaciones:notificacion:ver |
Descarga del PDF original. |
GET |
/notificaciones/<id>/acuse |
notificaciones:notificacion:ver |
Descarga del acuse firmado por DEHú. |
GET |
/notificaciones/resumen |
notificaciones:notificacion:ver |
KPIs por estado y por módulo. |
GET |
/notificaciones/configuracion |
notificaciones:dehu:admin |
Configuración de la entidad (sin exponer dehu_cert_path, solo dehu_cert_configurado: bool). |
PUT |
/notificaciones/configuracion |
notificaciones:dehu:admin |
Actualiza la configuración con validación PRODUCTION (URL + cert + DIR3 obligatorios para activar). |
Códigos de error
code |
Cuándo |
|---|---|
NOTIF-NOT-FOUND |
ID no existe en la entidad actual. |
NOTIF-SIN-IDENT |
No hay identificador externo para consultar / acuse. |
NOTIF-CONSULTA-ERROR |
DEHú devolvió error al consultar. |
NOTIF-ESTADO-INVALIDO |
Acción no permitida en el estado actual. |
NOTIF-PDF-NO-DISP |
PDF no encontrado en storage. |
NOTIF-ACUSE-NO-DISP |
DEHú no ha emitido acuse todavía. |
NOTIF-CFG-MODO-INVALIDO |
dehu_modo no es MOCK ni PRODUCTION. |
NOTIF-CFG-INCOMPLETA |
Falta URL / cert / DIR3 para activar PRODUCTION. |
5. Helper notificar_ciudadano()
Punto de entrada único para los módulos emisores. Firma (todos kwargs):
from app.modules.notificaciones.servicios.helper import notificar_ciudadano
notif = notificar_ciudadano(
entidad_id=...,
documento=...,
nombre=...,
email=...,
asunto=...,
pdf_path=...,
modulo_origen=...,
referencia_origen=...,
tercero_id=None,
usuario_id=None,
cuerpo=None,
)
# notif: Optional[NotificacionElectronica]
Flujo interno:
- Cortocircuito por entidad: lee
NotificacionesConfiguracion. Sidehu_activo=False→ devuelveNone(silencioso, sin emit_event). - Calcula
sha256ytamano_bytesdel PDF si existe. - Crea fila
NotificacionElectronicacon estado inicialPUESTA_A_DISPOSICION(pesimista, se corrige si el cliente devuelve error). - Llama a
cliente_dehu.poner_a_disposicion(notif). - Si OK: persiste
identificador_externo,fecha_puesta_disposiciony calculafecha_limite_acuse = puesta + 10 días. - Si KO: estado pasa a
ERROR, guardamensaje_error. Devuelve la fila igual (para que el emisor pueda enlazarla y luego se reintente). - Emite
emit_event(severidad INFO si OK, WARNING si ERROR).
Patrón en módulos emisores
Los emisores envuelven la llamada en try/except y guardan la FK al
notif.id si la respuesta no es None. Si el helper devuelve None o
levanta una excepción, el módulo sigue con su flujo postal habitual sin
incidente.
6. Cliente DEHú
servicios/cliente_dehu.py. Tres operaciones:
| Función | Para qué |
|---|---|
poner_a_disposicion(notif) |
Crea la notificación en el canal y devuelve identificador_externo. |
consultar_estado(entidad_id, identificador) |
Pregunta el estado actual y la fecha del evento. |
descargar_acuse(entidad_id, identificador) |
Bytes del PDF de acuse firmado por DEHú. |
Modo MOCK
Reglas deterministas que permiten ejercitar el flujo sin DEHú real:
| Condición | Resultado |
|---|---|
| Sin email | ERROR SIN_CANAL. |
Documento empieza por "ERR" |
ERROR MOCK_ERROR. |
| Cualquier otra | Identificador externo MOCK-{n} generado de forma determinista. |
| Evolución en consultas | 60 % siguen en PUESTA_A_DISPOSICION, 25 % COMPARECIDA, 10 % RECHAZADA, 5 % VENCIDA. |
Modo PRODUCTION
Hoy es un stub que devuelve siempre ERROR con código PRODUCTION_NO_IMPL.
Por implementar cuando exista cert FNMT + alta MINHAP. La interfaz ya está
fijada para que la sustitución sea local: solo hay que reemplazar el cuerpo de
las tres funciones por las llamadas SOAP/REST reales.
7. Cron de vigilancia
Acción MOS notificaciones.vigilar_vencimientos (servicios/vigilancia.py),
registrada con @register_action. Cron por defecto: 0 5 * * * (diario).
Hace dos cosas, cada una por entidad:
_auto_rechazar_vencidas()—UPDATE notificacion_electronica SET estado='RECHAZADA', fecha_rechazo=now(), mensaje_error='Vencido plazo art. 43.2 LPAC' WHERE fecha_limite_acuse < now() AND estado='PUESTA_A_DISPOSICION'. Independiente del estado del toggle (es un cumplimiento legal, no requiere DEHú activo)._refrescar_estados()— solo sidehu_activo=True. Recorre las notificaciones PUESTA_A_DISPOSICION con identificador externo y consulta el estado actual a DEHú; si ha evolucionado, persiste el cambio.
8. Permisos
| Código | Quién lo tiene típicamente |
|---|---|
notificaciones:notificacion:ver |
Operativos de Padrón y Registro. |
notificaciones:notificacion:reenviar |
Jefes de unidad / Administradores. |
notificaciones:notificacion:cancelar |
Jefes de unidad / Administradores. |
notificaciones:dehu:admin |
Administrador del sistema. |
Sembrados en seed.py (PERMISOS_SEED).
9. Integración con MOS
Eventos emitidos por el módulo:
event_code |
Cuándo | Severidad |
|---|---|---|
NOTIF_EMITIDA |
Notificación creada y entregada al canal. | INFO |
NOTIF_ERROR_CANAL |
El canal devolvió error al poner a disposición. | WARNING |
NOTIF_REENVIADA |
Reenvío manual desde la UI. | INFO |
NOTIF_CANCELADA |
Cancelación manual. | WARNING |
NOTIF_COMPARECIDA |
Cambio de estado al consultar. | INFO |
NOTIF_RECHAZADA_SILENCIO |
Auto-rechazo del cron por silencio art. 43.2. | INFO |
Recursos auditados (resource_type="notificacion_electronica").
10. Decisiones de diseño
- Módulo común, no extra-modular: la bandeja vive en un único sitio para evitar duplicar UI en cada módulo emisor y consolidar la trazabilidad.
- Opt-in por entidad: el ayuntamiento decide cuándo conectar a DEHú real; hasta entonces no hay riesgo de envíos accidentales.
Optional[Notif]en lugar de excepciones: el helper devuelveNonecuando DEHú está apagado para no obligar a los emisores a manejar una excepción ruidosa por algo que es una decisión administrativa, no un error.- Best-effort en emisores: el
try/exceptenvolvente garantiza que un fallo de DEHú nunca rompa el flujo legal postal de Padrón o Registro. - Hash + tamaño + ruta: la notificación guarda referencia al PDF realmente entregado para que, ante una reclamación, sea reproducible.
mensaje_errorreutilizado tanto para errores técnicos como para motivos de cancelación (con prefijo "Cancelada por X:") — simplifica el modelo y la UI solo necesita interpretar el estado para colorear.
11. Pendientes documentados
- Sustituir el stub PRODUCTION del cliente DEHú por la implementación SOAP real cuando el ayuntamiento tenga alta MINHAP + cert FNMT (ver puesta a producción).
- Integrar Recaudación cuando exista deuda en vía ejecutiva (DEHú es obligatorio para notificar providencias de apremio).
- Notificación a representantes legales además del titular cuando los datos de representación estén en el módulo.