d60b4b1d77
Admin/superadmin dan acceso de solo lectura (Resumen, Equipos, Software) a otros usuarios; el backend valida el acceso en cada endpoint y el menu aparece solo a quien lo tenga. Migracion 026 (device_viewers). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
13 KiB
13 KiB
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_activedel usuario en cada request (no solo la firma del JWT).
Formato de respuesta
{
"success": true,
"data": {},
"error": null
}
Endpoints principales
Auth
POST /api/auth/login—{ email, password }→{ token, user }. Si el usuario no estaactive(pending_verification/pending_approval) ois_active=0,403con 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 apending_approvaly notifica in-app a losadmin/superadminactivos.GET /api/auth/invite?token=...— público.{ email }de la invitación (para prellenar el formulario), o400si es inválida/expiró.POST /api/auth/accept-invite— público.{ token, name, password }→ completa la cuenta creada porPOST /api/projects/:id/memberscon{ email }(ver arriba): elige nombre y clave, pasa aactivedirecto (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 dereset-password, que usa un token de correo sin sesión).POST /api/auth/logoutGET /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/activePOST /api/users/:id/approve—{ roleId?, departmentId? }, solo sobre usuariospending_approvalPOST /api/users/:id/reject— borra al usuariopending_approval(se audita el email antes)
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: estado, responsable, prioridad;trash=true= papelera, solo superadmin) ·POST /api/projectsGET /api/projects/:id·PUT /api/projects/:idDELETE /api/projects/:id— eliminación lógica (deleted_at/deleted_by), mismo permiso que editar el proyecto.POST /api/projects/:id/restoreyDELETE /api/projects/:id/permanent(borrado físico, irreversible, arrastra actividades/comentarios/adjuntos) — ambos solo superadmin;permanentexige 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 cuentainvitedy le manda un correo de invitación para que complete su registro; la fila deproject_membersya queda creada de una vez).roleInProjectopcional:'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 alowner) ·DELETE /api/projects/:id/members/:userId
Actividades (/api/activities)
GET /api/projects/:projectId/activities·POST /api/projects/:projectId/activitiesGET /api/activities/:id·PUT /api/activities/:idDELETE /api/activities/:id— bloqueado con400si la actividad tiene tareas (hay que eliminarlas primero); limpia comentarios/adjuntos de la actividad de paso (no hay FK física cruzada). Arrastraactivity_dependenciesporON DELETE CASCADE.POST /api/activities/:id/dependencies·DELETE /api/activities/:id/dependencies/:dependsOnIdPUT /api/projects/:projectId/activities/reorder—{ ids: [...] }(orden manual, arrastrar con el mouse); posiciones 1..N, nunca 0 (0 = "nunca reordenado a mano", versort_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 (referencianactivity_id, noproject_id). Las dependencias existentes (en cualquiera de los dos sentidos) se eliminan — solo tienen sentido dentro del mismo proyecto.sort_ordervuelve 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/:idPUT /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/:idPOST /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 quePOST /.- Body:
{ name, description?, projectId?, responsibleUserId?, intervalUnit: 'days'|'months'|'years', intervalValue, nextDueDate, endDate?, reminderDaysBefore, reminderHour: 0-23 }.endDatevacío = se repite sin fin; al completar un ciclo que la supera,is_activepasa afalsesolo. - Recordatorio por correo: un job en el propio proceso del backend revisa cada 15 minutos
(
src/jobs/recurringTaskReminders.js) y manda el correo cuandonext_due_date - reminderDaysBeforecae hoy, a la horareminderHour. Un solo envío por ciclo (last_reminder_sent_at, se limpia al completar el ciclo).
Comentarios (/api/comments)
GET /api/comments?entityType=project&entityId=1·POST /api/comments
Adjuntos (/api/attachments)
GET /api/attachments?entityType=activity&entityId=1·POST /api/attachments(multipart)GET /api/attachments/:id/download·DELETE /api/attachments/:id
Notas (/api/notes)
GET /api/notes— solo las del usuario autenticado, ordenadas porupdated_atdescendente.POST /api/notes—{ content }.PUT /api/notes/:id—{ content }.DELETE /api/notes/:id.- Siempre privadas: una nota de otro usuario responde
404(no403, 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 }.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 (GETde 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; auditareveal.GET/POST /api/devices/tokens·PATCH/DELETE /api/devices/tokens/:id— tokens de ingesta (el token en claro solo se devuelve en elPOST).
Ingesta del agente GLPI (sin JWT; token de ingesta)
POST /api/inventory(aliasPOST /front/inventory.php) — cuerpo JSON/XML, opcionalmente comprimido (gzip/zlib). Auth: password Basic /Bearer/X-Ingest-Token/?token=.401sin token válido,400formato 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.qcon 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 laVERSIONlocal contraCHANGELOG.mddemainen 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 }] }.criticalityse deriva del dígito de versión que cambió (X.Y.Z: 1ro=alta, 2do=media, 3ro=baja), nunca se etiqueta a mano.hasMigrationscompara los archivos debackend/database/migrations/locales contra los demain. Es solo informativo — no aplica ningún cambio, la actualización real sigue siendo manual.
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 enmedia). Si llega un token válido enAuthorization, 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 guardastack/url/method/usuario automáticamente en cada excepción 500 no manejada (vererrorHandler.js) conseverity=altasiempre — 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ímiteGET /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-statusesGET/POST/PUT/DELETE /api/catalogs/activity-statusesGET/POST/PUT/DELETE /api/catalogs/prioritiesGET/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 }(nunca la clave)PUT /api/settings/smtp—{ host, port, user, from, pass? }(passvacío = no cambiarla)POST /api/settings/smtp/test—{ host, port, user, from, pass?, to }(passvacío = usa la guardada). Envía un correo real con los valores del formulario (aunque no se hayan guardado) y responde{ sent: true }o400con 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 };namepor defecto "LinkedCorp" si nunca se configuró.GET /api/company/logo— público. Devuelve el archivo de imagen del logo, o404si no hay ninguno configurado (el cliente cae al logo estático embebido).PUT /api/company— solo Superadministrador, multipart:name?(texto) ylogo?(archivo, PNG/JPG/WEBP/SVG, máx 3MB). Al subir un logo nuevo se borra el archivo anterior (nunca quedan huérfanos). Guardado ensystem_settings(mismo patrón que SMTP), sin tabla nueva.
Errores esperados
400validación401no autenticado / token inválido o expirado403sin permisos para la acción (según rol)404no encontrado (incluye cuando el recurso existe pero no pertenece al usuario/alcance)500error interno (sanitizado, sin detalle de stack en producción)