Files
project.linkedcorp.net/API.md
T
2026-10-29 07:57:36 -05:00

23 KiB
Raw Blame History

API - project.linkedcorp.net

Base URL

  • Desarrollo: http://localhost:4019/api
  • Producción: https://project.linkedcorp.net/api

Autenticacion

  • Tipo: Bearer JWT (Authorization: Bearer <token>), emitido por /api/auth/login.
  • El backend revalida is_active del usuario en cada request (no solo la firma del JWT).
  • El JWT incluye sv y sid. Cada petición valida la cuenta y la fila activa de user_sessions. Cambiar la clave conserva la sesión actual y cierra las demás; restablecerla cierra todas. Una sesión revocada responde 401 con code: "SESSION_REVOKED"; los JWT antiguos sin sid requieren volver a iniciar sesión tras aplicar la migración 033.

Formato de respuesta

{
  "success": true,
  "data": {},
  "error": null
}

Edición concurrente

Los PUT de proyectos, comentarios, actividades, tareas, departamentos, usuarios, los cuatro catálogos, notas, tareas recurrentes, equipos y SMTP exigen editVersion (entero positivo) obtenido de la lectura correspondiente como edit_version. El PUT /api/company exige el mismo campo en multipart. La API responde 428 si falta o es inválido y 409 si otro guardado cambió el registro. En empresa y SMTP la respuesta GET incluye edit_version. Las reuniones siguen el mismo contrato (PUT /api/meetings/:id).

Agenda médica (v4.7.0)

Todas las rutas bajo /api/clinic exigen JWT, módulo clinic y fila activa en clinic_staff. Sin la fila se responde 403. scope=own solo accede a su médico; scope=all gestiona todos. Configuración exige además rol admin/superadmin. Fechas YYYY-MM-DD HH:mm:ss en hora civil America/Guayaquil; rangos de listado de máximo 93 días.

Método y ruta Uso
GET /staff/me alcance y condición de médico
GET /doctors médicos visibles con horario y tipos activos
GET /availability?doctorId=&date=&typeId= huecos de 15 minutos con duración del tipo
GET /appointments?from=&to=[&doctorId=][&patientId=] citas del rango, incluidas canceladas y eliminadas (deleted_at) para trazabilidad; patientId solo recepción
POST /appointments, PUT /appointments/:id crear o reprogramar/editar; PUT exige editVersion
POST /appointments/:id/delete {reason,editVersion} borrado lógico (solo recepción): libera el horario, conserva la fila con quién/cuándo/motivo
POST /appointments/:id/status {status,editVersion}; médico propio solo attended/no_show
POST /appointments/:id/cancel {reason,editVersion}, solo recepción
GET /blocks?from=&to=[&doctorId=] ocurrencias expandidas del rango
POST /blocks, PUT /blocks/:id bloqueo; {confirmAffected:true} para guardar si afecta citas
DELETE /blocks/:id?editVersion= eliminar bloqueo con versión
GET /patients?search=&page=&pageSize= búsqueda paginada, solo recepción
GET /patients/:id/appointments últimas 100 citas, solo recepción
POST /patients, PUT /patients/:id alta y edición, solo recepción; PUT exige editVersion
GET /config/staff, PUT /config/staff/:id personal y permiso interno
GET/PUT /config/schedules/:id tramos; PUT recibe {segments:[{weekday,startsTime,endsTime}]}
GET/POST /config/types, PUT /config/types/:id tipos de cita

Una cita fuera del horario o en choque devuelve 409 con motivo concreto. Un bloqueo afectado responde {requiresConfirmation:true,affected:[...]} y no se guarda sin confirmación. Las ediciones con versión obsoleta devuelven 409. Las altas, ediciones, cambios de estado y cancelaciones se auditan sin copiar datos de pacientes al detalle de auditoría.

Calendario de reuniones (v4.4.0)

Todas las rutas requieren JWT y módulo calendar. Las fechas son YYYY-MM-DD HH:mm:ss en hora civil America/Guayaquil (UTC-5). GET /api/meetings?from=&to=&page=&pageSize=[&projectId=] (sin projectId: solo reuniones que el usuario organiza o a las que fue invitado, cualquier rol; con projectId: todas las del proyecto si puede leerlo, si no 404) devuelve {items,total,page,pageSize} con ocurrencias expandidas (occurrence_start, occurrence_end); rango máximo 93 días y hasta 1500 ocurrencias por página (predeterminado: 1500). GET /api/meetings/activities?from=&to= devuelve hasta 1000 vencimientos de actividades donde el usuario es responsable (con is_closed y overdue). GET /api/meetings/tasks?from=&to=: tareas donde el usuario es responsable (activity_id, project_id, is_closed, overdue). GET /api/meetings/projects?from=&to=: proyectos donde es dueño o miembro, una fila por fecha en rango (milestone: start | end). Ambas requieren módulo projects. GET /api/meetings/recurring?from=&to= (requiere también el módulo recurring_tasks) devuelve los próximos vencimientos de las tareas recurrentes activas que el usuario creó o de las que es responsable, calculados al vuelo desde next_due_date con la misma regla que advanceCycle y cortados en end_date. Cada fila trae due_date, is_next (el ciclo pendiente real), overdue y can_open (si puede abrirla en Tareas recurrentes).

Todas las filas de calendario traen shared (lo creó/organiza otra persona y me incluye).

  • GET /api/meetings/:id: detalle, participantes con RSVP y excepciones.
  • POST /api/meetings: {title,description,startsAt,endsAt,allDay,location,videoUrl,projectId,reminderMinutes,participantUserIds,externalEmails,recurrence}.
  • PUT /api/meetings/:id: mismo cuerpo con editVersion (aplica a toda la serie).
  • DELETE /api/meetings/:id: cancelación lógica de toda la serie, con METHOD:CANCEL.
  • POST /api/meetings/:id/occurrences/:date/cancel: excluye una ocurrencia; date URL encoded.
  • PUT /api/meetings/:id/rsvp: {status:"accepted"|"declined"|"tentative"}; solo participante propio.

recurrence es null o {frequency:"daily"|"weekly"|"monthly",interval,weekdays,until,count}; weekdays usa 0=lunes a 6=domingo. Se exige exactamente uno de until o count y máximo 365 ocurrencias. Cuando hay choque de participantes, POST/PUT devuelve {requiresConfirmation:true,warnings:[nombres]} sin guardar; reenviar con confirmConflicts:true confirma. Correo y avisos se despachan en segundo plano; sin SMTP el correo se registra en consola de desarrollo. Antes de usar estas rutas se debe aplicar la migración 034.

Endpoints principales

Auth

  • POST /api/auth/login — { email, password } → { token, user }. Si el usuario no esta active (pending_verification/pending_approval) o is_active=0, 403 con mensaje especifico.
  • POST /api/auth/register — público. { name, email, password } → si es el primer usuario del sistema: { bootstrap: true, token, user } (superadmin, sesion directa); si no: { bootstrap: false } y se envia (o loguea, sin SMTP) un correo de verificación.
  • GET /api/auth/verify-email?token=... — público. Pasa el usuario a pending_approval y notifica in-app a los admin/superadmin activos.
  • GET /api/auth/invite?token=... — público. { email } de la invitación (para prellenar el formulario), o 400 si es inválida/expiró.
  • POST /api/auth/accept-invite — público. { token, name, password } → completa la cuenta creada por POST /api/projects/:id/members con { email } (ver arriba): elige nombre y clave, pasa a active directo (sin aprobación — quien invitó ya la vetó) y devuelve { token, user } con sesión iniciada, igual que el bootstrap del primer usuario.
  • POST /api/auth/forgot-password — público. { email } → respuesta siempre genérica.
  • POST /api/auth/reset-password — público. { token, password }.
  • PUT /api/auth/change-password — autenticado. { currentPassword, newPassword }, exige la clave actual (distinto de reset-password, que usa un token de correo sin sesión).
  • POST /api/auth/logout — revoca la sesión actual.
  • GET /api/auth/me — usuario actual
  • GET /api/auth/sessions — sesiones activas propias (id, navegador, SO, IP, fechas, current).
  • DELETE /api/auth/sessions/:id — cierra una sesión propia; la actual equivale a logout.
  • POST /api/auth/sessions/revoke-others — cierra las demás sesiones propias.

Usuarios (/api/users) — lectura para cualquier autenticado; escritura solo Administrador/Superadministrador

  • GET /api/users · POST /api/users · PUT /api/users/:id · PATCH /api/users/:id/active
  • GET /api/users/:id/sessions · DELETE /api/users/:id/sessions/:sessionId · POST /api/users/:id/sessions/revoke-all — solo admin/superadmin; un admin no puede administrar sesiones de un superadmin. Desactivar un usuario también revoca sus sesiones.
  • POST /api/users/:id/approve — { roleId?, departmentId?, editVersion }, solo sobre usuarios pending_approval; también rechaza cambios concurrentes con 409.
  • POST /api/users/:id/reject — borra al usuario pending_approval (se audita el email antes)
  • GET /api/users/:id/modules · PUT /api/users/:id/modules { modules: [...] } — solo Superadministrador. Módulos: projects, calendar, recurring_tasks, notes, vault, devices, organization. No aplica a un superadmin (400). GET /api/auth/me y el login devuelven modules (efectivos del usuario). Las rutas de un módulo deshabilitado responden 403; la búsqueda global devuelve vacío.

Departamentos (/api/departments)

  • GET /api/departments (árbol) · POST /api/departments · PUT /api/departments/:id · DELETE /api/departments/:id

Proyectos (/api/projects)

  • GET /api/projects (filtros: statusId, priorityId, ownerUserId, dueSoon=true, progress=hide|only, q, trash=true solo superadmin; page desde 1 y pageSize 1–100, predeterminado 20) → { items, total, page, pageSize } · POST /api/projects.
  • GET /api/projects/options → { id, code, name }[] accesibles al usuario para selectores.
  • GET /api/projects/:id · PUT /api/projects/:id — para editar, enviar editVersion obtenido de GET; 428 sin versión, 409 si otra edición la cambió.
  • DELETE /api/projects/:id — eliminación lógica (deleted_at/deleted_by), mismo permiso que editar el proyecto. POST /api/projects/:id/restore y DELETE /api/projects/:id/permanent (borrado físico, irreversible, arrastra actividades/comentarios/adjuntos) — ambos solo superadmin; permanent exige que el proyecto ya esté en la papelera (400 si no).
  • POST /api/projects/:id/members — { userId } (usuario existente) o { email } (si el correo no pertenece a nadie todavía, crea una cuenta invited y le manda un correo de invitación para que complete su registro; la fila de project_members ya queda creada de una vez). roleInProject opcional: 'admin' = administrador del proyecto. Responde { members, invited }.
  • PUT /api/projects/:id/members/:userId/role ({ admin: true|false }, otorga o quita el rol de administrador de ese proyecto — un proyecto puede tener uno o más administradores, sin importar el rol global del usuario; nunca toca al owner) · DELETE /api/projects/:id/members/:userId

Actividades (/api/activities)

  • GET /api/projects/:projectId/activities · POST /api/projects/:projectId/activities
  • GET /api/activities/:id · PUT /api/activities/:id
  • DELETE /api/activities/:id — bloqueado con 400 si la actividad tiene tareas (hay que eliminarlas primero); limpia comentarios/adjuntos de la actividad de paso (no hay FK física cruzada). Arrastra activity_dependencies por ON DELETE CASCADE.
  • POST /api/activities/:id/dependencies · DELETE /api/activities/:id/dependencies/:dependsOnId
  • PUT /api/projects/:projectId/activities/reorder — { ids: [...] } (orden manual, arrastrar con el mouse); posiciones 1..N, nunca 0 (0 = "nunca reordenado a mano", ver sort_order).
  • PUT /api/activities/:id/move — { projectId }. Mueve la actividad a otro proyecto; exige permiso de escritura en ambos proyectos. Tareas/comentarios/adjuntos se mueven solos (referencian activity_id, no project_id). Las dependencias existentes (en cualquiera de los dos sentidos) se eliminan — solo tienen sentido dentro del mismo proyecto. sort_order vuelve a 0 en el destino.
  • POST /api/activities/:id/convert-to-project — { projectName, taskActivityName }. Crea un proyecto nuevo (projectName) y, dentro, una actividad nueva (taskActivityName) que recibe las tareas/comentarios/adjuntos de la actividad original; la original se borra al final (ya sin tareas). Exige el mismo permiso que crear un proyecto (no solo escribir la actividad). Responde { project, activity } (201).

Tareas (/api/activities/:activityId/tasks)

  • GET · POST · PUT /api/tasks/:id · PATCH /api/tasks/:id/done · DELETE /api/tasks/:id
  • PUT /api/activities/:activityId/tasks/reorder — { ids: [...] } (mismo mecanismo de orden manual que las actividades).

Tareas recurrentes (/api/recurring-tasks) — menú propio, independiente de Proyectos

  • GET · POST · PUT /api/recurring-tasks/:id · DELETE /api/recurring-tasks/:id
  • POST /api/recurring-tasks/:id/complete — cierra el ciclo actual y calcula el próximo vencimiento sumando la periodicidad (interval_value + interval_unit).
  • POST /api/recurring-tasks/from-task/:taskId — crea una tarea recurrente a partir de una tarea puntual de una actividad (la original no se borra); mismo body que POST /.
  • Body: { name, description?, projectId?, responsibleUserId?, intervalUnit: 'days'|'months'|'years', intervalValue, nextDueDate, endDate?, reminderDaysBefore, reminderHour: 0-23 }. endDate vacío = se repite sin fin; al completar un ciclo que la supera, is_active pasa a false solo.
  • Recordatorio por correo: un job en el propio proceso del backend revisa cada 15 minutos (src/jobs/recurringTaskReminders.js) y manda el correo cuando next_due_date - reminderDaysBefore cae hoy, a la hora reminderHour. Un solo envío por ciclo (last_reminder_sent_at, se limpia al completar el ciclo).

Comentarios (/api/comments)

  • PUT /api/comments/:id — { message, editVersion }; 428 sin versión y 409 si cambió.
  • GET /api/comments?entityType=project&entityId=1 · POST /api/comments

Adjuntos (/api/attachments)

  • GET /api/attachments?entityType=activity&entityId=1 · POST /api/attachments?entityType=activity&entityId=1 (multipart; entidad y permiso se validan antes de recibir el archivo)
  • GET /api/attachments/:id/download · DELETE /api/attachments/:id

Notas (/api/notes)

  • GET /api/notes — solo las del usuario autenticado, ordenadas por updated_at descendente.
  • POST /api/notes — { content }. PUT /api/notes/:id — { content, editVersion }. DELETE /api/notes/:id.
  • Siempre privadas: una nota de otro usuario responde 404 (no 403, para no revelar que existe). Sin restricción de rol — cualquier autenticado tiene las suyas.

Equipos (/api/devices) — superadmin / admin / it_manager (tokens: solo superadmin / admin)

  • GET /api/devices?q=&kind=&status=&stale=1&diskFull=1 · GET /api/devices/:id (con hardware, red, software e historial) · PUT /api/devices/:id — { status, assignedUserId, departmentId, notes, editVersion }.
  • GET /api/devices/dashboard — KPIs y top (SO, tipo, software). GET /api/devices/software · GET /api/devices/software/devices?name= — software transversal.
  • GET /api/devices/access — {canView, canEdit, canManage} del usuario (lo usa el menú). Lectura (GET de dashboard/listado/detalle/software) también para usuarios con acceso compartido.
  • GET/POST /api/devices/viewers · DELETE /api/devices/viewers/:userId — compartir Equipos (admin/superadmin).
  • GET /api/devices/:id/raw — último inventario crudo del agente.
  • GET /api/devices/tokens/:id/secret — token en claro (descifrado) para armar el instalador; audita reveal.
  • GET/POST /api/devices/tokens · PATCH/DELETE /api/devices/tokens/:id — tokens de ingesta (el token en claro solo se devuelve en el POST).

Ingesta del agente GLPI (sin JWT; token de ingesta)

  • POST /api/inventory (alias POST /front/inventory.php) — cuerpo JSON/XML, opcionalmente comprimido (gzip/zlib). Auth: password Basic / Bearer / X-Ingest-Token / ?token=. 401 sin token válido, 400 formato inválido. Responde {"status":"ok"} (o <REPLY/> en XML).

Notificaciones (/api/notifications)

  • GET /api/notifications · PATCH /api/notifications/:id/read · PATCH /api/notifications/read-all

Búsqueda (/api/search)

  • GET /api/search?q=texto — global: proyectos, actividades y tareas (de cualquier proyecto) que coincidan. { projects: [...], activities: [...], tasks: [...] }, cada item de actividades/tareas incluye los datos de su proyecto (y actividad, para tareas) para armar la migaja de pan en el cliente. q con menos de 2 caracteres devuelve listas vacías. Máx 20 resultados por tipo.
  • GET /api/search?q=texto&projectId=1 — acotado a un proyecto: solo { activities, tasks } de ESE proyecto (sin el dato del proyecto, ya es el contexto).

Actualizaciones (/api/updater) — solo Superadministrador

  • GET /api/updater/status — compara la VERSION local contra CHANGELOG.md de main en Gitea (leído de forma anónima por HTTPS, sin token — requiere repo público y vista anónima habilitada en el servidor de Gitea). { currentVersion, upToDate, latestVersion, overallCriticality?, hasMigrations, pending: [{ version, date, criticality, summary }] }. criticality se deriva del dígito de versión que cambió (X.Y.Z: 1ro=alta, 2do=media, 3ro=baja), nunca se etiqueta a mano. hasMigrations compara los archivos de backend/database/migrations/ locales contra los de main. Es solo informativo — no aplica ningún cambio, la actualización real sigue siendo manual.

IA (/api/ai) — requiere clave configurada por el superadmin

  • GET /api/ai/status → { enabled } (cualquier autenticado).
  • POST /api/ai/projects/:projectId/plan { description?, lang? } → { activities: [{ name, description, durationDays, tasks: [] }] }. Requiere módulo Proyectos y escritura en el proyecto; 30/hora.
  • POST /api/ai/projects/:projectId/plan/apply { activities } → { created } (crea actividades y tareas).
  • POST /api/ai/new-project/propose { objective, startDate, lang? } → { name, objective, description, projectTypeId, projectTypeName, estimatedEndDate, activities }; 400 si el objetivo es corto o la fecha inválida (sin llamar a la IA), 422 si no es un proyecto. 30/hora.
  • POST /api/ai/new-project { objective, startDate, name, description?, projectTypeId?, requestingDepartmentId, activities } → { id, code }. Roles que crean proyectos.
  • POST /api/ai/projects/:projectId/report { lang? } → { project, generatedAt, summary, health, achievements, delays: [{ item, reason }], risks, nextSteps }. Requiere lectura del proyecto; 30/hora.
  • GET /api/settings/ai/usage?period=month|all → { period, users: [{ userId, userName, calls, inputTokens, outputTokens, costUsd, lastUsed }], total } — solo superadmin.
  • GET/PUT /api/settings/ai { apiKey?, model, editVersion } y POST /api/settings/ai/test — solo superadmin; la clave nunca se devuelve.

Log de errores (/api/error-logs)

  • POST /api/error-logs — público (sin auth, rate-limited a 30/15min), lo usa el frontend para reportar errores propios (incluso sin sesión). { message, stack?, url?, severity? } (severity: alta/media/baja; si falta o es inválida, cae en media). Si llega un token válido en Authorization, se guarda el usuario asociado, sin exigirlo.
  • GET /api/error-logs?source=backend|frontend&severity=alta|media|baja&limit= — solo Superadministrador. Lista los errores más recientes (máx 1000 guardados, se recorta solo). Backend guarda stack/url/method/usuario automáticamente en cada excepción 500 no manejada (ver errorHandler.js) con severity=alta siempre — nunca se etiqueta a mano.
  • DELETE /api/error-logs — solo Superadministrador. Borra todo el historial.

Dashboard (/api/dashboard)

  • GET /api/dashboard/summary — KPIs (activos, atrasados, pendientes/vencidas/hoy, avance promedio)
  • GET /api/dashboard/calendar?month=2026-09 — actividades por fecha límite
  • GET /api/projects/:id/gantt — actividades con fechas/avance para el Gantt

Catálogos (/api/catalogs) — lectura general, escritura solo Superadministrador

  • GET/POST/PUT/DELETE /api/catalogs/project-statuses
  • GET/POST/PUT/DELETE /api/catalogs/activity-statuses
  • GET/POST/PUT/DELETE /api/catalogs/priorities
  • GET/POST/PUT/DELETE /api/catalogs/project-types

Auditoría (/api/audit) — solo Superadministrador / Administrador / Jefe de TI

  • GET /api/audit?entityType=project&entityId=1

Configuración SMTP (/api/settings) — solo Superadministrador

  • GET /api/settings/smtp — { host, port, user, from, configured, edit_version } (nunca la clave)
  • PUT /api/settings/smtp — { host, port, user, from, pass?, editVersion } (pass vacío = no cambiarla)
  • POST /api/settings/smtp/test — { host, port, user, from, pass?, to } (pass vacío = usa la guardada). Envía un correo real con los valores del formulario (aunque no se hayan guardado) y responde { sent: true } o 400 con el error real del servidor SMTP. Rate-limited (10/15min).

Marca de la empresa (/api/company)

  • GET /api/company — público (sin auth, lo usan login/registro/recuperación). { name, hasLogo, logoVersion }; name por defecto "LinkedCorp" si nunca se configuró.
  • GET /api/company/logo — público. Devuelve el archivo de imagen del logo, o 404 si no hay ninguno configurado (el cliente cae al logo estático embebido).
  • PUT /api/company — solo Superadministrador, multipart: name? (texto), editVersion y logo? (archivo, PNG/JPG/WEBP, máx 3MB; se comprueba la firma real del archivo). Al subir un logo nuevo se borra el archivo anterior (nunca quedan huérfanos). Guardado en system_settings (mismo patrón que SMTP), sin tabla nueva.

Errores esperados

  • 400 validación
  • 401 no autenticado / token inválido o expirado
  • 403 sin permisos para la acción (según rol)
  • 404 no encontrado (incluye cuando el recurso existe pero no pertenece al usuario/alcance)
  • 500 error interno (sanitizado, sin detalle de stack en producción)

Cambio de correo (v4.6.0)

  • GET /api/account/email-change: última solicitud propia (requiere JWT).
  • POST /api/account/email-change: { newEmail, currentPassword }; crea una solicitud tras validar la clave, con límite de 20 solicitudes por 15 minutos.
  • DELETE /api/account/email-change: cancela la solicitud propia pendiente o aprobada.
  • GET /api/users/email-change-requests: pendientes que el admin/superadmin puede decidir.
  • POST /api/users/email-change-requests/:id/approve: aprueba y envía el enlace de confirmación al correo nuevo.
  • POST /api/users/email-change-requests/:id/reject: { reason } obligatorio; avisa al correo actual.
  • POST /api/auth/email-change/self-approve: { token }; enlace público de aprobación exclusiva del superadmin, un solo uso.
  • POST /api/auth/email-change/confirm: { token }; enlace público de confirmación del correo nuevo. Revoca todas las sesiones al aplicar.

Los endpoints públicos tienen límite de 20 solicitudes por 15 minutos. Los enlaces duran 24 horas; las solicitudes abiertas vencen a los 7 días. Los tokens se almacenan solo como SHA-256.