Microservicio de gestión de usuarios y autenticación desarrollado en .NET 9 con Clean Architecture. Proporciona gestión completa de usuarios, preferencias de accesibilidad, sesiones y autenticación JWT.
⚡ Nota: Este microservicio forma parte de un ecosistema donde el Gateway gestiona rate limiting, caching (Redis), circuit breaker y load balancing. El microservicio se enfoca en su lógica de dominio específica.
Microservicio empresarial para:
- Gestión de usuarios con operaciones CRUD completas
- Autenticación JWT con login, logout y recuperación de contraseña
- Preferencias de accesibilidad personalizadas por usuario
- Gestión de sesiones con control de sesiones activas
- i18n integrado con soporte multiidioma (es, en, pt)
- CRUD completo de usuarios con validación
- Búsqueda por email con unicidad garantizada
- Eliminación de usuarios y datos asociados
- Creación de usuarios con preferencias incluidas
- Actualización masiva de usuarios con preferencias
- JWT Authentication con tokens seguros
- Login con email/contraseña
- Logout con invalidación de tokens
- Reset de contraseña con confirmación por email
- Confirmación de email para activación de cuentas
- Configuración personalizada por usuario
- Preferencias de contraste, tamaño de fuente, modo oscuro
- Lector de pantalla, navegación por teclado
- Animaciones reducidas y otras opciones WCAG
- CRUD completo de preferencias
- Control de sesiones activas por usuario
- Listado de todas las sesiones
- Cierre de sesión específica por ID
- Cierre masivo de sesiones por usuario
- Auditoría de sesiones activas
- Soporte multiidioma (es, en, pt)
- Mensajes de error localizados
- Content negotiation automático
- Headers de idioma en responses
- Database connectivity check
- Application health monitoring
- Memory usage tracking
- Endpoints de salud personalizados
- Prometheus Metrics integrado
- Métricas de negocio personalizadas (usuarios, logins, sesiones)
- Endpoint
/metricsexpuesto - Monitoreo de autenticación y operaciones
- Histogramas de duración de operaciones
- Gauges de sesiones activas y usuarios totales
- DateTimeProvider Service para manejo consistente de fechas
- Configuración de zona horaria Ecuador (America/Guayaquil, UTC-5)
- MySQL configurado con timezone local
- Entity Framework con ValueConverter para DateTime
- Todas las fechas se almacenan y muestran en hora de Ecuador
┌───────────────────────────────────────────────────┐
│ 👥 USERS MICROSERVICE API │
│ (Port 8081) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ Controllers │ │ Middleware │ │ Health │ │
│ │ (6 APIs) │ │ (Context) │ │ Checks │ │
│ └─────────────┘ └─────────────┘ └──────────┘ │
│ │ │ │ │
│ └────────────────┴───────────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ APPLICATION │ │
│ │ Services │ │
│ │ Use Cases │ │
│ └───────┬───────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ DOMAIN │ │
│ │ Entities │ │
│ │ Interfaces │ │
│ └───────┬───────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │INFRASTRUCTURE │ │
│ │ EF Core │ │
│ │ Repositories│ │
│ └───────┬───────┘ │
└──────────────────────┼───────────────────────────┘
│
▼
┌──────────────┐
│ MySQL DB │
│ (users_db) │
└──────────────┘
Clean Architecture con 4 capas:
- API: Controllers, Middleware, Health Checks
- Application: Services, DTOs, Use Cases
- Domain: Entities, Interfaces, Business Logic
- Infrastructure: EF Core, Repositories, MySQL
- .NET 9.0 SDK
- MySQL 8.0+
- Docker & Docker Compose (opcional)
# Clonar repositorio
git clone https://github.com/your-org/accessibility-ms-users.git
cd accessibility-ms-users
# Configurar base de datos
mysql -u root -p < init-users-db.sql
# Configurar variables de entorno
cp .env.example .env
# Editar .env con tus credenciales de MySQL
# Restaurar dependencias
dotnet restore
# Compilar
dotnet build --configuration Release
# Ejecutar
dotnet run --project src/Users.Api/Users.Api.csproj# Levantar todos los servicios
docker-compose up -d
# Ver logs
docker-compose logs -f users-api
# Verificar estado
docker-compose ps
# Detener servicios
docker-compose down# Health check
curl http://localhost:8081/health
# Crear usuario de prueba
curl -X POST http://localhost:8081/api/users \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"Test123!"}'| Método | Endpoint | Descripción |
|---|---|---|
| POST | /api/Auth/login |
Login con email/password |
| POST | /api/Auth/logout |
Logout y cierre de sesión |
| POST | /api/Auth/reset-password |
Reset de contraseña |
| POST | /api/Auth/confirm-email |
Confirmar email del usuario |
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /api/users |
Listar todos los usuarios |
| POST | /api/users |
Crear nuevo usuario |
| GET | /api/users/by-email |
Buscar usuario por email |
| DELETE | /api/users |
Eliminar usuario por ID |
| DELETE | /api/users/by-email |
Eliminar usuario por email |
| DELETE | /api/users/all-data |
Eliminar todos los datos del usuario |
| Método | Endpoint | Descripción |
|---|---|---|
| POST | /api/users-with-preferences |
Crear usuario con preferencias |
| PATCH | /api/users-with-preferences/by-email |
Actualizar usuario y preferencias |
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /api/preferences/by-user |
Obtener preferencias por usuario ID |
| POST | /api/preferences |
Crear preferencias |
| DELETE | /api/preferences |
Eliminar preferencias |
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /api/sessions |
Listar todas las sesiones activas |
| GET | /api/sessions/user |
Obtener sesiones por usuario |
| DELETE | /api/sessions |
Cerrar sesión específica por ID |
| DELETE | /api/sessions/by-user |
Cerrar todas las sesiones de un usuario |
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /health |
Health check general |
| GET | /health/ready |
Readiness probe |
| GET | /health/live |
Liveness probe |
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /metrics |
Métricas Prometheus |
Total: 26 endpoints disponibles
Estado General: ✅ 386/386 tests exitosos (100%)
Cobertura Total: 94.71% (1290/1362 líneas cubiertas)
| Capa | Cobertura | Tests | Estado |
|---|---|---|---|
| Users.Api | 88.2% | Controllers + Middleware | ✅ |
| AuthController | 100% | Login, Logout, Reset | ✅ |
| PreferenceController | 99.1% | CRUD Preferencias | ✅ |
| SessionController | 94.5% | Gestión Sesiones | ✅ |
| UserController | 93.1% | CRUD Usuarios | ✅ |
| UsersWithPreferencesController | 100% | Usuarios + Prefs | ✅ |
| Users.Application | 95%+ | Services + DTOs | ✅ |
| Users.Domain | 100% | Entities + Interfaces | ✅ |
| Users.Infrastructure | 85%+ | Repositories + EF | ✅ |
Métricas detalladas:
- Cobertura de líneas: 94.71% (1290/1362)
- Cobertura de ramas: 90.93%
- Tiempo de ejecución: 17.6s para 386 tests
- Tasa de éxito: 100%
# Todos los tests con cobertura
.\manage-tests.ps1 -GenerateCoverage -OpenReport
# Solo tests unitarios
.\manage-tests.ps1 -TestType Unit
# Tests de integración
.\manage-tests.ps1 -TestType Integration
# Ver dashboard interactivo
Start-Process .\test-dashboard.htmlUnit Tests:
- Validación de entidades (User, Preference, Session)
- Lógica de servicios (AuthService, UserService)
- DTOs y mappers
- Validadores de dominio
Integration Tests:
- Controllers con base de datos en memoria
- Repositorios con MySQL real
- Health checks completos
- Middleware de contexto de usuario
E2E Tests:
- Flows completos de autenticación
- Creación de usuario + preferencias
- Gestión de sesiones activas
- Recuperación de contraseña
El microservicio expone métricas detalladas en el endpoint /metrics para monitoreo con Prometheus/Grafana.
Contadores (Counters):
# Total de usuarios registrados
users_registered_total
# Total de logins exitosos/fallidos
auth_login_total{status="success|failure"}
# Total de sesiones creadas
sessions_created_total
# Total de preferencias actualizadas
preferences_updated_total
# Total de password resets solicitados
password_resets_requested_total
Histogramas (Histograms):
# Duración de operaciones de autenticación
auth_operation_duration_seconds{operation="login|logout|reset"}
# Duración de consultas de usuarios
user_query_duration_seconds{operation="get_all|get_by_email|create"}
# Duración de operaciones de sesión
session_operation_duration_seconds{operation="create|close|get_active"}
Gauges:
# Sesiones activas actualmente
active_sessions_count
# Usuarios registrados totales
total_users_count
# Ver todas las métricas
curl http://localhost:8081/metrics
# Filtrar métricas de autenticación
curl http://localhost:8081/metrics | grep "auth_login_total"
# Verificar sesiones activas
curl http://localhost:8081/metrics | grep "active_sessions_count"# Panel 1: Tasa de registro de usuarios
rate(users_registered_total[5m])
# Panel 2: Tasa de login exitoso vs fallido
sum(rate(auth_login_total[5m])) by (status)
# Panel 3: Duración promedio de login
histogram_quantile(0.95, rate(auth_operation_duration_seconds_bucket{operation="login"}[5m]))
# Panel 4: Sesiones activas en tiempo real
active_sessions_count# Health check básico
curl http://localhost:8081/health
# Readiness (listo para recibir tráfico)
curl http://localhost:8081/health/ready
# Liveness (proceso está vivo)
curl http://localhost:8081/health/liveRespuesta Health Check:
{
"status": "Healthy",
"totalDuration": "00:00:00.0234567",
"entries": {
"database": {
"status": "Healthy",
"description": "Database connection is healthy",
"duration": "00:00:00.0123456"
},
"memory": {
"status": "Healthy",
"description": "Memory usage is within limits",
"data": {
"allocatedMB": 128,
"thresholdMB": 512
},
"duration": "00:00:00.0001234"
},
"application": {
"status": "Healthy",
"description": "Users API is running",
"duration": "00:00:00.0001000"
}
}
}┌──────────┐ JWT ┌─────────┐ X-User-* ┌──────────┐ IUserContext ┌────────────┐
│ Client │ ───────> │ Gateway │ ──────────> │ Middleware│ ──────────────> │ Controller │
└──────────┘ └─────────┘ └──────────┘ └────────────┘
│ │ │
Valida JWT Extrae headers Valida IsAuthenticated
+ Secret + Crea UserContext + Usa UserId/Role
1. GatewaySecretValidationMiddleware
// Valida que la petición provenga del Gateway autorizado
// Header requerido: X-Gateway-Secret
// Configuración: appsettings.json -> Gateway:Secret2. UserContextMiddleware
// Extrae información de usuario de headers propagados por Gateway
// Headers procesados:
// - X-User-Id → UserId
// - X-User-Email → Email
// - X-User-Role → Role
// - X-User-Name → Name
// Inyecta IUserContext en controllers vía DIpublic interface IUserContext
{
bool IsAuthenticated { get; }
int UserId { get; }
string Email { get; }
string Role { get; }
string Name { get; }
}public class UserController(
IUserService service,
IUserContext userContext) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAll()
{
// ✅ Validación de autenticación
if (!userContext.IsAuthenticated)
return Unauthorized(new { message = "User not authenticated" });
// ✅ Verificar permisos de admin
if (userContext.Role != "Admin")
return Forbid();
var users = await service.GetAllAsync();
return Ok(users);
}
}appsettings.json:
{
"Gateway": {
"Secret": "VGhpc0lzQVNlY3JldEtleUZvckdhdGV3YXkyMDI0"
},
"JwtSettings": {
"SecretKey": "your-jwt-secret-key-min-32-chars",
"Issuer": "https://accessibility.company.com",
"Audience": "https://accessibility.company.com",
"ExpiryHours": 24
}
}Generar Secrets:
# Generar JWT Secret Key
.\Generate-JwtSecretKey.ps1
# Validar configuración JWT
.\Validate-JwtConfig.ps11. Login (POST /api/auth/login)
Client → Gateway → Users API
1. Valida email/password contra DB
2. Genera JWT token con claims (UserId, Email, Role)
3. Crea sesión activa en DB
4. Retorna token + datos de usuario
2. Operaciones Protegidas
Client (con JWT) → Gateway → Users API
1. Gateway valida JWT y extrae claims
2. Gateway agrega headers X-User-*
3. UserContextMiddleware crea IUserContext
4. Controller valida IsAuthenticated y permisos
5. Ejecuta lógica de negocio
3. Logout (POST /api/auth/logout)
Client → Gateway → Users API
1. Invalida token en sistema
2. Cierra sesión activa en DB
3. Retorna confirmación
1. Generate-JwtSecretKey.ps1
Genera una clave secreta segura para JWT de forma automática.
# Ejecutar script
.\Generate-JwtSecretKey.ps1
# Salida:
# ✅ JWT Secret Key generada exitosamente
# 🔑 Clave: AbCdEf12...GhIjKl34 (32+ caracteres)
# 📝 Agregar en appsettings.json -> JwtSettings:SecretKeyCaracterísticas:
- Genera claves de 32+ caracteres automáticamente
- Usa RNGCryptoServiceProvider (cryptographically secure)
- Valida longitud mínima requerida
- Formato Base64 URL-safe
2. Validate-JwtConfig.ps1
Valida la configuración JWT en appsettings.json antes de deployment.
# Ejecutar validación
.\Validate-JwtConfig.ps1
# Salida exitosa:
# ✅ JwtSettings:SecretKey existe
# ✅ Longitud: 64 caracteres (>= 32 requeridos)
# ✅ JwtSettings:Issuer configurado
# ✅ JwtSettings:Audience configurado
# ✅ JwtSettings:ExpiryHours configurado: 24
# ✅ Configuración JWT válida para producción
# Salida con errores:
# ❌ JwtSettings:SecretKey no encontrada
# ❌ SecretKey muy corta: 16 caracteres (mínimo 32)
# ⚠️ Considere ejecutar .\Generate-JwtSecretKey.ps1Validaciones:
- ✅ Existencia de
JwtSettings:SecretKey - ✅ Longitud mínima (32 caracteres)
- ✅ Configuración de
IssueryAudience - ✅ Valor de
ExpiryHours - ✅ Formato JSON válido
3. init-test-databases.ps1 / .sh
Inicializa bases de datos de prueba para testing local.
# Windows
.\init-test-databases.ps1
# Linux/Mac
chmod +x init-test-databases.sh
./init-test-databases.shFuncionalidad:
- Crea contenedor MySQL para testing
- Ejecuta script
init-users-db.sql - Configura usuario y permisos
- Configura timezone de Ecuador (UTC-5)
- Verifica conexión antes de salir
Variables de entorno requeridas:
MYSQL_TEST_ROOT_PASSWORD=test_root_pass
MYSQL_TEST_DATABASE=users_test_db
MYSQL_TEST_USER=test_user
MYSQL_TEST_PASSWORD=test_pass
TZ=America/Guayaquil4. manage-tests.ps1
Script unificado para ejecutar tests con diferentes configuraciones.
# Ejecutar todos los tests
.\manage-tests.ps1
# Tests con cobertura y reporte HTML
.\manage-tests.ps1 -GenerateCoverage -OpenReport
# Tests por tipo
.\manage-tests.ps1 -TestType Unit
.\manage-tests.ps1 -TestType Integration
# Ver solo resumen
.\manage-tests.ps1 -SummaryCaracterísticas:
- Ejecuta xUnit con configuración personalizada
- Genera reportes de cobertura (Coverlet)
- Filtra por tipo (Unit/Integration/E2E)
- Exporta resultados a
TestResults/ - Abre dashboard interactivo HTML
DatabaseManager.cs
Utilidad para gestión de esquema de base de datos en testing.
public class DatabaseManager
{
// Crear esquema completo desde cero
public static async Task CreateSchemaAsync(string connectionString);
// Limpiar todos los datos (mantiene estructura)
public static async Task CleanDatabaseAsync(string connectionString);
// Resetear base de datos (drop + recreate)
public static async Task ResetDatabaseAsync(string connectionString);
// Verificar conexión
public static async Task<bool> CanConnectAsync(string connectionString);
// Seed data de prueba
public static async Task SeedTestDataAsync(string connectionString);
}Uso en tests:
[Fact]
public async Task Integration_CreateUser_ShouldSucceed()
{
// Arrange: Limpiar estado previo
await DatabaseManager.CleanDatabaseAsync(_connectionString);
// Act: Ejecutar test
var user = await _userService.CreateAsync(dto);
// Assert
Assert.NotNull(user);
Assert.Equal(dto.Email, user.Email);
}DateTimeProvider.cs
Servicio para manejo consistente de fechas en zona horaria de Ecuador.
public interface IDateTimeProvider
{
DateTime Now { get; }
DateTime UtcNow { get; }
}
public class DateTimeProvider : IDateTimeProvider
{
private static readonly TimeZoneInfo EcuadorTimeZone =
TimeZoneInfo.FindSystemTimeZoneById("America/Guayaquil");
public DateTime Now => TimeZoneInfo.ConvertTimeFromUtc(
DateTime.UtcNow, EcuadorTimeZone);
public DateTime UtcNow => DateTime.UtcNow;
}Ventajas:
- Centralización del manejo de fechas
- Testeable con mocks
- Independiente de configuración del servidor
init-users-db.sql
Script de inicialización de base de datos con esquema completo.
-- Estructura:
-- 1. Creación de base de datos con UTF-8
-- 2. Tablas principales (users, preferences, sessions)
-- 3. Índices para performance
-- 4. Foreign keys y relaciones
-- 5. Usuario y permisos
-- 6. Configuración de timezone
-- Ejecutar manualmente:
mysql -u root -p < init-users-db.sqlTablas creadas:
users- Usuarios del sistemapreferences- Preferencias de accesibilidad por usuariosessions- Sesiones activas
Índices creados:
idx_users_email- Búsqueda rápida por email (UNIQUE)idx_sessions_user- Sesiones por usuarioidx_sessions_token- Validación de tokens
# Build image
docker build -t msusers-api:latest .
# Run standalone
docker run -d \
--name msusers-api \
-p 8081:8081 \
-e ConnectionStrings__Default="server=mysql;database=usersdb;user=msuser;password=UsrApp2025SecurePass;DateTimeKind=Local" \
-e JwtSettings__SecretKey="9b3e7ER@S^glvxPWKX8nN?DTqtrd%Yj!oVIfh+BG&piHwZz6ky4Q52MumOFA-Lc0" \
-e Gateway__Secret="VGhpc0lzQVNlY3JldEtleUZvckdhdGV3YXkyMDI0" \
msusers-api:latestversion: "3.8"
services:
mysql:
image: mysql:8.4
container_name: msusers-mysql
environment:
MYSQL_ROOT_PASSWORD: aF3MK0ZuWMHHXyX1ZwWjmKoS4baBAUgL
MYSQL_DATABASE: usersdb
MYSQL_USER: msuser
MYSQL_PASSWORD: UsrApp2025SecurePass
TZ: America/Guayaquil # Ecuador UTC-5
command: --default-time-zone=-05:00
ports:
- "3307:3306"
volumes:
- msusers_mysql:/var/lib/mysql
- ./init-users-db.sql:/docker-entrypoint-initdb.d/01-init-users.sql:ro
networks:
- default
- accessibility-shared
healthcheck:
test:
[
"CMD",
"mysqladmin",
"ping",
"-h",
"localhost",
"-paF3MK0ZuWMHHXyX1ZwWjmKoS4baBAUgL",
]
interval: 10s
timeout: 5s
retries: 10
api:
image: msusers-api:latest
container_name: msusers-api
depends_on:
mysql:
condition: service_healthy
environment:
ASPNETCORE_ENVIRONMENT: Development
ConnectionStrings__Default: "server=mysql;port=3306;database=usersdb;user=msuser;password=UsrApp2025SecurePass;TreatTinyAsBoolean=false;ConvertZeroDateTime=True;DateTimeKind=Local"
JwtSettings__SecretKey: 9b3e7ER@S^glvxPWKX8nN?DTqtrd%Yj!oVIfh+BG&piHwZz6ky4Q52MumOFA-Lc0
JwtSettings__Issuer: https://accessibility.company.com
JwtSettings__Audience: https://accessibility.company.com
JwtSettings__ExpiryHours: 24
Gateway__Secret: VGhpc0lzQVNlY3JldEtleUZvckdhdGV3YXkyMDI0
ports:
- "8081:8081"
networks:
- default
- accessibility-shared
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8081/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
labels:
- "prometheus.scrape=true"
- "prometheus.port=8081"
- "prometheus.path=/metrics"
- "service.name=users-api"
- "service.version=1.0"
volumes:
msusers_mysql:
networks:
default:
name: accessibility-ms-users_default
accessibility-shared:
external: true
name: accessibility-sharedNotas importantes:
- Red compartida:
accessibility-sharedconecta todos los microservicios - Timezone MySQL: Configurado en
-05:00(Ecuador) - DateTimeKind=Local: En ConnectionString para manejo correcto de fechas
- Healthchecks: MySQL espera estar healthy antes de iniciar API
- Labels Prometheus: Para monitoreo y métricas
# ASP.NET Core
ASPNETCORE_ENVIRONMENT=Production|Development
ASPNETCORE_URLS=http://+:8081
# Base de Datos
ConnectionStrings__Default=server=localhost;port=3306;database=usersdb;user=msuser;password=UsrApp2025SecurePass;TreatTinyAsBoolean=false;ConvertZeroDateTime=True;DateTimeKind=Local
DB_ROOT_PASSWORD=aF3MK0ZuWMHHXyX1ZwWjmKoS4baBAUgL
DB_NAME=usersdb
DB_USER=msuser
DB_PASSWORD=UsrApp2025SecurePass
DB_PORT=3307
# MySQL Timezone (Ecuador UTC-5)
TZ=America/Guayaquil
MYSQL_TIMEZONE=-05:00
# JWT Configuration
JwtSettings__SecretKey=9b3e7ER@S^glvxPWKX8nN?DTqtrd%Yj!oVIfh+BG&piHwZz6ky4Q52MumOFA-Lc0
JwtSettings__Issuer=https://accessibility.company.com
JwtSettings__Audience=https://accessibility.company.com
JwtSettings__ExpiryHours=24
# Gateway Secret (comunicación entre servicios)
Gateway__Secret=VGhpc0lzQVNlY3JldEtleUZvckdhdGV3YXkyMDI0
GATEWAY_SECRET=VGhpc0lzQVNlY3JldEtleUZvckdhdGV3YXkyMDI0
# Email Configuration (para reset password)
Email__SmtpHost=smtp.gmail.com
Email__SmtpPort=587
Email__SmtpUser=your-email@gmail.com
Email__SmtpPassword=your-app-password
# Localization
DefaultLanguage=es
SupportedLanguages=es,en,pt
# Docker
API_HOST_PORT=8081
# Logging
Serilog__MinimumLevel=Information
Serilog__WriteTo__Console=true-- Crear base de datos con charset UTF-8
CREATE DATABASE usersdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Ejecutar script de inicialización
SOURCE init-users-db.sql;
-- Verificar zona horaria (debe mostrar -05:00 para Ecuador)
SELECT @@global.time_zone, @@session.time_zone;El microservicio implementa manejo de zona horaria para Ecuador (UTC-5):
1. MySQL Timezone:
# docker-compose.yml
environment:
TZ: America/Guayaquil
command: --default-time-zone=-05:002. ConnectionString con DateTimeKind:
ConnectionStrings__Default="...;DateTimeKind=Local"3. DateTimeProvider Service:
// Servicio personalizado para manejo de fechas en Ecuador
public class DateTimeProvider : IDateTimeProvider
{
private static readonly TimeZoneInfo EcuadorTimeZone =
TimeZoneInfo.FindSystemTimeZoneById("America/Guayaquil");
public DateTime Now => TimeZoneInfo.ConvertTimeFromUtc(
DateTime.UtcNow,
EcuadorTimeZone
);
}4. Entity Framework ValueConverter:
// Todas las fechas se convierten automáticamente a Local
var dateTimeConverter = new ValueConverter<DateTime, DateTime>(
v => DateTime.SpecifyKind(v, DateTimeKind.Local),
v => DateTime.SpecifyKind(v, DateTimeKind.Local)
);Resultado: Todas las fechas se guardan y recuperan en hora de Ecuador (UTC-5).
Servicio personalizado para manejo consistente de zona horaria:
Ubicación: Users.Application/Services/DateTimeProvider.cs
public interface IDateTimeProvider
{
DateTime Now { get; }
DateTime UtcNow { get; }
}
public class DateTimeProvider : IDateTimeProvider
{
private static readonly TimeZoneInfo EcuadorTimeZone =
TimeZoneInfo.FindSystemTimeZoneById("America/Guayaquil");
public DateTime Now => TimeZoneInfo.ConvertTimeFromUtc(
DateTime.UtcNow,
EcuadorTimeZone
);
public DateTime UtcNow => DateTime.UtcNow;
}Registro en DI Container:
// Program.cs
builder.Services.AddSingleton<IDateTimeProvider, DateTimeProvider>();Uso en servicios:
public class UserService
{
private readonly IDateTimeProvider _dateTimeProvider;
public async Task<User> CreateUserAsync(CreateUserDto dto)
{
var user = new User
{
Email = dto.Email,
CreatedAt = _dateTimeProvider.Now, // Hora de Ecuador
UpdatedAt = _dateTimeProvider.Now
};
// ...
}
}Ventajas:
- ✅ Centralización del manejo de fechas
- ✅ Consistencia en toda la aplicación
- ✅ Facilita testing con mocks
- ✅ Independiente de la configuración del servidor
- ✅ Compatible con diferentes zonas horarias
- Runtime: .NET 9.0
- Framework: ASP.NET Core Web API
- ORM: Entity Framework Core 9.0
- Database: MySQL 8.4
- Authentication: JWT Bearer
- Timezone: America/Guayaquil (Ecuador UTC-5)
- Logging: Serilog
- Testing: xUnit + Moq + FluentAssertions
- Coverage: Coverlet + ReportGenerator
- Container: Docker + Docker Compose
- Networks: Docker shared network (accessibility-shared)
Este microservicio forma parte del ecosistema de accesibilidad:
- Gateway (Port 8080): Enrutamiento, rate limiting, circuit breaker
- Analysis Service (Port 5002): Análisis de accesibilidad WCAG
- Reports Service (Port 5003): Generación de reportes
- Middleware (Port 3001): Orquestación y lógica de negocio
- UI (Port 5173): Interfaz de usuario
Red compartida: Todos los servicios se conectan a través de accessibility-shared network.
Proprietary Software License v1.0
Copyright (c) 2025 Geovanny Camacho. All rights reserved.
IMPORTANT: This software and associated documentation files (the "Software") are the exclusive property of Geovanny Camacho and are protected by copyright laws and international treaty provisions.
-
OWNERSHIP: The Software is licensed, not sold. Geovanny Camacho retains all right, title, and interest in and to the Software, including all intellectual property rights.
-
RESTRICTIONS: You may NOT:
- Copy, modify, or create derivative works of the Software
- Distribute, transfer, sublicense, lease, lend, or rent the Software
- Reverse engineer, decompile, or disassemble the Software
- Remove or alter any proprietary notices or labels on the Software
- Use the Software for any commercial purpose without explicit written permission
- Share access credentials or allow unauthorized access to the Software
-
CONFIDENTIALITY: The Software contains trade secrets and confidential information. You agree to maintain the confidentiality of the Software and not disclose it to any third party.
-
TERMINATION: This license is effective until terminated. Your rights under this license will terminate automatically without notice if you fail to comply with any of its terms.
-
NO WARRANTY: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-
LIMITATION OF LIABILITY: IN NO EVENT SHALL GEOVANNY CAMACHO BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
GOVERNING LAW: This license shall be governed by and construed in accordance with the laws of the jurisdiction in which Geovanny Camacho resides, without regard to its conflict of law provisions.
-
ENTIRE AGREEMENT: This license constitutes the entire agreement between you and Geovanny Camacho regarding the Software and supersedes all prior or contemporaneous understandings.
FOR LICENSING INQUIRIES:
Geovanny Camacho
Email: fgiocl@outlook.com
By using this Software, you acknowledge that you have read this license, understand it, and agree to be bound by its terms and conditions.
Author: Geovanny Camacho (fgiocl@outlook.com)
Last Update: 05/11/2025