Files
project.linkedcorp.net/API.md
T
2026-10-23 18:19:24 -05:00

16 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 (versión de sesión). Cambiar o restablecer la clave revoca los JWT anteriores; se responde 401 y el usuario vuelve a iniciar sesión.

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.

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
  • GET /api/auth/me — usuario actual

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
  • 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, 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)