¡Hola! Soy Joseph, estudiante de Informática en Waterloo, y recientemente he terminado unas prácticas de Ingeniería de Software en Slash, de septiembre a diciembre de 2025. En resumen, me lo he pasado genial y creo que deberías apuntarte. Gracias por leerme.

Vale, esta vez va en serio. Anteriormente había realizado prácticas en diversas empresas, desde pequeñas startups relacionadas con la música hasta grandes empresas tecnológicas, pero Slash me ofrecía una combinación única de intensidad y responsabilidad que no había encontrado en ningún otro sitio. El equipo era inteligente y muy unido, y aprendí en profundidad sobre muchos temas complejos, desde el mantenimiento de Kubernetes hasta el rendimiento de las bases de datos. Y lo que es más importante, tuve que resolver algunos retos realmente interesantes, ¡por lo que decidí escribir un blog sobre mis experiencias!

Para resumir rápidamente lo que hace Slash, comenzaron como una plataforma financiera para personas como emprendedores y revendedores de zapatillas, pero ahora son una empresa fintech bancaria general que ofrece tarjetas corporativas, transferencias internacionales, cuentas criptográficas y más. Ahora bien, yo no sabía mucho de esto cuando descubrí Slash por primera vez. Al fin y al cabo, no soy un fanático de las zapatillas deportivas ni estaba muy metido en el mundo de la tecnología financiera, así que pasé mucho tiempo pensando si esta oportunidad merecía la pena o no. Sin embargo, como se puede deducir de esta entrada del blog, mi decisión de unirme acabó siendo una de las mejores que pude haber tomado en los inicios de mi carrera profesional.

Las principales razones por las que finalmente elegí Slash en lugar de otras empresas fueron las siguientes:

  • Era una empresa de serie B. – Nunca había trabajado en una startup de este tamaño y quería experimentar ese entorno.
  • Tenían unos ingresos anualizados de 150 millones, lo cual es increíblemente alto para una empresa de serie B. No solo estaban sobreviviendo o aguantando, sino que estaban creciendo activamente. Como ventaja adicional, eso significaba que probablemente tenían un buen DX y un rigor técnico del que carecen muchas pequeñas empresas emergentes.
  • Estaban creciendo activamente. – Multiplicaron por seis sus ingresos en un año y recientemente recaudaron su serie B. Esta empresa se encontraba en su época dorada de expansión, y yo quería trabajar en una empresa que estuviera experimentando ese impulso.
  • Es un equipo joven y lleno de energía. – Por mis entrevistas, pude darme cuenta de que la gente aquí era tranquila y agradable para conversar. Me entusiasmaba la idea de trabajar con compañeros con los que pudiera relacionarme.
  • Es una empresa pequeña y ágil. ¿He mencionado que estos 150 millones de dólares de ingresos anuales recurrentes (ARR) fueron conseguidos por un equipo de unos 12 ingenieros? Eso supone 12,5 millones de dólares de ARR. por ingeniero, lo cual es una locura. Está claro que la gente de Slash es de lo mejorcito, y yo tendría tanto una gran participación en la propiedad como una excelente tutoría.
  • La remuneración y las ventajas para pequeñas empresas son de primer nivel para las prácticas... 🤫

TLDRPensé que el entorno, la gente con talento y la energía emprendedora me proporcionarían un lugar increíble para crecer. Supuse que tendría la oportunidad de encargarme de un proyecto de principio a fin, desde la creación de la infraestructura hasta la planificación de la escalabilidad, pasando por el diseño del sistema y mucho más, ¡así que aproveché la oportunidad!

Proyecto: Notificaciones V2

Hay algo especial que aprendí inmediatamente sobre Slash: asignan proyectos importantes a los becarios y confían en que pueden llevarlos a cabo. En mi segunda semana, me encargaron renovar nuestras notificaciones de rechazo de transacciones con tarjeta. No parece tan difícil, ¿verdad? Sin embargo, el sistema de notificaciones existente tenía algunos problemas:

  • Estrecha integración con nuestro sistema interno de eventos, con lógica empresarial integrada en el núcleo del servicio.
  • Métricas de correo electrónico deficientes y peor capacidad de entrega de correo electrónico debido al proveedor que utilizamos.
  • Experiencia de desarrollo difícil, lo que llevó a los ingenieros a eludir por completo el sistema y llamar directamente a las API de los proveedores.

Y así llegó la insidiosa tarea: para crear mejores correos electrónicos de rechazo, necesitábamos algo más que un parche. Necesitábamos un sistema de notificaciones completamente rediseñado, y yo me convertí en el responsable directo de diseño, desarrollo, implementación y escalado de todo el proyecto. Era algo muy intimidante... pero también una oportunidad increíble para diseñar algo que acabaría gestionando millones de notificaciones. Estaba emocionado por ponerme manos a la obra.

Tras estudiar el sistema de notificación anterior y debatir con los ingenieros, descubrí algunos principios que este nuevo sistema debería cumplir:

  1. Un diseño independiente del evento – El servicio debe poder funcionar en cualquier lugar, ya sea en scripts únicos o en controladores de eventos.
  2. Un diseño independiente del proveedor – La oportunidad de eliminar e introducir nuevos proveedores fácilmente con un mínimo esfuerzo sería invaluable.
  3. Un diseño pensado para los desarrolladores – Se acabó el tener que pasar por alto la frustración. La experiencia de desarrollo debe ser lo más intuitiva y fácil posible.
  4. Observable y escalable - Las notificaciones deben tener un seguimiento completo del ciclo de vida y, con el tiempo, deberíamos poder gestionar 3 millones de correos electrónicos al mes.

Y de ahí surgió el diseño basado en controladores de Notifications V2. El sistema se centra en los controladores de notificaciones, que exponen funciones comunes de controlador, como enviar notificaciones o procesar webhooks, para proveedores externos específicos de canales (por ejemplo, Resend para correos electrónicos, Twilio para SMS).

El núcleo del sistema es el NotificaciónIntento, un objeto transitorio genérico utilizado para transportar todos los datos de una notificación, como los argumentos del proveedor, el canal y la carga útil. Cada controlador define la forma de sus propias intenciones, y estos objetos son de tipo fuerte y, por lo tanto, admiten comprobaciones de tipos en tiempo estático e introspección. Esto significa que los desarrolladores no necesitan conocer todas las complejidades de cada proveedor, sino que pueden aprovechar la función de autocompletado del IDE y confiar en que la comprobación de tipos les guiará.

This example briefly illustrates how to use the service to send a variety of email notifications for an outgoing payment. All these create functions create notification intents, and they are strongly typed to suit the provider (in this case, Resend)
const sendOutgoingPaymentNotification = async (context: {
 transactionId: string;
 amount: number;
 recipientEmail: string;
 alertEmail: string;
}) => {
 const { transactionId, amount, recipientEmail, alertEmail } = context;
 const { originalSenderId } = getOriginalSenderForTransaction(transactionId);


 await notificationsV2Service.sendNotifications([
   // We can create a one-off notification easily...
   ResendNotification.create({
     email: {
       recipient: alertEmail,
       content: {
         subject: "INTERNAL ALERT: An employee just sent an outgoing payment",
         body: `An employee just sent an outgoing payment of $${amount}.`,
       },
     },
   }),
   // Or use a React Email template for pretty emails..
   ResendNotification.create({
     email: {
       recipient: recipientEmail,
       content: YouJustReceivedPaymentTemplate.withProps({ amount }),
     },
   }),
 ]);


 // Or send to specific users, respecting their preferences + registered emails
 await notificationsV2Service.sendNotificationsToUsers({
   users: [originalSenderId],
   preference: NotificationPreferences.PaymentConfirmation,
   createIntents: (user) => [
     ResendNotification.create({
       email: {
         recipient: user.registeredEmail,
         content: PaymentConfirmationTemplate.withProps({ amount }),
       },
     }),
   ],
 });
};

Para garantizar que la lógica empresarial, como la comprobación de las preferencias del usuario, se implementara de forma poco acoplada, una vez que un desarrollador llama a una función de envío, las intenciones pasan a través de un NotificaciónMiddleware primero el canal. Los middlewares de notificación son funciones de orden superior que tienen un paso de preprocesamiento para permitir búsquedas compartidas en la base de datos y una función de proceso que nos permite permitir, transformar o descartar intenciones de notificación. Con este diseño, ahora podemos definir diferentes funciones de envío que utilizan un sistema subyacente para definir y compartir la lógica de validación. Por ejemplo:

  • Podemos definir una función básica de «enviar notificación» que tenga flujos de intenciones a través de un Destinatario bloqueado middleware, que descarta las intenciones si la información del destinatario (correo electrónico, número de teléfono) se encuentra en una lista de bloqueo personalizada.
  • Podemos definir una función «enviar al usuario» más compleja que tenga flujos de intenciones a través del mismo Destinatario bloqueado middleware, así como un Respetar las preferencias del usuario middleware, que descarta las intenciones si el usuario ha optado por no recibir notificaciones en ese canal.

Como ventaja adicional, a los futuros desarrolladores les resultará fácil añadir, eliminar o modificar nuevas funciones de envío sin tener que rastrear un laberinto de funciones.


Un ejemplo de algunos flujos que decidimos utilizar. Observe cómo sendNotificationToUsers y sendNotification utilizan ambos el middleware heurístico, pero la función de envío al usuario ejecuta validadores más específicos para el usuario.


Después del paso del middleware, el servicio crea Notificación Objetos del ciclo de vida antes de iniciar un flujo de trabajo temporal. Temporal es excelente porque nos ofrece reintentos integrados para los fallos del flujo de trabajo, un estado duradero y garantías de coordinación. Todos estos aspectos son importantes para garantizar la entrega de las notificaciones. El flujo de trabajo pone las notificaciones en cola, limita los envíos para respetar los límites de velocidad del proveedor y así no enviamos spam a los usuarios, y luego pasa los lotes a los controladores del proveedor. A continuación, los controladores se encargan de enviar la notificación al proveedor y el servicio actualiza el ciclo de vida en consecuencia.


Una vez que cualquier proveedor externo nos envía una respuesta webhook, los controladores la convierten en un tipo de datos estandarizado y emiten acciones tipificadas. Las acciones nos proporcionan una forma estructurada y segura de responder a los eventos cuando recibimos una notificación, al tiempo que mantenemos el servicio lo más imparcial posible. Por ejemplo,

  • Tenemos una acción para actualizar los destinatarios. De esta manera, los administradores pueden actualizar los destinatarios como «bloqueados permanentemente» o no, dependiendo de lo que el proveedor y el canal determinen como «bloqueado».
  • Tenemos una acción para reintentar una notificación; por ejemplo, si un correo electrónico rebota temporalmente, queremos reintentar el envío de forma exponencial. Si el controlador determina que la respuesta del proveedor de correo electrónico fue «rebotada», puede activar esta acción.

ProcessWebhook returns the same body structure, allowing specific webhook logic to be handled by the actual provider itself.
const statusMap: Record<string, string> = {
 bounced: "failed",
 // more statuses here...
};


const ResendHandler = createHandler({
 // This is a simple example of something we could do
 processWebhook: (webhookBody: ResendWebhook) => {
   const notificationId = extractIdFromWebhook(webhookBody);


   const newInternalStatus = statusMap[webhookBody.state];
  
   const actions = newInternalStatus === "failed"
     ? [{ action: "retryNotification", notificationId }]
     : [];


   return {
     status: newInternalStatus,
     actions,
   };
 },
});

¡Uf! Eso es casi todo. Por el camino, me encontré con otros retos de ingeniería interesantes, como

  • Admite reintentos exponenciales: programamos futuros trabajos de reintento dentro de Temporal en función del número de reintentos ya realizados.
  • Buena trazabilidad: por ejemplo, almacenamos identificadores de rastreo, así como notificaciones y sus reintentos, en un Conjunto de notificaciones entidad
  • Envío por lotes: la integración del envío individual y por lotes nos permite evitar exceder los límites de velocidad del proveedor y ofrece a los desarrolladores control sobre cómo desean que se envíen las notificaciones.

Dejaré que los nuevos ingenieros descubran los detalles en el código, una vez que se incorporen 😉

Proyecto: Renovación

El Proyecto Facelift fue el rediseño integral de la experiencia web de Slash realizado por nuestro equipo (véase el blog de ingeniería). Aquí, por Albert Tian, el padre del lifting facial.). No voy a entrar en detalles sobre qué era Facelift (de nuevo, consultad el magnífico blog de ingeniería), pero básicamente consistía en introducir una nueva biblioteca de componentes + Tailwind en todo nuestro enorme frontend. Ahora, para prepararse para el lanzamiento, el equipo de ingeniería decidió divertirse un poco y organizar una hackweek específica. Sin embargo, no fue la típica hackweek en la oficina: nos subimos a un par de coches, condujimos tres horas hasta una cabaña en Bass Lake, junto a Yosemite, y pasamos la semana construyendo, refactorizando y divirtiéndonos de verdad juntos.

Durante esta semana, pasé por completo del trabajo de backend a ayudar a renovar las transacciones y los flujos de disputas. Se trataba principalmente de trabajo de frontend, lo que supuso un cambio de ritmo refrescante con respecto a mi trabajo anterior, y también significó que pude trabajar en gran parte de nuestra infraestructura de diseño de frontend, como por ejemplo:

  • Primitivas básicasPor ejemplo, cómo generamos nuestros paneles laterales, ventanas modales y ventanas emergentes.
  • Componentes reutilizables y combinables: por ejemplo, Búsqueda seleccionable
  • Administrador de tablas: nuestro componente personalizado para crear tablas con funciones sencillas de clasificación, filtrado, paginación y búsqueda.
  • Validación robusta de subformularios: utilizando Arktype, formularios Tanstack y nuestro propio sistema de generación de modelos.

El cambio de imagen no se centró tanto en la compleja lógica del producto como en ser testigos de la rapidez con la que nuestro equipo puede avanzar cuando se centra y se alinea en un proyecto colectivo. Ver cómo se transformaba todo el sitio web a lo largo de la semana, además de crear vínculos con mis compañeros en dicha cabaña, fue sin duda uno de los momentos más destacados de mis prácticas. Sin embargo, el trabajo de cambio de imagen no terminó esa semana: durante las dos semanas siguientes, nos aseguramos de que el lanzamiento se desarrollara sin problemas trabajando con el equipo de asistencia para resolver cualquier detalle relacionado con el producto.

Proyecto: Plantillas

Al final de cada período de trabajo, mi universidad suele enviar un pequeño formulario con una pregunta sencilla: «¿En qué medida te han sido útiles los conocimientos adquiridos en tus estudios para tu trabajo?». Pues bien, durante mi estancia en Slash, ¡pude aplicar directamente algunos conceptos que había aprendido en clase! Verás, queríamos que nuestros correos electrónicos transaccionales coincidieran con nuestro nuevo diseño Facelift, por lo que tenía sentido que nuestro sistema de notificaciones admitiera plantillas personalizadas. Para asegurarnos de utilizar los mismos colores y espaciados que en nuestra interfaz, acabamos creando estas plantillas en código utilizando React Email, una biblioteca de plantillas de correo electrónico que nos permite utilizar JSX para estas plantillas. Esto era perfecto para nosotros, ya que nuestro equipo está muy familiarizado con React JSX, por lo que podíamos garantizar la coherencia de la marca utilizando el mismo sistema de diseño Tailwind CSS v4 tanto en la web como en el correo electrónico.

Sin embargo, había un inconveniente: una de las principales ventajas de TailwindCSS 4 es su capacidad para utilizar archivos CSS personalizados en lugar del tradicional objeto de configuración JS, que nuestro equipo aprovechó. Sin embargo, el componente Tailwind de React Email en realidad... requerido el objeto de configuración JS.

¡Esto significaba que tenía que crear un transpilador!

@import "tailwindcss";

@import "../custom-styles.css";

@custom-variant dark (&:where(.dark, .dark *):not(:where(.light, .light *)));

@theme {
  --color-*: initial;

  --spacing-base-scale-0: 0px;
  --spacing-base-scale-1: 1px;
  --spacing-base-scale-2: 2px;
  --spacing-base-scale-4: 4px;
  --spacing-base-scale-6: 6px;
  --spacing-base-scale-8: 8px;
  --spacing-base-scale-12: 12px;
  --spacing-base-scale-14: 14px;
  --spacing-base-scale-16: 16px;
  --spacing-base-scale-20: 20px;
  --spacing-base-scale-24: 24px;
  --spacing-base-scale-28: 28px;
  --spacing-base-scale-32: 32px;
  --spacing-base-scale-36: 36px;
  --spacing-base-scale-40: 40px;
  --spacing-base-scale-44: 44px;
  --spacing-base-scale-48: 48px;
  --spacing-base-scale-52: 52px;
  --spacing-base-scale-56: 56px;
  --spacing-base-scale-60: 60px;
  --spacing-base-scale-64: 64px;
  --spacing-base-scale-68: 68px;
  --spacing-base-scale-72: 72px;
  --spacing-base-scale-76: 76px;
  --spacing-base-scale-80: 80px;
  --spacing-base-scale-84: 84px;
  --spacing-base-scale-88: 88px;
  --spacing-base-scale-92: 92px;
  --spacing-base-scale-96: 96px;
  --spacing-base-scale-100: 100px;
  --spacing-base-scale-104: 104px;
// WARNING: This file is generated by a script. Do not edit it manually.
// Generated from: ./lib/tailwind.build.css
// To regenerate: yarn build:tailwind-config

import type { GenericAny } from '@slashfi/slash-base';

const plugin = require('tailwindcss/plugin');

export default {
	theme: {
		extend: {
			backgroundColor: {
				"surface-subtle": "#f9f8f7",
				"surface-neutral": "#ffffff",
				"neutral-subtle-default": "#fcfcfb",
				"neutral-subtle-hover": "#f9f8f7",
				"neutral-subtle-active": "#f9f8f7",
				"neutral-subtle-disabled": "#fcfcfb",
				"neutral-normal-default": "#f9f8f7",
				"neutral-prominent-default": "#f4f3f1",
				"neutral-muted-default": "#e9e7e3",
				"neutral-bold-default": "#958f82",
				"neutral-bold-hover": "#6d685f",
				"neutral-bold-active": "#6d685f",
				"neutral-bold-disabled": "#bdb7aa",
				"neutral-muted-hover": "#e0ddd6",
				"neutral-muted-active": "#d9d6ce",
				"neutral-muted-disabled": "#efeeeb",
				"neutral-prominent-hover": "#efeeeb",
				"neutral-prominent-active": "#efeeeb",
				"neutral-prominent-disabled": "#fcfcfb",
				"neutral-normal-hover": "#f4f3f1",
				"neutral-normal-active": "#f4f3f1",
				"neutral-normal-disabled": "#fcfcfb",
				"neutral-bolder-default": "#6d685f",
				"neutral-boldest-default": "#32312c",
				"neutral-boldest-hover": "#32312c",
				"neutral-boldest-active": "#32312c",
				"neutral-boldest-disabled": "#bdb7aa",
				"neutral-bolder-hover": "#32312c",
				"neutral-bolder-active": "#32312c",
				"neutral-bolder-disabled": "#bdb7aa",
				"interactive-neutral-subtle-default": "#4b3a1c",
				"interactive-neutral-normal-default": "#4b3a1c",
				"interactive-neutral-prominent-default": "#4b3a1c",
				"interactive-neutral-prominent-hover": "#4b3a1c",
				"interactive-neutral-prominent-active": "#4b3a1c",
				"interactive-neutral-prominent-disabled": "#4b3a1c",
				"interactive-neutral-normal-hover": "#4b3a1c",
				"interactive-neutral-normal-active": "#4b3a1c",
				"interactive-neutral-normal-disabled": "#4b3a1c",
				"interactive-neutral-subtle-hover": "#4b3a1c",
				"interactive-neutral-subtle-active": "#4b3a1c",

Para describirlo con mayor precisión, durante la fase de compilación, añadí un script para crear un árbol sintáctico abstracto (AST) a partir de los archivos CSS del sistema de diseño. Este AST proporcionaba una representación estructurada de las reglas CSS y me permitía analizar fácilmente el archivo para:

  • Filtrar clases CSS no compatiblesPor ejemplo, los estilos de desplazamiento y las utilidades del puntero no funcionan en la mayoría de los clientes de correo electrónico.
  • Convertir a un sistema de color compatible: Hemos utilizado OKLCH para definir nuestros colores, pero los clientes de correo electrónico no pueden reproducirlos, por lo que se han convertido a HEX.
  • Crear el objeto de configuración TS.: Esto se compila en JS y, a continuación, se puede importar fácilmente a cualquier parte del código base.

Este paso de transpilación fue una forma rápida de implementar una única fuente de verdad para nuestro sistema de diseño. Si se modificaba un color, este se mantendría coherente en todos nuestros medios (web o correo electrónico), ya que se transpilaba inmediatamente una vez reconstruido. Además, otros clientes, como nuestra aplicación móvil, podían utilizar este objeto transpilado según fuera necesario. Con el objeto de configuración generado, pude crear un conjunto de componentes React comunes para nuestros correos electrónicos que reflejaban nuestros componentes web, como Tipografía y Botón componentes, con accesorios casi idénticos. Todo esto contribuyó a mejorar la experiencia de los desarrolladores, haciendo que la creación de plantillas fuera menos tediosa.

Como nota final, también hice que las plantillas de notificación aprovecharan nuestro marco de registro interno, una estructura de datos personalizada que creamos (véase esta entrada del blog). En resumen, los registros nos proporcionan una forma segura de definir plantillas, serializarlas y deserializarlas desde la base de datos, y mantener la lógica empresarial definida de forma conjunta. Por ejemplo, el plantilla-de-notificación-de-autorización-de-tarjeta.tsx Se encuentra justo al lado del código del controlador de eventos de autorización de tarjetas, lo que resulta muy útil para ver rápidamente qué eventos tienen notificaciones. Fue fantástico poder utilizar registros en este proyecto, ya que realmente ilustraba el compromiso del equipo de escribir código bueno y reutilizable.


¿Qué me llevé?

Vine pensando que sería una experiencia única, y estoy feliz de haber decidido unirme: Slash me enseñó más sobre propiedad, diseño de plataformas y velocidad de ingeniería que cualquier otro puesto que haya tenido antes. Algunas de las lecciones clave que aprendí fueron:

  • La confianza no tiene precio. – Slash no tiene reuniones diarias. Slash no tiene sprints semanales ni backlog grooming. Tenemos una reunión a la semana, y es solo para mostrar al equipo completo lo que hemos creado. Esto funciona porque el equipo en general tiene una comunicación sólida y unas habilidades de responsabilidad impresionantes. Eso no quiere decir que no haya tutoría (tengo reuniones individuales semanales para desbloquearme), sino que Slash confía en que darás lo mejor de ti y fomenta un entorno en el que puedes concentrarte al máximo.
  • El equipo es increíble. – En mis otras cooperativas, mis colegas eran profesionales, pero aquí, mis compañeros de trabajo eran mi gente. Está claro que Slash tiene una cultura estupenda, con gente divertida, solidaria y con la que es fácil trabajar: no hubo ni una sola persona con la que no disfrutara trabajando o que me intimidara, y te tratan como si fueras un empleado a tiempo completo. Además, puedes ir a jugar al baloncesto con ellos los fines de semana 😃.
  • Como becario, aquí crecerás exponencialmente en comparación con cualquier otro lugar. – En cualquier otra empresa, al becario no se le habría asignado como proyecto todo el sistema central de notificaciones. Los problemas difíciles, que fomentan el crecimiento, habrían sido resueltos o estarían siendo resueltos por algún ingeniero sénior. Lo que hace único a Slash es la combinación de equipos reducidos, problemas reales críticos para el negocio y una cultura que confía incluso a los becarios trabajos importantes.

Aquí hay otros recuerdos divertidos de estas prácticas:

  • Nos comimos unos 9 kg de filete en una semana... ¡gracias, hack week!
  • Aplasté a mi compañero de prácticas en una competición de billar y, acto seguido, un empleado a tiempo completo me dio una lección de humildad.
  • ¡Por fin conseguí levantar una placa!
  • Soy uno de los principales responsables de la «glotonería generalizada en la oficina» (y, posiblemente relacionado con ello, he descubierto que me encanta Shake Shack).

Estos cuatro meses han pasado más rápido de lo que esperaba, y estoy muy agradecido por la experiencia que he tenido en Slash. Pronto me graduaré y, independientemente de lo que me depare la vida dentro de un mes, un año o una década, sé que las prácticas en Slash me han proporcionado los conocimientos y la confianza necesarios para tener éxito allá donde vaya.

Si te interesa seguir charlando, ¡No dudes en ponerte en contacto conmigo a través de LinkedIn! Estaré encantado de responder a cualquier pregunta que pueda tener.

Read more from us