cambios de denuncias

This commit is contained in:
2026-04-06 17:12:06 +02:00
parent ec76b9a8ae
commit df0e7c0c15
62 changed files with 11751 additions and 1471 deletions

View File

@@ -0,0 +1,593 @@
@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;
}
}