# Intervención 2 — Especificación del email reorganizado Documento de acompañamiento de `02_email_template.html`. Define el comportamiento esperado de la plantilla, casos extremos y diseño responsive. --- ## 1. Filosofía del email El email pasa de ser una **lista cronológica de menciones** a un **resumen accionable estructurado por urgencia operativa**. Los principios: 1. **Lo importante arriba**: bases (con temario) en el primer bloque, destacado visualmente. Es lo que requiere acción de la academia. 2. **Densidad de información sin saturar**: cada hit lleva su contexto suficiente para decidir si abrir el PDF, sin tener que abrirlo todos. 3. **Plegado para lo accesorio**: sub-fases y posibles falsos positivos van en bloques colapsables (`
`), accesibles pero sin ruido. 4. **Acción inmediata accesible**: los botones de feedback están en cada hit; un clic basta. 5. **Visión de proceso**: el bloque "Procesos actualizados hoy" muestra cómo van avanzando los hilos que el usuario sigue. --- ## 2. Lógica de orden y agrupación ### 2.1 Orden de bloques (siempre fijo, no cambia) 1. Cabecera + resumen del día. 2. **BASES** (verde). Aparece primero porque es lo más importante. 3. **APERTURA DE PLAZO** (ámbar). Acción urgente. 4. **OEP** (azul). Importante, no urgente. 5. **PROCESOS ACTUALIZADOS** (panel lateral, gris claro). 6. **SUB-FASES** (gris, plegado). 7. **FALSOS POSITIVOS** (rojo, plegado). 8. Boletines no cubiertos (pie, plegado, fuente pequeña). 9. Footer con enlaces. Si un bloque está vacío, se omite por completo (no se muestra el encabezado). ### 2.2 Orden dentro de cada bloque - **BASES**: ordenadas por número de plazas descendente. Más plazas arriba (más impacto). En empate, alfabético por administración. - **APERTURA**: ordenadas por fecha de fin de plazo ascendente (las que acaban antes, arriba). En empate, por número de plazas. - **OEP**: ordenadas por número de plazas total descendente. - **SUB-FASES**: ordenadas por tipo (admitidos → tribunal → resultados → nombramientos), y dentro de cada tipo alfabéticamente. - **FALSOS POSITIVOS**: por boletín y regla aplicada, agrupados. ### 2.3 Deduplicación visible Si el mismo registro (mismo `codigo_cve`) aparece en varias páginas del mismo boletín, se muestra **una sola tarjeta** con la indicación `↻ N menciones`. El campo `paginas_duplicadas` contiene la lista. Si el mismo proceso aparece referenciado en dos boletines distintos (caso raro: OEP estatal en BOE y referencia secundaria en autonómico), se muestran ambos pero vinculados al mismo `id_proceso`. --- ## 3. Datos estructurados en BASES Solo el bloque BASES muestra la rejilla de "datos estructurados". Se rellena con lo que `04_parser_bases.py` haya conseguido extraer del PDF. Campos: | Campo | Cómo se obtiene | Fallback si falla parser | |---|---|---| | Turno libre | Tabla del PDF | "No detectado" | | Promoción interna | Tabla del PDF | omitir | | Reserva discapacidad | Tabla del PDF | omitir | | Ejercicios | Sección "fase de oposición" | omitir | | Tasa examen | Patrón "tasa de XX,XX euros" | omitir | | Sistema selectivo | Patrón "oposición" / "concurso-oposición" / "concurso" | omitir | Si el parser no consigue extraer nada útil, la rejilla simplemente se omite y solo se muestra el fragmento OCR. El email nunca falla por la ausencia de datos estructurados. --- ## 4. Casos extremos ### 4.1 Día sin hits Si todos los bloques están vacíos: ``` Resumen del lunes 11 de mayo de 2026 Hoy no hay coincidencias para tu categoría "Hacienda". Boletines procesados: 38 de 45 Boletines sin publicación hoy: 0 Boletines no cubiertos: 7 [Ver detalle de boletines] ``` Email corto. No silenciar — el usuario debe saber que el sistema sigue funcionando. ### 4.2 Día con cientos de hits (caso OEP estatal) Cuando se publica una OEP grande, se generan muchos hits a la vez (uno por cuerpo). En lugar de mostrar 50 tarjetas individuales: - Agrupar todos los hits OEP del mismo documento en **una tarjeta única** con la cabecera del RD. - Listar dentro las plazas relevantes para la categoría del usuario ("Tus categorías afectadas: 130 Inspectores Hacienda, 446 Técnicos Hacienda, 1.000 Agentes Hacienda"). - Enlace "Ver todos los cuerpos del RD". Esto se decide en backend antes de renderizar la plantilla. ### 4.3 Hits con OCR roto Si el fragmento del hit es ilegible (OCR pobre), poner mensaje: ``` [Fragmento parcialmente ilegible — recomendamos abrir el PDF] ``` Y mantener el resto de campos (boletín, página, patrón, botones). Nunca mostrar texto basura. ### 4.4 Procesos huérfanos Si un proceso del histórico lleva > N meses esperando bases o > M esperando apertura, incluir en el bloque "Procesos actualizados hoy" un aviso: ``` ⚠️ Inspectores Hacienda 2025 — esperando bases desde hace 14 meses (media histórica: 4–6 meses). Posiblemente convocatoria retrasada. ``` --- ## 5. Responsive y compatibilidad ### 5.1 Anchos - Desktop: contenedor `max-width: 720px` centrado. - Mobile: el `` y los anchos en % se encargan. Las tarjetas mantienen padding interno pequeño en móvil. ### 5.2 Compatibilidad de clientes Probar como mínimo en: - Gmail web - Gmail Android e iOS - Outlook.com web - Outlook desktop (Windows 10+) - Apple Mail (macOS e iOS) - Thunderbird Gotchas conocidos: - **Outlook desktop** no soporta `
` colapsable. Solución: detectar User-Agent del cliente y renderizar versión expandida si es Outlook, o sustituir `
` por bloques con título y separador visual sin colapso. La librería `premailer` puede ayudar. - **Outlook desktop** no soporta `display: grid`. La rejilla de datos estructurados pasa a `` de dos columnas. Detección por User-Agent o forzar `
` siempre. - **Gmail** recorta emails > 102 KB. Vigilar tamaño total: si más, hay que enlazar a la versión web en lugar de incluir todos los hits. - **Dark mode**: en Gmail iOS y Apple Mail se invierten colores. Las marcas con `mark` (resaltado amarillo) pueden quedar mal. Añadir `@media (prefers-color-scheme: dark)` con paleta alternativa. ### 5.3 Versión texto plano Generar también versión `text/plain` para clientes sin HTML y para filtros de spam (mejora deliverability). Estructura simplificada con los mismos bloques pero como texto: ``` ================================================= HACIENDA · 11-05-2026 ================================================= 1 BASES · 4 plazos abiertos · 2 OEP · 7 sub-fases 🟢 BASES DE LA CONVOCATORIA (1) ----------------------------------------------- Ayuntamiento de Mataró — Técnico de Hacienda · 5 plazas BOPB, pág. 23 · CVE: bop-2026-2167 Turno libre 2 · Promoción interna 3 · 4 ejercicios · Tasa 25 € PDF: https://... Temario (60 temas): https://... Diff con anterior: https://... ✓ Correcta: ... ✗ FP: ... 🟡 APERTURA DE PLAZO (4) ----------------------------------------------- ... ``` --- ## 6. Tracking y feedback ### 6.1 Enlaces de feedback Cada tarjeta lleva dos enlaces: - `{{ url_feedback }}?hit={{ hit.id_hit }}&v=ok` - `{{ url_feedback }}?hit={{ hit.id_hit }}&v=no` El backend recibe esos clics y los persiste en la tabla `hit_feedback` (definida en `03_db_procesos_schema.sql`). El clic registra: - `id_hit`, `id_usuario`, `valor` (ok/no), `timestamp`, `user_agent`. La página de respuesta confirma "Gracias, registrado" y, opcionalmente, ofrece añadir un comentario libre. ### 6.2 Píxel de apertura Incluir un píxel transparente al final del ``: ```html ``` Permite saber qué resúmenes se abren. Cuidado con clientes que bloquean imágenes (Apple Mail desde iOS 15 con "Mail Privacy Protection" los desactiva sistemáticamente). No tomar la ausencia de apertura como señal de "no abierto". ### 6.3 Tracking de clics en PDF Los enlaces `Descargar PDF` no apuntan directos al boletín sino a un redirector propio: ``` https://buscador.example/redirect?hit={{ hit.id_hit }}&target={{ url_pdf_codificado }} ``` El redirector registra el clic y redirige (HTTP 302). Permite medir qué hits realmente generan interés. --- ## 7. Integración con el motor El backend, antes de renderizar la plantilla, prepara este contexto: ```python contexto = { 'asunto': f'Multisearch {categoria} · {fecha}', 'categoria': 'Hacienda y gestión tributaria', 'fecha': '11-05-2026', 'fecha_humanizada': 'lunes 11 de mayo de 2026', 'boletines_procesados': 38, 'boletines_sin_publi': 0, 'boletines_no_cubiertos': ['Almería', 'Córdoba', ...], 'hits_bases': lista_hits_bases_ordenada, 'hits_apertura': lista_hits_apertura_ordenada, 'hits_oep': lista_hits_oep_ordenada, 'hits_subfases': lista_hits_subfases_ordenada, 'hits_falsos_positivos': lista_hits_fp_ordenada, 'procesos_actualizados': lista_procesos_con_cambio_hoy, 'url_feedback': 'https://buscador.example/feedback', 'url_preferencias': '...', 'url_dashboard': '...', 'url_baja': '...', } from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader('templates/')) tpl = env.get_template('02_email_template.html') html_final = tpl.render(**contexto) ``` El `fragmento_html` de cada hit ya debe llegar pre-procesado con el resaltado: ```python def resaltar_patron(texto: str, patron: str) -> str: """Envuelve cada ocurrencia del patrón con .""" import re from html import escape texto_escapado = escape(texto) # patrón puede tener sintaxis tipo "monitor/a deportiv/o/a" regex = compilar_patron_a_regex(patron) return regex.sub(lambda m: f'{m.group(0)}', texto_escapado) ``` --- ## 8. Métricas del email Tracking estructurado de cada resumen enviado: | Métrica | Cómo | Dashboard | |---|---|---| | Tasa de apertura | Píxel + correlación con clics | semanal | | Tasa de clic en PDF | Redirector | por categoría | | Tasa de feedback | Botones ok/no clicados / hits enviados | por categoría, por bloque | | % FP correctos | Feedback negativo / FP que el sistema marcó como ALTA | crítico | | % de bases que terminan abriéndose | Clics en "Descargar PDF" del bloque BASES | confianza en clasificador | Si la tasa de apertura cae < 30 % durante 2 semanas, alertar para revisar relevancia o frecuencia. Si la tasa de feedback en BASES es 0 % durante 1 mes, el usuario probablemente no está usando ese bloque — investigar.