Mesa247 · Backend / Full-stack de producto

Prueba técnica «El turno»

Seis decisiones. Dos horas. Nada que construir.

2 horastiempo previsto
6 casosno da tiempo de todos
2 entregablesrespuestas + un chat por caso
4 díasplazo, ampliable
Qué es esto

Seis decisiones que alguien tomó de verdad, y que salieron bien o mal en producción.

No te pedimos construir nada: ni levantar un proyecto, ni un CRUD, ni maquetar una pantalla. Te sentamos en el asiento del ingeniero de guardia durante un día y te damos seis cosas que decidir.

En este trabajo casi nunca se empieza de cero. Se entra a un sistema que llevan diez años tocando muchas manos, con dinero de por medio y restaurantes atendiendo en vivo, y se decide qué cambiar, qué no tocar y qué preguntar antes de tocar nada. Eso es lo que queremos ver.

Varios de estos casos tienen una respuesta que suena bien y es incorrecta. Un razonamiento honesto que llega a una conclusión distinta a la nuestra vale más que una respuesta correcta sin sustento.

No buscamos la respuesta de libro. Buscamos tu criterio.
Cómo se juega

Seis reglas

01

Dos horas

En serio, dos. Si te toma cinco, la prueba está mal diseñada y queremos saberlo: dínoslo en tu entrega.

02

No te va a dar tiempo de hacer los seis bien

Es a propósito. Prioriza, entrega lo que puedas y escribe al final qué dejaste fuera y por qué. Esa lista la leemos con la misma atención que el resto.

03

Casi no hay que escribir código

Solo si en algún caso te resulta más rápido explicarte con cinco líneas. No compilamos nada: leemos la idea.

04

Usa IA sin límite — y mándanos los chats

ChatGPT, Claude, Copilot, buscar, preguntarle a quien quieras. Sin restricciones. Es más: esas conversaciones son parte de la entrega, una por caso. Abajo está el porqué y el cómo.

05

Pregúntanos

Algunos casos están mal especificados a propósito. Si algo no se entiende o crees que un caso está mal planteado, escríbenos. Preguntar es parte de la prueba, no un punto en contra.

06

Después hay una conversación de 40 minutos, sin herramientas

Si avanzas. Te pediremos que expliques por qué tu respuesta es correcta, qué caso no cubre y qué harías distinto con mil veces más datos. Entrega solo lo que puedas sostener tú.

La entrega

Dos cosas

Sin repositorio, sin capturas.

Entregable 1

Tus respuestas

Un archivo. Para cada caso que respondas:

  1. Tu decisión, en una o dos frases. Primero la decisión, después el razonamiento.
  2. Por qué.
  3. Qué se rompe o qué asumiste: riesgos, efectos colaterales, supuestos que hiciste porque faltaba información.
  4. Qué preguntarías antes de ejecutar, si es que preguntarías algo.

Y al cerrar, dos secciones cortas: lo que dejé fuera y por qué, y lo que me faltó saber.

.md · .txt · .pdf
Media a una página por caso. Si escribes cuatro, estás explicando de más.

Entregable 2

Un chat por caso

Si usaste un asistente, mándanos la conversación de cada caso, por separado. Completa, tal cual salió, sin editarla ni maquillarla.

Si respondiste cuatro casos, esperamos cuatro conversaciones. Lo más simple es abrir un chat nuevo por caso; también vale uno solo si marcas dónde empieza cada uno.

Lo que no nos sirve es un bloque de tres horas sin saber qué parte corresponde a qué pregunta: ahí ya no podemos leer tu razonamiento sobre ese problema, que es justo lo que queremos ver.

Enlace para compartir por chat, exportación, o pegadas como anexo bajo un título por caso.
Si alguna tiene cosas personales o de otro trabajo, recórtalas y dínoslo.

Por qué pedimos los chats

Porque la respuesta ya no dice cómo pensaste

Dos candidatos nos entregan la misma respuesta correcta. Uno sabía por qué; el otro aceptó lo primero que le dijeron y tuvo suerte. En el documento se ven idénticos. En la conversación no se parecen en nada.

Esa diferencia es exactamente lo que contratamos. Acá el modelo te va a dar todos los días una respuesta que suena perfecta y no lo es, porque no conoce este sistema: no sabe que esa tabla tiene 48 millones de filas, que ese endpoint lo llaman tres apps distintas, ni que mergear despliega. El criterio es lo que haces con esa respuesta plausible, y eso solo se ve en la conversación.

Cuando la leemos, buscamos seis cosas:

Nada de eso se puede fingir escribiendo bonito, y nada de eso aparece en el documento final.

Y para que quede claro, porque suele preocupar

Usar mucho la IA no resta.
No medimos cuánto la usaste ni contamos prompts. Alguien que la usa a fondo y sabe auditar lo que recibe nos interesa más que alguien que no la usa.
No hace falta que se vea bien.
Nadie escribe prompts bonitos trabajando. Los callejones sin salida, las preguntas mal formuladas y las correcciones a mitad de camino son justo lo que queremos ver.
Si no usaste IA, perfecto.
Escríbelo en una línea, sin penalización, y cuéntanos en tres o cuatro frases cómo abordaste el caso que más te costó.
No es para pillarte.
No hay detector ni comparación automática. Lo leemos una persona y tú, para conversarlo en la entrevista.
La vara

Qué evaluamos

Criterio técnico, experiencia y forma de pensar. Todo lo de abajo sale de ahí.

  • El razonamiento, más que la conclusión. Cómo llegaste y qué descartaste.
  • Si ves la trampa cuando la hay.
  • Si arreglas el caso puntual o la clase de problema.
  • Si tu solución de emergencia le dice la verdad al usuario y al que venga detrás.
  • Si sabes lo que cuesta lo que propones: en dinero, en riesgo y en tiempo de otros.
  • Si preguntas cuando falta información, en vez de asumir y avanzar.
  • Si se te nota la cancha. A quien ya vio romperse un sistema en producción se le nota en qué le da miedo y en qué no.
  • Si sabes decir «esto no lo sé» y «esto no lo haría».

No

  • Ortografía, formato, extensión ni presentación del documento.
  • Cuánto usaste la IA, ni si tus prompts se ven ordenados.
  • Que conozcas nuestro stack, nuestro dominio ni las particularidades de las pasarelas de pago. Eso se aprende acá.
  • Que sepas de memoria la sintaxis exacta de nada.
  • Que llegues a la misma conclusión que nosotros.
El envío

Cómo y cuándo

Para
talento@mesa247.pe
Asunto
El turno — Nombre Apellido
Adjuntas
Tu archivo de respuestas y tus conversaciones con la IA, una por caso. Si no usaste IA, dilo en una línea.
Plazo
4 días desde que recibiste el correo

Si necesitas más tiempo por trabajo o por lo que sea, escríbenos y te lo damos: no evaluamos velocidad de calendario.

Y si algo de este documento no se entiende, escríbenos antes de entregar.

Antes de los casos

El sistema del que hablamos

Los seis casos ocurren en la misma plataforma ficticia, que se parece bastante a la nuestra.

ProductoReservas de restaurantes en cuatro países. 2.900 locales activos.
CódigoUn monolito PHP con ~1.700 rutas, escrito por unas diez personas a lo largo de nueve años. Quedan dos.
ClientesEl Libro (panel donde el restaurante gestiona sus reservas del día), el widget embebido en las webs de los restaurantes, y las apps móviles del comensal.
DespliegueMergear a producción despliega.No hay ambiente de pruebas equivalente ni despliegue gradual. Lo que apruebas, sale.
PruebasNo hay.Cuatro archivos en tests/, y dos son el ejemplo que trae el framework.
HorarioLos restaurantes atienden mientras despliegas.Un viernes a las 8 de la noche hay gente parada en la puerta de un local esperando que el Libro cargue.
DineroSe cobran tarjetas desde el sistema.Garantías de reserva, penalidades por no asistir y facturación mensual a los restaurantes.

Los nombres de tablas, archivos y locales de esta prueba son ficticios.

Caso 1 de 6Un PR para revisar

Te llega este PR. Eres el único revisor. Si lo apruebas, sale a producción en el siguiente merge.

PR
#411fix(security): quitar contraseña en texto plano del login de tarjetas
Autor
Un compañero de tu equipo
Ticket
SEC-411 · «Eliminar el uso de la columna PasswordPlain en el flujo de tarjetas»
CI
Verde
Contexto
Forma parte de la campaña de remediación posterior al incidente de seguridad del mes pasado. Quedan 18 tickets como este.
Descripción del autor

La columna PasswordPlain guarda la contraseña en claro y hay que dejar de usarla. El resto del login ya migró al verificador de hash.

El problema es el flujo de guardar/borrar tarjeta: el cliente móvil en ese punto no tiene la contraseña del usuario, manda el valor que ya tenía almacenado de la sesión. Por eso ahí se compara directo contra la columna Password. Probado en el Libro y en las dos apps.

app/Http/Controllers/Api/Auth/AuthController.php
@@ -104,16 +104,24 @@ class AuthController extends Controller
104104 public function login(Request $request)
105105 {
106106 $usuario = $request->input('usuario');
107107 $password = $request->input('password');
108108 $origen = $request->input('origen');
109109
110110 $cuenta = UsuarioPanel::where('Usuario', $usuario)->first();
111111
112112 if ($cuenta) {
113 if ($password != $cuenta->PasswordPlain)
114 $cuenta = null;
113 if ($origen == 'guardar_tarjeta' || $origen == 'borrar_tarjeta') {
114 if ($password != $cuenta->Password)
115 $cuenta = null;
116 } else {
117 if (! PasswordHasher::verify($password, $cuenta->Password, $cuenta->Salt))
118 $cuenta = null;
119 }
115120 }
116121
117122 if (! $cuenta)
118123 return response()->json(['message' => 'Credenciales inválidas'], 401);
119124
120125 return $this->emitirToken($cuenta);
121126 }

Datos adicionales del sistema, por si te sirven:

routes/api.php
Route::group(['middleware' => ['auth:api']], function () {
    // ... rutas del Libro
});

Route::post('/auth/login',     'Api\Auth\AuthController@login');
Route::post('/auth/recuperar', 'Api\Auth\AuthController@recuperar');

Tu tarea

¿Apruebas el PR? Escribe el comentario que dejarías en él.

Caso 2 de 6Un ticket asignado

Ticket
BUG-2288 — Inyección SQL en MenuAdicionalController, línea 214
Prioridad
Alta
Reportado
Por la consultora externa de seguridad
Detalle
El parámetro local_id se concatena directamente a la consulta.
Estimado
30 minutos

Este es el archivo. Está recortado: son 640 líneas en total, te dejamos las partes relevantes con su numeración original.

app/Http/Controllers/Api/Reservation/MenuAdicionalController.php
130 public function getList(Request $request)
131 {
132 $localId = $request->input('local_id');
133 $fecha = $request->input('fecha');
134
136 $items = MenuAdicional::select('Id', 'Nombre', 'Precio', 'Obligatorio',
138 'Minimo', 'Maximo', 'Orden', 'LocalId', 'EstadoId')
141 ->where('LocalId', $localId)
142 ->where('EstadoId', 1);
143
151 if ($request->canal == 'widget') {
152 $items->whereIn('Id', [768, 769]);
153 }
156 $items->where('MinFecha', '<=', $fecha);
164 $items->where('Id', '!=', 595);
176 $items->where('Id', '!=', 767);
195 $itemsV2 = clone $items;
···· · ·
210 // stock consumido en el día
211 $stock = DB::select("
212 SELECT MenuAdicionalId, SUM(Cantidad) AS usado
213 FROM ReservaMenuAdicional rma
214 INNER JOIN Reserva r ON r.Id = rma.ReservaId
215 WHERE r.LocalId = '$localId'
216 AND r.Fecha = '$fecha'
217 GROUP BY MenuAdicionalId
218 ");
···· · ·
232 return response()->json($items->orderBy('Orden')->get());
233 }
···· · ·
255 public function getListV2(Request $request)
256 {
257 $localId = $request->input('local_id');
258 $fecha = $request->input('fecha');
259
261 $items2 = MenuAdicional::select('Id', 'Nombre', 'Precio', 'Obligatorio',
263 'Minimo', 'Maximo', 'Orden', 'LocalId', 'EstadoId')
266 ->where('LocalId', $localId)
267 ->where('EstadoId', 1);
···· · ·
288 $stock2 = DB::select("
289 SELECT MenuAdicionalId, SUM(Cantidad) AS usado
290 FROM ReservaMenuAdicional rma
291 INNER JOIN Reserva r ON r.Id = rma.ReservaId
292 WHERE r.LocalId = '$localId'
293 AND r.Fecha = '$fecha'
294 GROUP BY MenuAdicionalId
295 ");
···· · ·
340 return response()->json($items2->orderBy('Orden')->get());
341 }

Sabemos, además, que getList la usa el widget y getListV2 la usan las dos apps móviles.

Tu tarea

¿Qué entregas y en cuánto tiempo? Si crees que el ticket está mal planteado, dilo.

Caso 3 de 62:14 de la madrugada

Te despierta una alerta. Alguien está enviando payloads de JavaScript por el campo nombre del widget de reservas. Los payloads se están guardando y se ejecutan cuando el restaurante abre su Libro por la mañana: el navegador del anfitrión ejecuta código del atacante.

Lo que tienes:

Cuatro caminos sobre la mesa:

Tu tarea

  1. ¿Qué haces en los próximos 15 minutos? Puedes combinar.
  2. ¿Qué rompe cada una de las cuatro? Queremos el costo de las que no elegiste.
  3. ¿Qué haces con las 30 reservas ya guardadas?
  4. Es martes. ¿Qué queda pendiente para el lunes y quién se asegura de que pase?

Caso 4 de 6Una petición comercial

Te escribe la gerente comercial por chat, un miércoles a las 11 de la mañana:

Chat interno · 11:04

Hola! Necesito que a Los Almendros, La Terraza Azul, Cuatro Vientos y Casa Mediterránea les salga automático el correo de confirmación cuando el comensal registra su tarjeta de garantía. A los demás no, porque se quejaron de que llegan muchos correos. Es urgente, se lo prometí a Los Almendros para hoy.

Sabes esto del sistema:

Tu tarea

  1. ¿Qué haces hoy? ¿Cuánto le dices que va a tomar?
  2. ¿Qué le respondes a ella, literalmente? Escribe el mensaje.
  3. Si hoy haces lo rápido, ¿qué pasa en marzo y quién paga ese costo?

Caso 5 de 6Una consulta antes de mergear

Un compañero te pide revisar esto antes de mergear. Es un comando programado que envía la encuesta de satisfacción a quienes reservaron y ya terminaron de comer. Corre cada hora, una vez por cada uno de los cuatro países.

app/Console/Commands/EnviarEncuestaCommand.php
SELECT r.Id, r.Email, r.Nombre, r.Horario, l.Nombre AS Local
FROM Reserva r
INNER JOIN Local l ON l.Id = r.LocalId
WHERE LEFT(r.Horario, 13) = ?      -- se le pasa '2026-09-08 14'
  AND r.EstadoId = 2
  AND l.PaisId   = ?

Contexto:

Tu tarea

  1. ¿Qué le pasa a esta consulta en producción?
  2. ¿Cómo lo compruebas antes de mergear? Sé concreto: qué ejecutas y qué esperas ver.
  3. ¿Qué propones? Si tu propuesta implica un cambio de esquema, di cómo lo desplegarías sobre una tabla de 48 millones de filas sin detener la operación.

Caso 6 de 6Un reporte

Te llega esto por el canal de soporte:

Soporte · hoy

Desde el 25 de julio se están perdiendo reservas. Serán unas 90 al día. Los restaurantes se quejan de que el comensal dice que reservó y en el Libro no aparece.

Y estas tres líneas de log, que es todo lo que te dieron:

logs · api
2026-09-05 19:41:22 [warning] api.reserva.crear   status=401  body={"message":"Unauthenticated."}  origen=app_android  local=1874
2026-09-05 19:41:23 [info]    api.token.emitir    usuario=reservador_1874  ok=true
2026-09-05 19:41:23 [warning] api.reserva.crear   status=401  body={"message":"Unauthenticated."}  origen=app_ios      local=2201

Tu tarea

Escribe lo que harías en los próximos 30 minutos. Si consideras que no puedes responder con lo que tienes, dilo y escribe exactamente qué necesitas y a quién se lo pides.