Files
project.linkedcorp.net/API.md
T
Milton Gonzalez 043433bf9b feat: convertir una actividad en proyecto (con sus tareas)
Una tarea siempre necesita una actividad contenedora, asi que convertir crea DOS cosas: el
proyecto nuevo y, dentro, una actividad nueva que recibe las tareas -- y comentarios/adjuntos --
de la actividad original. Se piden ambos nombres (precargados con el nombre actual) antes de
confirmar. La actividad original se borra al final, ya vacia de tareas (no choca con el bloqueo
de removeActivity). Exige el mismo permiso que crear un proyecto, no solo escribir la actividad.

Nuevo POST /api/activities/:id/convert-to-project. Probado de punta a punta en dev: prioridad
heredada, tareas y comentario movidos correctamente, actividad original eliminada, navega al
proyecto nuevo al terminar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 08:59:16 -05:00

11 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_active del 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 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? }, solo sobre usuarios pending_approval
  • POST /api/users/:id/reject — borra al usuario pending_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/projects
  • GET /api/projects/:id · PUT /api/projects/:id
  • 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)

  • 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

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.

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 } (nunca la clave)
  • PUT /api/settings/smtp — { host, port, user, from, pass? } (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) y logo? (archivo, PNG/JPG/WEBP/SVG, máx 3MB). 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)