diff --git a/content/la-meetup-2025/la-meetup/index.yaml b/content/la-meetup-2025/la-meetup/index.yaml index bb6d5f52..f917b29b 100644 --- a/content/la-meetup-2025/la-meetup/index.yaml +++ b/content/la-meetup-2025/la-meetup/index.yaml @@ -43,7 +43,7 @@ agenda: Desde tecnología hasta desarrollo profesional, ¡vos elegís! icon: MessageCircle - startTime: 2025-11-01T10:00:00.000Z + startTime: 2025-11-01T11:00:00.000Z endTime: 2025-11-01T13:30:00.000Z location: name: SINERGIA @@ -64,25 +64,47 @@ agenda: location: name: SHOPPING - id: 4 - title: "Charla #1" - description: Primera charla de la jornada + title: "¿Internet funciona? ¿Cómo? ¿… Y es segura?" + description: Cómo funciona Internet, DNS y seguridad cibernética. extendedDescription: >- - Actualizaremos quién presentará en este bloque el mismo día de La Meetup + **Menú degustación en 40 minutos:** De entrada degustaremos una de las + especialidades de los Chefs **¿Cómo funciona Internet?**, un plato típico + pero en ocasiones poco apreciado. + + + Luego como plato fuerte tendremos al **Sistema de Nombres de Dominio** y de + los **registros regionales**, cocinados a fuego lento desde la década de 1980. + Todo con una pizca de historias locales. + + + Y para finalizar como postre algunas consideraciones sobre **seguridad** con + salsa de ciberataques… si es que las hay. icon: Mic startTime: 2025-11-01T15:00:00.000Z - endTime: 2025-11-01T15:40:00.000Z - presenter: "" + endTime: 2025-11-01T15:45:00.000Z + presenters: + - carlos-martinez + - nicolas-antoniello location: name: SINERGIA - id: 5 - title: "Charla #2" - description: Segunda charla de la jornada + title: "Debugging Organizacional: Cuando el problema no es el código" + description: Detectando y resolviendo bugs en la comunicación y colaboración de equipos. extendedDescription: >- - Actualizaremos quién presentará en este bloque el mismo día de La Meetup + En el mundo del desarrollo de software estamos acostumbrados a buscar **bugs + en el código**, pero **¿qué pasa con los errores que no están en los sistemas, + sino en las dinámicas humanas?** + + + En esta charla proponemos un cambio de enfoque: detectar y resolver los + **'bugs organizacionales'**, esas fallas invisibles que surgen en la + **comunicación, la gestión y la colaboración** dentro de los equipos. icon: Mic - startTime: 2025-11-01T15:40:00.000Z - endTime: 2025-11-01T16:20:00.000Z - presenter: "" + startTime: 2025-11-01T15:45:00.000Z + endTime: 2025-11-01T16:25:00.000Z + presenters: + - johana-rios + - gaston-cabana location: name: SINERGIA - id: 6 @@ -95,19 +117,35 @@ agenda: Momento ideal para reflexionar sobre las charlas, continuar haciendo networking o relajarte antes del cierre. icon: Coffee - startTime: 2025-11-01T16:20:00.000Z - endTime: 2025-11-01T16:45:00.000Z + startTime: 2025-11-01T16:25:00.000Z + endTime: 2025-11-01T17:00:00.000Z location: name: SINERGIA - id: 7 - title: "Charla #3" - description: Tercera charla de la jornada + title: "LockSkin: un ransomware para bioimplantes" + description: Explorando los riesgos de seguridad en bioimplantes NFC y RFID. extendedDescription: >- - Actualizaremos quién presentará en este bloque el mismo día de La Meetup + Los **bioimplantes (NFC+RFID)** abren un mundo nuevo de posibilidades, pero + también traen algunos riesgos inesperados. En esta charla presentamos + **LockSkin**, un ransomware educativo pensado específicamente para bioimplantes. + + + El ataque consiste en **escribir una nota de rescate y una clave secreta + directamente en el chip**, dejando al usuario con un artefacto bloqueado + dentro de su propio cuerpo. + + + **¿Vas a abrir esa puerta? ¿Vas a agarrar ese micrófono? ¿Vas a apoyarte en + esa mesa?** + + + Yo me lo pensaría dos veces... icon: Mic - startTime: 2025-11-01T16:45:00.000Z - endTime: 2025-11-01T17:50:00.000Z - presenter: "" + startTime: 2025-11-01T17:00:00.000Z + endTime: 2025-11-01T17:40:00.000Z + presenters: + - mauro-eldritch + - santiago-perez location: name: SINERGIA - id: 8 @@ -123,8 +161,8 @@ agenda: ¡Esperamos verte en la **próxima edición**! icon: PartyPopper - startTime: 2025-11-01T17:50:00.000Z - endTime: 2025-11-01T18:00:00.000Z + startTime: 2025-11-01T17:40:00.000Z + endTime: 2025-11-01T18:30:00.000Z location: name: SINERGIA - id: 9 @@ -143,8 +181,8 @@ agenda: **Ambiente informal** perfecto para cerrar **La Meetup III** compartiendo experiencias. icon: Beer - startTime: 2025-11-01T18:00:00.000Z - endTime: 2025-11-02T00:00:00.000Z + startTime: 2025-11-01T18:30:00.000Z + endTime: 2025-11-01T23:59:59.999Z location: name: MVC openSpacePrimaryButtonName: Más sobre el open space @@ -223,6 +261,7 @@ talks: Yo me lo pensaría dos veces... speakers: - mauro-eldritch + - santiago-perez - title: "Debugging Organizacional: Cuando el problema no es el código" description: >- En el mundo del desarrollo de software estamos acostumbrados a buscar **bugs diff --git a/content/speakers/santiago-perez/index.yaml b/content/speakers/santiago-perez/index.yaml new file mode 100644 index 00000000..2a29bb10 --- /dev/null +++ b/content/speakers/santiago-perez/index.yaml @@ -0,0 +1,9 @@ +slug: santiago-perez +firstname: Santiago +lastname: Pérez +picture: /images/speakers/santiago-perez/picture.png +jobTitle: Offensive Security Analyst +company: Birmingham Cyber Arms +linkedin: https://www.linkedin.com/in/santiago-p%C3%A9rez-b86a35206/ +github: "" +x: "" diff --git a/public/images/speakers/santiago-perez/picture.png b/public/images/speakers/santiago-perez/picture.png new file mode 100644 index 00000000..1d76934e Binary files /dev/null and b/public/images/speakers/santiago-perez/picture.png differ diff --git a/src/app/lib/keystatic/utils.ts b/src/app/lib/keystatic/utils.ts index a6c1b025..b632d107 100644 --- a/src/app/lib/keystatic/utils.ts +++ b/src/app/lib/keystatic/utils.ts @@ -57,7 +57,7 @@ export interface AgendaItem { description: string; startTime: string; endTime: string; - presenter: string | null; + presenters: Array; location: { name: string; }; @@ -87,17 +87,11 @@ export async function transformArray( // Transform functions for each content type export async function transformAgendaItem(item: AgendaItem) { - const presenter = item.presenter ? await readRelatedContent("speakers", item.presenter) : null; + const presenters = await transformArray(item.presenters, transformSpeaker); return { ...item, - presenter: presenter - ? { - firstname: presenter.firstname, - lastname: presenter.lastname, - picture: presenter.picture ? formatImageUrl(presenter.picture) : undefined, - } - : undefined, + presenters, }; } diff --git a/src/components/Meetups/2025/Agenda/index.tsx b/src/components/Meetups/2025/Agenda/index.tsx index 80325c3e..2026a9f4 100644 --- a/src/components/Meetups/2025/Agenda/index.tsx +++ b/src/components/Meetups/2025/Agenda/index.tsx @@ -20,13 +20,13 @@ type AgendaProps = { readonly icon?: string; readonly startTime: string; readonly endTime: string; - readonly presenter?: { + readonly presenters?: readonly { readonly firstname: string; readonly lastname?: string; readonly picture?: { readonly url: string; }; - }; + }[]; readonly location?: { readonly name: string; }; @@ -81,13 +81,15 @@ export default function Agenda({ lastUpdate, agenda }: AgendaProps) {
{agenda?.map((item, index) => { - const { id, startTime, endTime, presenter, title, location, description, extendedDescription, icon } = item; + const { id, startTime, endTime, presenters, title, location, description, extendedDescription, icon } = + item; const IconComponent = getIconComponent(icon); const isSelected = selectedItem === index; const isVacante = isVacanteSpeaker(title); const cleanTitle = getCleanTitle(title); const hasExtendedDescription = extendedDescription && extendedDescription.trim().length > 0 && extendedDescription !== description; + const hasPresenters = presenters && presenters.length > 0; return (
{/* Speaker info - only show when there's a presenter or it's vacant */} - {(isVacante || presenter) && ( + {(isVacante || hasPresenters) && (
-
- {isVacante ? ( - <> + {isVacante ? ( + <> +

Vacante

Próximamente

- - ) : presenter ? ( - <> -

{`${presenter.firstname} ${presenter.lastname ?? ""}`}

-

Speaker

- - ) : null} -
- {isVacante ? ( -
- ? -
- ) : presenter ? ( - - - +
+
+ ? +
+ + ) : hasPresenters ? ( + <> +
+

+ {presenters?.map((p, i) => ( + + {`${p.firstname} ${p.lastname ?? ""}`} + {i < presenters.length - 1 ? ", " : ""} + + ))} +

+

+ {presenters && presenters.length > 1 ? "Speakers" : "Speaker"} +

+
+
+ {presenters?.map((presenter, i) => ( + + + + ))} +
+ ) : null}
)} @@ -264,7 +281,7 @@ export default function Agenda({ lastUpdate, agenda }: AgendaProps) {
{/* Speaker info - Mobile - only show when there's a presenter or it's vacant */} - {(isVacante || presenter) && ( + {(isVacante || hasPresenters) && (
{isVacante ? ( <> @@ -276,14 +293,30 @@ export default function Agenda({ lastUpdate, agenda }: AgendaProps) {

Próximamente

- ) : presenter ? ( + ) : hasPresenters ? ( <> - - - +
+ {presenters?.map((presenter, i) => ( + + + + ))} +
-

{`${presenter.firstname} ${presenter.lastname ?? ""}`}

-

Speaker

+

+ {presenters?.map((p, i) => ( + + {`${p.firstname} ${p.lastname ?? ""}`} + {i < presenters.length - 1 ? ", " : ""} + + ))} +

+

+ {presenters && presenters.length > 1 ? "Speakers" : "Speaker"} +

) : null} diff --git a/src/components/Meetups/2025/Speakers/SpeakerCard.tsx b/src/components/Meetups/2025/Speakers/SpeakerCard.tsx index f9876b82..9fffe977 100644 --- a/src/components/Meetups/2025/Speakers/SpeakerCard.tsx +++ b/src/components/Meetups/2025/Speakers/SpeakerCard.tsx @@ -47,7 +47,7 @@ export default function SpeakerCard({ const imageSrc = picture?.url || "/placeholder.webp"; const cardContent = ( -
+
{/* Circular profile image with border ring */}
diff --git a/src/components/Meetups/2025/Speakers/index.tsx b/src/components/Meetups/2025/Speakers/index.tsx index 7e3b499b..5b36e5ea 100644 --- a/src/components/Meetups/2025/Speakers/index.tsx +++ b/src/components/Meetups/2025/Speakers/index.tsx @@ -17,28 +17,12 @@ type Talk = { speakers: Speaker[]; }; -type SpeakerWithTalk = Speaker & { - talkTitle: string; - talkDescription: string; - allSpeakers: Speaker[]; // All speakers for this talk -}; - type SpeakersProps = { talks: Talk[]; }; export default function Speakers({ talks = [] }: SpeakersProps) { - // Map each speaker to include their talk information and all speakers for that talk - const speakersWithTalks: SpeakerWithTalk[] = talks.flatMap((talk) => - talk.speakers.map((speaker) => ({ - ...speaker, - talkTitle: talk.title, - talkDescription: talk.description, - allSpeakers: talk.speakers, // Pass all speakers for this talk - })) - ); - - if (talks.length === 0 || speakersWithTalks.length === 0) { + if (talks.length === 0) { return null; } @@ -51,22 +35,27 @@ export default function Speakers({ talks = [] }: SpeakersProps) {

-
- {speakersWithTalks.map((speaker, index) => ( - + {/* 3x2 Grid: 3 columns (talks) x 2 rows (speakers per talk) */} +
+ {talks.map((talk, talkIndex) => ( +
+ {talk.speakers.map((speaker, speakerIndex) => ( + + ))} +
))}
diff --git a/src/keystatic/collections/la-meetup-2025.ts b/src/keystatic/collections/la-meetup-2025.ts index 31eb333b..55754137 100644 --- a/src/keystatic/collections/la-meetup-2025.ts +++ b/src/keystatic/collections/la-meetup-2025.ts @@ -42,10 +42,13 @@ export const laMeetup2025 = collection({ }), startTime: fields.datetime({ label: "Start Time", validation: { isRequired: true } }), endTime: fields.datetime({ label: "End Time", validation: { isRequired: true } }), - presenter: fields.relationship({ - label: "Presenter", - collection: "speakers", - }), + presenters: fields.array( + fields.relationship({ + label: "Presenter", + collection: "speakers", + }), + { label: "Presenters" } + ), location: fields.object({ name: fields.text({ label: "Location Name", validation: { isRequired: true } }), }),