043433bf9b
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>
11 KiB
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_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
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.
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)