Este script de Python permite generar las firmas para el correo siguiendo un archivo de configuración correspondiente a la entidad para la que es la firma (que incluye cosas como fuentes y colores) y otro archivo con los datos de las personas para las que generar la firma.
Aquí también se guardarán todas las firmas HTML que vaya haciendo para los correos electrónicos, principalmente para:
- Delegación de Estudiantes de la Facultad de Ciencias (DEFC)
- Delegación General de Estudiantes (DGE)
- Asociación de Estudiantes de Matemáticas y Estadística de la UGR (AMAT)
- Coordinadora de Representantes de Estudiantes de Universidades Públicas (CREUP)
- XXVI Encuentro Nacional de Estudiantes de Matemáticas (ENEM)
Las firmas partieron de una base que supongo que será de @jesusjmma y, actualmente, utilizan iconos de Tabler Icons
- ✅ Generación de firmas HTML a partir de plantillas Jinja2
- ✅ Soporte para múltiples configuraciones de organizaciones
- ✅ Validación de datos CSV y configuración JSON
- ✅ CLI con argumentos para automatización
- ✅ Previsualización de firmas generadas
- ✅ Compatibilidad mejorada con clientes de correo (uso de tablas HTML)
- ✅ Macros reutilizables para componentes comunes
- ✅ Accesibilidad mejorada (ARIA, textos alternativos, semántica)
A continuación hay unas capturas de cómo se deberían de ver las firmas.
Cuando los iconos cargan, la firma se debería de ver así:
Y cuando no cargan, así:
Cuando los iconos cargan, la firma se debería de ver así:
Y cuando no cargan, así:
Cuando los iconos cargan, la firma se debería de ver así:
Y cuando no cargan, así:
Cuando los iconos cargan, la firma se debería de ver así:
Y cuando no cargan, así:
Cuando los iconos cargan, la firma se debería de ver así:
Y cuando no cargan, así:
Simplemente hay que irse a la configuración de la cuenta, marcar la casilla de utilizar un archivo como firma y seleccionar el archivo de firma descargado:
Primero hay que abrir en el navegador el archivo HTML de la firma, seleccionarlo todo con Ctrl + A y copiarlo con Ctrl + C.
En otra ventana con Gmail, hay que irse a los ajustes, ver todos los ajustes y, en la pestaña «General», al apartado de «Firma». Se crea una firma nueva y, en el campo de texto, se pega la firma con Ctrl + V.
No olvidar tampoco cambiar los «Valores predeterminados de firma» a la firma recién creada para que aparezca y guardar los cambios con el botón del final de la página.
El archivo de firma es un HTML y tiene la siguiente estructura:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title></title>
</head>
<body>
<div>...</div>
</body>
</html>Pues para webmail recomiendo solo copiar la parte de
<div>...</div>Y pegarla en el apartado de configuración de Webmail, en «Identidades», dándole al botón que hay más a la derecha que parece < >. Este botón es para editar la firma como HTML. Se abrirá una ventana donde hay que pegar el código copiado, sustituyendo todo lo que hubiera antes.
¿Por qué la recomendación de copiar solo esa parte de la firma HTML?
Simplemente porque pegando todo el contenido del archivo se pone un espacio en blanco al principio de la firma y es molesto eliminarlo manualmente.
Para generar firmas, primero hay que clonar o descargar este repositorio y tener instalado uv.
# Modo interactivo (por defecto)
uv run main.py
# Especificar archivo de configuración
uv run main.py -c signatures.json
# Seleccionar perfil por ID (sin interacción)
uv run main.py -p ENEM
# Especificar archivo CSV de firmas
uv run main.py -l mis_firmas.csv
# Generar índice de previsualización
uv run main.py --preview
# Modo verbose (más información)
uv run main.py -v
# Modo silencioso (solo errores)
uv run main.py -q
# Combinar opciones
uv run main.py -c config.json -p CREUP -l firmas.csv --preview| Opción | Descripción |
|---|---|
-c, --config |
Archivo JSON de configuración (por defecto: signatures.json) |
-p, --profile |
Seleccionar automáticamente el perfil con el ID especificado |
-l, --list |
Archivo CSV con la lista de firmas |
--preview |
Generar un index.html con todas las firmas para previsualización |
-v, --verbose |
Mostrar información detallada |
-q, --quiet |
Modo silencioso (solo errores) |
Lo primero que debes hacer es asegurarte de que tienes definida la configuración del tipo de firma en el archivo signatures.json. El archivo debe de seguir la siguiente estructura:
[
{
"id": "EJEMPLO",
"template": "original",
"output_path": "EJEMPLO",
"main_font": "Montserrat",
"name_font": "Open Sans",
"name_image": {
"image": "https://example.com/logo.png",
"description": "Logo de Mi Organización"
},
"color": "#3EB1C8",
"organization": "Mi Organización",
"organization_extra": "Entidad Superior (opcional)",
"phone": "123 456 789",
"phone_country_code": "+34",
"internal_phone": "12345",
"opt_mail": "info@ejemplo.es",
"max_width": 315,
"links": [
{
"url": "https://ejemplo.es",
"image": "https://example.com/web-icon.png",
"alt": "🌐",
"description": "Sitio web de ejemplo"
}
],
"sponsor_text": "Con la colaboración de:",
"sponsors": [
{
"url": "https://sponsor.com",
"image": "https://sponsor.com/logo.png",
"alt": "Sponsor",
"description": "Patrocinador principal",
"width": 100,
"height": 50
}
],
"supporter_text": "Con el apoyo de:",
"supporters": [
{
"url": "https://supporter.com",
"image": "https://supporter.com/logo.png",
"alt": "Supporter",
"description": "Colaborador",
"height": 55
}
],
"footer_address": "Calle Ejemplo, 123, 12345 Ciudad",
"footer_text": "Texto legal opcional..."
}
]| Campo | Obligatorio | Descripción |
|---|---|---|
id |
✅ | Identificador de la configuración |
template |
✅ | Plantilla a usar: original o wide-logo |
output_path |
❌ | Carpeta de salida (por defecto usa id) |
main_font |
✅ | Fuente principal del texto |
name_font |
✅ | Fuente del nombre de la persona |
name_image |
✅ | Objeto {image, url?, alt?, description?} (ver abajo) |
color |
✅ | Color hexadecimal (ej: #3EB1C8) |
organization |
✅ | Nombre de la organización |
organization_extra |
❌ | Organización superior/adicional |
phone |
❌ | Número de teléfono (sin código de país) |
phone_country_code |
❌ | Código de país (ej: +34) |
internal_phone |
❌ | Extensión interna |
opt_mail |
❌ | Email alternativo (se muestra si no hay teléfono) |
max_width |
❌ | Ancho máximo en píxeles |
links |
❌ | Lista de enlaces sociales |
sponsor_text |
❌ | Texto sobre los patrocinadores |
sponsors |
❌ | Lista de patrocinadores |
supporter_text |
❌ | Texto sobre los colaboradores |
supporters |
❌ | Lista de colaboradores |
footer_address |
❌ | Dirección postal |
footer_text |
❌ | Texto legal del footer |
Debe ser un objeto con las propiedades de la imagen:
"name_image": {
"image": "https://example.com/logo.png",
"url": "https://example.com",
"alt": "Logo",
"description": "Ir al sitio web"
}| Campo | Obligatorio | Descripción |
|---|---|---|
image |
✅ | URL de la imagen |
url |
❌ | URL del enlace (si hay, la imagen es clicable) |
alt |
❌ | Texto alternativo (por defecto: 👤) |
description |
❌ | Descripción (se usa en title y aria-label) |
| Campo | Obligatorio | Descripción |
|---|---|---|
url |
✅ | URL del enlace |
image |
✅ | URL del icono |
alt |
❌ | Texto alternativo (emoji recomendado) |
description |
❌ | Descripción (se usa en title y aria-label) |
| Campo | Obligatorio | Descripción |
|---|---|---|
url |
❌ | URL del enlace (si no hay, no es clicable) |
image |
✅ | URL del logo |
alt |
❌ | Texto alternativo |
description |
❌ | Descripción (se usa en title y aria-label) |
width |
❌ | Ancho en píxeles |
height |
❌ | Alto en píxeles |
| Plantilla | Descripción |
|---|---|
original |
Diseño clásico con imagen circular y barra horizontal |
wide-logo |
Logo ancho arriba con barra vertical al lado del contenido |
Una vez esté la configuración definida hay que crear la lista de firmas a generar, que es un archivo CSV (por defecto {id en minúsculas}_list.csv, ej: enem_list.csv).
name,position,mail
Estas columnas, si tienen valor, sobrescriben la configuración general:
output,phone,phone_country_code,internal_phone,opt_mail,organization_extra,main_font,name_font,max_width,name_image,color,organization
Nota: Usa
Noneen una celda para eliminar un valor opcional de la configuración general para esa firma específica.
Name,Position,Mail,Output,Phone,internal_phone
Ana García,Presidenta,presidencia@ejemplo.es,Firma Presidenta,123 456 789,12345
Juan López,Secretario,secretaria@ejemplo.es,Firma Secretario,,None
Las firmas incluyen varias características para mejorar la accesibilidad:
| Característica | Descripción |
|---|---|
role="presentation" |
Las tablas usadas para maquetación se marcan como decorativas para que los lectores de pantalla las ignoren |
aria-hidden="true" |
Los elementos decorativos (barras de color, iconos dentro de enlaces) se ocultan a lectores de pantalla |
aria-label |
Los enlaces con iconos usan description como etiqueta accesible descriptiva |
alt en imágenes |
Todas las imágenes tienen texto alternativo (emoji por defecto: 👤, 🌐, etc.) |
title en enlaces |
Los enlaces muestran tooltip con la descripción al pasar el ratón |
role="img" en avatar |
La imagen del avatar/logo se marca explícitamente como imagen semántica |
Tip: Para una accesibilidad óptima, configura
alt(emoji o texto corto) ydescription(texto descriptivo completo) en los enlaces e imágenes.
Las firmas ahora usan tablas HTML en lugar de flexbox para mejorar la compatibilidad con clientes de correo. Las pruebas no han sido muy exhaustivas, pero la firma en algunos sitios va bien 🟢, regulinchi (se ve bien en general pero puede fallar en algún detalle) 🟡 y mal 🔴. Esta es la lista:
🟢 Webmail
🟢 Thunderbird
🟢 Outlook web
🟢 Outlook móvil
🟢 Gmail web
🟢 Gmail móvil
🟡 Thunderbird móvil
🔴 Canary Mail
mail-signatures/
├── main.py # Script principal
├── schemas.py # Validación de datos
├── signatures.json # Configuración de organizaciones
├── *_list.csv # Listas de firmas por organización
├── templates/
│ ├── _base.html.j2 # Plantilla base con estructura común
│ ├── _macros.html.j2 # Macros reutilizables
│ ├── original.html.j2 # Plantilla clásica
│ └── wide-logo.html.j2# Plantilla con logo ancho
└── {OUTPUT}/ # Firmas generadas por organización
- Crea un archivo
templates/mi-plantilla.html.j2 - Extiende la plantilla base:
{% extends "templates/_base.html.j2" %} - Sobrescribe los bloques necesarios (
header,content,links,sponsors, etc.) - Añade el nombre
mi-plantillaen el campotemplatede la configuración
Nota: También puedes crear una plantilla desde cero importando los macros directamente con
{% from "templates/_macros.html.j2" import ... %}, pero extender_base.html.j2es más sencillo.
| Macro | Descripción |
|---|---|
social_link(link, size) |
Renderiza un enlace social individual |
social_links_bar(links, size, max_width) |
Barra de enlaces sociales |
sponsor_image(item, max_width) |
Imagen de sponsor/supporter |
sponsors_section(text, items, color, max_width, with_bar) |
Sección completa de sponsors |
footer(footer_address, footer_text, color, max_width) |
Footer con dirección y texto legal |
contact_info(phone, phone_country_code, internal_phone, mail, opt_mail) |
Información de contacto |
name_image_block(name_image, organization, size, rounded, wide) |
Bloque de imagen/avatar con nombre |







