diff --git a/docs/images/meetup/202511/01.jpg b/docs/images/meetup/202511/01.jpg new file mode 100644 index 0000000..c0841b4 Binary files /dev/null and b/docs/images/meetup/202511/01.jpg differ diff --git a/docs/images/meetup/202511/02.jpg b/docs/images/meetup/202511/02.jpg new file mode 100644 index 0000000..51d0f8d Binary files /dev/null and b/docs/images/meetup/202511/02.jpg differ diff --git a/docs/images/meetup/202511/03.jpg b/docs/images/meetup/202511/03.jpg new file mode 100644 index 0000000..d7f82e0 Binary files /dev/null and b/docs/images/meetup/202511/03.jpg differ diff --git a/docs/images/meetup/202511/04.jpg b/docs/images/meetup/202511/04.jpg new file mode 100644 index 0000000..6c6238e Binary files /dev/null and b/docs/images/meetup/202511/04.jpg differ diff --git a/docs/images/ponentes/alex-callejas.png b/docs/images/ponentes/alex-callejas.png new file mode 100644 index 0000000..3c447f6 Binary files /dev/null and b/docs/images/ponentes/alex-callejas.png differ diff --git a/docs/images/ponentes/default.jpg b/docs/images/ponentes/default.jpg new file mode 100644 index 0000000..f8126a5 Binary files /dev/null and b/docs/images/ponentes/default.jpg differ diff --git a/docs/meetups/2025/202511-noviembre.md b/docs/meetups/2025/202511-noviembre.md new file mode 100644 index 0000000..19ca975 --- /dev/null +++ b/docs/meetups/2025/202511-noviembre.md @@ -0,0 +1,128 @@ +--- +title: "SysAdmin Horror Story" +--- + +# Meetup #PythonCDMX :fontawesome-brands-python: - Noviembre 2025 + +
+

SysAdmin Horror Story

+ +

Historias de TECHrror

+ +
+ +## Información del evento + +
+
+

Fecha

+

Martes 11 De Noviembre, 2025

+
+
+

Hora

+

18:30 - 21:00

+
+
+

Lugar

+

Clara

+
+
+

¡GRATIS!

+

Entrada completamente gratuita

+
+
+

RSVP

+

¡Regístrate aquí!

+
+
+ + +## Ponente + +
+
+ +
+
+

Alex Callejas

+

Content Architect

+

Content Architect en Red Hat con más de 25 años de experiencia como SysAdmin, fuerte experiencia en el endurecimiento de la infraestructura y la automatización. Entusiasta del código abierto. Apoya a la comunidad compartiendo su conocimiento en diferentes eventos de acceso público y universidades. Autor del libro "Fedora Linux System Administrator" publicado en PacktPub.

+
+
+ +## Descripción de la Charla + +
+ + +

En está ocasión nuestro ponente, Alex Callejas, nos dará una adaptación libre de historias de terror clásicas (Edgar Allan Poe, Neil Gaiman) sobre las historias más terroríficas vividas en la administración de sistemas.

+ + +
+ + +## Así vivimos el Meetup PythonCDMX de Noviembre + + +El martes llegó con ese olor a polvo antiguo que sólo exhalan los días condenados a repetirse. Y como cada mes, descendimos a nuestro pequeño santuario de penumbra, convocados por un hambre insaciable: _no de carne, sino de cerebros rebosantes de conocimiento prohibido_. + +Allí, entre sombras que parecían respirar, nos aguardaban pesaddillas dispuestas a arrancarle el sueño **incluso al más valiente**; anécdotas tan inquietantes que los recién llegados se aferraban a sus asientos _como niños escuchando cuentos del más allá_. + +Y al centro de todo, **Alex Callejas**, con una interpretación tan densa y lúgubre que ni Edgar Allan Poe ni Neil Gaiman se habrían atrevido a permanecer sin cubrirse con una sábana, **deseando jamás haber escuchado lo que allí se narró.** + +![Foto 1 - Meetup Noviembre](/images/meetup/202511/01.jpg) + +Cada historia que escapaba de su voz era como un golpe de aldaba en la ventana de un mausoleo: un portal que se abría para que los murciélagos entraran en bandadas, para que las arañas descorrieran su paciencia y tejieran redes nuevas, para que desde los rincones más fríos se escucharan las risas huecas de brujas celebrando las travesuras que cada revelación desataba. + +![Foto 2 - Meetup Noviembre](/images/meetup/202511/02.jpg) + +Pero aquella noche no reservaba su oscuridad sólo para el espectáculo principal. Hubo almas intrépidas que, como zombies emergiendo de tumbas recién profanadas, subieron al escenario arrastrando historias de ultratumba. Una a una, _sus voces llenaron el recinto_, provocando escalofríos que se deslizaban por la nuca, _haciendo que más de uno buscara con los ojos la salida..._ o quizá el heroísmo inesperado que pudiera salvarlos de lo que escuchaban. + +![Foto 3 - Meetup Noviembre](/images/meetup/202511/03.jpg) + +Y aún así, entre sobresaltos y respiraciones contenidas, hubo una belleza extraña: **la de sabernos reunidos en torno al miedo**, como si cada trauma compartido fuese un faro en medio de un cementerio interminable. + +A todos los que acudieron a este _meetup de horror_, gracias por entregarse a la oscuridad con nosotros; por caminar entre historias que huelen a espanto, a memoria y a verdad. + +De igual forma, **un agradecimiento a Clara**, por ser la sede para este evento que reune gente con hambre de más conocimiento, **así como a JetBrains** por las licencias que brinda a nuestra comunidad. + +_Quizá esas sombras que escucharon vuelvan a ustedes...o quizá, sin darse cuenta, ya las estaban viviendo desde antes._ + +![Foto Grupal - Meetup Noviembre](/images/meetup/202511/04.jpg) + + +## ¡Mira la charla completa! + +
+
+
+ +
+
+
+ + +## Únete a #PythonCDMX + +💌 Si alguna duda, sugerencia o chispa de colaboración surge en tu mente, escríbenos a **[info@pythoncdmx.org](mailto:info@pythoncdmx.org)**. +¡Nos hará inmensamente felices saber de ti! + +--- + +💛 **Gracias por tejer comunidad con nosotros.** +Porque *cada historia compartida*, *cada línea de código* y *cada risa*, nos acerca un poco más a ese universo que construimos **juntos**. + +--- + +--8<-- "components/community-links.md" + +--- + +--8<-- "components/quick-navigation.md" + +--- \ No newline at end of file diff --git a/docs/meetups/2025/index.md b/docs/meetups/2025/index.md index 7cbd548..8e85fa6 100644 --- a/docs/meetups/2025/index.md +++ b/docs/meetups/2025/index.md @@ -12,6 +12,7 @@ | :fontawesome-solid-calendar: **Fecha** | :fontawesome-solid-microphone: **Charla** | :fontawesome-solid-user: **Ponente** | :fontawesome-solid-map-marker-alt: **Lugar** | :fontawesome-solid-link: **Detalles** | |:---|:---|:---|:---|:---| +| **11 Noviembre 2025** | Historias de TECHrror - SysAdmin Horror Story | Alex Callejas | Clara | [Ver detalles](202511-noviembre) | | **12 Agosto 2025** | Cómo preparar una ambiente de desarrollo con Python desde zero | Juan Guillermo Gómez | Jardin Chapultepec | [Ver detalles](202508-agosto) | | **08 Julio 2025** | Cómo preparar una ambiente de desarrollo con Python desde zero | David Sol | Clara | [Ver detalles](202507-julio) | | **10 Junio 2025** | Usando Python y software libre para crear nuevas herramientas: Traductor de voz español-inglés | Carlos Cesar Caballero | Wizeline México | [Ver detalles](202506-junio) | diff --git a/requirements-dev.txt b/requirements-dev.txt index 3bb3d24..fdca0c3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,3 +10,5 @@ pytest>=7.0.0 # Dependencias para validación de links requests>=2.25.0 + +tqdm diff --git a/scripts/generate_all_meetups.py b/scripts/generate_all_meetups.py deleted file mode 100755 index 337e479..0000000 --- a/scripts/generate_all_meetups.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python3 -import glob -import json -import os - -from jinja2 import Environment, FileSystemLoader - - -def create_meetup_file(json_file): - """Crea un archivo markdown para un meetup desde JSON usando templates Jinja2.""" - - # Configurar Jinja2 - env = Environment(loader=FileSystemLoader("templates")) - - # Cargar datos - with open(json_file, "r", encoding="utf-8") as f: - data = json.load(f) - - # Crear directorio - year = data["id"][:4] - output_dir = f"../docs/meetups/{year}" - os.makedirs(output_dir, exist_ok=True) - - # Generar archivo - filename = f"{data['id']}.md" - output_path = os.path.join(output_dir, filename) - - # Determinar qué template usar basado en el número de charlas - if len(data.get("talks", [])) > 1: - template = env.get_template("meetup-template-multiple-talks.md.j2") - else: - template = env.get_template("meetup-template.md.j2") - - # Preparar datos para el template - template_data = { - "event_title": data["event_title"], - "event_subtitle": data["event_subtitle"], - "event_date": data["event_date"], - "event_time": data["event_time"], - "event_location": data["event_location"], - "event_rsvp_link": data.get("event_rsvp_link", "#"), - "event_banner_image": data.get( - "event_banner_image", "/assets/images/default-banner.jpg" - ), - "event_month_year": f"{data['event_date'].split()[1]} {data['event_date'].split()[-1]}", - "tags": data["tags"], - "last_update": data.get("last_update", "Generado automáticamente"), - } - - # Agregar datos específicos según el tipo de template - if len(data.get("talks", [])) > 1: - template_data["talks"] = data["talks"] - else: - # Para template de una sola charla - if data.get("talks"): - talk = data["talks"][0] - template_data["talk"] = talk - template_data["speaker"] = talk.get("speaker", {}) - else: - # Fallback si no hay charlas definidas - template_data["talk"] = { - "title": data["event_title"], - "description": data["event_subtitle"], - "conclusion": "Más detalles próximamente...", - "tech_stack": [], - } - template_data["speaker"] = { - "name": "Ponente por confirmar", - "title": "TBD", - "bio": "Información del ponente próximamente...", - "photo": "/assets/images/default-speaker.jpg", - } - - # Agregar video si existe - if data.get("video"): - template_data["video"] = data["video"] - - # Renderizar template - content = template.render(**template_data) - - # Escribir archivo - with open(output_path, "w", encoding="utf-8") as f: - f.write(content) - - print(f"✅ {output_path}") - return output_path - - -def main(): - """Genera todos los meetups faltantes.""" - print("🚀 Generando meetups faltantes...") - - # Buscar archivos JSON en la carpeta metadata_json - json_files = ( - glob.glob("metadata_json/meetup-2023*.json") - + glob.glob("metadata_json/meetup-2024*.json") - + glob.glob("metadata_json/meetup-2025*.json") - ) - json_files.sort() - - print(f"📁 Procesando {len(json_files)} archivos...") - - count = 0 - for json_file in json_files: - try: - create_meetup_file(json_file) - count += 1 - except Exception as e: - print(f"❌ Error en {json_file}: {e}") - - print(f"\n🎉 Se generaron {count} archivos markdown!") - - -if __name__ == "__main__": - main() diff --git a/scripts/generate_meetups.py b/scripts/generate_meetups.py index 0503555..9b7dbbe 100644 --- a/scripts/generate_meetups.py +++ b/scripts/generate_meetups.py @@ -1,152 +1,188 @@ -#!/usr/bin/env python3 import json -import os -import sys +import locale -from jinja2 import Environment, FileSystemLoader - - -def create_meetup_file(json_file): - """Crea un archivo markdown para un meetup desde JSON usando templates Jinja2.""" - - # Configurar Jinja2 - env = Environment(loader=FileSystemLoader("templates")) - - # Cargar datos - with open(json_file, "r", encoding="utf-8") as f: - data = json.load(f) - - # Crear directorio - year = data["id"][:4] - output_dir = f"../docs/meetups/{year}" - os.makedirs(output_dir, exist_ok=True) +from pathlib import Path +from datetime import datetime +from tqdm import tqdm +from dataclasses import asdict, dataclass - # Generar archivo - filename = f"{data['id']}.md" - output_path = os.path.join(output_dir, filename) +from argparse import ArgumentParser +from typing import Any, Callable, TypeVar +from jinja2 import Environment, FileSystemLoader - # Determinar qué template usar basado en el número de charlas - if len(data.get("talks", [])) > 1: - template = env.get_template("meetup-template-multiple-talks.md.j2") - else: - template = env.get_template("meetup-template.md.j2") - - # Preparar datos para el template - template_data = { - "event_title": data["event_title"], - "event_subtitle": data["event_subtitle"], - "event_date": data["event_date"], - "event_time": data["event_time"], - "event_location": data["event_location"], - "event_rsvp_link": data.get("event_rsvp_link", "#"), - "event_banner_image": data.get( - "event_banner_image", "/assets/images/default-banner.jpg" - ), - "event_month_year": data.get("event_month_year", ""), - "tags": data["tags"], - "last_update": data.get("last_update", "Generado automáticamente"), - } - - # Agregar datos específicos según el tipo de template - if len(data.get("talks", [])) > 1: - template_data["talks"] = data["talks"] - else: - # Para template de una sola charla - if data.get("talks"): - talk = data["talks"][0] - template_data["talk"] = talk - template_data["speaker"] = talk.get("speaker", {}) - else: - # Fallback si no hay charlas definidas - template_data["talk"] = { - "title": data["event_title"], - "description": data["event_subtitle"], - "conclusion": "Más detalles próximamente...", - "tech_stack": [], - } - template_data["speaker"] = { - "name": "Ponente por confirmar", - "title": "TBD", - "bio": "Información del ponente próximamente...", - "photo": "/assets/images/default-speaker.jpg", - } - - # Agregar video si existe - if data.get("video"): - template_data["video"] = data["video"] - - # Renderizar template - content = template.render(**template_data) - - # Escribir archivo - with open(output_path, "w", encoding="utf-8") as f: - f.write(content) - - print(f"✅ {output_path}") - return output_path - - -def main(): - """Genera meetups específicos pasados como parámetros.""" - - if len(sys.argv) < 2: - print("🚀 Generador de Meetups PythonCDMX") - print("\nUso:") - print(" python generate_meetups.py [archivo2.json] ...") - print(" python generate_meetups.py --all") - print("\nEjemplos:") - print(" python generate_meetups.py metadata_json/meetup-202407.json") - print( - " python generate_meetups.py metadata_json/meetup-202407.json metadata_json/meetup-202408.json" +locale.setlocale(locale.LC_ALL, 'es_MX.UTF-8') + +@dataclass +class Speaker: + name: str + job_title: str + company: str | None + bio: str + photo: str | None + +@dataclass +class TalkDescription: + header: str | None + content: list[str] + footnotes: list[str] + +@dataclass +class Talk: + title: str + subtitle: str | None + description: TalkDescription + speaker: Speaker + +@dataclass +class MeetupLocation: + venue: str + address: str + +@dataclass +class MeetupMetadata: + id: str + start_date: datetime + end_date: datetime + title: str + subtitle: str | None + location: MeetupLocation + talks: list[Talk] + tags: list[str] + video_url: str | None + storytelling: list[str] # Cada linea es un parrafo en el render final (Soporta Markdown) + +def parse_speaker(speaker: dict[str, Any]) -> Speaker: + return Speaker( + name=speaker['name'], + job_title=speaker['title'], + company=speaker.get('company'), + bio=speaker['bio'], + photo=speaker.get('photo', '/images/ponentes/default.jpg') + ) + +def parse_description(description: dict[str, Any]) -> TalkDescription: + print(f'{description=}') + return TalkDescription( + header=description.get('header'), + content=description['content'], + footnotes=description.get('footnotes', []) + ) + +def parse_talk(talk: dict[str, Any]) -> Talk: + return Talk( + title=talk['title'], + subtitle=talk.get('subtitle', ''), + description=parse_description(talk['description']), + speaker=parse_speaker(talk['speaker']), + ) + +def parse_location(location: dict[str, Any]) -> MeetupLocation: + return MeetupLocation( + venue=location['venue'], + address=location.get('address', ''), + ) + +def metadata_parser(metadata: dict[str, Any]) -> MeetupMetadata: + print(f'New Parser {metadata}') + id = metadata['id'] + start_date = datetime.strptime(metadata['start_date'], '%Y-%m-%d %H:%M') + end_date = datetime.strptime(metadata['end_date'], '%Y-%m-%d %H:%M') + location = parse_location(metadata['location']) + tags = metadata.get('tags', []) + # Si talks no está definido, el parser fallará con KeyException + talks = [parse_talk(talk) for talk in metadata['talks']] + print(f'Procesando {len(talks)} charlas') + first_talk = talks[0] + storytelling = metadata.get('storytelling', []) + video_url = metadata.get('video_url') + # Si solo hay una charla, podemos heredar los campos de su metadata, + # si hay más de una charla, es mandatorio definir los campos en raiz. + if len(talks) == 1: + title = metadata.get('title', first_talk.title) + subtitle = metadata.get('subtitle', first_talk.subtitle) + + return MeetupMetadata( + id=id, + start_date=start_date, + end_date=end_date, + title=title, + subtitle=subtitle, + location=location, + talks=talks, + tags=tags, + storytelling=storytelling, + video_url=video_url ) - print(" python generate_meetups.py --all") - print("\nArchivos JSON disponibles:") - - # Listar archivos JSON disponibles - import glob - - json_files = glob.glob("metadata_json/meetup-*.json") - json_files.sort() - - for json_file in json_files: - print(f" - {json_file}") - - sys.exit(1) - - # Verificar si se quiere generar todos - if sys.argv[1] == "--all": - print("🚀 Generando todos los meetups...") - import glob - - json_files = glob.glob("metadata_json/meetup-*.json") - json_files.sort() else: - json_files = sys.argv[1:] - - print(f"📁 Procesando {len(json_files)} archivos...") - - count = 0 - errors = 0 - - for json_file in json_files: - try: - # Verificar que el archivo existe - if not os.path.exists(json_file): - print(f"❌ Archivo no encontrado: {json_file}") - errors += 1 - continue - - create_meetup_file(json_file) - count += 1 - except Exception as e: - print(f"❌ Error en {json_file}: {e}") - errors += 1 - - print(f"\n🎉 Resultado:") - print(f" ✅ Exitosos: {count}") - if errors > 0: - print(f" ❌ Errores: {errors}") + title = metadata['title'] + subtitle = metadata.get('subtitle', '') + + return MeetupMetadata( + id=id, + start_date=start_date, + end_date=end_date, + title=title, + subtitle=subtitle, + location=location, + talks=talks, + tags=tags, + storytelling=storytelling, + video_url=video_url + ) +T = TypeVar('T') +def parse_json(filename: str, parser: Callable[[dict[str, Any]], T]) -> T: + with open(filename, 'r') as file: + file_content = json.load(file) + return parser(file_content) + +def confirm_action(message: str) -> bool: + confirmation = input(message + ' [Y/n]') + if confirmation not in ('y', 'n', 'Y', 'N'): + print('El valor ingresado no es válido, intente nuevamente.') + return confirm_action(message) + return confirmation in ('y', 'Y') + +def parse_path(filename: str) -> Path | None: + path_obj = Path(filename) + if path_obj.exists(): + return path_obj + + confirmation = confirm_action('La ruta especificada no existe, deseas crearla?') + if confirmation: + path_obj.mkdir() + return path_obj + return None + + +def main(metadata: MeetupMetadata, templates_dir: Path, template_name: str, output_dir: Path): + print(metadata) + jinja_env = Environment(loader=FileSystemLoader(templates_dir)) + template = jinja_env.get_template(template_name) + output = template.render(**asdict(metadata)) + output_filename = f'{output_dir}/{metadata.start_date:%Y}/{metadata.id}.md' + with open(output_filename, 'w') as rendered_markdown: + rendered_markdown.writelines(output) if __name__ == "__main__": - main() + parser = ArgumentParser(description='Este script transforma un archivo de metadata (JSON) a un archivo Markdown listo para ser desplegado en nuestro sitio.') + parser.add_argument('--metadata', '-m', type=str, required=True, nargs='+', help='Archivos de metadata con la configuración del evento.') + parser.add_argument('--output-dir', '-o', default='docs/meetups', help='Directorio de salida donde se guardará el Markdown generado. Si el valor se omite, estará bajo el directorio meetups') + parser.add_argument('--templates-dir', '-t', default='templates', help='Directorio de plantillas donde se lee la plantilla base usada para renderizar las páginas.') + + args = parser.parse_args() + print(args.metadata) + + templates_dir = parse_path(args.templates_dir) + if not templates_dir: + raise FileNotFoundError('El directorio de plantillas no existe!') + output_dir = parse_path(args.output_dir) + if not output_dir: + raise FileNotFoundError('El directorio de salida no existe!') + + template = 'meetup.md.j2' + + for metadata_json in tqdm(args.metadata): + parsed_metadata = parse_json(metadata_json, metadata_parser) + main(parsed_metadata, templates_dir, template, output_dir) + diff --git a/scripts/metadata_json/meetup-202511.json b/scripts/metadata_json/meetup-202511.json new file mode 100644 index 0000000..d619af9 --- /dev/null +++ b/scripts/metadata_json/meetup-202511.json @@ -0,0 +1,48 @@ +{ + "id": "202511-noviembre", + "start_date": "2025-11-11 18:30", + "end_date": "2025-11-11 21:00", + "location": { + "venue": "Clara" + }, + "rsvp_link": "https://luma.com/ena2z4t0", + "event_banner_image": "/images/meetup/202511-pythoncdmx-noviembre.jpg", + "video_url": "https://www.youtube.com/embed/FSWqeOCTJFg", + "talks": [ + { + "title": "SysAdmin Horror Story", + "subtitle": "Historias de TECHrror", + "description": { + "content": [ + "En está ocasión nuestro ponente, Alex Callejas, nos dará una adaptación libre de historias de terror clásicas (Edgar Allan Poe, Neil Gaiman) sobre las historias más terroríficas vividas en la administración de sistemas." + ] + }, + "speaker": { + "name": "Alex Callejas", + "title": "Content Architect", + "bio": "Content Architect en Red Hat con más de 25 años de experiencia como SysAdmin, fuerte experiencia en el endurecimiento de la infraestructura y la automatización. Entusiasta del código abierto. Apoya a la comunidad compartiendo su conocimiento en diferentes eventos de acceso público y universidades. Autor del libro \"Fedora Linux System Administrator\" publicado en PacktPub.", + "photo": "/images/ponentes/alex-callejas.png" + } + } + ], + "storytelling": [ + "El martes llegó con ese olor a polvo antiguo que sólo exhalan los días condenados a repetirse. Y como cada mes, descendimos a nuestro pequeño santuario de penumbra, convocados por un hambre insaciable: _no de carne, sino de cerebros rebosantes de conocimiento prohibido_.", + "Allí, entre sombras que parecían respirar, nos aguardaban pesaddillas dispuestas a arrancarle el sueño **incluso al más valiente**; anécdotas tan inquietantes que los recién llegados se aferraban a sus asientos _como niños escuchando cuentos del más allá_.", + "Y al centro de todo, **Alex Callejas**, con una interpretación tan densa y lúgubre que ni Edgar Allan Poe ni Neil Gaiman se habrían atrevido a permanecer sin cubrirse con una sábana, **deseando jamás haber escuchado lo que allí se narró.**", + "![Foto 1 - Meetup Noviembre](/images/meetup/202511/01.jpg)", + "Cada historia que escapaba de su voz era como un golpe de aldaba en la ventana de un mausoleo: un portal que se abría para que los murciélagos entraran en bandadas, para que las arañas descorrieran su paciencia y tejieran redes nuevas, para que desde los rincones más fríos se escucharan las risas huecas de brujas celebrando las travesuras que cada revelación desataba.", + "![Foto 2 - Meetup Noviembre](/images/meetup/202511/02.jpg)", + "Pero aquella noche no reservaba su oscuridad sólo para el espectáculo principal. Hubo almas intrépidas que, como zombies emergiendo de tumbas recién profanadas, subieron al escenario arrastrando historias de ultratumba. Una a una, _sus voces llenaron el recinto_, provocando escalofríos que se deslizaban por la nuca, _haciendo que más de uno buscara con los ojos la salida..._ o quizá el heroísmo inesperado que pudiera salvarlos de lo que escuchaban.", + "![Foto 3 - Meetup Noviembre](/images/meetup/202511/03.jpg)", + "Y aún así, entre sobresaltos y respiraciones contenidas, hubo una belleza extraña: **la de sabernos reunidos en torno al miedo**, como si cada trauma compartido fuese un faro en medio de un cementerio interminable.", + "A todos los que acudieron a este _meetup de horror_, gracias por entregarse a la oscuridad con nosotros; por caminar entre historias que huelen a espanto, a memoria y a verdad.", + "De igual forma, **un agradecimiento a Clara**, por ser la sede para este evento que reune gente con hambre de más conocimiento, **así como a JetBrains** por las licencias que brinda a nuestra comunidad.", + "_Quizá esas sombras que escucharon vuelvan a ustedes...o quizá, sin darse cuenta, ya las estaban viviendo desde antes._", + "![Foto Grupal - Meetup Noviembre](/images/meetup/202511/04.jpg)" + ], + "tags": [ + "DevOps", + "SysAdmin", + "Terror" + ] +} diff --git a/templates/meetup.md.j2 b/templates/meetup.md.j2 new file mode 100644 index 0000000..16becd8 --- /dev/null +++ b/templates/meetup.md.j2 @@ -0,0 +1,135 @@ +--- +title: "{{ title }}" +--- + +# Meetup #PythonCDMX :fontawesome-brands-python: - {{ start_date.strftime('%B %Y') | capitalize }} + +
+

{{ title }}

+ {% if subtitle %} +

{{ subtitle }}

+ {% endif %} +
+ +## Información del evento + +
+
+

Fecha

+

{{ start_date.strftime('%A %-d de %B, %Y') | title }}

+
+
+

Hora

+

{{ start_date.strftime('%H:%M') + ' - ' + end_date.strftime('%H:%M') }}

+
+
+

Lugar

+

{{ location.venue }}

+
+
+

¡GRATIS!

+

Entrada completamente gratuita

+
+ +
+ +{% if talks|length > 1 %} +## Charlas! +{% for talk in talks %} + +### Ponente + +
+
+ +
+
+

{{ talk.speaker.name }}

+

{{ talk.speaker.job_title }}

+

{{ talk.speaker.bio }}

+
+
+ +### Descripción de la Charla + +
+

{{ talk.title }}

+

{{ talk.description }}

+
+ +{% endfor %} +{% else %} +## Ponente + +
+
+ +
+
+

{{ talks[0].speaker.name }}

+

{{ talks[0].speaker.job_title }}

+

{{ talks[0].speaker.bio }}

+
+
+ +## Descripción de la Charla + +
+ {% if talks[0].description.header %} +

{{ talks[0].description.header }}

+ {% endif %} + {% for p in talks[0].description.content %} +

{{ p }}

+ {% endfor %} + {% if talks[0].description.footnotes %} + {% for note in talks[0].description.footnotes %} + + {% endfor %} + {% endif %} +
+{% endif %} + +## Así vivimos el Meetup PythonCDMX de {{ start_date.strftime('%B') | capitalize }} + +{% for paragraph in storytelling %} +{{ paragraph }} +{% endfor %} + +## ¡Mira la charla completa! + +
+
+
+ +
+
+
+ + +## Únete a #PythonCDMX + +💌 Si alguna duda, sugerencia o chispa de colaboración surge en tu mente, escríbenos a **[info@pythoncdmx.org](mailto:info@pythoncdmx.org)**. +¡Nos hará inmensamente felices saber de ti! + +--- + +💛 **Gracias por tejer comunidad con nosotros.** +Porque *cada historia compartida*, *cada línea de código* y *cada risa*, nos acerca un poco más a ese universo que construimos **juntos**. + +--- + +--8<-- "components/community-links.md" + +--- + +--8<-- "components/quick-navigation.md" + +---