A continuación se detallan las dos partes que forman esta prueba técnica. Están pensadas para completarse en 2–3 horas. No buscamos perfección exhaustiva: buscamos ver cómo piensas, qué priorizas y cómo comunicas tus decisiones técnicas.
Puedes entregar en el idioma que prefieras (español o inglés).
README.md con las instrucciones para ejecutar el código si aplica.El siguiente código pertenece a una funcionalidad real de nuestro sistema: el registro de un afiliado y la asignación de su primer programa de comisión. Nuestro stack principal es PHP 8 + Symfony.
Tu tarea es revisar este código como si fuera una Pull Request de un desarrollador de tu equipo. Debes:
No es necesario reescribir todo el código. Sí es importante que priorices los problemas por impacto y que expliques tu razonamiento.
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\ORM\EntityManagerInterface;
class AffiliateController extends AbstractController
{
#[Route('/api/affiliates/register', methods: ['POST'])]
public function register(Request $request, EntityManagerInterface $em): JsonResponse
{
$data = json_decode($request->getContent(), true);
// Check if email already exists
$existing = $em->getConnection()->executeQuery(
"SELECT * FROM affiliates WHERE email = '" . $data['email'] . "'"
)->fetchAssociative();
if ($existing) {
return new JsonResponse(['error' => 'Email already exists'], 400);
}
$password = md5($data['password']);
$em->getConnection()->executeStatement(
"INSERT INTO affiliates (name, email, password, country, created_at)
VALUES ('" . $data['name'] . "',
'" . $data['email'] . "',
'" . $password . "',
'" . $data['country'] . "',
'" . date('Y-m-d H:i:s') . "')"
);
$affiliateId = $em->getConnection()->lastInsertId();
// Assign default commission program
$program = $em->getConnection()->executeQuery(
"SELECT * FROM commission_programs WHERE is_default = 1"
)->fetchAssociative();
if ($program) {
$em->getConnection()->executeStatement(
"INSERT INTO affiliate_programs (affiliate_id, program_id, assigned_at)
VALUES (" . $affiliateId . ", " . $program['id'] . ", NOW())"
);
}
// Send welcome email
$mailer = new \App\Service\MailerService();
$mailer->send(
$data['email'],
'Welcome to Betandeal!',
'welcome_affiliate',
['name' => $data['name'], 'program' => $program['name'] ?? 'Standard']
);
// Log the registration
file_put_contents(
'/var/log/affiliates.log',
date('Y-m-d H:i:s') . ' - New affiliate: ' . $data['email'] . "\n",
FILE_APPEND
);
return new JsonResponse([
'success' => true,
'affiliate_id' => $affiliateId,
'program' => $program['name'] ?? null,
]);
}
#[Route('/api/affiliates/{id}/stats', methods: ['GET'])]
public function getStats(int $id, Request $request, EntityManagerInterface $em): JsonResponse
{
$from = $request->query->get('from', date('Y-m-01'));
$to = $request->query->get('to', date('Y-m-d'));
$stats = $em->getConnection()->executeQuery("
SELECT
COUNT(c.id) as clicks,
COUNT(cv.id) as conversions,
SUM(cv.commission_amount) as revenue
FROM clicks c
LEFT JOIN conversions cv ON cv.click_id = c.id
WHERE c.affiliate_id = $id
AND c.created_at BETWEEN '$from' AND '$to'
")->fetchAssociative();
$topOffers = $em->getConnection()->executeQuery("
SELECT o.name, COUNT(c.id) as clicks
FROM clicks c
JOIN offers o ON o.id = c.offer_id
WHERE c.affiliate_id = $id
GROUP BY o.id
ORDER BY clicks DESC
LIMIT 5
")->fetchAllAssociative();
return new JsonResponse([
'stats' => $stats,
'top_offers' => $topOffers,
]);
}
}
<?php
namespace App\Service;
class MailerService
{
private $apiKey = 'SG.live_xK92mQpLTz3nVwRb8fYoAE';
private $fromEmail = 'noreply@betandeal.com';
public function send($to, $subject, $template, $vars = [])
{
$html = file_get_contents(__DIR__ . '/../../templates/emails/' . $template . '.html');
foreach ($vars as $key => $value) {
$html = str_replace(' . $key . ', $value, $html);
}
$ch = curl_init('https://api.sendgrid.com/v3/mail/send');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'personalizations' => [['to' => [['email' => $to]]]],
'from' => ['email' => $this->fromEmail],
'subject' => $subject,
'content' => [['type' => 'text/html', 'value' => $html]],
]));
curl_exec($ch);
curl_close($ch);
}
}
Entrega tu revisión en un fichero code-review.md con la siguiente estructura:
## Problema 1 — [Título corto]
**Severidad:** Crítica / Alta / Media / Baja
**Descripción:** ...
**Propuesta de mejora:** ...
[fragmento de código si aplica]
## Problema 2 — ...
Betandeal opera una plataforma de afiliación para iGaming. Los afiliados (webs, influencers, comparadores) generan tráfico hacia los operadores de casino y apuestas mediante links de seguimiento. Cada vez que un usuario hace clic en un link y posteriormente realiza una conversión (registro, depósito), el afiliado recibe una comisión.
Actualmente el sistema de tracking funciona así:
GET /track?aff=123&offer=456&sub=abcEl sistema actual tiene dificultades para escalar. En eventos especiales (Champions League, Grand Prix…) recibimos picos de 50.000–100.000 clics por hora. Además:
Diseña una arquitectura que resuelva estos problemas. Tu propuesta debe cubrir:
Un fichero architecture.md con tu propuesta. Incluye diagramas si lo consideras útil (puede ser ASCII art, Mermaid o una imagen). No es necesario que sea un documento exhaustivo: valoramos más la claridad y el criterio que la extensión.
| Área | Qué evaluamos |
|---|---|
| Code Review | Capacidad de identificar problemas reales, priorización por impacto, calidad de las propuestas de mejora, comunicación clara. |
| Arquitectura | Pragmatismo, conocimiento de patrones distribuidos, capacidad de equilibrar complejidad y coste, consideración del equipo y la migración incremental. |
| Comunicación técnica | Claridad de la escritura, estructura de las respuestas, capacidad de justificar decisiones. |
| Criterio Tech Lead | ¿Priorizas bien? ¿Piensas en el equipo? ¿Equilibras deuda técnica vs. entrega? |