Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 63 additions & 24 deletions content/la-meetup-2025/la-meetup/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions content/speakers/santiago-perez/index.yaml
Original file line number Diff line number Diff line change
@@ -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: ""
Binary file added public/images/speakers/santiago-perez/picture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 3 additions & 9 deletions src/app/lib/keystatic/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export interface AgendaItem {
description: string;
startTime: string;
endTime: string;
presenter: string | null;
presenters: Array<string | null>;
location: {
name: string;
};
Expand Down Expand Up @@ -87,17 +87,11 @@ export async function transformArray<T, R>(

// Transform functions for each content type
export async function transformAgendaItem(item: AgendaItem) {
const presenter = item.presenter ? await readRelatedContent<Speaker>("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,
};
}

Expand Down
93 changes: 63 additions & 30 deletions src/components/Meetups/2025/Agenda/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -81,13 +81,15 @@ export default function Agenda({ lastUpdate, agenda }: AgendaProps) {

<div className="relative space-y-3">
{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 (
<div
Expand Down Expand Up @@ -149,29 +151,44 @@ export default function Agenda({ lastUpdate, agenda }: AgendaProps) {
</div>

{/* Speaker info - only show when there's a presenter or it's vacant */}
{(isVacante || presenter) && (
{(isVacante || hasPresenters) && (
<div className="flex flex-shrink-0 items-center gap-2">
<div className="text-right">
{isVacante ? (
<>
{isVacante ? (
<>
<div className="text-right">
<p className="text-sm font-medium text-orange-400">Vacante</p>
<p className="text-xs text-gray-400">Próximamente</p>
</>
) : presenter ? (
<>
<p className="text-sm font-medium text-white">{`${presenter.firstname} ${presenter.lastname ?? ""}`}</p>
<p className="text-xs text-gray-400">Speaker</p>
</>
) : null}
</div>
{isVacante ? (
<div className="flex h-10 w-10 items-center justify-center rounded-full border-2 border-orange-400/50 bg-orange-400/10">
<span className="text-sm font-bold text-orange-400">?</span>
</div>
) : presenter ? (
<Avatar className="h-10 w-10">
<AvatarImage src={presenter.picture?.url ?? "/carpincho.png"} />
</Avatar>
</div>
<div className="flex h-10 w-10 items-center justify-center rounded-full border-2 border-orange-400/50 bg-orange-400/10">
<span className="text-sm font-bold text-orange-400">?</span>
</div>
</>
) : hasPresenters ? (
<>
<div className="text-right">
<p className="text-sm font-medium text-white">
{presenters?.map((p, i) => (
<span key={i}>
{`${p.firstname} ${p.lastname ?? ""}`}
{i < presenters.length - 1 ? ", " : ""}
</span>
))}
</p>
<p className="text-xs text-gray-400">
{presenters && presenters.length > 1 ? "Speakers" : "Speaker"}
</p>
</div>
<div className="flex -space-x-2">
{presenters?.map((presenter, i) => (
<Avatar key={i} className="h-12 w-12 bg-zinc-800 p-[3px]">
<AvatarImage
src={presenter.picture?.url ?? "/carpincho.png"}
className="rounded-full bg-[#000214]/50 object-cover"
/>
</Avatar>
))}
</div>
</>
) : null}
</div>
)}
Expand Down Expand Up @@ -264,7 +281,7 @@ export default function Agenda({ lastUpdate, agenda }: AgendaProps) {
</div>

{/* Speaker info - Mobile - only show when there's a presenter or it's vacant */}
{(isVacante || presenter) && (
{(isVacante || hasPresenters) && (
<div className="mb-2 ml-12 flex items-center gap-2">
{isVacante ? (
<>
Expand All @@ -276,14 +293,30 @@ export default function Agenda({ lastUpdate, agenda }: AgendaProps) {
<p className="text-xs text-gray-400">Próximamente</p>
</div>
</>
) : presenter ? (
) : hasPresenters ? (
<>
<Avatar className="h-8 w-8">
<AvatarImage src={presenter.picture?.url ?? "/carpincho.png"} />
</Avatar>
<div className="flex -space-x-2">
{presenters?.map((presenter, i) => (
<Avatar key={i} className="h-10 w-10 bg-zinc-800 p-[3px]">
<AvatarImage
src={presenter.picture?.url ?? "/carpincho.png"}
className="rounded-full bg-[#000214]/50 object-cover"
/>
</Avatar>
))}
</div>
<div>
<p className="text-sm font-medium text-white">{`${presenter.firstname} ${presenter.lastname ?? ""}`}</p>
<p className="text-xs text-gray-400">Speaker</p>
<p className="text-[13px] font-medium text-white">
{presenters?.map((p, i) => (
<span key={i}>
{`${p.firstname} ${p.lastname ?? ""}`}
{i < presenters.length - 1 ? ", " : ""}
</span>
))}
</p>
<p className="text-xs text-gray-400">
{presenters && presenters.length > 1 ? "Speakers" : "Speaker"}
</p>
</div>
</>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Meetups/2025/Speakers/SpeakerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function SpeakerCard({
const imageSrc = picture?.url || "/placeholder.webp";

const cardContent = (
<article className="group flex h-full w-full min-w-[250px] max-w-[300px] cursor-pointer flex-col items-center rounded-md bg-white/10 p-[1px] transition-all lg:min-w-[230px] lg:max-w-[230px]">
<article className="group flex h-full w-full min-w-[250px] max-w-[300px] cursor-pointer flex-col items-center rounded-md bg-white/10 p-[1px] transition-all lg:min-w-[230px] lg:max-w-[240px]">
<div className="flex h-full w-full flex-col items-center rounded-md bg-[#000214]/50 px-6 py-6 transition">
{/* Circular profile image with border ring */}
<div className="relative mb-4 h-[120px] w-[120px] shrink-0 md:h-[150px] md:w-[150px]">
Expand Down
Loading