2025-10-02 15:32:23 -03:00
using Dapper ;
using Inventario.API.Data ;
2025-10-02 15:37:06 -03:00
using Inventario.API.DTOs ;
2025-10-02 15:32:23 -03:00
using Inventario.API.Helpers ;
using Inventario.API.Models ;
using Microsoft.AspNetCore.Mvc ;
2025-10-02 15:37:06 -03:00
using System.Data ;
using System.Net.NetworkInformation ;
using Microsoft.Data.SqlClient ;
2025-10-02 15:32:23 -03:00
namespace Inventario.API.Controllers
{
[ApiController]
[Route("api/[controller] ")]
public class EquiposController : ControllerBase
{
private readonly DapperContext _context ;
public EquiposController ( DapperContext context )
{
_context = context ;
}
2025-10-02 15:37:06 -03:00
// --- MÉTODOS CRUD BÁSICOS (Ya implementados) ---
// GET /api/equipos
2025-10-02 15:32:23 -03:00
[HttpGet]
public async Task < IActionResult > Consultar ( )
{
var query = @ "
SELECT
2025-10-02 15:37:06 -03:00
e . Id , e . Hostname , e . Ip , e . Mac , e . Motherboard , e . Cpu , e . Ram_installed , e . Ram_slots , e . Os , e . Architecture ,
s . Id as Id , s . Nombre ,
u . Id as Id , u . Username , u . Password ,
d . Id as Id , d . Mediatype , d . Size ,
mr . Id as Id , mr . part_number as PartNumber , mr . Fabricante , mr . Tamano , mr . Velocidad , emr . Slot
2025-10-02 15:32:23 -03:00
FROM dbo . equipos e
LEFT JOIN dbo . sectores s ON e . sector_id = s . id
LEFT JOIN dbo . usuarios_equipos ue ON e . id = ue . equipo_id
LEFT JOIN dbo . usuarios u ON ue . usuario_id = u . id
LEFT JOIN dbo . equipos_discos ed ON e . id = ed . equipo_id
LEFT JOIN dbo . discos d ON ed . disco_id = d . id
LEFT JOIN dbo . equipos_memorias_ram emr ON e . id = emr . equipo_id
LEFT JOIN dbo . memorias_ram mr ON emr . memoria_ram_id = mr . id ; ";
using ( var connection = _context . CreateConnection ( ) )
{
var equipoDict = new Dictionary < int , Equipo > ( ) ;
await connection . QueryAsync < Equipo , Sector , Usuario , Disco , MemoriaRamDetalle , Equipo > (
query , ( equipo , sector , usuario , disco , memoria ) = >
{
if ( ! equipoDict . TryGetValue ( equipo . Id , out var equipoActual ) )
{
equipoActual = equipo ;
equipoActual . Sector = sector ;
equipoDict . Add ( equipoActual . Id , equipoActual ) ;
}
if ( usuario ! = null & & ! equipoActual . Usuarios . Any ( u = > u . Id = = usuario . Id ) )
equipoActual . Usuarios . Add ( usuario ) ;
if ( disco ! = null & & ! equipoActual . Discos . Any ( d = > d . Id = = disco . Id ) )
equipoActual . Discos . Add ( disco ) ;
if ( memoria ! = null & & ! equipoActual . MemoriasRam . Any ( m = > m . Id = = memoria . Id & & m . Slot = = memoria . Slot ) )
equipoActual . MemoriasRam . Add ( memoria ) ;
return equipoActual ;
} ,
2025-10-02 15:37:06 -03:00
splitOn : "Id,Id,Id,Id" // Dapper divide en cada 'Id'
2025-10-02 15:32:23 -03:00
) ;
2025-10-02 15:37:06 -03:00
return Ok ( equipoDict . Values . OrderBy ( e = > e . Sector ? . Nombre ) . ThenBy ( e = > e . Hostname ) ) ;
2025-10-02 15:32:23 -03:00
}
}
// --- GET /api/equipos/{hostname} ---
[HttpGet("{hostname}")]
public async Task < IActionResult > ConsultarDetalle ( string hostname )
{
var query = @ "SELECT
e . * ,
s . Id as SectorId , s . Nombre as SectorNombre ,
u . Id as UsuarioId , u . Username , u . Password
FROM dbo . equipos e
LEFT JOIN dbo . sectores s ON e . sector_id = s . id
LEFT JOIN dbo . usuarios_equipos ue ON e . id = ue . equipo_id
LEFT JOIN dbo . usuarios u ON ue . usuario_id = u . id
WHERE e . Hostname = @Hostname ; ";
using ( var connection = _context . CreateConnection ( ) )
{
var equipoDict = new Dictionary < int , Equipo > ( ) ;
var equipo = ( await connection . QueryAsync < Equipo , Sector , Usuario , Equipo > (
query , ( e , sector , usuario ) = >
{
if ( ! equipoDict . TryGetValue ( e . Id , out var equipoActual ) )
{
equipoActual = e ;
equipoActual . Sector = sector ;
equipoDict . Add ( equipoActual . Id , equipoActual ) ;
}
if ( usuario ! = null & & ! equipoActual . Usuarios . Any ( u = > u . Id = = usuario . Id ) )
equipoActual . Usuarios . Add ( usuario ) ;
return equipoActual ;
} ,
new { Hostname = hostname } ,
splitOn : "SectorId,UsuarioId"
) ) . FirstOrDefault ( ) ;
if ( equipo = = null ) return NotFound ( "Equipo no encontrado." ) ;
return Ok ( equipo ) ;
}
}
// --- POST /api/equipos/{hostname} ---
[HttpPost("{hostname}")]
public async Task < IActionResult > Ingresar ( string hostname , [ FromBody ] Equipo equipoData )
{
var findQuery = "SELECT * FROM dbo.equipos WHERE Hostname = @Hostname;" ;
using ( var connection = _context . CreateConnection ( ) )
{
var equipoExistente = await connection . QuerySingleOrDefaultAsync < Equipo > ( findQuery , new { Hostname = hostname } ) ;
if ( equipoExistente = = null )
{
// Crear
var insertQuery = @ "INSERT INTO dbo.equipos (Hostname, Ip, Mac, Motherboard, Cpu, Ram_installed, Ram_slots, Os, Architecture)
VALUES ( @Hostname , @Ip , @Mac , @Motherboard , @Cpu , @Ram_installed , @Ram_slots , @Os , @Architecture ) ;
SELECT CAST ( SCOPE_IDENTITY ( ) as int ) ; ";
var nuevoId = await connection . ExecuteScalarAsync < int > ( insertQuery , equipoData ) ;
equipoData . Id = nuevoId ;
return CreatedAtAction ( nameof ( ConsultarDetalle ) , new { hostname = equipoData . Hostname } , equipoData ) ;
}
else
{
// Actualizar y registrar historial
var cambios = new Dictionary < string , ( string anterior , string nuevo ) > ( ) ;
// Comparamos campos para registrar en historial
if ( equipoData . Ip ! = equipoExistente . Ip ) cambios [ "ip" ] = ( equipoExistente . Ip , equipoData . Ip ) ;
if ( equipoData . Mac ! = equipoExistente . Mac ) cambios [ "mac" ] = ( equipoExistente . Mac ? ? "" , equipoData . Mac ? ? "" ) ;
var updateQuery = @ "UPDATE dbo.equipos SET Ip = @Ip, Mac = @Mac, Motherboard = @Motherboard,
Cpu = @Cpu , Ram_installed = @Ram_installed , Ram_slots = @Ram_slots , Os = @Os , Architecture = @Architecture
WHERE Hostname = @Hostname ; ";
await connection . ExecuteAsync ( updateQuery , equipoData ) ;
if ( cambios . Count > 0 )
{
await HistorialHelper . RegistrarCambios ( _context , equipoExistente . Id , cambios ) ;
}
return Ok ( equipoData ) ;
}
}
}
// --- PUT /api/equipos/{id} ---
[HttpPut("{id}")]
public async Task < IActionResult > Actualizar ( int id , [ FromBody ] Equipo equipoData )
{
var updateQuery = @ "UPDATE dbo.equipos SET Hostname = @Hostname, Ip = @Ip, Mac = @Mac, Motherboard = @Motherboard,
Cpu = @Cpu , Ram_installed = @Ram_installed , Ram_slots = @Ram_slots , Os = @Os , Architecture = @Architecture
WHERE Id = @Id ; ";
using ( var connection = _context . CreateConnection ( ) )
{
// Asignamos el ID del parámetro de la ruta al objeto que recibimos.
equipoData . Id = id ;
// Ahora pasamos el objeto completo a Dapper.
var filasAfectadas = await connection . ExecuteAsync ( updateQuery , equipoData ) ;
if ( filasAfectadas = = 0 ) return NotFound ( "Equipo no encontrado." ) ;
return NoContent ( ) ;
}
}
// --- DELETE /api/equipos/{id} ---
[HttpDelete("{id}")]
public async Task < IActionResult > Borrar ( int id )
{
// La base de datos está configurada con ON DELETE CASCADE, por lo que las relaciones se borrarán automáticamente.
var query = "DELETE FROM dbo.equipos WHERE Id = @Id;" ;
using ( var connection = _context . CreateConnection ( ) )
{
var filasAfectadas = await connection . ExecuteAsync ( query , new { Id = id } ) ;
if ( filasAfectadas = = 0 ) return NotFound ( "Equipo no encontrado." ) ;
return NoContent ( ) ;
}
}
// --- GET /api/equipos/{hostname}/historial ---
[HttpGet("{hostname}/historial")]
public async Task < IActionResult > ConsultarHistorial ( string hostname )
{
var query = @ "SELECT h.* FROM dbo.historial_equipos h
JOIN dbo . equipos e ON h . equipo_id = e . id
WHERE e . Hostname = @Hostname
ORDER BY h . fecha_cambio DESC ; ";
using ( var connection = _context . CreateConnection ( ) )
{
var equipo = await connection . QueryFirstOrDefaultAsync < Equipo > ( "SELECT Id FROM dbo.equipos WHERE Hostname = @Hostname" , new { Hostname = hostname } ) ;
if ( equipo = = null ) return NotFound ( "Equipo no encontrado." ) ;
var historial = await connection . QueryAsync < HistorialEquipo > ( query , new { Hostname = hostname } ) ;
return Ok ( new { equipo = hostname , historial } ) ;
}
}
2025-10-02 15:37:06 -03:00
// --- MÉTODOS DE ASOCIACIÓN Y COMANDOS ---
[HttpPatch("{id_equipo}/sector/{id_sector}")]
public async Task < IActionResult > AsociarSector ( int id_equipo , int id_sector )
{
var query = "UPDATE dbo.equipos SET sector_id = @IdSector WHERE Id = @IdEquipo;" ;
using ( var connection = _context . CreateConnection ( ) )
{
var filasAfectadas = await connection . ExecuteAsync ( query , new { IdSector = id_sector , IdEquipo = id_equipo } ) ;
if ( filasAfectadas = = 0 ) return NotFound ( "Equipo o sector no encontrado." ) ;
return Ok ( new { success = true } ) ; // Devolvemos una respuesta simple de éxito
}
}
[HttpPost("{hostname}/asociarusuario")]
public async Task < IActionResult > AsociarUsuario ( string hostname , [ FromBody ] AsociacionUsuarioDto dto )
{
var query = @ "
INSERT INTO dbo . usuarios_equipos ( equipo_id , usuario_id )
SELECT e . id , u . id
FROM dbo . equipos e , dbo . usuarios u
WHERE e . Hostname = @Hostname AND u . Username = @Username ; ";
using ( var connection = _context . CreateConnection ( ) )
{
try
{
var filasAfectadas = await connection . ExecuteAsync ( query , new { Hostname = hostname , dto . Username } ) ;
if ( filasAfectadas = = 0 ) return NotFound ( "Equipo o usuario no encontrado." ) ;
return Ok ( new { success = true } ) ;
}
catch ( SqlException ex ) when ( ex . Number = = 2627 ) // Error de clave primaria duplicada
{
return Conflict ( "El usuario ya está asociado a este equipo." ) ;
}
}
}
[HttpDelete("{hostname}/usuarios/{username}")]
public async Task < IActionResult > DesasociarUsuario ( string hostname , string username )
{
var query = @ "
DELETE FROM dbo . usuarios_equipos
WHERE equipo_id = ( SELECT id FROM dbo . equipos WHERE Hostname = @Hostname )
AND usuario_id = ( SELECT id FROM dbo . usuarios WHERE Username = @Username ) ; ";
using ( var connection = _context . CreateConnection ( ) )
{
var filasAfectadas = await connection . ExecuteAsync ( query , new { Hostname = hostname , Username = username } ) ;
if ( filasAfectadas = = 0 ) return NotFound ( "Asociación no encontrada." ) ;
return Ok ( new { success = true } ) ;
}
}
[HttpPost("{hostname}/asociardiscos")]
2025-10-06 14:55:58 -03:00
public async Task < IActionResult > AsociarDiscos ( string hostname , [ FromBody ] List < Disco > discosDesdeCliente )
2025-10-02 15:37:06 -03:00
{
2025-10-06 14:55:58 -03:00
// 1. OBTENER EL EQUIPO
var equipoQuery = "SELECT * FROM dbo.equipos WHERE Hostname = @Hostname;" ;
using var connection = _context . CreateConnection ( ) ;
connection . Open ( ) ;
var equipo = await connection . QuerySingleOrDefaultAsync < Equipo > ( equipoQuery , new { Hostname = hostname } ) ;
if ( equipo = = null )
{
return NotFound ( "Equipo no encontrado." ) ;
}
// Iniciar una transacción para asegurar que todas las operaciones se completen o ninguna lo haga.
using var transaction = connection . BeginTransaction ( ) ;
try
{
// 2. OBTENER ASOCIACIONES Y DISCOS ACTUALES DE LA BD
var discosActualesQuery = @ "
SELECT d . Id , d . Mediatype , d . Size , ed . Id as EquipoDiscoId
FROM dbo . equipos_discos ed
JOIN dbo . discos d ON ed . disco_id = d . id
WHERE ed . equipo_id = @EquipoId ; ";
// Creamos una clase anónima temporal para mapear el resultado del JOIN
var discosEnDb = ( await connection . QueryAsync ( discosActualesQuery , new { EquipoId = equipo . Id } , transaction ) ) . Select ( d = > new
{
Id = ( int ) d . Id ,
Mediatype = ( string ) d . Mediatype ,
Size = ( int ) d . Size ,
EquipoDiscoId = ( int ) d . EquipoDiscoId
} ) . ToList ( ) ;
// 3. AGRUPAR Y CONTAR DISCOS (del cliente y de la BD)
// Crea un diccionario estilo: {"SSD_256": 2, "HDD_1024": 1}
var discosClienteContados = discosDesdeCliente
. GroupBy ( d = > $"{d.Mediatype}_{d.Size}" )
. ToDictionary ( g = > g . Key , g = > g . Count ( ) ) ;
var discosDbContados = discosEnDb
. GroupBy ( d = > $"{d.Mediatype}_{d.Size}" )
. ToDictionary ( g = > g . Key , g = > g . Count ( ) ) ;
var cambios = new Dictionary < string , ( string anterior , string nuevo ) > ( ) ;
// 4. CALCULAR Y EJECUTAR ELIMINACIONES
var discosAEliminar = new List < int > ( ) ;
foreach ( var discoDb in discosEnDb )
{
var key = $"{discoDb.Mediatype}_{discoDb.Size}" ;
if ( discosClienteContados . TryGetValue ( key , out int count ) & & count > 0 )
{
// Este disco todavía existe en el cliente, decrementamos el contador y lo saltamos.
discosClienteContados [ key ] - - ;
}
else
{
// Este disco ya no está en el cliente, marcamos su asociación para eliminar.
discosAEliminar . Add ( discoDb . EquipoDiscoId ) ;
// Registrar para el historial
var nombreDisco = $"Disco {discoDb.Mediatype} {discoDb.Size}GB" ;
var anterior = discosDbContados . GetValueOrDefault ( key , 0 ) ;
if ( ! cambios . ContainsKey ( nombreDisco ) ) cambios [ nombreDisco ] = ( anterior . ToString ( ) , ( anterior - 1 ) . ToString ( ) ) ;
else cambios [ nombreDisco ] = ( anterior . ToString ( ) , ( int . Parse ( cambios [ nombreDisco ] . nuevo ) - 1 ) . ToString ( ) ) ;
}
}
if ( discosAEliminar . Any ( ) )
{
await connection . ExecuteAsync ( "DELETE FROM dbo.equipos_discos WHERE Id IN @Ids;" , new { Ids = discosAEliminar } , transaction ) ;
}
// 5. CALCULAR Y EJECUTAR INSERCIONES
foreach ( var discoCliente in discosDesdeCliente )
{
var key = $"{discoCliente.Mediatype}_{discoCliente.Size}" ;
if ( discosDbContados . TryGetValue ( key , out int count ) & & count > 0 )
{
// Este disco ya existía, decrementamos para no volver a añadirlo.
discosDbContados [ key ] - - ;
}
else
{
// Este es un disco nuevo que hay que asociar.
var disco = await connection . QuerySingleOrDefaultAsync < Disco > ( "SELECT * FROM dbo.discos WHERE Mediatype = @Mediatype AND Size = @Size;" , discoCliente , transaction ) ;
if ( disco = = null ) continue ; // Si el disco no existe en la tabla maestra, lo ignoramos
await connection . ExecuteAsync ( "INSERT INTO dbo.equipos_discos (equipo_id, disco_id) VALUES (@EquipoId, @DiscoId);" , new { EquipoId = equipo . Id , DiscoId = disco . Id } , transaction ) ;
// Registrar para el historial
var nombreDisco = $"Disco {disco.Mediatype} {disco.Size}GB" ;
var anterior = discosDbContados . GetValueOrDefault ( key , 0 ) ;
if ( ! cambios . ContainsKey ( nombreDisco ) ) cambios [ nombreDisco ] = ( anterior . ToString ( ) , ( anterior + 1 ) . ToString ( ) ) ;
else cambios [ nombreDisco ] = ( anterior . ToString ( ) , ( int . Parse ( cambios [ nombreDisco ] . nuevo ) + 1 ) . ToString ( ) ) ;
}
}
// 6. REGISTRAR CAMBIOS Y CONFIRMAR TRANSACCIÓN
if ( cambios . Count > 0 )
{
// Formateamos los valores para el historial
var cambiosFormateados = cambios . ToDictionary (
kvp = > kvp . Key ,
kvp = > ( $"{kvp.Value.anterior} Instalados" , $"{kvp.Value.nuevo} Instalados" )
) ;
await HistorialHelper . RegistrarCambios ( _context , equipo . Id , cambiosFormateados ) ;
}
transaction . Commit ( ) ;
return Ok ( new { message = "Discos sincronizados correctamente." } ) ;
}
catch ( Exception ex )
{
transaction . Rollback ( ) ;
// Loggear el error en el servidor
Console . WriteLine ( $"Error al asociar discos para {hostname}: {ex.Message}" ) ;
return StatusCode ( 500 , "Ocurrió un error interno al procesar la solicitud." ) ;
}
2025-10-02 15:37:06 -03:00
}
[HttpPost("{hostname}/ram")]
2025-10-06 14:59:39 -03:00
public async Task < IActionResult > AsociarRam ( string hostname , [ FromBody ] List < MemoriaRamDetalle > memoriasDesdeCliente )
2025-10-02 15:37:06 -03:00
{
2025-10-06 14:59:39 -03:00
var equipoQuery = "SELECT * FROM dbo.equipos WHERE Hostname = @Hostname;" ;
using var connection = _context . CreateConnection ( ) ;
connection . Open ( ) ;
var equipo = await connection . QuerySingleOrDefaultAsync < Equipo > ( equipoQuery , new { Hostname = hostname } ) ;
if ( equipo = = null )
{
return NotFound ( "Equipo no encontrado." ) ;
}
using var transaction = connection . BeginTransaction ( ) ;
try
{
// 1. OBTENER ASOCIACIONES DE RAM ACTUALES
var ramActualQuery = @ "
SELECT emr . Id as EquipoMemoriaRamId , emr . Slot , mr . Id , mr . part_number as PartNumber , mr . Fabricante , mr . Tamano , mr . Velocidad
FROM dbo . equipos_memorias_ram emr
JOIN dbo . memorias_ram mr ON emr . memoria_ram_id = mr . id
WHERE emr . equipo_id = @EquipoId ; ";
var ramEnDb = ( await connection . QueryAsync < dynamic > ( ramActualQuery , new { EquipoId = equipo . Id } , transaction ) ) . ToList ( ) ;
// 2. CREAR "HUELLAS DIGITALES" ÚNICAS PARA COMPARAR
// Una huella única para cada módulo en un slot. Ej: "DIMM0_Kingston_8_3200"
Func < dynamic , string > crearHuella = ram = >
$"{ram.Slot}_{ram.PartNumber ?? ""}_{ram.Tamano}_{ram.Velocidad ?? 0}" ;
var huellasCliente = new HashSet < string > ( memoriasDesdeCliente . Select ( crearHuella ) ) ;
var huellasDb = new HashSet < string > ( ramEnDb . Select ( crearHuella ) ) ;
// 3. CALCULAR Y EJECUTAR ELIMINACIONES
var asociacionesAEliminar = ramEnDb
. Where ( ramDb = > ! huellasCliente . Contains ( crearHuella ( ramDb ) ) )
. Select ( ramDb = > ( int ) ramDb . EquipoMemoriaRamId )
. ToList ( ) ;
if ( asociacionesAEliminar . Any ( ) )
{
await connection . ExecuteAsync ( "DELETE FROM dbo.equipos_memorias_ram WHERE Id IN @Ids;" , new { Ids = asociacionesAEliminar } , transaction ) ;
}
// 4. CALCULAR Y EJECUTAR INSERCIONES
var memoriasAInsertar = memoriasDesdeCliente . Where ( ramCliente = > ! huellasDb . Contains ( crearHuella ( ramCliente ) ) ) . ToList ( ) ;
foreach ( var memInfo in memoriasAInsertar )
{
// Buscar o crear el módulo de RAM en la tabla maestra 'memorias_ram'
var findRamQuery = @ "SELECT * FROM dbo.memorias_ram WHERE
( part_number = @PartNumber OR ( part_number IS NULL AND @PartNumber IS NULL ) ) AND
tamano = @Tamano AND ( velocidad = @Velocidad OR ( velocidad IS NULL AND @Velocidad IS NULL ) ) ; ";
var memoriaMaestra = await connection . QuerySingleOrDefaultAsync < MemoriaRam > ( findRamQuery , memInfo , transaction ) ;
int memoriaMaestraId ;
if ( memoriaMaestra = = null )
{
var insertRamQuery = @ "INSERT INTO dbo.memorias_ram (part_number, fabricante, tamano, velocidad)
VALUES ( @PartNumber , @Fabricante , @Tamano , @Velocidad ) ;
SELECT CAST ( SCOPE_IDENTITY ( ) as int ) ; ";
memoriaMaestraId = await connection . ExecuteScalarAsync < int > ( insertRamQuery , memInfo , transaction ) ;
}
else
{
memoriaMaestraId = memoriaMaestra . Id ;
}
// Crear la asociación en la tabla intermedia
var insertAsociacionQuery = "INSERT INTO dbo.equipos_memorias_ram (equipo_id, memoria_ram_id, slot) VALUES (@EquipoId, @MemoriaRamId, @Slot);" ;
await connection . ExecuteAsync ( insertAsociacionQuery , new { EquipoId = equipo . Id , MemoriaRamId = memoriaMaestraId , memInfo . Slot } , transaction ) ;
}
// (Opcional, pero recomendado) Registrar cambios en el historial.
// La lógica exacta para el historial de RAM puede ser compleja y la omitimos por ahora para centrarnos en la sincronización.
transaction . Commit ( ) ;
return Ok ( new { message = "Módulos de RAM sincronizados correctamente." } ) ;
}
catch ( Exception ex )
{
transaction . Rollback ( ) ;
Console . WriteLine ( $"Error al asociar RAM para {hostname}: {ex.Message}" ) ;
return StatusCode ( 500 , "Ocurrió un error interno al procesar la solicitud de RAM." ) ;
}
2025-10-02 15:37:06 -03:00
}
[HttpPost("ping")]
public async Task < IActionResult > EnviarPing ( [ FromBody ] PingRequestDto request )
{
if ( string . IsNullOrWhiteSpace ( request . Ip ) )
return BadRequest ( "La dirección IP es requerida." ) ;
try
{
using ( var ping = new Ping ( ) )
{
var reply = await ping . SendPingAsync ( request . Ip , 2000 ) ; // Timeout de 2 segundos
return Ok ( new { isAlive = reply . Status = = IPStatus . Success , latency = reply . RoundtripTime } ) ;
}
}
catch ( PingException ex )
{
// Maneja errores comunes como "Host desconocido"
return Ok ( new { isAlive = false , error = ex . Message } ) ;
}
catch ( Exception ex )
{
return StatusCode ( 500 , $"Error interno al hacer ping: {ex.Message}" ) ;
}
}
// WOL (Wake-On-LAN) es más complejo porque requiere ejecutar comandos de sistema operativo.
// Lo dejamos pendiente para no añadir complejidad de configuración de SSH por ahora.
[HttpPost("wake-on-lan")]
public IActionResult EnviarWol ( [ FromBody ] WolRequestDto request )
{
Console . WriteLine ( $"Recibida solicitud WOL para MAC: {request.Mac}" ) ;
return Ok ( new { message = "Solicitud WOL recibida. La ejecución del comando está pendiente de implementación." } ) ;
}
2025-10-02 15:32:23 -03:00
}
2025-10-02 15:37:06 -03:00
// DTOs locales para las peticiones
public class PingRequestDto { public string? Ip { get ; set ; } }
public class WolRequestDto { public string? Mac { get ; set ; } }
2025-10-02 15:32:23 -03:00
}