Nueva marca configurable desde Configuracion -> Empresa (solo superadmin): nombre y logo,
guardados en system_settings (mismo patron que SMTP, sin tabla nueva). Nuevo endpoint publico
GET /api/company (+ /logo) para que login/registro/recuperacion de clave puedan mostrar la marca
antes de iniciar sesion. Sin configurar, se ve "LinkedCorp" y el logo estatico de siempre.
Probado en dev (build + Playwright: panel de Configuracion, sidebar expandido/compacto, login
publico), sin desplegar a produccion por pedido explicito del usuario -- este commit sirve de
punto de control en git antes de decidir el despliegue.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
El pie del menu lateral mostraba el texto fijo "v0.1.0" en vez de la version real (se lee ahora
de package.json via import JSON de Vite). El logo de LinkedCorp se mueve del encabezado del menu
al pie, sobre el numero de version, en modo expandido y compacto.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hook reutilizable useDragReorder (drag & drop nativo HTML5, sin
libreria nueva) usado en dos lugares: ListView (opt-in via onReorder,
deshabilitado mientras haya un ordenamiento por columna activo) para
el listado de actividades de un proyecto, y el listado de tareas de
una actividad. Orden persistido en sort_order (activities y tasks,
migracion 012, posiciones siempre 1..N).
Fix real encontrado durante las pruebas: sincronizar el estado interno
del hook por la REFERENCIA del arreglo de entrada entraba en loop
infinito de renders cuando el llamador pasa "algo || []" (un arreglo
nuevo en cada render mientras los datos cargan). Corregido para
sincronizar por el contenido (ids), no por referencia.
Probado en dev con curl (persistencia del orden) y Playwright
(arrastrar filas en el navegador, orden visible y guardado
correctamente, sin errores de consola tras el fix).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No existia forma de borrar una actividad. Agregado DELETE
/api/activities/:id con boton "Eliminar" en el listado (popup de
confirmacion). Si tiene tareas, se bloquea con 400 y mensaje claro en
vez de arrastrar todo en cascada sin avisar.
De paso: la logica de borrar adjunto (fila + archivo en disco) se
extrajo a un attachments/service.js reutilizable, usado tanto por el
borrado individual como por el borrado en cascada de una actividad
(evita archivos huerfanos). Comentarios de la actividad tambien se
limpian (sin FK fisica cruzada, entity_type/entity_id es polimorfico).
Probado en dev con curl (bloquea con tarea, permite sin tareas, 404
tras borrar) y Playwright (mensaje de advertencia visible).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Columna con fecha y hora, orden por defecto (mas reciente primero) en
vez de fecha de creacion.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
El backend ya soportaba renombrar una tarea (PUT /api/tasks/:id), solo
faltaba el boton en la UI. Edicion en linea: input + guardar (Enter o
icono) / cancelar (Escape o icono).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reportado en produccion: el icono nativo del input type="date" se
dibujaba negro sobre fondo oscuro. Se agrega color-scheme: dark para
esos inputs cuando data-theme="dark", asi el navegador elige un icono
claro automaticamente. Verificado en dev con Playwright.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Vacio = se repite sin fin (comportamiento previo). Al completar un
ciclo cuyo proximo vencimiento supera "hasta", la tarea se desactiva
sola. Probado en dev con curl (3 ciclos seguidos, se desactiva justo
al superar la fecha) y validacion de que "hasta" no puede ser anterior
al proximo vencimiento.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Menu propio en la barra lateral, independiente de Proyectos:
- Periodicidad configurable (cada N dias/meses/años), proximo
vencimiento, responsable, proyecto de origen opcional.
- "Completar ciclo" avanza el proximo vencimiento sumando la
periodicidad y limpia el recordatorio ya enviado.
- Recordatorio por correo automatico N dias antes del vencimiento
(default 7), a una hora del dia configurable. Un job en el propio
proceso del backend (setInterval cada 15 min) revisa y envia, sin
infraestructura nueva (cron del SO, cola, etc).
- Desde la pestaña Tareas de una actividad, cada tarea tiene un boton
"Convertir en recurrente" que la promueve (la tarea original no se
borra).
Probado en dev con curl (crear, completar ciclo avanza next_due_date,
promover desde una tarea real, disparo real del job de recordatorios
verificado forzando fecha/hora, segunda corrida no reenvia) y Playwright
(listado, popup de creacion, popup de promocion anidado sobre el detalle
de actividad) sin errores de consola.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
El backend ya soportaba filtrar por statusId/priorityId; solo faltaba
exponerlo en la UI. "Proximos a vencer" es client-side, ventana de 7
dias (misma definicion que el KPI del dashboard).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
canWriteProject/ListView ya soportaba ordenar por cualquier columna con
clic en el encabezado; solo Prioridad ordenaba alfabetico por el texto
en vez de por severidad real. Se agrega pr.level a la consulta y
sortValue en la columna.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migracion 006 de una sola vez (marcador en system_settings, no se repite
en deploys futuros): reemplaza el proyecto de prueba "Sistema ERP" por
los 6 proyectos reales de TI clasificados a partir del listado que
compartio el usuario, y borra la cuenta pflores@hotmail.es (autorregistro
nunca verificado). Probado en dev incluyendo la idempotencia (un proyecto
creado despues de correr la migracion sobrevive a un segundo "npm run
migrate").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Antes solo se podia rechazar un usuario en pending_approval. Una cuenta
autorregistrada que nunca confirmo su correo quedaba en
pending_verification para siempre, sin ninguna accion disponible en
Usuarios para quitarla.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
"Agregar dependencia" no daba ninguna señal cuando el combo quedaba vacio
(proyecto con una sola actividad, o ya se agregaron todas las demas) --
se podia percibir como que el boton "no hacia nada". El mecanismo en si
funcionaba bien (verificado con curl y Playwright con 2+ actividades).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Con 001_initial.sql creando `tasks` directamente para instalaciones
nuevas, el destino ya existe para cuando corre 005 en una instalacion
que aun tenia `subtasks` -- RENAME TABLE fallaba con "Table already
exists" (se vio en produccion, sin perdida de datos: subtasks estaba
vacia). Corregido para copiar los datos de subtasks a tasks (con id
explicito) y borrar subtasks, corrigiendo el AUTO_INCREMENT despues.
Probado con datos reales simulados en dev, idempotente.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Actividad -> Tarea, sin nivel "sub" intermedio (no hay una entidad "Tarea"
de la que estas fueran sub-, asi que el prefijo sobraba). Tabla subtasks
-> tasks (RENAME TABLE idempotente en la migracion 005; 001_initial.sql
tambien actualizado para que una instalacion nueva cree la tabla ya con
el nombre final, sin pasar por el nombre viejo). Modulo backend, rutas,
servicio frontend, pestaña del detalle de actividad, i18n y documentacion
actualizados. Sin cambios de comportamiento, solo nombres.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Al agregar un miembro, el popup ahora permite elegir un usuario existente
o escribir un correo nuevo. Si ese correo no pertenece a nadie, se crea
una cuenta en estado 'invited' (nombre/clave placeholder) y se le manda
un correo con un enlace de un solo uso para que complete su registro
(elige su nombre y clave). La fila de project_members se crea de una vez
al invitar, asi que en cuanto acepta ya es miembro sin pasos adicionales.
A diferencia del autorregistro publico, no pasa por aprobacion de un
admin -- quien lo agrego a su proyecto ya lo vetó.
Nuevos endpoints publicos GET /api/auth/invite y POST /api/auth/accept-invite.
Nueva pagina AcceptInvitePage (/aceptar-invitacion). Migracion 004 agrega
el status 'invited' al enum de users.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Icono derivado del original de LinkedCorp (circulo azul #1C55FF, red de
nodos) con una insignia en el acento de la app (#0078D4) para identificar
el modulo de Proyectos. Actualiza logoLinked1.png, logoLinkedCorpTxt.png
y logoLinkedCorpTxtBlack.png (sidebar y login, temas oscuro/claro) y
agrega favicon.svg/png + apple-touch-icon.png (antes no habia favicon).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Un proyecto puede tener uno o mas administradores (role_in_project=
'admin' en project_members), independiente del rol global del usuario.
Nuevo PUT /api/projects/:id/members/:userId/role; canWriteProject lo
reconoce ademas del comportamiento previo (admin/superadmin/it_manager
global, o project_lead miembro). UI: casilla al agregar miembro + boton
de escudo para promover/degradar (nunca toca al Dueño).
- Menu "Configuracion" (apariencia, catalogos, SMTP) visible y accesible
solo para el superadmin: oculto en el nav y con guarda en la ruta
/configuracion para quien la teclee directo. Escritura de catalogos
restringida de [superadmin, admin] a solo superadmin para que el
backend sea consistente con la UI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- DELETE /api/projects/🆔 eliminacion logica (deleted_at/deleted_by), mismo
permiso que editar el proyecto. Popup de confirmacion antes de aplicarla.
- Papelera (GET /api/projects?trash=true, POST /:id/restore, DELETE
/:id/permanent) restringida al superadmin; el borrado fisico exige que el
proyecto ya este en la papelera y arrastra actividades/comentarios/adjuntos
por ON DELETE CASCADE.
- Dashboard (KPIs, actividades pendientes/calendario, proyectos por estado)
excluye proyectos eliminados logicamente.
- Columna "Creado" en el listado de proyectos, ordenado por defecto de mas
nuevo a mas antiguo (ListView admite initialSort ademas del orden por clic
en cabecera que ya tenia).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Componente reutilizable PasswordInput (icono de ojo) usado en login, registro,
restablecer clave, cambiar clave, crear usuario y la clave SMTP de
Configuracion, para que el usuario pueda verificar que la esta escribiendo
correctamente.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
trim() no corregia un espacio colado a mitad del host (ej. "mail.linkedcorp. net"),
que es exactamente el caso real que seguia fallando en produccion tras el fix
anterior. Un dominio o correo nunca lleva espacios: ahora se remueven todos.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Un espacio de mas en el host (tecleado por error, p.ej. "mail.linkedcorp. net")
rompia la resolucion DNS sin ningun indicio visual del problema. Ahora se
recorta con trim() antes de guardar o de probar, tanto en updateSmtpConfig
como en testSmtpConfig.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>