Skip to main content
Un Job es la unidad de trabajo entre un cliente y un camello: se crea, se asigna, se acepta o se rechaza, y se cierra por finalización o cancelación.

Origen: EXPLORE vs REQUEST

Un Job nace de dos maneras distintas, y el campo source registra cuál:
  • EXPLORE: el cliente eligió un camello concreto en el explorador y el body de creación incluye camelloId.
  • REQUEST: el cliente pidió el servicio sin elegir camello (por ejemplo desde /trabajos/nuevo); no hay camelloId y el admin asigna uno después.

Ciclo de vida

Los cuatro estados son PENDIENTE, EN_PROGRESO, COMPLETADO y CANCELADO. En texto:
  • El cliente crea el Job (POST /api/jobs).
  • El admin asigna un camello a un Job PENDIENTE sin camello (POST /api/admin/jobs/:id/assign).
  • El camello acepta (POST /api/jobs/:id/accept): pasa de PENDIENTE a EN_PROGRESO. O rechaza (POST /api/jobs/:id/reject): se le quita el camelloId, se borra el precio referencial y el Job vuelve a quedar sin asignar.
  • complete (POST /api/jobs/:id/complete) solo es válido desde EN_PROGRESO.
  • Tras aceptar, la orden de servicio queda en /trabajos/:id/comprobante (también actúa como comprobante al completar). Lleva orderNumber y, si existe, el precio referencial. El detalle está en Precio referencial del trabajo.
  • El camello puede calificar al cliente con POST /api/jobs/:id/client-review cuando el job está COMPLETADO.
  • cancel (POST /api/jobs/:id/cancel) solo sale de PENDIENTE o EN_PROGRESO. El motivo opcional queda descrito en Motivo de cancelación.

Quién puede cada transición

El rol CAMELLOS no puede crear Jobs: POST /api/jobs responde 403 para ese rol. Un camello participa en el ciclo solo a partir de la asignación.

Restricciones de asignación

POST /api/admin/jobs/:id/assign exige que:
  • el Job esté PENDIENTE y sin camelloId todavía;
  • el camello esté ACTIVO;
  • el camello tenga userId (esté vinculado a una cuenta de usuario, no sea solo una ficha del catálogo).
El endpoint de asignación no comprueba que el camello ofrezca el serviceId del Job: ese filtro solo aplica al pool de GET /api/admin/jobs/:id/candidates, así que “mismo servicio” es una convención de la UI del admin (que asigna desde ese pool), no una garantía que imponga /assign.

Quién asignó

POST /api/admin/jobs/:id/assign guarda assignedById con el admin de la sesión, además de assignedAt. El trabajo lo expone como assignedBy (id y name). Si el cliente eligió el camello al crear el trabajo (EXPLORE), no hay admin: assignedBy queda en null aunque assignedAt sí se escriba. Un rechazo posterior no borra ese dato; la siguiente asignación lo pisa.

Precio referencial del trabajo

El ítem UNI-04 del backlog de unificación (origen andago9/camellap, Job.price) se retiró el 2026-10-22. Job.quotedPrice es un entero opcional en pesos colombianos. No es un cobro. La cita (scheduledStart / scheduledEnd) no forma parte de este dato. El ítem UNI-08 se retiró el mismo día. Job.quotedPriceUnit guarda la unidad que tenía el camello en ese momento (hora, servicio o dia). Viaja con el monto. El monto se copia de Camello.priceFrom y quotedPriceUnit de Camello.priceUnit en los mismos tres momentos:
  • al crear un trabajo EXPLORE, cuando el cliente ya eligió camello;
  • al asignar con POST /api/admin/jobs/:id/assign;
  • al aceptar con POST /api/jobs/:id/accept, solo si el trabajo todavía no tiene monto.
Si el monto ya estaba, aceptar no cambia ni el monto ni la unidad. Sin monto, la unidad queda null. 0 es un monto válido. Un rechazo borra los dos campos junto con el camello, para que el siguiente no herede el precio del anterior. La unidad guardada es hora, servicio o dia. En /trabajos la tarjeta lo muestra como «Precio referencial» en COP cuando hay monto y, si hay unidad, el mismo sufijo que la ficha (/ hora, / servicio, / día). El mismo texto sale en /trabajos/:id/comprobante. Sin unidad, solo el monto. El origen en la ficha del camello está en Precio referencial.

Motivo de cancelación

El ítem UNI-05 del backlog de unificación (origen andago9/camellap, Job.cancelReason) se retiró el 2026-10-22. El cliente dueño o un admin cancela desde la tarjeta de /trabajos mientras el trabajo está PENDIENTE o EN_PROGRESO. El camello no cancela: si sigue pendiente, lo rechaza y el trabajo vuelve a la cola. POST /api/jobs/:id/cancel acepta { reason }, texto opcional de hasta 500 caracteres. Sin cuerpo, o con {}, cancela y deja cancelReason en null. Un JSON ilegible responde 400 «JSON invalido». Un reason que no es texto o pasa de 500 caracteres responde 400 «Parametros invalidos» y no cancela. Si el trabajo ya está COMPLETADO o CANCELADO, responde 409 y no pisa cancelledAt ni el motivo. El texto se guarda en Job.cancelReason junto con cancelledAt. En /trabajos la tarjeta lo muestra como «Motivo: …». El aviso JOB_STATUS_CHANGED incluye ese texto cuando existe. Al eliminar la cuenta (DELETE /api/me), los trabajos abiertos del cliente pasan a CANCELADO con el motivo «La cuenta del cliente fue eliminada». Ese camino no envía notificación.

Lista en /trabajos

La página parte la lista en cinco pestañas: Todos, Pendientes, En progreso, Completados y Cancelados. Un trabajo CANCELADO sigue apareciendo en Todos y, además, en su propia pestaña. ?estado= abre la pestaña correspondiente: pendiente, en-progreso, completado o cancelado. Un valor que no está en esa lista abre Todos. El admin puede sumar ?sinAsignar=1 para ver solo trabajos sin camello. Cambiar de pestaña en la página no reescribe la URL.

Visibilidad de GET /api/jobs

La lista de trabajos depende del rol de la sesión: el cliente ve los que creó, el camello los que tiene asignados a su perfil, y el admin los ve todos.
Un Job ajeno responde 404, no 403, tanto en GET /api/jobs/:id como en las acciones sobre un Job concreto. Es intencional: no se revela que el recurso existe a quien no tiene visibilidad sobre él.

Cita

scheduledStart y scheduledEnd son opcionales. La cita dura 60 minutos y solo existe si el trabajo tiene camello. Una solicitud abierta (REQUEST) nace sin hora; el cliente la fija con POST /api/jobs/:id/schedule cuando ya hay camello y el estado es PENDIENTE o EN_PROGRESO. Si el cliente eligió camello al crear el trabajo, puede mandar scheduledStart en POST /api/jobs. Sin camelloId, esa fecha responde 400. Cuando el camello rechaza, la cita se borra junto con el camelloId: el hueco vuelve a quedar libre. Un trabajo CANCELADO tampoco bloquea el horario. El detalle de ventanas y huecos está en Agenda.

Coordinación por WhatsApp

Tras la aceptación, WhatsApp queda como canal de coordinación secundaria entre cliente y camello; no reemplaza el estado del Job ni sus transiciones.

Roles y permisos

La matriz completa de qué rol puede llamar a cada endpoint —incluyendo los de Jobs— vive en Autenticación y roles.