Tabla de Contenidos
1. Resumen Ejecutivo
Esta guía describe la implementación completa de una Intranet Empresarial desarrollada con tecnologías Microsoft (.NET 8 + SQL Server), integrada con Profit ERP para datos de empleados y utilizando SFTP para gestión documental.
Alcance del Proyecto:
Módulos: Autenticación, Drive, RRHH (Vacaciones, Permisos, Nómina), Chat, Organigrama, Capacitación, Normativas.
Módulos: Autenticación, Drive, RRHH (Vacaciones, Permisos, Nómina), Chat, Organigrama, Capacitación, Normativas.
| Parámetro | Valor |
|---|---|
| Duración | 3 meses (12-13 semanas) |
| Inversión | $8,000 - $10,000 USD |
| Equipo | 1 Desarrollador Senior Full Stack |
| Capacidad | 500+ usuarios concurrentes |
| Base de Datos | SQL Server 2019+ (existente) |
2. Arquitectura del Sistema
2.1 Diagrama de Componentes
Frontend
HTML + Tailwind
→
HTML + Tailwind
API .NET 8
REST + SignalR
→
REST + SignalR
SQL Server
Datos + Chat
Datos + Chat
API .NET 8
→
SFTP Server
Archivos
Archivos
API .NET 8
→
Profit ERP
Empleados
Empleados
2.2 Flujo de Datos
- Autenticación: Usuario → API valida SQL Server → JWT → Acceso
- Empleados: API consulta vista Profit → Cache → Frontend
- Archivos: Upload → API → SFTP → Metadatos SQL
- Chat: Mensaje → SignalR → SQL Server → Broadcast
3. Estructura de la Solución .NET
3.1 Organización de Proyectos
IntranetEmpresarial/
├── src/
│ ├── Intranet.Web/ # Proyecto Principal (MVC)
│ │ ├── Controllers/ # Controladores API
│ │ ├── Views/ # Vistas Razor
│ │ ├── wwwroot/ # Assets estáticos
│ │ ├── Hubs/ # SignalR Hubs
│ │ └── Program.cs
│ │
│ ├── Intranet.Core/ # Capa de Dominio
│ │ ├── Entities/ # Entidades
│ │ ├── Interfaces/ # Repositorios
│ │ └── Services/ # Lógica de negocio
│ │
│ ├── Intranet.Infrastructure/ # Capa de Infraestructura
│ │ ├── Data/ # DbContext EF Core
│ │ ├── Repositories/ # Implementaciones
│ │ ├── Services/ # Servicios externos
│ │ └── Sftp/ # Cliente SFTP
│ │
│ └── Intranet.Integration/ # Integraciones
│ ├── Profit/ # Conector Profit ERP
│ └── Notifications/ # Email/Push
│
├── tests/
│ ├── Intranet.UnitTests/
│ └── Intranet.IntegrationTests/
│
└── Intranet.sln
3.2 Capas de la Arquitectura
| Capa | Responsabilidad | Ejemplos |
|---|---|---|
| Web | Controladores, Views, Hubs | AuthController, ChatHub |
| Core | Entidades, Interfaces, Lógica | User, IVacationRepository |
| Infrastructure | EF Core, Repositorios, SFTP | AppDbContext, SftpService |
| Integration | Conectores externos | ProfitConnector |
4. Esquema SQL Server Completo
IMPORTANTE: No duplicar datos de empleados. Usar vista de Profit ERP.
4.1 Vista de Profit (Lectura)
-- Crear Linked Server a Profit (ejecutar una vez)
EXEC sp_addlinkedserver
@server = 'PROFIT_SERVER',
@srvproduct = 'SQL Server';
-- Vista de empleados desde Profit
CREATE VIEW vw_Profit_Employees AS
SELECT
employee_code,
first_name,
last_name,
email,
department_code,
position,
hire_date,
status,
supervisor_code
FROM PROFIT_SERVER.ProfitDB.dbo.Employees;
-- Crear vista de departamentos
CREATE VIEW vw_Profit_Departments AS
SELECT
department_code,
department_name,
cost_center
FROM PROFIT_SERVER.ProfitDB.dbo.Departments;
4.2 Tablas Intranet (Datos Propios)
-- Extensión de empleados (datos adicionales)
CREATE TABLE Employee_Extensions (
id INT IDENTITY(1,1) PRIMARY KEY,
employee_code VARCHAR(50) NOT NULL,
avatar_url NVARCHAR(500),
phone_extension VARCHAR(20),
mobile VARCHAR(50),
bio NVARCHAR(MAX),
skills NVARCHAR(MAX), -- JSON
two_factor_enabled BIT DEFAULT 0,
notification_preferences NVARCHAR(MAX), -- JSON
created_at DATETIME2 DEFAULT GETDATE(),
FOREIGN KEY (employee_code) REFERENCES vw_Profit_Employees(employee_code)
);
-- Usuarios y credenciales
CREATE TABLE users_credentials (
id INT IDENTITY(1,1) PRIMARY KEY,
employee_code VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
is_active BIT DEFAULT 1,
last_login_at DATETIME2,
two_factor_enabled BIT DEFAULT 0,
created_at DATETIME2 DEFAULT GETDATE()
);
-- Archivos (metadatos)
CREATE TABLE file_system_items (
id INT IDENTITY(1,1) PRIMARY KEY,
file_id UNIQUEIDENTIFIER DEFAULT NEWID(),
parent_id INT NULL,
name NVARCHAR(500) NOT NULL,
item_type VARCHAR(20) CHECK (item_type IN ('folder', 'file')),
file_size BIGINT,
sftp_path NVARCHAR(1000),
owner_code VARCHAR(50) NOT NULL,
is_shared BIT DEFAULT 0,
created_at DATETIME2 DEFAULT GETDATE(),
FOREIGN KEY (parent_id) REFERENCES file_system_items(id) ON DELETE CASCADE
);
-- Chat - Conversaciones
CREATE TABLE conversations (
id INT IDENTITY(1,1) PRIMARY KEY,
conversation_type VARCHAR(20) CHECK (conversation_type IN ('direct', 'group')),
title NVARCHAR(255),
created_by VARCHAR(50) NOT NULL,
created_at DATETIME2 DEFAULT GETDATE()
);
-- Chat - Mensajes (con purga automática)
CREATE TABLE messages (
id INT IDENTITY(1,1) PRIMARY KEY,
conversation_id INT NOT NULL,
sender_code VARCHAR(50) NOT NULL,
content NVARCHAR(MAX),
sent_at DATETIME2 DEFAULT GETDATE(),
purge_date AS (DATEADD(year, 1, sent_at)) PERSISTED,
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
);
CREATE INDEX IX_Messages_Purge ON messages(purge_date);
-- Vacaciones
CREATE TABLE vacation_requests (
id INT IDENTITY(1,1) PRIMARY KEY,
employee_code VARCHAR(50) NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
total_days INT NOT NULL,
reason NVARCHAR(MAX),
status VARCHAR(50) DEFAULT 'pending',
supervisor_code VARCHAR(50),
approved_at DATETIME2,
requested_at DATETIME2 DEFAULT GETDATE()
);
-- Nómina (extension de Profit)
CREATE TABLE payroll_receipts (
id INT IDENTITY(1,1) PRIMARY KEY,
employee_code VARCHAR(50) NOT NULL,
period_year INT NOT NULL,
period_month INT NOT NULL,
net_salary DECIMAL(12,2) NOT NULL,
receipt_file_path NVARCHAR(1000),
is_paid BIT DEFAULT 0,
created_at DATETIME2 DEFAULT GETDATE(),
UNIQUE(employee_code, period_year, period_month)
);
-- Cursos y capacitación
CREATE TABLE courses (
id INT IDENTITY(1,1) PRIMARY KEY,
code VARCHAR(50) UNIQUE NOT NULL,
title NVARCHAR(255) NOT NULL,
description NVARCHAR(MAX),
duration_minutes INT,
instructor_code VARCHAR(50),
is_published BIT DEFAULT 0,
created_at DATETIME2 DEFAULT GETDATE()
);
-- Inscripciones a cursos
CREATE TABLE course_enrollments (
id INT IDENTITY(1,1) PRIMARY KEY,
course_id INT NOT NULL,
employee_code VARCHAR(50) NOT NULL,
enrollment_date DATETIME2 DEFAULT GETDATE(),
completion_percentage DECIMAL(5,2) DEFAULT 0,
completed_at DATETIME2,
FOREIGN KEY (course_id) REFERENCES courses(id),
UNIQUE(course_id, employee_code)
);
-- Normativas
CREATE TABLE policies (
id INT IDENTITY(1,1) PRIMARY KEY,
title NVARCHAR(500) NOT NULL,
content NVARCHAR(MAX) NOT NULL,
version VARCHAR(20) NOT NULL,
effective_date DATE NOT NULL,
is_mandatory BIT DEFAULT 1,
created_by VARCHAR(50) NOT NULL,
created_at DATETIME2 DEFAULT GETDATE()
);
-- Reconocimiento de normativas
CREATE TABLE policy_acknowledgments (
id INT IDENTITY(1,1) PRIMARY KEY,
policy_id INT NOT NULL,
employee_code VARCHAR(50) NOT NULL,
is_read BIT DEFAULT 0,
is_acknowledged BIT DEFAULT 0,
acknowledged_at DATETIME2,
FOREIGN KEY (policy_id) REFERENCES policies(id),
UNIQUE(policy_id, employee_code)
);
4.3 Job de Purga Automática (Mensajes > 1 año)
-- Stored Procedure para purga
CREATE PROCEDURE sp_PurgeOldMessages
AS
BEGIN
SET NOCOUNT ON;
DECLARE @BatchSize INT = 1000;
DECLARE @RowsDeleted INT = 1;
WHILE @RowsDeleted > 0
BEGIN
DELETE TOP (@BatchSize) FROM messages
WHERE sent_at < DATEADD(year, -1, GETDATE());
SET @RowsDeleted = @@ROWCOUNT;
IF @RowsDeleted > 0
WAITFOR DELAY '00:00:01';
END
END;
-- Crear Job SQL Agent (ejecutar diariamente a las 3 AM)
EXEC sp_add_job
@job_name = N'Purge Old Chat Messages',
@enabled = 1;
EXEC sp_add_jobstep
@job_name = N'Purge Old Chat Messages',
@step_name = N'Execute Purge',
@subsystem = N'TSQL',
@command = N'EXEC sp_PurgeOldMessages';
EXEC sp_add_schedule
@schedule_name = N'Daily at 3AM',
@freq_type = 4, -- Daily
@freq_interval = 1,
@active_start_time = 030000; -- 3:00 AM
EXEC sp_attach_schedule
@job_name = N'Purge Old Chat Messages',
@schedule_name = N'Daily at 3AM';
EXEC sp_add_jobserver
@job_name = N'Purge Old Chat Messages';
5. APIs y Endpoints Detallados
5.1 Autenticación (/api/auth)
POST
/api/auth/login
Request:
Request:
{
"email": "usuario@empresa.com",
"password": "********",
"rememberMe": true
}
Response 200:{
"token": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2g...",
"expiresAt": "2024-04-23T18:30:00Z",
"user": {
"employeeCode": "EMP001",
"name": "Juan Pérez",
"role": "employee",
"department": "IT"
}
}
POST
/api/auth/refresh
Request:
Request:
{
"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2g..."
}
5.2 Archivos (/api/files)
GET
/api/files?parentId={id}
Response: Lista de archivos/carpetas
Response: Lista de archivos/carpetas
POST
/api/files/upload
Content-Type: multipart/form-data
Proceso:
Content-Type: multipart/form-data
Proceso:
- Cliente sube archivo
- API guarda en SFTP
- API guarda metadatos en SQL
- Retorna URL de acceso
GET
/api/files/{id}/download
Response: Redirect a URL temporal SFTP o stream directo
Response: Redirect a URL temporal SFTP o stream directo
5.3 Chat (/api/chat)
GET
/api/chat/conversations
Lista de conversaciones del usuario autenticado
Lista de conversaciones del usuario autenticado
GET
/api/chat/conversations/{id}/messages
Query params: page, pageSize, beforeId
Query params: page, pageSize, beforeId
POST
/api/chat/conversations/{id}/messages
WebSocket alternativo: SignalR Hub
WebSocket alternativo: SignalR Hub
/chatHub
5.4 RRHH - Vacaciones (/api/vacations)
GET
/api/vacations/balance
Saldo de vacaciones del usuario actual
Saldo de vacaciones del usuario actual
POST
/api/vacations/request
{
"startDate": "2024-05-15",
"endDate": "2024-05-20",
"totalDays": 5,
"reason": "Vacaciones familiares"
}
PUT
/api/vacations/{id}/approve
Aprobar/rechazar solicitud (solo supervisors)
Aprobar/rechazar solicitud (solo supervisors)
5.5 Nómina (/api/payroll)
GET
/api/payroll/receipts
Lista de recibos de pago del usuario
Lista de recibos de pago del usuario
GET
/api/payroll/receipts/{id}/download
Descarga PDF del recibo (generado desde datos Profit + template)
Descarga PDF del recibo (generado desde datos Profit + template)
5.6 Organigrama (/api/orgchart)
GET
/api/orgchart
Response: Estructura jerárquica completa desde Profit
Response: Estructura jerárquica completa desde Profit
GET
/api/orgchart/{employeeCode}/team
Equipo directo de un empleado (subordinados)
Equipo directo de un empleado (subordinados)
GET
/api/orgchart/{employeeCode}/hierarchy
Cadena de mando (superiores hasta CEO)
Cadena de mando (superiores hasta CEO)
5.7 Capacitación (/api/training)
GET
/api/training/courses
Lista de cursos disponibles
Lista de cursos disponibles
POST
/api/training/courses/{id}/enroll
Inscribirse a curso
Inscribirse a curso
PUT
/api/training/progress
Actualizar progreso (% completado)
Actualizar progreso (% completado)
GET
/api/training/certificates/{id}/download
Descargar certificado en PDF
Descargar certificado en PDF
5.8 Normativas (/api/policies)
GET
/api/policies
Lista de políticas y normativas
Lista de políticas y normativas
POST
/api/policies/{id}/acknowledge
Confirmar lectura (firma digital simple)
Confirmar lectura (firma digital simple)
6. Integración Profit ERP
6.1 Métodos de Integración
| Método | Ventajas | Desventajas | Recomendado |
|---|---|---|---|
| Linked Server | Directo, rápido | Requiere permisos DB | ✅ Sí |
| API REST Profit | Seguro, estándar | Latencia, rate limits | Si tiene API |
| Replication | Datos locales | Complejo, delay | No recomendado |
6.2 Implementación Linked Server
-- Paso 1: Crear Linked Server
EXEC sp_addlinkedserver
@server = 'PROFIT_SERVER',
@srvproduct = '',
@provider = 'SQLOLEDB',
@datasrc = '192.168.1.100', -- IP servidor Profit
@catalog = 'ProfitDB';
-- Paso 2: Configurar seguridad
EXEC sp_addlinkedsrvlogin
@rmtsrvname = 'PROFIT_SERVER',
@useself = 'FALSE',
@locallogin = NULL,
@rmtuser = 'intranet_user',
@rmtpassword = 'password_seguro';
-- Paso 3: Probar conexión
SELECT * FROM OPENQUERY(PROFIT_SERVER, 'SELECT COUNT(*) FROM Employees');
-- Paso 4: Crear vistas locales
CREATE VIEW vw_Profit_Employees AS
SELECT
e.employee_code,
e.first_name,
e.last_name,
e.email,
e.department_code,
d.department_name,
e.position,
e.hire_date,
e.status,
e.supervisor_code
FROM PROFIT_SERVER.ProfitDB.dbo.Employees e
LEFT JOIN PROFIT_SERVER.ProfitDB.dbo.Departments d
ON e.department_code = d.department_code
WHERE e.status = 'active';
-- Paso 5: Crear sinónimos (opcional)
CREATE SYNONYM Profit_Employees FOR PROFIT_SERVER.ProfitDB.dbo.Employees;
6.3 Cache de Datos Profit
Para evitar saturar el servidor Profit:
// Servicio de cache para empleados
public class ProfitEmployeeCacheService
{
private readonly IMemoryCache _cache;
private readonly ApplicationDbContext _context;
public async Task GetEmployeeAsync(string employeeCode)
{
// Cache por 5 minutos
return await _cache.GetOrCreateAsync(
$"emp_{employeeCode}",
async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
return await _context.Employees
.FromSqlRaw("SELECT * FROM vw_Profit_Employees WHERE employee_code = {0}",
employeeCode)
.FirstOrDefaultAsync();
});
}
// Invalidar cache cuando cambian datos
public void InvalidateCache(string employeeCode)
{
_cache.Remove($"emp_{employeeCode}");
}
}
6.4 Sincronización Programada
-- Job para sincronizar cambios cada 15 minutos
CREATE PROCEDURE sp_SyncProfitEmployees
AS
BEGIN
-- Actualizar extensión de empleados nuevos
INSERT INTO Employee_Extensions (employee_code)
SELECT e.employee_code
FROM vw_Profit_Employees e
LEFT JOIN Employee_Extensions ex ON e.employee_code = ex.employee_code
WHERE ex.employee_code IS NULL;
-- Desactivar usuarios de empleados dados de baja
UPDATE users_credentials
SET is_active = 0
WHERE employee_code NOT IN (
SELECT employee_code FROM vw_Profit_Employees WHERE status = 'active'
);
END;
-- Programar cada 15 minutos
EXEC sp_add_job @job_name = 'Sync Profit Employees';
EXEC sp_add_jobstep @job_name = 'Sync Profit Employees',
@step_name = 'Sync', @command = 'EXEC sp_SyncProfitEmployees';
EXEC sp_add_schedule @schedule_name = 'Every15Min', @freq_type = 4,
@freq_interval = 1, @freq_subday_type = 4, @freq_subday_interval = 15;
EXEC sp_attach_schedule @job_name = 'Sync Profit Employees',
@schedule_name = 'Every15Min';
7. Configuración SFTP
7.1 Estructura de Carpetas
/intranet/
├── files/
│ ├── 2024/
│ │ ├── 01/
│ │ ├── 02/
│ │ └── ...
│ └── 2025/
├── temp/
│ └── uploads/ # Archivos temporales durante upload
├── backups/
│ └── daily/ # Backups automáticos
└── system/
└── avatars/ # Fotos de perfil
7.2 Implementación Cliente SFTP (.NET)
using SSH.NET;
public class SftpService
{
private readonly string _host;
private readonly string _username;
private readonly string _password;
private readonly string _basePath;
public async Task UploadFileAsync(Stream fileStream, string fileName)
{
using var sftp = new SftpClient(_host, _username, _password);
await sftp.ConnectAsync();
var year = DateTime.Now.Year.ToString();
var month = DateTime.Now.Month.ToString("D2");
var folderPath = $"{_basePath}/files/{year}/{month}";
// Crear directorio si no existe
if (!sftp.Exists(folderPath))
sftp.CreateDirectory(folderPath);
var uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
var fullPath = $"{folderPath}/{uniqueFileName}";
await sftp.UploadFileAsync(fileStream, fullPath);
return fullPath;
}
public async Task DownloadFileAsync(string filePath)
{
using var sftp = new SftpClient(_host, _username, _password);
await sftp.ConnectAsync();
var memoryStream = new MemoryStream();
await sftp.DownloadFileAsync(filePath, memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
public async Task DeleteFileAsync(string filePath)
{
using var sftp = new SftpClient(_host, _username, _password);
await sftp.ConnectAsync();
if (sftp.Exists(filePath))
sftp.DeleteFile(filePath);
}
}
7.3 Configuración appsettings.json
{
"Sftp": {
"Host": "sftp.empresa.com",
"Port": 22,
"Username": "intranet_service",
"Password": "${SFTP_PASSWORD}",
"BasePath": "/intranet/files",
"MaxFileSizeMB": 100
},
"Profit": {
"Server": "PROFIT_SERVER",
"CacheMinutes": 5
}
}
8. Guía de Implementación Paso a Paso
Fase 1: Setup Inicial (Semana 1)
1
Crear proyecto .NET 8
dotnet new mvc -n Intranet.Webdotnet new classlib -n Intranet.Coredotnet new classlib -n Intranet.Infrastructure
2
Configurar EF Core con SQL Server
Instalar paquetes:
Instalar paquetes:
Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore.Tools
3
Configurar Linked Server a Profit
Ejecutar scripts SQL de sección 6.2
Ejecutar scripts SQL de sección 6.2
4
Crear tablas de base de datos
Ejecutar scripts de sección 4
Ejecutar scripts de sección 4
5
Configurar conexión SFTP
Instalar
Instalar
SSH.NET, probar conexión
Fase 2: Autenticación (Semana 2)
6
Implementar JWT Authentication
Configurar
Configurar
AddAuthentication().AddJwtBearer()
7
Crear endpoints /api/auth/login y /api/auth/refresh
8
Integrar login con datos de Profit
Validar usuario contra vista vw_Profit_Employees
Validar usuario contra vista vw_Profit_Employees
9
Crear middleware de autorización por roles
Fase 3: Drive / Archivos (Semanas 3-4)
10
Implementar SftpService
Upload, download, delete, crear carpetas
Upload, download, delete, crear carpetas
11
Crear FileController
Endpoints: listar, subir, descargar, compartir
Endpoints: listar, subir, descargar, compartir
12
Conectar frontend existente a API
Adaptar drive.html para usar endpoints reales
Adaptar drive.html para usar endpoints reales
Fase 4: RRHH (Semanas 5-7)
13
Vacaciones: modelo y endpoints
Solicitud, aprobación, calendario, saldos
Solicitud, aprobación, calendario, saldos
14
Permisos: flujo de aprobación
Con notificaciones por email
Con notificaciones por email
15
Nómina: integrar con Profit
Leer datos de nómina, generar PDFs de recibos
Leer datos de nómina, generar PDFs de recibos
16
Fideicomiso y Beneficios
Estados de cuenta, catálogo de beneficios
Estados de cuenta, catálogo de beneficios
Fase 5: Chat (Semanas 8-9)
17
Configurar SignalR Hub
app.MapHub("/chatHub");
18
Implementar mensajería en tiempo real
Enviar/recibir mensajes, indicadores de escritura
Enviar/recibir mensajes, indicadores de escritura
19
Adjuntos en chat (integración con Drive)
20
Configurar job de purga de mensajes
SQL Agent Job diario (sección 4.3)
SQL Agent Job diario (sección 4.3)
Fase 6: Organigrama y Otros (Semana 10)
21
Organigrama desde Profit
API que retorna jerarquía completa
API que retorna jerarquía completa
22
Capacitación: cursos y progreso
23
Normativas: lectura y firmas
Fase 7: Testing y Deploy (Semanas 11-13)
24
Testing unitario e integración
xUnit, pruebas de APIs
xUnit, pruebas de APIs
25
Configurar CI/CD
GitHub Actions o Azure DevOps
GitHub Actions o Azure DevOps
26
Deploy en servidor de producción
IIS o Docker + Reverse Proxy
IIS o Docker + Reverse Proxy
27
Configurar SSL y dominio
HTTPS, redirección www
HTTPS, redirección www
28
Capacitación de usuarios y entrega
9. Seguridad y Buenas Prácticas
9.1 Autenticación y Autorización
- ✅ JWT con expiración corta (15 min access, 7 días refresh)
- ✅ HTTPS obligatorio en producción
- ✅ Rate limiting: 5 intentos login, luego bloqueo 15 min
- ✅ Passwords hasheadas con BCrypt (work factor 12+)
- ✅ 2FA opcional para roles administrativos
9.2 Datos Sensibles
- ✅ Nunca loguear passwords o tokens
- ✅ Encriptar datos sensibles en SQL Server (Always Encrypted)
- ✅ Sanitizar inputs para prevenir SQL Injection
- ✅ Validar archivos subidos (tipo, tamaño máximo 100MB)
9.3 SFTP
- ✅ Usar SFTP (no FTP plano)
- ✅ Credenciales en variables de entorno (no en código)
- ✅ Usuario SFTP con permisos mínimos (solo su carpeta)
- ✅ Validar rutas (prevenir path traversal)
9.4 Headers de Seguridad
// En Program.cs
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
await next();
});
9.5 Backup y Recuperación
- ✅ Backup SQL Server: Diario completo, diferencial cada 4 horas
- ✅ Backup SFTP: Rsync diario a servidor secundario
- ✅ Probar restauración mensualmente
- ✅ Retención: 30 días
10. Checklist de Entrega
Desarrollo
- Setup proyecto .NET 8 configurado
- Base de datos SQL Server creada con todas las tablas
- Linked Server a Profit configurado y probado
- Autenticación JWT funcionando
- Drive con upload/download vía SFTP
- Módulo de vacaciones completo
- Módulo de permisos con aprobaciones
- Nómina integrada con Profit (PDFs)
- Chat en tiempo real con SignalR
- Job de purga de mensajes configurado
- Organigrama desde Profit
- Capacitación y normativas
Calidad
- Tests unitarios cobertura > 70%
- Tests de integración de APIs
- Pruebas de carga (100 usuarios concurrentes)
- Revisión de seguridad
- Frontend responsive probado en móviles
Documentación
- Documentación técnica (APIs, arquitectura)
- Manual de usuario final
- Manual de administrador
- Diagrama de base de datos
- Lista de variables de entorno
Deploy
- Aplicación en servidor de producción
- SSL configurado (HTTPS)
- Dominio configurado
- CI/CD pipeline funcionando
- Monitoreo básico (logs, uptime)
- Backup automatizado configurado
Entrega
- Código fuente en repositorio Git
- Scripts de base de datos versionados
- Capacitación a usuarios administradores
- Capacitación a usuarios finales
- Soporte 3 meses activado
¡Proyecto Completado!
La intranet está lista para uso productivo.
La intranet está lista para uso productivo.