diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..c0aa857 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,25 @@ +# --- Etapa 1: Build --- +# Usamos la imagen del SDK de .NET 8 para compilar +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +# Copiamos el archivo .csproj para restaurar dependencias primero (cache optimization) +COPY Inventario.API/Inventario.API.csproj Inventario.API/ +RUN dotnet restore "Inventario.API/Inventario.API.csproj" + +# Copiamos el resto del código fuente +COPY . . + +# Publicamos la aplicación en modo Release +WORKDIR "/src/Inventario.API" +RUN dotnet publish "Inventario.API.csproj" -c Release -o /app/publish /p:UseAppHost=false + +# --- Etapa 2: Final --- +# Usamos la imagen de runtime de ASP.NET, mucho más ligera +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final +WORKDIR /app +COPY --from=build /app/publish . + +# El puerto por defecto que exponen los contenedores de .NET es 8080 +EXPOSE 8080 +ENTRYPOINT ["dotnet", "Inventario.API.dll"] \ No newline at end of file diff --git a/backend/appsettings.json b/backend/appsettings.json index fbb00fb..154b777 100644 --- a/backend/appsettings.json +++ b/backend/appsettings.json @@ -7,7 +7,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "Server=TECNICA3;Database=InventarioDB;User Id=apiequipos;Password=@Apiequipos513@;TrustServerCertificate=True" + "DefaultConnection": "Server=db-sqlserver;Database=InventarioDB;User Id=apiequipos;Password=@Apiequipos513@;TrustServerCertificate=True" }, "SshSettings": { "Host": "192.168.10.1", diff --git a/backend/obj/Debug/net9.0/Inventario.API.AssemblyInfo.cs b/backend/obj/Debug/net9.0/Inventario.API.AssemblyInfo.cs index e7a26c1..a9a4ec3 100644 --- a/backend/obj/Debug/net9.0/Inventario.API.AssemblyInfo.cs +++ b/backend/obj/Debug/net9.0/Inventario.API.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Inventario.API")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+99d98cc588b3922b6aa3ab9045fcee9cb31de1f3")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+04f1134be432cfc59ba887bba19eb9b563256780")] [assembly: System.Reflection.AssemblyProductAttribute("Inventario.API")] [assembly: System.Reflection.AssemblyTitleAttribute("Inventario.API")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..eb2f616 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +services: + # Servicio del Backend + inventario-api: + build: + context: . + dockerfile: backend/Dockerfile + container_name: inventario-api + restart: unless-stopped + # 'expose' hace que el puerto sea accesible para otros contenedores en la misma red, + # pero NO lo publica en la máquina host. Esto es lo correcto. + expose: + - "8080" + networks: + - inventario-net + - shared-net # Para conectar con la DB + + # Servicio del Frontend + inventario-frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: inventario-frontend + restart: unless-stopped + expose: + - "80" # El Nginx del frontend escucha en el puerto 80 interno + networks: + - inventario-net + + # Proxy Inverso que expone los servicios al exterior + inventario-proxy: + image: nginx:1.25-alpine + container_name: inventario-proxy + restart: unless-stopped + volumes: + - ./proxy/nginx.conf:/etc/nginx/conf.d/default.conf + ports: + # Este es el ÚNICO punto de entrada a la aplicación desde el exterior. + - "8900:80" + networks: + - inventario-net + depends_on: + - inventario-api + - inventario-frontend + +networks: + # Red interna para la comunicación entre los servicios de este stack + inventario-net: + driver: bridge + + # Red externa para conectar con servicios compartidos (la base de datos) + shared-net: + external: true \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..5996e41 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,19 @@ +# --- Etapa 1: Build --- +FROM node:20-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# --- Etapa 2: Producción --- +FROM nginx:1.25-alpine + +# Copia los archivos estáticos construidos a la carpeta que Nginx sirve por defecto +COPY --from=build /app/dist /usr/share/nginx/html + +# Copia la configuración de Nginx específica para el frontend +COPY frontend.nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/frontend.nginx.conf b/frontend/frontend.nginx.conf new file mode 100644 index 0000000..26bdbf6 --- /dev/null +++ b/frontend/frontend.nginx.conf @@ -0,0 +1,10 @@ +server { + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + # Esta línea es crucial para que las rutas de React (SPA) funcionen + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file diff --git a/frontend/proxy/nginx.conf b/frontend/proxy/nginx.conf new file mode 100644 index 0000000..25359aa --- /dev/null +++ b/frontend/proxy/nginx.conf @@ -0,0 +1,21 @@ +server { + listen 80; + + # Pasa todas las peticiones a la API al servicio del backend + location /api/ { + proxy_pass http://inventario-api:8080/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; + } + + # Pasa el resto de las peticiones al servicio del frontend + location / { + proxy_pass http://inventario-frontend:80; + 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; + } +} \ No newline at end of file diff --git a/frontend/src/components/GestionComponentes.tsx b/frontend/src/components/GestionComponentes.tsx index 40d4102..6719f19 100644 --- a/frontend/src/components/GestionComponentes.tsx +++ b/frontend/src/components/GestionComponentes.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import toast from 'react-hot-toast'; import styles from './SimpleTable.module.css'; -const BASE_URL = 'http://localhost:5198/api'; +const BASE_URL = '/api'; // Interfaces para los diferentes tipos de datos interface TextValue { diff --git a/frontend/src/components/GestionSectores.tsx b/frontend/src/components/GestionSectores.tsx index 78a6bf6..467693c 100644 --- a/frontend/src/components/GestionSectores.tsx +++ b/frontend/src/components/GestionSectores.tsx @@ -4,7 +4,7 @@ import type { Sector } from '../types/interfaces'; import styles from './SimpleTable.module.css'; import ModalSector from './ModalSector'; -const BASE_URL = 'http://localhost:5198/api'; +const BASE_URL = '/api'; const GestionSectores = () => { const [sectores, setSectores] = useState([]); diff --git a/frontend/src/components/ModalAnadirEquipo.tsx b/frontend/src/components/ModalAnadirEquipo.tsx index f82d6bd..8a6dad2 100644 --- a/frontend/src/components/ModalAnadirEquipo.tsx +++ b/frontend/src/components/ModalAnadirEquipo.tsx @@ -10,7 +10,7 @@ interface ModalAnadirEquipoProps { onSave: (nuevoEquipo: Omit) => void; } -const BASE_URL = 'http://localhost:5198/api'; +const BASE_URL = '/api'; const ModalAnadirEquipo: React.FC = ({ sectores, onClose, onSave }) => { const [nuevoEquipo, setNuevoEquipo] = useState({ diff --git a/frontend/src/components/ModalAnadirUsuario.tsx b/frontend/src/components/ModalAnadirUsuario.tsx index 79f288f..26bdc64 100644 --- a/frontend/src/components/ModalAnadirUsuario.tsx +++ b/frontend/src/components/ModalAnadirUsuario.tsx @@ -7,7 +7,7 @@ interface Props { onSave: (usuario: { username: string }) => void; } -const BASE_URL = 'http://localhost:5198/api'; +const BASE_URL = '/api'; const ModalAnadirUsuario: React.FC = ({ onClose, onSave }) => { const [username, setUsername] = useState(''); diff --git a/frontend/src/components/ModalDetallesEquipo.tsx b/frontend/src/components/ModalDetallesEquipo.tsx index 5903d9a..0fde8f8 100644 --- a/frontend/src/components/ModalDetallesEquipo.tsx +++ b/frontend/src/components/ModalDetallesEquipo.tsx @@ -19,7 +19,7 @@ interface ModalDetallesEquipoProps { onAddComponent: (type: 'disco' | 'ram' | 'usuario') => void; } -const BASE_URL = 'http://localhost:5198/api'; +const BASE_URL = '/api'; const ModalDetallesEquipo: React.FC = ({ equipo, isOnline, historial, sectores, onClose, onDelete, onRemoveAssociation, onEdit, onAddComponent diff --git a/frontend/src/components/SimpleTable.tsx b/frontend/src/components/SimpleTable.tsx index 5f49590..33ed13b 100644 --- a/frontend/src/components/SimpleTable.tsx +++ b/frontend/src/components/SimpleTable.tsx @@ -37,7 +37,7 @@ const SimpleTable = () => { const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [addingComponent, setAddingComponent] = useState<'disco' | 'ram' | 'usuario' | null>(null); const [isLoading, setIsLoading] = useState(true); - const BASE_URL = 'http://localhost:5198/api'; + const BASE_URL = '/api'; useEffect(() => { const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;