594 lines
21 KiB
Plaintext
594 lines
21 KiB
Plaintext
@page "/Buscador"
|
|
@rendermode InteractiveServer
|
|
@attribute [Authorize]
|
|
|
|
@using System.Net.Http
|
|
@using System.Net.Http.Headers
|
|
@using System.Text
|
|
@using System.Text.Json
|
|
@using System.Linq
|
|
@using GestionaDenunciasAN.Models
|
|
|
|
@inject HttpClient Http
|
|
@inject IConfiguration Configuration
|
|
|
|
<h3>Buscador de expedientes por tercero</h3>
|
|
|
|
<div class="card mt-3">
|
|
<div class="card-body">
|
|
<!-- NIF -->
|
|
<div class="row g-2">
|
|
<div class="col-md-4">
|
|
<label class="form-label">DNI / NIF</label>
|
|
<input class="form-control"
|
|
placeholder="Ej.: 12345678Z"
|
|
@bind="nifBuscado"
|
|
@bind:event="oninput" />
|
|
</div>
|
|
</div>
|
|
|
|
<hr />
|
|
|
|
<!-- Modo de fechas -->
|
|
<div class="row g-2">
|
|
<div class="col-12">
|
|
<label class="form-label d-block">Rango de fechas</label>
|
|
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input"
|
|
type="radio"
|
|
id="modoTodas"
|
|
name="modoFecha"
|
|
checked="@IsModo(ModoTodas)"
|
|
@onchange="@(() => SetModo(ModoTodas))" />
|
|
<label class="form-check-label" for="modoTodas">
|
|
Todas las fechas
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input"
|
|
type="radio"
|
|
id="modoRango"
|
|
name="modoFecha"
|
|
checked="@IsModo(ModoRango)"
|
|
@onchange="@(() => SetModo(ModoRango))" />
|
|
<label class="form-check-label" for="modoRango">
|
|
Entre fechas
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input"
|
|
type="radio"
|
|
id="modoUltimos"
|
|
name="modoFecha"
|
|
checked="@IsModo(ModoUltimos)"
|
|
@onchange="@(() => SetModo(ModoUltimos))" />
|
|
<label class="form-check-label" for="modoUltimos">
|
|
Últimos X meses
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rango de fechas -->
|
|
@if (modoFecha == ModoRango)
|
|
{
|
|
<div class="row g-2 mt-2">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Desde</label>
|
|
<input class="form-control"
|
|
type="date"
|
|
@bind="fechaDesde" />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Hasta</label>
|
|
<input class="form-control"
|
|
type="date"
|
|
@bind="fechaHasta" />
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (modoFecha == ModoUltimos)
|
|
{
|
|
<div class="row g-2 mt-2">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Últimos meses</label>
|
|
<select class="form-select" @bind="mesesUltimos">
|
|
<option value="3">3 meses</option>
|
|
<option value="6">6 meses</option>
|
|
<option value="9">9 meses</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Botón buscar -->
|
|
<div class="row g-2 mt-3">
|
|
<div class="col-md-3">
|
|
<button class="btn btn-primary"
|
|
@onclick="BuscarAsync"
|
|
disabled="@isSearching">
|
|
@if (isSearching)
|
|
{
|
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
|
<span class="ms-2">Buscando…</span>
|
|
}
|
|
else
|
|
{
|
|
<i class="bi bi-search"></i>
|
|
<span class="ms-1">Buscar</span>
|
|
}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
|
{
|
|
<div class="alert alert-danger mt-3">@errorMessage</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@if (haBuscado)
|
|
{
|
|
<div class="mt-4">
|
|
<h5>Resultados para <strong>@nifMostrado</strong></h5>
|
|
|
|
@if (expedientes == null || !expedientes.Any())
|
|
{
|
|
<div class="alert alert-warning mt-3">
|
|
No se han encontrado expedientes asociados a este tercero con los filtros seleccionados.
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<table class="table table-striped table-hover mt-3">
|
|
<thead>
|
|
<tr>
|
|
<th>Expediente</th>
|
|
<th>Asunto</th>
|
|
<th>Fecha creación</th>
|
|
<th>Estado</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var exp in expedientes)
|
|
{
|
|
<tr>
|
|
<td>@exp.CodigoExpediente</td>
|
|
<td>@exp.Asunto</td>
|
|
<td>@(exp.FechaCreacion?.ToLocalTime().ToString("dd/MM/yyyy HH:mm"))</td>
|
|
<td>@exp.Estado</td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-outline-primary"
|
|
@onclick="() => ToggleDetalle(exp)">
|
|
@(expedienteSeleccionado == exp ? "Ocultar" : "Abrir expediente")
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
|
|
@if (expedienteSeleccionado == exp)
|
|
{
|
|
<tr class="table-active">
|
|
<td colspan="5">
|
|
<dl class="row mb-0">
|
|
<dt class="col-sm-2">Expediente</dt>
|
|
<dd class="col-sm-10">@exp.CodigoExpediente</dd>
|
|
|
|
<dt class="col-sm-2">Asunto</dt>
|
|
<dd class="col-sm-10">@exp.Asunto</dd>
|
|
|
|
<dt class="col-sm-2">Fecha creación</dt>
|
|
<dd class="col-sm-10">
|
|
@exp.FechaCreacion?.ToLocalTime().ToString("dd/MM/yyyy HH:mm")
|
|
</dd>
|
|
|
|
<dt class="col-sm-2">Estado</dt>
|
|
<dd class="col-sm-10">@exp.Estado</dd>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(exp.FileUrl))
|
|
{
|
|
<dt class="col-sm-2">Enlace Gestiona</dt>
|
|
<dd class="col-sm-10">
|
|
<a href="@exp.FileUrl" target="_blank">@exp.FileUrl</a>
|
|
</dd>
|
|
}
|
|
</dl>
|
|
</td>
|
|
</tr>
|
|
}
|
|
}
|
|
</tbody>
|
|
</table>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
// ============================================================
|
|
// CONFIG (mismo origen que GestionaService)
|
|
// ============================================================
|
|
|
|
private string ApiBase => Configuration["Gestiona:ApiBase"] ?? "";
|
|
private string AccessToken => Configuration["Gestiona:AccessToken"] ?? "";
|
|
|
|
private string RestBaseUrl =>
|
|
string.IsNullOrWhiteSpace(ApiBase)
|
|
? ""
|
|
: $"{ApiBase.TrimEnd('/')}/rest";
|
|
|
|
private readonly JsonSerializerOptions jsonOpts = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true
|
|
};
|
|
|
|
// ============================================================
|
|
// ESTADO UI
|
|
// ============================================================
|
|
|
|
private string nifBuscado = string.Empty;
|
|
private string nifMostrado = string.Empty;
|
|
|
|
private const string ModoTodas = "todas";
|
|
private const string ModoRango = "rango";
|
|
private const string ModoUltimos = "ultimos";
|
|
|
|
private string modoFecha = ModoTodas;
|
|
|
|
private DateTime? fechaDesde;
|
|
private DateTime? fechaHasta;
|
|
|
|
private int mesesUltimos = 3;
|
|
|
|
private bool isSearching;
|
|
private bool haBuscado;
|
|
private string errorMessage = string.Empty;
|
|
|
|
private List<ExpedienteTerceroDto> expedientes = new();
|
|
|
|
// expediente cuyo detalle está abierto
|
|
private ExpedienteTerceroDto? expedienteSeleccionado;
|
|
|
|
private bool IsModo(string valor) => string.Equals(modoFecha, valor, StringComparison.Ordinal);
|
|
private void SetModo(string valor) => modoFecha = valor;
|
|
|
|
private void ToggleDetalle(ExpedienteTerceroDto exp)
|
|
{
|
|
if (expedienteSeleccionado == exp)
|
|
expedienteSeleccionado = null;
|
|
else
|
|
expedienteSeleccionado = exp;
|
|
}
|
|
|
|
// ============================================================
|
|
// MODELOS AUXILIARES (similares a tu servicio)
|
|
// ============================================================
|
|
|
|
private class LinkDto
|
|
{
|
|
public string Rel { get; set; } = string.Empty;
|
|
public string Href { get; set; } = string.Empty;
|
|
}
|
|
|
|
private class ThirdDto
|
|
{
|
|
public string Id { get; set; } = string.Empty;
|
|
public string Nif { get; set; } = string.Empty;
|
|
public List<LinkDto> Links { get; set; } = new();
|
|
}
|
|
|
|
// ============================================================
|
|
// BÚSQUEDA PRINCIPAL
|
|
// ============================================================
|
|
|
|
private async Task BuscarAsync()
|
|
{
|
|
errorMessage = string.Empty;
|
|
haBuscado = false;
|
|
expedienteSeleccionado = null;
|
|
expedientes.Clear();
|
|
|
|
var nif = (nifBuscado ?? string.Empty).Trim().ToUpperInvariant();
|
|
if (string.IsNullOrWhiteSpace(nif))
|
|
{
|
|
errorMessage = "Introduce un NIF para buscar.";
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(RestBaseUrl) || string.IsNullOrWhiteSpace(AccessToken))
|
|
{
|
|
errorMessage = "No está configurada la conexión a Gestiona (ApiBase / AccessToken).";
|
|
return;
|
|
}
|
|
|
|
DateTimeOffset? desde = null;
|
|
DateTimeOffset? hasta = null;
|
|
|
|
// Calcular rango según el modo
|
|
if (modoFecha == ModoRango)
|
|
{
|
|
if (!fechaDesde.HasValue || !fechaHasta.HasValue)
|
|
{
|
|
errorMessage = "Debes indicar las dos fechas (Desde y Hasta).";
|
|
return;
|
|
}
|
|
|
|
desde = new DateTimeOffset(fechaDesde.Value.Date, TimeSpan.Zero);
|
|
hasta = new DateTimeOffset(fechaHasta.Value.Date.AddDays(1).AddTicks(-1), TimeSpan.Zero);
|
|
|
|
if (desde > hasta)
|
|
{
|
|
errorMessage = "La fecha 'Desde' no puede ser mayor que 'Hasta'.";
|
|
return;
|
|
}
|
|
}
|
|
else if (modoFecha == ModoUltimos)
|
|
{
|
|
if (mesesUltimos <= 0)
|
|
{
|
|
errorMessage = "El número de meses debe ser mayor que 0.";
|
|
return;
|
|
}
|
|
|
|
var ahora = DateTimeOffset.UtcNow;
|
|
desde = ahora.AddMonths(-mesesUltimos);
|
|
hasta = ahora;
|
|
}
|
|
|
|
try
|
|
{
|
|
isSearching = true;
|
|
|
|
// 1) Buscar el tercero por NIF
|
|
var tercero = await BuscarTerceroPorNifAsync(nif);
|
|
if (string.IsNullOrEmpty(tercero.Id) || string.IsNullOrEmpty(tercero.SelfHref))
|
|
{
|
|
errorMessage = "No se ha encontrado ningún tercero con ese NIF.";
|
|
return;
|
|
}
|
|
|
|
// 2) Listar expedientes usando third_rest_link
|
|
expedientes = await ObtenerExpedientesPorTerceroFiltradoAsync(
|
|
tercero.SelfHref,
|
|
desde,
|
|
hasta
|
|
);
|
|
|
|
nifMostrado = nif;
|
|
haBuscado = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Error al buscar expedientes: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
isSearching = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// LLAMADAS A GESTIONA
|
|
// ============================================================
|
|
|
|
private void AddBasicHeaders(HttpRequestMessage req)
|
|
{
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", AccessToken);
|
|
req.Headers.TryAddWithoutValidation("Accept", "application/vnd.gestiona.files-page+json");
|
|
}
|
|
|
|
private async Task<(string Id, string SelfHref)> BuscarTerceroPorNifAsync(string nif)
|
|
{
|
|
var filtro = new
|
|
{
|
|
result = new { max_results = 25 },
|
|
filter = new { nif }
|
|
};
|
|
var jsonFiltro = JsonSerializer.Serialize(filtro);
|
|
var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonFiltro));
|
|
var url = $"{ApiBase.TrimEnd('/')}/rest/thirds?filter-view={Uri.EscapeDataString(b64)}";
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, url);
|
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", AccessToken);
|
|
req.Headers.TryAddWithoutValidation("Accept", "application/vnd.gestiona.thirds-page+json");
|
|
|
|
using var resp = await Http.SendAsync(req);
|
|
resp.EnsureSuccessStatusCode();
|
|
var body = await resp.Content.ReadAsStringAsync();
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
if (!doc.RootElement.TryGetProperty("content", out var content) ||
|
|
content.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
var match = content.EnumerateArray().FirstOrDefault(e =>
|
|
e.TryGetProperty("nif", out var nifProp) &&
|
|
string.Equals(nifProp.GetString(), nif, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (match.ValueKind == JsonValueKind.Undefined)
|
|
return default;
|
|
|
|
var id = match.GetProperty("id").GetString() ?? string.Empty;
|
|
|
|
string selfHref = string.Empty;
|
|
if (match.TryGetProperty("links", out var linksEl) && linksEl.ValueKind == JsonValueKind.Array)
|
|
{
|
|
var selfLink = linksEl.EnumerateArray().FirstOrDefault(l =>
|
|
l.TryGetProperty("rel", out var rel) &&
|
|
string.Equals(rel.GetString(), "self", StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (selfLink.ValueKind != JsonValueKind.Undefined &&
|
|
selfLink.TryGetProperty("href", out var hrefEl))
|
|
{
|
|
selfHref = hrefEl.GetString() ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
return (id, selfHref);
|
|
}
|
|
|
|
private async Task<List<ExpedienteTerceroDto>> ObtenerExpedientesPorTerceroFiltradoAsync(
|
|
string thirdSelfHref,
|
|
DateTimeOffset? desde,
|
|
DateTimeOffset? hasta)
|
|
{
|
|
var url = $"{ApiBase.TrimEnd('/')}/rest/files";
|
|
|
|
var bodyObj = new
|
|
{
|
|
third_rest_link = new
|
|
{
|
|
rel = "third",
|
|
href = thirdSelfHref
|
|
}
|
|
};
|
|
|
|
var json = JsonSerializer.Serialize(bodyObj, jsonOpts);
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, url)
|
|
{
|
|
Content = new StringContent(json, Encoding.UTF8, "application/vnd.gestiona.filter.files")
|
|
};
|
|
|
|
AddBasicHeaders(req);
|
|
|
|
using var resp = await Http.SendAsync(req);
|
|
var respBody = await resp.Content.ReadAsStringAsync();
|
|
|
|
if (!resp.IsSuccessStatusCode)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"GET /rest/files filtrado: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{respBody}");
|
|
}
|
|
|
|
using var doc = JsonDocument.Parse(respBody);
|
|
|
|
JsonElement content;
|
|
if (doc.RootElement.TryGetProperty("content", out var contentEl) &&
|
|
contentEl.ValueKind == JsonValueKind.Array)
|
|
{
|
|
content = contentEl;
|
|
}
|
|
else if (doc.RootElement.ValueKind == JsonValueKind.Array)
|
|
{
|
|
content = doc.RootElement;
|
|
}
|
|
else
|
|
{
|
|
return new List<ExpedienteTerceroDto>();
|
|
}
|
|
|
|
var lista = new List<ExpedienteTerceroDto>();
|
|
|
|
foreach (var item in content.EnumerateArray())
|
|
{
|
|
// ===== Fecha creación (string o número) =====
|
|
DateTimeOffset? creation = null;
|
|
if (item.TryGetProperty("creation_date", out var pCreation))
|
|
{
|
|
if (pCreation.ValueKind == JsonValueKind.Number &&
|
|
pCreation.TryGetInt64(out var tsNum))
|
|
{
|
|
creation = DateTimeOffset.FromUnixTimeSeconds(tsNum);
|
|
}
|
|
else if (pCreation.ValueKind == JsonValueKind.String &&
|
|
long.TryParse(pCreation.GetString(), out var tsStr))
|
|
{
|
|
creation = DateTimeOffset.FromUnixTimeSeconds(tsStr);
|
|
}
|
|
}
|
|
|
|
if (desde.HasValue && creation.HasValue && creation.Value < desde.Value)
|
|
continue;
|
|
|
|
if (hasta.HasValue && creation.HasValue && creation.Value > hasta.Value)
|
|
continue;
|
|
|
|
// ===== URLs =====
|
|
string? selfHref = null;
|
|
if (item.TryGetProperty("links", out var linksEl) &&
|
|
linksEl.ValueKind == JsonValueKind.Array)
|
|
{
|
|
foreach (var l in linksEl.EnumerateArray())
|
|
{
|
|
if (l.TryGetProperty("rel", out var relProp) &&
|
|
string.Equals(relProp.GetString(), "self", StringComparison.OrdinalIgnoreCase) &&
|
|
l.TryGetProperty("href", out var hrefProp))
|
|
{
|
|
selfHref = hrefProp.GetString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
string? ehomeUrl = null;
|
|
if (item.TryGetProperty("ehome_url", out var pEhome) &&
|
|
pEhome.ValueKind == JsonValueKind.String)
|
|
{
|
|
ehomeUrl = pEhome.GetString();
|
|
}
|
|
|
|
var fileUrl = ehomeUrl ?? selfHref;
|
|
if (string.IsNullOrWhiteSpace(fileUrl))
|
|
continue;
|
|
|
|
var code = GetJsonString(item, "code");
|
|
var subject = GetJsonString(item, "subject");
|
|
var freeTitle = GetJsonString(item, "free_title");
|
|
var selectableTitle = GetJsonString(item, "selectable_title");
|
|
var procedureName = GetJsonString(item, "procedure_name");
|
|
var asunto = FirstNonEmpty(freeTitle, subject, selectableTitle, procedureName) ?? string.Empty;
|
|
|
|
// ===== Estado (state / status / full_state) =====
|
|
string? state = null;
|
|
if (item.TryGetProperty("state", out var pState) && pState.ValueKind == JsonValueKind.String)
|
|
state = pState.GetString();
|
|
else if (item.TryGetProperty("status", out var pStatus) && pStatus.ValueKind == JsonValueKind.String)
|
|
state = pStatus.GetString();
|
|
else if (item.TryGetProperty("full_state", out var pFullState) && pFullState.ValueKind == JsonValueKind.String)
|
|
state = pFullState.GetString();
|
|
|
|
var dto = new ExpedienteTerceroDto
|
|
{
|
|
FileUrl = fileUrl!,
|
|
CodigoExpediente = code ?? string.Empty,
|
|
Asunto = asunto,
|
|
FechaCreacion = creation,
|
|
Estado = state ?? string.Empty
|
|
};
|
|
|
|
lista.Add(dto);
|
|
}
|
|
|
|
return lista;
|
|
}
|
|
|
|
private static string? GetJsonString(JsonElement item, string propertyName)
|
|
{
|
|
return item.TryGetProperty(propertyName, out var property) &&
|
|
property.ValueKind == JsonValueKind.String
|
|
? property.GetString()
|
|
: null;
|
|
}
|
|
|
|
private static string? FirstNonEmpty(params string?[] values)
|
|
{
|
|
foreach (var value in values)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|