dec8891506
Panel nuevo en Configuracion ("Errores del sistema", solo superadmin): errores de backend
(excepciones 500 no manejadas, con stack/ruta/metodo/usuario) y de frontend (errores JS y
promesas sin catch en el navegador del usuario, antes invisibles para cualquiera mas alla de esa
persona). Filtro por origen, boton para limpiar el historial.
Error Boundary de React: si algo rompe el render, ya no queda pantalla en blanco -- aviso simple
con boton de recargar, y el error queda reportado igual.
Nuevo error_logs (tabla, se recorta sola a 1000 filas) + GET/DELETE /api/error-logs (superadmin)
+ POST /api/error-logs (publico, rate-limited, para reportar incluso sin sesion).
Probado de punta a punta en dev: error real de backend (500) capturado con stack completo y
usuario correcto, error JS global y promesa rechazada capturados desde el navegador, panel
muestra ambos y el boton de limpiar funciona. Se encontro y corrigio un bug real en el camino: la
identificacion de usuario opcional leia jwt.payload.id en vez de .sub (como realmente lo firma
auth/service.js), asi que nunca identificaba a nadie.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9.7 KiB
9.7 KiB
Base de Datos - project.linkedcorp.net
Motor
- Motor: MariaDB
- Base principal:
project_linkedcorp - Host desarrollo:
192.168.222.20 - Host producción:
192.168.222.32 - Puerto:
3306 - Charset:
utf8mb4 - Collation:
utf8mb4_unicode_ci
Fuente de verdad
- La base ya existe: sí (creada en dev el 2026-09-03, usuario
project_linkedcorp). - Archivo SQL base:
backend/database/migrations/(SQL idempotente, numerado,CREATE TABLE IF NOT EXISTS). - Seeds:
backend/database/seeds/(roles, catálogos, usuario admin de dev).
Tablas principales (MVP)
| Tabla | Contenido |
|---|---|
roles |
catálogo fijo de 6 roles (superadmin, admin, it_manager, project_lead, collaborator, viewer) |
users |
usuarios, con rol, departamento y ciclo de vida de registro (status, tokens) |
system_settings |
clave/valor de configuración del sistema (hoy: SMTP, clave cifrada) |
departments |
departamentos/áreas, jerarquía simple vía parent_id |
project_types |
catálogo de tipos de proyecto |
priorities |
catálogo de prioridades (baja/media/alta/crítica) |
project_statuses |
catálogo de estados de proyecto |
activity_statuses |
catálogo de estados de actividad |
projects |
proyectos |
project_members |
usuarios asignados a un proyecto (reemplaza "equipos" como módulo aparte) |
activities |
actividades de un proyecto |
activity_dependencies |
dependencias simples entre actividades ("depende de") |
tasks |
checklist liviano dentro de una actividad ("Tareas" en la UI; renombrada desde subtasks en v1.8.0) |
recurring_tasks |
recordatorios periódicos (ej. renovación de licencias), menú propio "Tareas recurrentes" |
comments |
comentarios sobre proyecto o actividad (entity_type + entity_id) |
attachments |
archivos adjuntos a proyecto o actividad |
notifications |
notificaciones in-app por usuario |
audit_log |
auditoría central (quién, qué, sobre qué, cuándo) |
error_logs |
errores de backend (excepciones 500) y frontend (JS/promesas sin catch), panel "Errores del sistema" en Configuración |
Relaciones clave
users.role_id -> roles.id,users.department_id -> departments.iddepartments.parent_id -> departments.id(jerarquía opcional, ej. TI → Desarrollo)projects.status_id -> project_statuses.id,projects.priority_id -> priorities.id,projects.project_type_id -> project_types.id,projects.owner_user_id -> users.id,projects.requesting_department_id / responsible_department_id -> departments.idproject_members.project_id -> projects.id,project_members.user_id -> users.idactivities.project_id -> projects.id,activities.status_id -> activity_statuses.id,activities.priority_id -> priorities.id,activities.responsible_user_id -> users.idactivity_dependencies.activity_id / depends_on_activity_id -> activities.idtasks.activity_id -> activities.id,tasks.responsible_user_id -> users.id(nullable)comments.entity_id/attachments.entity_idapuntan aprojects.idoactivities.idsegúnentity_type(sin FK física cruzada — se valida en el servicio)
Reglas de datos
projects.progress_pctyactivities.progress_pctson calculados por el servicio, no editables directo por el cliente: actividad con tareas = % de tareas hechas; actividad sin tareas = manual (0-100); proyecto = promedio del avance de sus actividades.users.password_hashcon bcrypt, nunca texto plano ni reversible.users.status(pending_verification/pending_approval/active/invited) es el ciclo de vida de la cuenta, independiente deis_active(que sigue siendo el interruptor manual del admin para desactivar a alguien ya activo). Un usuariopending_approvalrechazado se borra físicamente (no hay estado "rechazado" — se audita el email enaudit_logantes de borrar).invited: cuenta placeholder creada al agregar por correo a alguien que aún no es usuario desde Miembros de un proyecto (name=email,password_hashaleatorio inservible, reutilizaverification_token/verification_token_expires— 7 días en vez de 24h). Al aceptar la invitación (POST /api/auth/accept-invite) pasa directo aactivesin pasar porpending_approval— a diferencia del autorregistro público, aquí quien lo invitó ya lo vetó al agregarlo a su proyecto. La fila deproject_membersse crea en el momento de invitar, no al aceptar, así que en cuanto acepta ya aparece como miembro sin pasos adicionales.recurring_tasks:project_id/responsible_user_idson opcionales (NULL) — se puede crear directo desde "Tareas recurrentes" o promoviendo una tarea puntual de una actividad (project_idqueda como referencia del proyecto de origen, la tarea puntual original NO se borra).last_reminder_sent_atse limpia a NULL cada vez que se completa un ciclo (POST /:id/complete, que sumainterval_value/interval_unitanext_due_date) — es lo que evita reenviar el correo dos veces en el mismo ciclo. El envío real lo hace un job en el propio proceso del backend (setIntervalcada 15 min, sin cron del SO ni cola nueva).system_settings.valueparasmtp_pass_encva cifrado (AES-256-GCM,ENCRYPTION_KEYdel.env) — nunca en texto plano, nunca se devuelve por la API (soloconfigured: true/false).system_settings:company_name/company_logo_filename/company_logo_mime— marca configurable (nombre y logo de la empresa,GET /api/companypúblico,PUTsolo superadmin), mismo patrón clave/valor que SMTP, sin tabla nueva. El archivo del logo vive enSTORAGE_ROOT/branding/, un solo archivo a la vez (se borra el anterior al subir uno nuevo). Sin valor guardado = "LinkedCorp" y logo estático embebido (comportamiento por defecto, no rompe instalaciones existentes).activities.sort_order/tasks.sort_order: orden manual (arrastrar con el mouse en la UI).0= nunca reordenado a mano (la migración012lo inicializa con elidpara preservar el orden previo); el endpoint de reordenar siempre escribe posiciones1..N, nunca0, para que esa migración siga siendo idempotente en deploys futuros sin pisar un orden real ya guardado.- Borrado lógico donde aplique (
users.is_active); nunca DELETE físico de usuarios activos con historial (lospending_approvalrechazados sí se borran, ver arriba). projects.deleted_at/deleted_by: eliminación lógica (quien puede editar el proyecto puede "eliminarlo" — se oculta de listados/dashboard, sigue en BD). Borrado físico (DELETE, arrastra actividades/comentarios/adjuntos porON DELETE CASCADE) solo víapermanentlyDeleteProject, solo superadmin, y solo sobre un proyecto que ya tienedeleted_at(dos pasos, nunca directo).project_members.role_in_project:'owner'(dueño, se asigna solo al crear el proyecto, nunca se toca desde el endpoint de rol),'admin'(administrador de ese proyecto puntual — un proyecto puede tener varios, otorgado por quien ya puede escribir el proyecto, sin importar el rol global de a quién se lo otorgan),NULL/otro = miembro sin permisos de escritura (salvo que además seaproject_leadglobal, comportamiento previo sin cambios).- Empresa asumida única (no hay tabla
companiesen el MVP). - Producción siempre usa
192.168.222.32. Desarrollo siempre usa192.168.222.20. No mezclar. error_logs: sin límite de tiempo, pero se recorta solo a los 1000 registros más recientes en cada inserción (evita crecimiento sin control si algo entra en loop de errores). Se puede vaciar del todo desde el panel ("Limpiar registro").
Riesgos o decisiones
- Sin triggers de BD para recalcular avances: toda la lógica vive en la capa
service/del backend (más simple de depurar y de extender en fase 2). risk_leveles un campo simple enprojects(bajo/medio/alto) — el módulo completo de Riesgos (probabilidad/impacto/mitigación) queda para fase 2, sin necesitar migrar este campo.
Archivos relacionados
backend/.env.examplebackend/database/migrations/*.sqlbackend/database/seeds/*.sql
Crear la base de datos en DESARROLLO (.20)
Ya ejecutado para este proyecto (2026-09-03):
bash /home/mgonzalez/desarrollo/project-md-templates/crear-db-dev.sh project_linkedcorp project_linkedcorp
BD project_linkedcorp y usuario project_linkedcorp@192.168.222.40 creados en 192.168.222.20.
Las credenciales están en backend/.env (no versionado).
Verificar:
mysql --defaults-extra-file=~/.config/mysql/dev20-admin.cnf -e "SHOW DATABASES LIKE 'project_linkedcorp';"
Aplicar migraciones/seeds (una vez creado backend/src/scripts/migrate-db.js):
cd backend
set -a && source .env && set +a
node src/scripts/migrate-db.js
node src/scripts/seed-db.js
Setup de base de datos en producción — ejecutado el 2026-09-06
BD project_linkedcorp creada en .32 con crear-db-prod.sh (grant a 192.168.222.3). Detalle
del proceso y estado real en PRODUCCION.md.
Migración en el servidor de aplicaciones (.3):
ssh deploy@192.168.222.3 -p 2203
cd /opt/apps/project.linkedcorp.net/source/backend
set -a && source /opt/apps/project.linkedcorp.net/backend/.env && set +a
node src/scripts/migrate-db.js
node src/scripts/seed-db.js # solo catálogos/roles; sin datos de ejemplo en produccion
node src/scripts/create-admin.js "Nombre" correo@dominio # solo la primera vez, bootstrap manual
Actualizaciones de esquema en producción
Método: archivos SQL idempotentes en backend/database/migrations/00X-nombre.sql
(CREATE TABLE IF NOT EXISTS / information_schema guards), aplicados con
node src/scripts/migrate-db.js. Siempre backup (mysqldump --single-transaction) antes de migrar
en producción.