La IA consulta solo el nombre del procesador y devuelve el año (el mas antiguo si hay dos). Migracion 039. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
24 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). - El JWT incluye
svysid. Cada petición valida la cuenta y la fila activa deuser_sessions. Cambiar la clave conserva la sesión actual y cierra las demás; restablecerla cierra todas. Una sesión revocada responde401concode: "SESSION_REVOKED"; los JWT antiguos sinsidrequieren volver a iniciar sesión tras aplicar la migración033.
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).
Conexiones externas
/api/integrations (JWT). Superadmin: GET/POST /connections, PUT /connections/:id,
POST /connections/:id/test, GET /connections/:id/browse?source=&table= (fuentes, tablas,
estructura + 20 filas), PUT /sources/:purpose (patients/employees, valida el mapeo contra la
estructura real), POST /sources/patients/sync {full}. Admin y superadmin: GET /sources (el admin
solo ve estado), GET /employees[?available=1], POST /employees/sync, GET /providers?search= (busca en la fuente
Proveedores, marca employee_id), POST /providers/:codigo/import {lastNames, firstNames, position}. El token nunca se devuelve.
En la Agenda (recepción): GET /api/clinic/patients/source, GET /api/clinic/patients/external?search=
(modo en vivo) y POST /api/clinic/patients/external/:codigo/import. POST /api/users acepta
employeeId (Desde nómina).
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 coneditVersion(aplica a toda la serie).DELETE /api/meetings/:id: cancelación lógica de toda la serie, conMETHOD:CANCEL.POST /api/meetings/:id/occurrences/:date/cancel: excluye una ocurrencia;dateURL 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 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/logout— revoca la sesión actual.GET /api/auth/me— usuario actualGET /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/activeGET /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 usuariospending_approval; también rechaza cambios concurrentes con409.POST /api/users/:id/reject— borra al usuariopending_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/mey el login devuelvenmodules(efectivos del usuario). Las rutas de un módulo deshabilitado responden403; 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=truesolo superadmin;pagedesde 1 ypageSize1–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, enviareditVersionobtenido deGET;428sin versión,409si 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/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)
PUT /api/comments/:id—{ message, editVersion };428sin versión y409si 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 porupdated_atdescendente.POST /api/notes—{ content }.PUT /api/notes/:id—{ content, editVersion }.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, 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 (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).POST /api/devices/:id/cpu-year→ equipo concpu_year: la IA consulta solo el nombre del procesador y devuelve el año de lanzamiento (el más antiguo si hay dos). Roles que editan equipos;400sin procesador o IA sin configurar,422si no se pudo determinar; 30/hora.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.
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 };400si el objetivo es corto o la fecha inválida (sin llamar a la IA),422si 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 }yPOST /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 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, edit_version }(nunca la clave)PUT /api/settings/smtp—{ host, port, user, from, pass?, editVersion }(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),editVersionylogo?(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 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)
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.