Skip to main content
Cuando un admin va a asignar un camello a un Job, GET /api/admin/jobs/:id/candidates calcula un score por candidato y devuelve el pool ordenado. El score se compone de razones (reasons) independientes que se suman; cada razón trae su etiqueta y sus puntos, así que la respuesta explica por qué un candidato quedó donde quedó, no solo el número final.
Esta página documenta el mecanismo tal como está implementado en src/lib/match-score-shared.ts. Los pesos son constantes reales del código (MATCH_WEIGHTS), no una estimación.

Factores y pesos

Estos valores son los de la constante MATCH_WEIGHTS en src/lib/match-score-shared.ts:

Geografía

La ciudad y el barrio se comparan normalizados (minúsculas, sin tildes, sin espacios sobrantes) para que variaciones de escritura no rompan la coincidencia. Si el candidato no está en la misma ciudad del Job, se revisa si declaró esa ciudad entre sus municipalities (los municipios que él mismo dijo cubrir, no una cobertura inferida por defecto). Si tampoco, la razón de geografía suma 0 puntos pero igual aparece en la respuesta (“Fuera de cobertura”).

Reputación

No se usa el promedio de calificación en bruto: se calcula una media bayesiana con un prior de priorRating: 4 y peso priorWeight: 3. La fórmula es:
Sin reseñas, bayes cae exactamente en priorRating (4 sobre 5): un camello nuevo no queda enterrado por no tener historial, pero tampoco una única reseña de 5 estrellas lo dispara al tope. Esa media bayesiana se escala contra el máximo de rating (5) y se multiplica por el peso rating (25): points = round((bayes / 5) * 25).

Carga

load son los trabajos activos del candidato. Los puntos son max(0, 20 - 7 * load): 20 puntos si está libre, 13 con un trabajo activo, 6 con dos, y 0 a partir de tres — nunca negativo.

Disponibilidad

Si el Job no tiene scheduledStart, esta razón no aparece y el score queda igual que sin agenda. Si la tiene, se cruza con CamelloAvailability en hora de Colombia. La cita dura 60 minutos y tiene que caber entera dentro de una franja del mismo día de la semana:
  • franja que cubre la hora y el hueco está libre: 10 puntos, «Disponible en la cita»;
  • franja que no cubre esa hora: 0 puntos, «Fuera de horario»;
  • el camello no publicó franjas: 0 puntos, «Sin horario»;
  • otro trabajo distinto de CANCELADO solapa el intervalo: 0 puntos, «Ocupado en la cita».
No saca a nadie del pool. Quien no publicó horario sigue siendo asignable; solo queda por debajo de quien sí puede atender esa cita.

Premium

Si el candidato es premium, se suma un bono fijo de 15 puntos como una razón adicional (kind: "premium"). Si no lo es, esa razón simplemente no aparece en la lista de motivos.

Desempate

El score es la suma de todas las razones aplicables. Cuando dos candidatos empatan en score, rankCandidates desempata en este orden: más reseñas (reviewCount) primero, luego orden alfabético del nombre (localeCompare en español), y por último el id como desempate final y determinista.

El endpoint

GET /api/admin/jobs/:id/candidates devuelve { items }: el pool de candidatos para el servicio de ese Job, ya ordenado por score, con los motivos (reasons) de cada puntaje incluidos por candidato. Cada ítem trae también city, activeJobs y categories (las categorías de los servicios que ofrece). La tarjeta del admin recorta esa lista ya rankeada por categoría, ciudad o carga; el tope y el orden no cambian. Acepta limit de 1 a 50 (default 20) y responde 404 si el trabajo no existe.