Skip to main content

Ticket — Casos de prueba de controladores, DTOs, jobs y repositorios

Documenta los specs de src/app/modules/ticket/:

  • controllers/ticket.controller.spec.ts, ticket.controller.filter-agents.spec.ts, ticket.controller.filter-permission.spec.ts y ticket.controller.permissions.spec.ts
  • dtos/transfer-ticket.dto.spec.ts
  • jobs/csat-expiration.job.spec.ts, jobs/ticket-reactivation.job.spec.ts y jobs/ticket-inactivity-resolution.job.spec.ts
  • repositories/ticket-contact-reason.repository.spec.ts y repositories/ticket.repository.spec.ts

Los specs de controladores son unitarios (o de guards con supertest); los de repositorios usan MongoMemoryServer con modelos reales; DTOs y jobs usan mocks.

Resumen: 10 specs, 44 suites (describe) y 106 tests (25 + 3 + 3 + 4 en controladores, 9 en DTO, 3 + 2 + 9 en jobs, 4 + 44 en repositorios).

controllers/ticket.controller.spec.ts​

Suite TicketController. Tests unitarios del controlador: se invocan los métodos directamente (los guards AuthGuard y PermissionsGuard se sobrescriben con canActivate: () => true). El tenant se obtiene de user.tenantId.toString().

Mocks: TicketService (getTicketsList, postponeTicket, cancelPostpone, markUnread, resolveTicket, deleteTicket, lockTicket, unlockTicket, forceUnlockTicket), TranslateService.get (devuelve la clave), SocketBroadcastService (broadcastTicketStatusChanged, broadcastTicketDeleted, broadcastTicketLockChanged), PermissionResolverService y UserService.

getTicketsList​

TestEscenarioResultado esperadoMocks
should call service with tenantId from current userLlamada con filtros vacíos.getTicketsList(MOCK_TENANT_ID, {}).TicketService
should return translated message, results and metaEl servicio devuelve un ticket y meta.{ message: 'ticket.findAll', data: { results, meta } }.TicketService, TranslateService
should forward filters to the serviceFiltros status: 'open' y hasUnread: true.El servicio recibe esos filtros intactos.TicketService
should return empty results when service returns empty arrayServicio devuelve resultados vacíos.data.results vacío y meta.total 0.TicketService
should use tenantId.toString() so it works with ObjectId or string tenantIdUsuario con tenantId como ObjectId.El servicio recibe el tenant como string.TicketService
should call translate with FIND_ALL key and langLlamada con idioma es.translate.get('ticket.findAll', 'es').TranslateService

postponeTicket​

TestEscenarioResultado esperadoMocks
forwards ticketId, date, tenantId and requesting user to the serviceBody con postponedUntil en ISO.postponeTicket(ticketId, Date, tenantId, userId, undefined, expect.any(String)).TicketService
broadcasts the postponed status change and returns the updated ticketEl servicio devuelve el ticket aplazado.broadcastTicketStatusChanged(tenant, ticketId, 'postponed') y respuesta ticket.updated.TicketService, broadcastTicketStatusChanged

cancelPostpone​

TestEscenarioResultado esperadoMocks
forwards ticketId, tenantId and requesting user to the serviceCancelación del aplazamiento.cancelPostpone(ticketId, tenantId, userId, undefined, expect.any(String)).TicketService
broadcasts the open status change and returns the updated ticketEl servicio devuelve el ticket open.broadcastTicketStatusChanged(tenant, ticketId, 'open') y respuesta ticket.updated.TicketService, broadcastTicketStatusChanged

resolveTicket vs resolveNoFarewellTicket (regression parity)​

TestEscenarioResultado esperadoMocks
both routes broadcast the same "resolved" status-change effect and return the same response shapeSe llama a resolveTicket y luego a resolveNoFarewellTicket.Mismos argumentos de broadcast (tenant, ticketId, 'resolved') y misma respuesta ticket.updated.TicketService.resolveTicket, broadcastTicketStatusChanged
both routes invoke the resolve service with the same ticket and tenant identifiersMismas dos llamadas.resolveTicket recibe el mismo ticketId y tenantId en ambas rutas.TicketService.resolveTicket

markUnread​

TestEscenarioResultado esperadoMocks
forwards ticketId, agentUnread and tenantId to the serviceBody agentUnread: true.markUnread(ticketId, true, tenantId).TicketService
returns the translated message and the updated ticketBody agentUnread: false.{ message: 'ticket.updated', data: ticket }.TicketService, TranslateService

deleteTicket​

TestEscenarioResultado esperadoMocks
forwards ticketId and tenantId to the serviceEliminación de un ticket.deleteTicket(ticketId, tenantId).TicketService
broadcasts ticket_deleted to the owning tenant room only after the service call resolvesSe registra el orden de las llamadas.Orden service y luego broadcast; broadcastTicketDeleted(tenantId, ticketId).deleteTicket, broadcastTicketDeleted
does not broadcast when the service call throws (e.g. ticket not found)El servicio rechaza con nope.Propaga el error y no hay broadcast.deleteTicket rechaza
returns the translated deleted messageEliminación exitosa.Devuelve { message: 'ticket.deleted' }.TranslateService
never fans out to a tenant other than the deleted ticket’s own tenantTicket del tenant A eliminado.Un solo broadcast con el tenant A; nunca con el tenant B.broadcastTicketDeleted

lockTicket​

TestEscenarioResultado esperadoMocks
forwards ticketId, tenantId and requesting user to the serviceBloqueo de un ticket.lockTicket(ticketId, tenantId, userId).TicketService
broadcasts ticket_lock_changed with the requesting user as the new holder and returns the ticketBloqueo exitoso.broadcastTicketLockChanged(tenant, { ticketId, lockedBy: userId, lockedByName: 'byron' }) y respuesta ticket.updated.broadcastTicketLockChanged

unlockTicket​

TestEscenarioResultado esperadoMocks
forwards ticketId, tenantId and requesting user to the serviceDesbloqueo.unlockTicket(ticketId, tenantId, userId).TicketService
broadcasts ticket_lock_changed with a null holder and returns the ticketDesbloqueo exitoso.Broadcast con lockedBy: null y lockedByName: null; respuesta ticket.updated.broadcastTicketLockChanged

forceUnlockTicket​

TestEscenarioResultado esperadoMocks
forwards ticketId and tenantId to the service (no ownership check at this layer)Desbloqueo forzado.forceUnlockTicket(ticketId, tenantId) sin usuario.TicketService
broadcasts ticket_lock_changed with a null holder and returns the ticketDesbloqueo forzado exitoso.Broadcast con titular null y respuesta ticket.updated.broadcastTicketLockChanged

controllers/ticket.controller.filter-agents.spec.ts​

Suite TicketController — GET /tickets/filter-agents (D11). Endpoint dedicado GET /v1/tickets/filter-agents, protegido solo por el permiso TICKET_FILTER_BY_AGENT (sin depender de USER_READ). Usa el pipeline HTTP real (supertest, ValidationPipe, PermissionsGuard real); AuthGuard se reemplaza por un FakeAuthGuard que inyecta el usuario.

Mocks: PermissionResolverService.resolve (devuelve el set grantedAcronyms), UserService.findFilterableAgents (2 agentes), TicketService y SocketBroadcastService vacíos.

TicketController — GET /tickets/filter-agents (D11)​

TestEscenarioResultado esperadoMocks
returns 403 without TICKET_FILTER_BY_AGENT, even with TICKET_READ and USER_READ grantedSolo TICKET_READ y USER_READ.HTTP 403 y no se llama a findFilterableAgents.PermissionResolverService
returns 200 with ONLY TICKET_FILTER_BY_AGENT granted — no USER_READ neededSolo TICKET_FILTER_BY_AGENT.HTTP 200; findFilterableAgents(tenantId) y 2 resultados en data.results.PermissionResolverService, UserService
response payload exposes only _id, firstname, lastName, username — no sensitive fieldsRespuesta 200.Cada agente tiene solo _id, firstname, lastName, username; sin email, password ni roleId.UserService

controllers/ticket.controller.filter-permission.spec.ts​

Suite TicketController — assignedUserIds authorization guard-clause (D5). Prueba el rechazo dentro del controlador de assignedUserIds mediante PermissionResolverService (no un segundo @RequirePermission). Pipeline HTTP real con ValidationPipe({ transform: true }) para probar también la transformación toStringArray del DTO.

Mocks: TicketService.getTicketsList (resultado vacío), PermissionResolverService.resolve, UserService (solo para DI).

TicketController — assignedUserIds authorization guard-clause (D5)​

TestEscenarioResultado esperadoMocks
returns 200 and forwards no assignedUserIds when the param is absent, even without TICKET_FILTER_BY_AGENT (zero-cost hot path)GET /v1/tickets/get-all sin assignedUserIds, solo con TICKET_READ.HTTP 200 y filters.assignedUserIds es undefined.getTicketsList
returns 403 when assignedUserIds is sent without TICKET_FILTER_BY_AGENT (TICKET_READ alone is not enough)Se envían dos assignedUserIds sin el permiso.HTTP 403 y no se llama a getTicketsList.PermissionResolverService
returns 200 and forwards the normalized assignedUserIds array when TICKET_FILTER_BY_AGENT is grantedSe envían dos assignedUserIds con TICKET_FILTER_BY_AGENT.HTTP 200 y filters.assignedUserIds es el arreglo normalizado de los dos ids.getTicketsList

controllers/ticket.controller.permissions.spec.ts​

Prueba de integración de guards: el PermissionsGuard real y Reflector a través del pipeline HTTP (FakeAuthGuard inyecta tenant y rol; el resolver está mockeado), para demostrar que el guard está cableado y no solo que el método funciona.

Mocks: TicketService (resolveTicket, forceUnlockTicket), SocketBroadcastService, PermissionResolverService.resolve, UserService.

TicketController — coarse-update (TICKET_UPDATE) permission guard integration​

TestEscenarioResultado esperadoMocks
returns 403 Forbidden for a lifecycle-transition endpoint when TICKET_UPDATE is missingPATCH /v1/tickets/resolve/:id con solo TICKET_READ.HTTP 403 y no se llama a resolveTicket.PermissionResolverService
returns 200 OK for a lifecycle-transition endpoint when TICKET_UPDATE is grantedMismo endpoint con TICKET_UPDATE.HTTP 200 y se llama a resolveTicket.PermissionResolverService, resolveTicket

TicketController — force-unlock (TICKET_UNLOCK_ANY) permission guard integration​

TestEscenarioResultado esperadoMocks
returns 403 Forbidden when only TICKET_UPDATE is granted (NOT a role bypass — dedicated acronym required)PATCH /v1/tickets/force-unlock/:id con solo TICKET_UPDATE.HTTP 403 y no se llama a forceUnlockTicket.PermissionResolverService
returns 200 OK when TICKET_UNLOCK_ANY is grantedMismo endpoint con TICKET_UNLOCK_ANY.HTTP 200 y se llama a forceUnlockTicket.PermissionResolverService, forceUnlockTicket

dtos/transfer-ticket.dto.spec.ts​

Suite TransferTicketDto — validation. Valida con class-validator (plainToInstance + validate) los campos del DTO de transferencia. userId es opcional: transferir solo a un departamento es válido; el servicio exige que haya al menos uno de userId o departmentId (400). No usa mocks.

userId​

TestEscenarioResultado esperadoMocks
passes when userId is a valid MongoIduserId con un ObjectId válido.Sin errores para userId.ninguno
fails when userId is empty stringuserId: ''.Hay errores para userId.ninguno
passes when userId is missing but departmentId is presentSolo departmentId.Sin errores para userId (campo opcional, SPEC 06).ninguno
fails when userId is not a valid MongoIduserId: 'not-a-mongo-id'.Hay errores para userId.ninguno

departmentId​

TestEscenarioResultado esperadoMocks
passes when departmentId is absent (optional field)Solo userId.Sin errores para departmentId.ninguno
passes when departmentId is a valid MongoIduserId y departmentId válidos.Sin errores para departmentId.ninguno
fails when departmentId is present but not a valid MongoIddepartmentId: 'bad-id'.Hay errores para departmentId.ninguno

internalNote​

TestEscenarioResultado esperadoMocks
passes when absentSin internalNote.Sin errores para internalNote.ninguno
passes when present as stringinternalNote: 'nota interna'.Sin errores para internalNote.ninguno

jobs/csat-expiration.job.spec.ts​

Suite CsatExpirationJob. Prueba el tick handleInterval que expira las encuestas CSAT vencidas. Mock: TicketRepository.expireCsatSurveys.

CsatExpirationJob​

TestEscenarioResultado esperadoMocks
expires due CSAT surveys via the repository on each tickexpireCsatSurveys devuelve 3.Se llama a expireCsatSurveys con una Date.TicketRepository
does not throw and does not trigger any other ticket effect when nothing to expireexpireCsatSurveys devuelve 0.handleInterval resuelve sin lanzar.TicketRepository
swallows errors so one bad tick does not stop future ticksexpireCsatSurveys rechaza (db down).handleInterval resuelve sin lanzar.TicketRepository rechaza

jobs/ticket-reactivation.job.spec.ts​

Suite TicketReactivationJob. Mock: TicketService.reactivateDueTickets.

TicketReactivationJob​

TestEscenarioResultado esperadoMocks
delegates to TicketService.reactivateDueTickets on every tickhandleInterval con reactivateDueTickets devolviendo [].reactivateDueTickets se llama exactamente una vez.TicketService
swallows errors from reactivateDueTickets so one bad tick does not stop future ticksreactivateDueTickets rechaza (db down).handleInterval resuelve a undefined.TicketService rechaza

jobs/ticket-inactivity-resolution.job.spec.ts​

Suite TicketInactivityResolutionJob. Prueba el tick que notifica y resuelve tickets por inactividad del contacto según la configuración efectiva (tenant o departamento). La configuración base tiene resolveInMinutes: 5, notifyInMinutes: 1 y un mensaje de aviso.

Mocks: TicketRepository (findOpenWithContactActivity, markAutoResolutionNotified), TicketResolutionEffectsService.apply, DepartmentRepository.findById, TenantSettingsService.getOrCreate, AutoResolutionPrecedenceService.resolve, ContactRepository, MessageTemplateService.interpolate, Message model create, OutboundRouterService.route y SocketBroadcastService.

TicketInactivityResolutionJob​

TestEscenarioResultado esperadoMocks
resolves a ticket via TicketResolutionEffectsService once resolveInMinutes is exceededÚltimo mensaje del contacto hace 10 min, aviso ya enviado y resolveInMinutes: 5.apply(ticket, { source: 'inactivity', useConnectionFarewell: true }) y no se vuelve a marcar el aviso.findOpenWithContactActivity, precedence.resolve, apply
sends the notification once notifyInMinutes is reached, without resolving yetÚltimo mensaje hace 2 min (entre notifyInMinutes y resolveInMinutes).No resuelve; crea el mensaje de aviso, lo enruta y llama markAutoResolutionNotified(ticket._id).Message.create, route, markAutoResolutionNotified
notifies before resolving when the notify window was skippedHace 10 min pero sin aviso previo.No resuelve todavía; marca el aviso como enviado.apply, markAutoResolutionNotified
resolves without notifying when notifyInMinutes is not below resolveInMinutesnotifyInMinutes igual a resolveInMinutes (5) y hace 10 min.Resuelve con apply(...) y no crea mensaje de aviso.apply, Message.create
does not resend the notification when autoResolutionNotifiedAt is already setHace 2 min con autoResolutionNotifiedAt informado.No crea mensaje ni vuelve a marcar el aviso.Message.create, markAutoResolutionNotified
does nothing for a ticket whose effective config has enabled=falseConfiguración efectiva con enabled: false y hace 60 min.No resuelve ni crea mensajes.precedence.resolve, apply
resolves using the department config when overrideGlobalResolution=true (precedence)Ticket con departamento con overrideGlobalResolution: true y resolveInMinutes: 2; hace 3 min.Consulta el departamento con findById(departmentId, tenantId), llama a precedence.resolve(department, config global) y resuelve con useConnectionFarewell: false.DepartmentRepository, precedence.resolve, apply
does not look up a department when the ticket has no departmentIdTicket sin departmentId.No se llama a DepartmentRepository.findById.DepartmentRepository
swallows errors so one bad tick does not stop future ticksfindOpenWithContactActivity rechaza (db down).handleInterval resuelve a undefined.TicketRepository rechaza

repositories/ticket-contact-reason.repository.spec.ts​

Suite TicketContactReasonRepository — integration (mongodb-memory-server). Usa MongoDB en memoria con el modelo real; sin mocks. Los registros se limpian tras cada test.

create​

TestEscenarioResultado esperadoMocks
stores record and returns it with createdAtCrea un registro con tenant, ticket, departamento y descripción.Devuelve _id, ticketId, tenantId, departmentId, description y createdAt.ninguno
creates record without departmentId (undefined allowed)Crea sin departmentId.Se crea y devuelve _id.ninguno

findByTicket​

TestEscenarioResultado esperadoMocks
returns records for the given ticket and tenant, sorted newest firstDos registros del ticket 1 (tenant A), uno de otro ticket y uno de otro tenant.Devuelve solo 2 registros, todos del ticket y tenant solicitados.ninguno
returns empty array when no records exist for the ticketSin registros.Devuelve [].ninguno

repositories/ticket.repository.spec.ts​

Suite TicketRepository — integration (mongodb-memory-server). Tests de integración del repositorio con MongoDB en memoria; sin mocks de la capa de datos. Cubre aislamiento por tenant (REQ-13), aplazamiento, CSAT, bloqueo (ticket-lock) y operaciones atómicas con CAS (compare-and-set).

findByIdAndTenant​

TestEscenarioResultado esperadoMocks
REQ-13 Scenario 13.1 — returns null when tenantId does not match (cross-tenant)Se busca el ticket del tenant A con el tenant B.Devuelve null.ninguno
REQ-13 Scenario 13.2 — returns the full TicketDocument when _id and tenantId matchId y tenant correctos.Devuelve el documento con _id, tenantId y ContactId correctos.ninguno
returns null when _id does not exist in the collection at allId inexistente.Devuelve null.ninguno

updateLastMessage​

TestEscenarioResultado esperadoMocks
sets lastContactMessageAt when isFromContact=trueMensaje del contacto.Guarda lastMessage y lastContactMessageAt como Date.ninguno
does NOT set lastContactMessageAt when isFromContact=false (agent message)Mensaje de agente.Guarda lastMessage; lastContactMessageAt queda null.ninguno
defaults to NOT setting lastContactMessageAt when the flag is omittedSin el flag.lastContactMessageAt queda null.ninguno
does NOT overwrite a previously set lastContactMessageAt with an agent messagePrimero mensaje del contacto y luego de agente.lastMessage pasa al segundo mensaje; lastContactMessageAt conserva el valor original.ninguno

postpone​

TestEscenarioResultado esperadoMocks
sets sendType to postponed and persists postponedUntilAplaza un ticket open con fecha futura.sendType postponed y postponedUntil persistida.ninguno

cancelPostpone​

TestEscenarioResultado esperadoMocks
sets sendType back to open and unsets postponedUntilTicket postponed.sendType open y postponedUntil undefined.ninguno

setAgentUnread​

TestEscenarioResultado esperadoMocks
sets agentUnread to true and persists itsetAgentUnread(id, true).agentUnread true.ninguno
sets agentUnread back to false and persists itTicket con agentUnread: true.agentUnread false.ninguno
returns null when the ticket does not existId inexistente.Devuelve null.ninguno

findDuePostponed​

TestEscenarioResultado esperadoMocks
returns only postponed tickets whose postponedUntil has already passedUn aplazado vencido, uno futuro y uno open.Solo incluye el vencido.ninguno

reactivatePostponed​

TestEscenarioResultado esperadoMocks
CAS: only one of two concurrent reactivation attempts winsDos reactivaciones concurrentes del mismo ticket vencido.Solo una devuelve el ticket (open, sin postponedUntil); la otra devuelve null.ninguno
returns null when the ticket is not currently postponedTicket open.Devuelve null.ninguno

findOpenWithContactActivity​

TestEscenarioResultado esperadoMocks
returns only open/in-progress tickets that have a lastContactMessageAt setTickets open y inProgress con actividad, open sin actividad y resolved con actividad.Incluye los dos con actividad y excluye el que no la tiene y el resuelto.ninguno

markAutoResolutionNotified​

TestEscenarioResultado esperadoMocks
sets autoResolutionNotifiedAt to the current timeMarca el aviso de auto-resolución.autoResolutionNotifiedAt no es null.ninguno
returns null when the ticket does not existId inexistente.Devuelve null.ninguno

markOutOfHoursNotified​

TestEscenarioResultado esperadoMocks
sets outOfHoursNotifiedAt to the current timeMarca el aviso fuera de horario.outOfHoursNotifiedAt no es null.ninguno
returns null when the ticket does not existId inexistente.Devuelve null.ninguno
lets only one of two simultaneous calls claim the noticeDos llamadas simultáneas.Solo una devuelve resultado (la otra null).ninguno

reopenFromResolved​

TestEscenarioResultado esperadoMocks
clears outOfHoursNotifiedAt so the reopened ticket can be notified againTicket resolved con ambas marcas de aviso.Queda open con outOfHoursNotifiedAt y autoResolutionNotifiedAt en null.ninguno

findOrCreateForContact​

TestEscenarioResultado esperadoMocks
returns the existing open ticket instead of creating a new oneEl contacto ya tiene un ticket open.created: false y el mismo ticket.ninguno
creates a new ticket when the contact has no open/in-progress/csat-pending ticketContacto sin tickets.created: true y ticket open.ninguno
returns a RESOLVED ticket with csat.status=pending when the message content is a rating reply (regression)Ticket resolved con CSAT pending y mensaje 5.created: false y devuelve el ticket resuelto.ninguno
W2: creates/finds a ticket normally (does NOT match the resolved ticket) when csat.status=pending but the message content is NOT a rating replyCSAT pending y mensaje que no es una calificación.created: true y ticket open nuevo.ninguno
W2: creates a new ticket when csat.status=pending and no message content is given at allCSAT pending sin contenido de mensaje.created: true.ninguno
returns a RESOLVED ticket with csat.status=awaiting_comment instead of creating a new one, regardless of message contentCSAT awaiting_comment y cualquier mensaje.created: false y devuelve el ticket resuelto.ninguno
creates a new ticket when the only prior ticket is RESOLVED with csat not_applicable (regression: unaffected contacts)Único ticket previo resolved sin CSAT.created: true.ninguno

triggerCsat​

TestEscenarioResultado esperadoMocks
sets csat.status=pending with the trigger snapshot fieldstriggerCsat con triggeredBy, fechas y snapshots de agente y departamento.csat.status pending con todos los campos persistidos.ninguno

recordCsatRating​

TestEscenarioResultado esperadoMocks
sets rating, respondedAt and the given next statusCSAT pending, calificación 5, estado answered.rating 5, status answered y respondedAt correctos.ninguno

recordCsatComment​

TestEscenarioResultado esperadoMocks
sets the comment and closes the survey as answeredCSAT awaiting_comment con calificación 4.comment guardado y status answered.ninguno

expireCsatSurveys​

TestEscenarioResultado esperadoMocks
expires pending/awaiting_comment surveys past expiresAt and leaves others untouchedCuatro tickets: pending vencido, awaiting_comment vencido, pending vigente y answered vencido.Devuelve 2 modificados; los dos vencidos pasan a expired y los otros no cambian.ninguno

lockTicket​

TestEscenarioResultado esperadoMocks
sets lockedBy when the ticket is currently unlocked and owned by ownerIdTicket sin bloqueo del dueño.lockedBy es el dueño.ninguno
CAS: returns null when the ticket is already locked (no owner match on lockedBy:null)Ticket ya bloqueado por otro.Devuelve null.ninguno
returns null when the requester does not own the ticketSolicitante distinto del dueño.Devuelve null.ninguno

unlockTicket​

TestEscenarioResultado esperadoMocks
clears lockedBy when expectedLockedBy matches the current lock ownerexpectedLockedBy coincide con el titular.lockedBy queda null.ninguno
CAS: returns null when expectedLockedBy does not match the current lock ownerexpectedLockedBy distinto.Devuelve null.ninguno
force unlock (expectedLockedBy=null): clears lockedBy regardless of who holds itexpectedLockedBy null.Limpia lockedBy sin importar el titular.ninguno

auto-clear lockedBy on terminal states​

TestEscenarioResultado esperadoMocks
markResolved clears lockedBySe resuelve un ticket bloqueado.lockedBy null.ninguno
updateStatus clears lockedBy when the new status is a terminal one (closed)updateStatus(id, 'closed').lockedBy null.ninguno
updateStatus clears lockedBy when the new status is cancelledupdateStatus(id, 'cancelled').lockedBy null.ninguno
updateStatus does NOT clear lockedBy for a non-terminal status (inProgress)updateStatus(id, 'inProgress').lockedBy se conserva.ninguno
releaseToQueue clears lockedByreleaseToQueue(id, owner).lockedBy null.ninguno