d7478d0f21
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>
7.7 KiB
7.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") |
subtasks |
checklist liviano dentro de una actividad |
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) |
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.idsubtasks.activity_id -> activities.id,subtasks.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 subtareas = % de subtareas hechas; actividad sin subtareas = 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.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).- 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.
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.