2 Commits

Author SHA1 Message Date
06d95d32ca Try Docker 1811 2025-10-29 18:11:25 -03:00
e354433cd6 Merge branch 'feature/ux-ui-mejoras' 2025-10-29 14:17:30 -03:00
12 changed files with 175 additions and 6 deletions

View File

@@ -0,0 +1,30 @@
# Etapa 1: Compilación
# Usamos la imagen del SDK de .NET 8 para compilar la aplicación
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# Copiamos el archivo de proyecto y restauramos las dependencias primero
# Esto aprovecha el caché de capas de Docker
COPY ["Titulares.Api/Titulares.Api.csproj", "Titulares.Api/"]
RUN dotnet restore "Titulares.Api/Titulares.Api.csproj"
# Copiamos el resto del código fuente
COPY . .
WORKDIR "/src/Titulares.Api"
# Compilamos la aplicación en modo Release y la publicamos en la carpeta /app/publish
RUN dotnet publish "Titulares.Api.csproj" -c Release -o /app/publish --no-restore
# Etapa 2: Imagen Final
# Usamos la imagen de runtime de ASP.NET, que es mucho más ligera
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
# Exponemos el puerto 8080. El backend escuchará en este puerto dentro del contenedor.
EXPOSE 8080
# Establecemos el entorno a Producción para que se use appsettings.Production.json
ENV ASPNETCORE_ENVIRONMENT=Production
# Punto de entrada para ejecutar la aplicación
ENTRYPOINT ["dotnet", "Titulares.Api.dll"]

View File

@@ -23,12 +23,15 @@ builder.Services.AddScoped<CsvService>();
builder.Services.AddSingleton<ConfiguracionRepositorio>();
builder.Services.AddSingleton<EstadoProcesoService>();
// Obtener los orígenes permitidos desde la configuración
var allowedOrigins = builder.Configuration.GetValue<string>("AllowedOrigins")?.Split(',') ?? new[] { "http://localhost:5173" };
// Añadimos la política de CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowReactApp", builder =>
options.AddPolicy("AllowReactApp", policyBuilder =>
{
builder.WithOrigins("http://localhost:5173")
policyBuilder.WithOrigins(allowedOrigins)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();

View File

@@ -0,0 +1,11 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=db-sqlserver;Database=TitularesDB;User Id=titularesApi;Password=PTP847Titulares;TrustServerCertificate=True;"
},
"AllowedOrigins": "http://192.168.5.128:8905",
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}

View File

@@ -6,6 +6,7 @@
}
},
"AllowedHosts": "*",
"AllowedOrigins": "http://localhost:5173",
"ConnectionStrings": {
"DefaultConnection": "Server=TECNICA3;Database=TitularesDB;User Id=titularesApi;Password=PTP847Titulares;Trusted_Connection=True;TrustServerCertificate=True;"
}

View File

@@ -1,5 +1,5 @@
{
"RutaCsv": "C:\\temp\\titulares.csv",
"RutaCsv": "/data/titulares.csv",
"IntervaloMinutos": 15,
"CantidadTitularesAScrapear": 4,
"ScrapingActivo": false

41
docker-compose.yml Normal file
View File

@@ -0,0 +1,41 @@
services:
# Servicio del Backend
backend:
build:
context: ./backend/src/Titulares.Api
dockerfile: Dockerfile
container_name: titulares-api
# No exponemos puertos al exterior, el proxy se encarga de eso.
environment:
# Le decimos a ASP.NET Core que escuche en el puerto 8080 en todas las interfaces de red
- ASPNETCORE_URLS=http://+:8080
networks:
- app-net
- shared-net # Conectamos a la red externa para la DB
restart: unless-stopped
# Servicio de Nginx (Proxy Inverso + Servidor Frontend)
nginx-proxy:
build:
context: ./frontend # Construimos desde la carpeta del frontend...
dockerfile: Dockerfile # ...usando su Dockerfile para generar los estáticos
container_name: titulares-proxy
# Montamos nuestra configuración personalizada de Nginx
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
# Mapeamos el puerto 8905 del host al puerto 80 del contenedor Nginx
- "8905:80"
networks:
- app-net
depends_on:
- backend # Aseguramos que el backend se inicie antes que el proxy
restart: unless-stopped
networks:
# Red interna para la comunicación entre el proxy y el backend
app-net:
driver: bridge
# Definimos la red compartida como externa, ya que fue creada por otro stack
shared-net:
external: true

View File

@@ -0,0 +1 @@
VITE_API_BASE_URL=http://localhost:5174

1
frontend/.env.production Normal file
View File

@@ -0,0 +1 @@
VITE_API_BASE_URL=

30
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
# Etapa 1: Construcción de la aplicación React
# Usamos una imagen de Node.js (versión Alpine por ser ligera)
FROM node:20-alpine AS build
WORKDIR /app
# Copiamos package.json y package-lock.json para instalar dependencias
COPY package*.json ./
RUN npm install
# Copiamos el resto de los archivos del frontend
COPY . .
# Construimos la aplicación para producción. Los archivos resultantes irán a /app/dist
RUN npm run build
# Etapa 2: Servidor Nginx
# Usamos la imagen oficial de Nginx (versión Alpine por ser ligera)
FROM nginx:stable-alpine
WORKDIR /usr/share/nginx/html
# Eliminamos el contenido por defecto de Nginx
RUN rm -rf ./*
# Copiamos los archivos estáticos construidos en la etapa anterior
COPY --from=build /app/dist .
# Exponemos el puerto 80, que es el puerto por defecto de Nginx
EXPOSE 80
# El comando por defecto de la imagen de Nginx es suficiente para iniciar el servidor
# CMD ["nginx", "-g", "daemon off;"]

View File

@@ -3,7 +3,10 @@
import { useEffect, useRef, useState } from 'react';
import * as signalR from '@microsoft/signalr';
const HUB_URL = 'http://localhost:5174/titularesHub';
// La URL base viene de la variable de entorno
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
// Construimos la URL completa del Hub
const HUB_URL = `${API_BASE_URL}/titularesHub`;
// Definimos un tipo para el estado de la conexión para más claridad
export type ConnectionStatus = 'Connecting' | 'Connected' | 'Disconnected' | 'Reconnecting';

View File

@@ -3,10 +3,12 @@
import axios from 'axios';
import type { Configuracion, Titular } from '../types';
const API_URL = 'http://localhost:5174/api';
// La URL base viene de la variable de entorno
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
const apiClient = axios.create({
baseURL: API_URL,
// Construimos la URL completa para las llamadas a la API REST
baseURL: `${API_BASE_URL}/api`,
headers: { 'Content-Type': 'application/json' },
});

46
nginx/nginx.conf Normal file
View File

@@ -0,0 +1,46 @@
# Define un "upstream" que apunta a nuestro servicio de backend.
# 'backend' será el nombre del servicio en docker-compose, y 8080 es el puerto
# que expusimos en su Dockerfile.
upstream backend_api {
server backend:8080;
}
server {
# Nginx escuchará en el puerto 80 DENTRO del contenedor.
listen 80;
server_name 192.168.5.128; # Opcional, puedes usar localhost o tu dominio
# Ubicación raíz: sirve los archivos de la aplicación React.
location / {
root /usr/share/nginx/html;
try_files $uri /index.html; # Clave para Single Page Applications (SPA)
}
# Ubicación para la API REST.
# Todas las peticiones a http://.../api/... serán redirigidas al backend.
location /api {
proxy_pass http://backend_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# ----------- CONFIGURACIÓN CRÍTICA PARA SIGNALR -----------
# Ubicación para el Hub de SignalR.
location /titularesHub {
proxy_pass http://backend_api;
# Requerido para que la conexión WebSocket funcione
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
# Aumenta el timeout para conexiones de larga duración
proxy_read_timeout 90s;
# Evita que el proxy almacene en caché la negociación de WebSockets
proxy_cache_bypass $http_upgrade;
}
}