Files
Antifraude.Net/Antifraude.Net/GestionaDenunciasAN/Components/Pages/Actualizaciones.razor
Pedro b6c61238a5 Añadir endpoint de campos para Gestiona y adaptar login GlobalLeaks a DPoP
- Añade GET /api/denuncias/{id}/gestiona-fields para exponer los campos diarios requeridos por la integración con Gestiona.
- Devuelve un DTO cerrado con fecha, canal, resumen, datos de hechos, protección, sexo y preferencias de notificación.
- Adapta el login contra GlobalLeaks al nuevo flujo DPoP exigido desde la versión 5.0.94.
- Genera proof DPoP con clave EC P-256 efímera y lo envía en la cabecera DPoP junto a X-Token.
- Mejora el mensaje de error cuando GlobalLeaks rechaza el proof DPoP.
2026-07-09 14:55:29 +02:00

1403 lines
60 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@page "/Actualizaciones"
@rendermode InteractiveServer
@attribute [Authorize]
@using GestionaDenuncias.Shared.Models
@using System.Globalization
@using System.IO
@using System.Linq
@using System.Text
@using GestionaDenuncias.Shared.Helpers
@using GestionaDenunciasAN.Services
@attribute [StreamRendering]
@inject GestionaDenunciasAN.Models.UserState userState
@inject NavigationManager Navigation
@inject IHostEnvironment HostEnvironment
@inject IDenunciaStore DenunciaStore
@inject ApiDenunciasClient ApiDenuncias
@inject UiBusyService Busy
<PageTitle>Actualizaciones</PageTitle>
<style>
.pendientes-list {
margin-top: 20px;
}
.collapse-card {
margin-bottom: 1rem;
border: 1px solid #ccc;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
background-color: #fff;
}
.collapse-card:hover {
transform: scale(1.02);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.collapse-card .card-header {
padding: 0.75rem 1.25rem;
cursor: pointer;
background-color: #f7f7f7;
transition: background-color 0.3s ease;
}
.collapse-card .card-header:hover {
background-color: #e2e2e2;
}
.section-heading {
text-align: center;
font-weight: bold;
text-decoration: underline;
font-size: 1.1em;
margin: 1rem 0 0.5rem;
}
/* Tarjetas de actualización (azules) */
.collapse-card.update-card {
background-color: #e3f2fd;
}
.collapse-card.update-card > .card-header {
background-color: #bbdefb;
}
.seleccionar-col {
width: 50px;
text-align: center;
vertical-align: middle;
}
/* === Estética de modal igual que en Pendientes === */
.custom-modal {
background: rgba(0, 0, 0, 0.5);
}
.custom-modal-dialog {
max-width: 500px;
margin: 2rem auto;
}
.custom-modal-content {
border-radius: 0.5rem;
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
border: none;
}
.custom-modal-header {
background-color: #007bff;
color: white;
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
padding: 1rem;
}
.custom-modal-body {
padding: 1.5rem;
background-color: #f8f9fa;
}
.custom-modal-footer {
border-top: none;
padding: 1rem;
}
.modal-section-heading {
font-weight: bold;
font-size: 1.1em;
margin-bottom: 0.5rem;
border-bottom: 2px solid #007bff;
padding-bottom: 0.25rem;
}
</style>
<h3>Actualizaciones</h3>
@if (!string.IsNullOrWhiteSpace(loadError))
{
<div class="alert alert-danger">@loadError</div>
}
@if (!string.IsNullOrWhiteSpace(operationNotice))
{
<div class="alert alert-warning">@operationNotice</div>
}
<input type="text"
class="form-control"
placeholder="Buscar actualizaciones..."
@bind="busqueda"
@bind:event="oninput"
style="margin-bottom:20px;" />
@if (!hasLoaded)
{
<div class="alert alert-info">Cargando datos...</div>
}
else if (actualizaciones == null || !actualizaciones.Any())
{
<div class="alert alert-secondary">No hay actualizaciones pendientes.</div>
}
else
{
<div class="pendientes-list">
@foreach (var denuncia in actualizaciones.Where(d =>
string.IsNullOrWhiteSpace(busqueda) ||
d.Id_Denuncia.ToString().Contains(busqueda) ||
(!string.IsNullOrEmpty(d.Asunto) && d.Asunto.Contains(busqueda, StringComparison.OrdinalIgnoreCase)) ||
(!string.IsNullOrEmpty(d.Nombre) && d.Nombre.Contains(busqueda, StringComparison.OrdinalIgnoreCase)) ||
(!string.IsNullOrEmpty(d.Apellidos) && d.Apellidos.Contains(busqueda, StringComparison.OrdinalIgnoreCase)) ||
(!string.IsNullOrEmpty(d.RazonSocial) && d.RazonSocial.Contains(busqueda, StringComparison.OrdinalIgnoreCase))
))
{
var collapseId = $"upd{denuncia.Id_Denuncia}";
<div class="card mb-2 collapse-card update-card">
<div class="card-header d-flex justify-content-between align-items-center"
data-bs-toggle="collapse"
data-bs-target="#@collapseId"
aria-expanded="false"
aria-controls="@collapseId">
<h5 class="mb-0">Denuncia ID: @denuncia.Id_Denuncia (Actualización)</h5>
<div class="d-flex align-items-center">
<div class="text-muted small me-3">
@if (denuncia.FechaSubidaAGestiona != DateTime.MinValue)
{
<span>Procesada: @denuncia.FechaSubidaAGestiona.ToString("dd/MM/yyyy")</span>
}
else if (denuncia.Fecha != DateTime.MinValue)
{
<span>Creada: @denuncia.Fecha.ToString("dd/MM/yyyy")</span>
}
</div>
<button type="button"
class="btn btn-success btn-sm"
@onclick:stopPropagation="true"
@onclick="() => OpenEnviarAGestionaModal(denuncia)">
Configurar actualizacion expediente
</button>
</div>
</div>
<div id="@collapseId" class="collapse">
<div class="card-body">
<h5 class="section-heading">Datos Generales</h5>
<dl class="row">
@if (denuncia.Id_RegistroDenuncia != 0)
{
<dt class="col-sm-3">ID Registro</dt>
<dd class="col-sm-9">@denuncia.Id_RegistroDenuncia</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.ExpedienteGestionaMostrable))
{
<dt class="col-sm-3">Nº expediente Gestiona</dt>
<dd class="col-sm-9">@denuncia.ExpedienteGestionaMostrable</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.Etiqueta))
{
<dt class="col-sm-3">Etiqueta</dt>
<dd class="col-sm-9">@denuncia.Etiqueta</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.Estado))
{
<dt class="col-sm-3">Estado</dt>
<dd class="col-sm-9">@denuncia.Estado</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.Tipo_Denuncia))
{
<dt class="col-sm-3">Tipo</dt>
<dd class="col-sm-9">@denuncia.Tipo_Denuncia</dd>
}
</dl>
@if (IsExternalGestionaUpdate(denuncia))
{
<div class="alert alert-warning" role="alert">
@GetExternalUpdateWarningText()
</div>
}
@if (!string.IsNullOrWhiteSpace(denuncia.NombreResuelto) ||
!string.IsNullOrWhiteSpace(denuncia.ApellidosResueltos) ||
!string.IsNullOrWhiteSpace(denuncia.RazonSocialResuelta) ||
!string.IsNullOrWhiteSpace(denuncia.DocumentoResuelto))
{
<h5 class="section-heading">Denunciante</h5>
<dl class="row">
@if (!string.IsNullOrWhiteSpace(denuncia.TipoDenunciante))
{
<dt class="col-sm-3">Tipo de denunciante</dt>
<dd class="col-sm-9">@denuncia.TipoDenunciante</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.RazonSocialResuelta))
{
<dt class="col-sm-3">Razón social</dt>
<dd class="col-sm-9">@denuncia.RazonSocialResuelta</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.NombreResuelto))
{
<dt class="col-sm-3">Nombre</dt>
<dd class="col-sm-9">@denuncia.NombreResuelto</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.PrimerApellidoResuelto))
{
<dt class="col-sm-3">1º apellido</dt>
<dd class="col-sm-9">@denuncia.PrimerApellidoResuelto</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.SegundoApellidoResuelto))
{
<dt class="col-sm-3">2º apellido</dt>
<dd class="col-sm-9">@denuncia.SegundoApellidoResuelto</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.ApellidosResueltos))
{
<dt class="col-sm-3">Apellidos</dt>
<dd class="col-sm-9">@denuncia.ApellidosResueltos</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.DocumentoResuelto))
{
<dt class="col-sm-3">@GetDocumentoLabel(denuncia)</dt>
<dd class="col-sm-9">@denuncia.DocumentoResuelto</dd>
}
</dl>
}
<h5 class="section-heading">Detalles</h5>
<dl class="row">
<dt class="col-sm-3">Asunto</dt>
<dd class="col-sm-9">@denuncia.Asunto</dd>
<dt class="col-sm-3">A Quién</dt>
<dd class="col-sm-9">@denuncia.A_Quien_Denuncia</dd>
@if (!string.IsNullOrWhiteSpace(denuncia.DenunciadoDetalle))
{
<dt class="col-sm-3">Detalle denunciado</dt>
<dd class="col-sm-9">@denuncia.DenunciadoDetalle</dd>
}
<dt class="col-sm-3">Descripción</dt>
<dd class="col-sm-9">@denuncia.Descripcion_Denuncia</dd>
@if (!string.IsNullOrWhiteSpace(denuncia.OrganismoDenunciado))
{
<dt class="col-sm-3">Organismo previo</dt>
<dd class="col-sm-9">@denuncia.OrganismoDenunciado</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.SolicitaProteccion))
{
<dt class="col-sm-3">Solicita protección</dt>
<dd class="col-sm-9">@denuncia.SolicitaProteccion</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.MedidasProteccionSolicitadas))
{
<dt class="col-sm-3">Medidas solicitadas</dt>
<dd class="col-sm-9">@denuncia.MedidasProteccionSolicitadas</dd>
}
<dt class="col-sm-3">Lugar</dt>
<dd class="col-sm-9">@denuncia.Lugar_Hechos</dd>
@if (denuncia.Fecha_Hechos != DateTime.MinValue)
{
<dt class="col-sm-3">Fecha Hechos</dt>
<dd class="col-sm-9">@denuncia.Fecha_Hechos.ToString("dd/MM/yyyy")</dd>
}
@if (!string.IsNullOrWhiteSpace(denuncia.AutorizaRemision))
{
<dt class="col-sm-3">Autoriza remisión</dt>
<dd class="col-sm-9">@denuncia.AutorizaRemision</dd>
}
</dl>
@{
var camposFormulario = denuncia.GetCamposFormulario();
}
@if (camposFormulario.Count > 0)
{
<h5 class="section-heading">Formulario Original</h5>
@foreach (var grupoCampos in camposFormulario.GroupBy(field => string.IsNullOrWhiteSpace(field.Section) ? "Sin sección" : field.Section))
{
<h6 class="mt-3">@grupoCampos.Key</h6>
<dl class="row">
@foreach (var campo in grupoCampos)
{
<dt class="col-sm-4">@campo.Label</dt>
<dd class="col-sm-8">@(string.IsNullOrWhiteSpace(campo.Value) ? "—" : campo.Value)</dd>
}
</dl>
}
}
@if (ficherosAdjuntos.TryGetValue(denuncia.Id_Denuncia, out var fAdj) && fAdj.Any())
{
<h5 class="section-heading">Ficheros pendientes de subir</h5>
<table class="table table-striped">
<thead>
<tr>
<th>Subir</th>
<th>Nombre</th>
<th>Fecha</th>
<th>Motivo</th>
<th>Tamaño (bytes)</th>
<th>Ver</th>
</tr>
</thead>
<tbody>
@foreach (var f in fAdj)
{
var isReport = IsReportFileName(f.NombreFichero);
<tr>
<td>
<input class="form-check-input"
type="checkbox"
checked="@IsFileSelectedForUpload(denuncia.Id_Denuncia, f.NombreFichero)"
disabled="@isReport"
title='@(isReport ? "El report se sube siempre" : "Incluir este fichero en la subida")'
@onchange="args => ToggleFileSelection(denuncia.Id_Denuncia, f.NombreFichero, args.Value is bool selected && selected)" />
</td>
<td>
@f.NombreFichero
@if (isReport)
{
<span class="badge bg-primary ms-2">Obligatorio</span>
}
</td>
<td>@FormatFileDate(f.Fecha)</td>
<td>
@if (f.EsReport)
{
<span class="badge bg-primary">Siempre se sube para notificar</span>
}
else if (IsExternalGestionaUpdate(denuncia))
{
<span class="badge bg-warning text-dark">@GetExternalUpdateFileReason()</span>
}
else
{
<span class="badge bg-warning text-dark">Hash nuevo pendiente</span>
}
</td>
<td>@f.Fichero?.Length</td>
<td>
@if (f.Fichero != null && f.Fichero.Length > 0)
{
<a class="btn btn-primary btn-sm" href="@BuildAttachmentContentUrl(denuncia.Id_Denuncia, f.NombreFichero)" target="_blank" rel="noopener">
<i class="bi bi-eye"></i> Ver
</a>
}
else
{
<span class="text-muted">—</span>
}
</td>
</tr>
}
</tbody>
</table>
}
else
{
<div class="alert alert-light">Sin ficheros pendientes.</div>
}
</div>
</div>
</div>
}
</div>
}
<script>
function createObjectUrl(base64, contentType) {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); }
const blob = new Blob([bytes], { type: contentType });
return URL.createObjectURL(blob);
}
function openFile(event, base64, contentType) {
event.preventDefault();
event.stopPropagation();
const objectUrl = createObjectUrl(base64, contentType);
window.open(objectUrl, '_blank');
}
</script>
@if (showModal && selectedDenuncias != null)
{
<div class="modal custom-modal fade show update-modal" style="display:block;" tabindex="-1">
<div class="modal-dialog custom-modal-dialog">
<div class="modal-content custom-modal-content">
<div class="modal-header custom-modal-header">
<h5 class="modal-title">Enviar a Gestiona</h5>
<button type="button" class="btn-close" @onclick="CloseModal"></button>
</div>
<div class="modal-body custom-modal-body">
@if (autoSearchLoading)
{
<div class="alert alert-info d-flex align-items-center" role="alert">
<div class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></div>
Buscando expediente existente en Gestiona…
</div>
}
else if (autoSearchTried && !string.IsNullOrWhiteSpace(autoFoundFileUrl))
{
<div class="alert alert-success" role="alert">
<div class="fw-semibold">Expediente detectado en Gestiona.</div>
<div>Asunto: <strong>@(autoFoundTitle ?? "(sin título)")</strong></div>
<div class="text-muted small">@autoFoundFileUrl</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="chkUsarDetectado" @bind="useAutoFoundExpediente" />
<label class="form-check-label" for="chkUsarDetectado">
Añadir documentos a este expediente.
</label>
</div>
</div>
}
else if (autoSearchTried && string.IsNullOrWhiteSpace(autoFoundFileUrl) && string.IsNullOrWhiteSpace(selectedDenuncias!.Expediente_Gestiona))
{
<div class="alert alert-warning" role="alert">
<div class="fw-semibold">No se ha detectado expediente en Gestiona por asunto.</div>
<div class="small">Puede que esto no sea una actualización del mismo caso.</div>
</div>
}
@if (!string.IsNullOrWhiteSpace(operationError))
{
<div class="alert alert-danger" role="alert">
@operationError
</div>
}
@if (IsExternalGestionaUpdate(selectedDenuncias))
{
<div class="alert alert-warning" role="alert">
@GetExternalUpdateWarningText()
</div>
}
@if (!string.IsNullOrWhiteSpace(selectedDenuncias.ExpedienteGestionaMostrable))
{
<div class="alert alert-info" role="alert">
<div class="fw-semibold">Expediente de Gestiona donde se hará la actualización</div>
<div>Nº expediente: <strong>@selectedDenuncias.ExpedienteGestionaMostrable</strong></div>
</div>
}
<h6 class="modal-section-heading">Descripción</h6>
<div class="mb-3">
<input type="text" class="form-control" @bind="nuevoAsunto" readonly />
</div>
<h6 class="modal-section-heading">Nombre de los documentos</h6>
<div class="mb-3">
<input type="text" class="form-control" @bind="nombreDocumentos"
placeholder="Ej.: Gestión AN (Documento Adjunto 1 Gestión AN...)" />
<small class="text-muted">
Se aplica al modo individual. <em>report.txt</em> se sube como <strong>Denuncia</strong> si entra en esta actualización.
</small>
</div>
<h6 class="modal-section-heading">Modo de subida</h6>
<div class="form-check">
<input class="form-check-input" type="radio" name="uploadMode" id="modoMerge"
checked='@(uploadMode == "merge")' @onclick='() => uploadMode = "merge"' />
<label class="form-check-label" for="modoMerge">Unir todos los ficheros en un único PDF</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="uploadMode" id="modoIndividual"
checked='@(uploadMode == "individual")' @onclick='() => uploadMode = "individual"' />
<label class="form-check-label" for="modoIndividual">Subir ficheros de forma independiente</label>
</div>
<h6 class="modal-section-heading">Grupo de destino</h6>
<div class="form-check">
<input class="form-check-input" type="radio" name="selectedGroup" id="grupo600"
checked='@(selectedGroup == "600")' @onclick='() => selectedGroup = "600"' />
<label class="form-check-label" for="grupo600">
600. Asuntos Jurídicos y Protección a la Persona Denunciante
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="selectedGroup" id="grupo510"
checked='@(selectedGroup == "510")' @onclick='() => selectedGroup = "510"' />
<label class="form-check-label" for="grupo510">510. SDI Investigación Entradas</label>
</div>
@{
var modalThirdParty = selectedThirdParty ?? ThirdPartyIdentityData.FromComplaint(selectedDenuncias);
}
<h6 class="modal-section-heading mt-3">Tercero (denunciante)</h6>
<div class="alert alert-light border mb-3">
Los datos del tercero se cargan automáticamente desde la denuncia y no se pueden editar aquí.
</div>
@if (modalThirdParty.IsAnonymous)
{
<div class="alert alert-warning mb-3">
Denuncia anónima. Se enlazará automáticamente el tercero <strong>00000000T</strong>.
</div>
}
<div class="row g-2">
<div class="col-6 mb-2">
<label class="form-label">@GetDocumentoLabel(selectedDenuncias)</label>
<input class="form-control" value="@GetReadOnlyValue(modalThirdParty.DocumentId)" readonly />
</div>
<div class="col-6 mb-2">
<label class="form-label">Email</label>
<input class="form-control" value="@GetReadOnlyValue(modalThirdParty.Email)" readonly />
</div>
</div>
@if (!string.IsNullOrWhiteSpace(selectedDenuncias.TipoDenunciante))
{
<div class="row g-2">
<div class="col-12 mb-2">
<label class="form-label">Tipo de denunciante</label>
<input class="form-control" value="@selectedDenuncias.TipoDenunciante" readonly />
</div>
</div>
}
@if (modalThirdParty.IsLegalEntity)
{
<div class="row g-2">
<div class="col-12 mb-2">
<label class="form-label">Razón social</label>
<input class="form-control" value="@GetReadOnlyValue(selectedDenuncias.RazonSocialResuelta)" readonly />
</div>
</div>
}
else
{
<div class="row g-2">
<div class="col-4 mb-2">
<label class="form-label">Nombre</label>
<input class="form-control" value="@GetReadOnlyValue(selectedDenuncias.NombreResuelto)" readonly />
</div>
<div class="col-4 mb-2">
<label class="form-label">1º apellido</label>
<input class="form-control" value="@GetReadOnlyValue(selectedDenuncias.PrimerApellidoResuelto)" readonly />
</div>
<div class="col-4 mb-2">
<label class="form-label">2º apellido</label>
<input class="form-control" value="@GetReadOnlyValue(selectedDenuncias.SegundoApellidoResuelto)" readonly />
</div>
</div>
}
@if (!modalThirdParty.IsAnonymous && HasPostalAddress(selectedDenuncias))
{
<div class="row g-2">
<div class="col-12 mb-2">
<label class="form-label">Dirección postal</label>
<textarea class="form-control" rows="2" readonly>@BuildPostalAddressSummary(selectedDenuncias)</textarea>
</div>
</div>
}
<small class="text-muted">
Antes de subir la actualización se comprobará el tercero extraído del formulario y, si no está enlazado al expediente, se enlazará.
</small>
</div>
<div class="modal-footer custom-modal-footer">
<button type="button" class="btn btn-secondary" @onclick="CloseModal">Cancelar</button>
<button type="button" class="btn btn-primary" disabled="@isUploading" @onclick="ConfirmarEnvio">Confirmar</button>
</div>
</div>
</div>
</div>
<div class="modal-backdrop fade show"></div>
}
@code {
private bool hasLoaded = false;
private string busqueda = "";
private List<DenunciasGestiona> actualizaciones = new();
private Dictionary<int, List<FicherosDenuncias>> ficherosAdjuntos = new();
private Dictionary<int, HashSet<string>> excludedUploadFiles = new();
private string loadError = string.Empty;
private string operationError = string.Empty;
private string operationNotice = string.Empty;
private DateOnly? externalUpdateCutoffDate;
// --- Modal / estado ---
private bool showModal = false;
private bool isUploading = false;
private string uploadMode = "individual";
private string selectedGroup = "600";
private string nuevoAsunto = "";
private string nombreDocumentos = "";
private DenunciasGestiona? selectedDenuncias;
private ThirdPartyIdentityData? selectedThirdParty;
// --- Detección automática en Gestiona por asunto ---
private bool autoSearchLoading = false;
private bool autoSearchTried = false;
private string? autoFoundFileUrl = null;
private string? autoFoundTitle = null;
private bool useAutoFoundExpediente = true;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
await CargarDatosAsync();
}
private async Task CargarDatosAsync()
{
try
{
loadError = string.Empty;
var config = await ApiDenuncias.GetAppConfigurationAsync();
externalUpdateCutoffDate = ParseConfiguredCutoffDate(config.ExternalUpdateCutoffDate);
var todas = await CargarDenunciasJsonAsync();
actualizaciones = todas
.Where(d => d.EsActualizacion)
.OrderByDescending(d => d.FechaSubidaAGestiona != DateTime.MinValue ? d.FechaSubidaAGestiona : d.Fecha)
.ToList();
ficherosAdjuntos = await CargarFicherosPorDenunciaAsync(actualizaciones);
actualizaciones = actualizaciones
.Where(d => ficherosAdjuntos.ContainsKey(d.Id_Denuncia))
.ToList();
}
catch (Exception ex)
{
actualizaciones.Clear();
ficherosAdjuntos.Clear();
loadError = $"No se han podido cargar las actualizaciones: {ex.Message}";
}
hasLoaded = true;
StateHasChanged();
}
private async Task<List<DenunciasGestiona>> CargarDenunciasJsonAsync()
{
return await DenunciaStore.GetDenunciasByScopeAsync(DenunciaListScope.Updates);
}
private async Task<Dictionary<int, List<FicherosDenuncias>>> CargarFicherosPorDenunciaAsync(IEnumerable<DenunciasGestiona> denuncias)
{
var result = new Dictionary<int, List<FicherosDenuncias>>();
foreach (var denuncia in denuncias.Where(d => d.Id_Denuncia > 0).DistinctBy(d => d.Id_Denuncia))
{
var ficheros = GetPendingUpdateFilesForComplaint(
denuncia,
await DenunciaStore.GetFicherosByDenunciaAsync(denuncia.Id_Denuncia));
if (ficheros.Count > 0)
{
result[denuncia.Id_Denuncia] = ficheros;
}
}
return result;
}
private async Task OpenEnviarAGestionaModal(DenunciasGestiona d)
{
selectedDenuncias = d;
nuevoAsunto = string.IsNullOrWhiteSpace(d.NombreDenuncia) ? $"Denuncia-{d.Id_Denuncia}-CD" : d.NombreDenuncia;
nombreDocumentos = "";
selectedThirdParty = ThirdPartyIdentityData.FromComplaint(d);
selectedGroup = NormalizeUpdateGroup(selectedGroup);
operationError = string.Empty;
operationNotice = string.Empty;
autoFoundFileUrl = null;
autoFoundTitle = null;
useAutoFoundExpediente = true;
autoSearchTried = false;
if (!string.IsNullOrWhiteSpace(d.Expediente_Gestiona) &&
string.IsNullOrWhiteSpace(d.CodigoExpedienteGestiona))
{
await SincronizarExpedienteGestionaAsync(d, d.Expediente_Gestiona);
}
if (string.IsNullOrWhiteSpace(d.Expediente_Gestiona))
{
autoSearchLoading = true;
showModal = true;
StateHasChanged();
try
{
// Búsqueda automática desactivada temporalmente
}
catch
{
// no bloquea
}
finally
{
autoSearchLoading = false;
autoSearchTried = true;
StateHasChanged();
}
}
else
{
autoFoundFileUrl = null;
autoFoundTitle = null;
autoSearchTried = false;
useAutoFoundExpediente = true;
showModal = true;
StateHasChanged();
}
}
private void CloseModal()
{
showModal = false;
selectedDenuncias = null;
selectedThirdParty = null;
nuevoAsunto = "";
nombreDocumentos = "";
operationError = string.Empty;
autoFoundFileUrl = null;
autoFoundTitle = null;
autoSearchLoading = false;
autoSearchTried = false;
useAutoFoundExpediente = true;
}
private static bool IsReportFileName(string? fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return false;
}
var name = Path.GetFileNameWithoutExtension(fileName);
var extension = Path.GetExtension(fileName);
return name.StartsWith("report", StringComparison.OrdinalIgnoreCase) &&
(extension.Equals(".txt", StringComparison.OrdinalIgnoreCase) ||
extension.Equals(".pdf", StringComparison.OrdinalIgnoreCase));
}
private string FixFileName(string input)
{
var n = input.Normalize(NormalizationForm.FormD);
var sb = new StringBuilder();
foreach (var c in n)
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) sb.Append(c);
var clean = sb.ToString().Normalize(NormalizationForm.FormC).Replace(' ', '_');
return new string(clean.Where(ch => ch <= 127).ToArray());
}
private string BuildMergedAttachmentsFileName(int denunciaId, DateTime timestampUtc)
{
var baseName = string.IsNullOrWhiteSpace(nombreDocumentos)
? $"Adjuntos {denunciaId}_{timestampUtc:yyyyMMddHHmmss}"
: nombreDocumentos.Trim();
return FixFileName($"{Path.GetFileNameWithoutExtension(baseName)}.pdf");
}
private async Task ActualizarDenunciaAsync(DenunciasGestiona d)
{
await DenunciaStore.UpsertDenunciaAsync(d);
}
private async Task ConfirmarEnvio()
{
if (selectedDenuncias == null) return;
if (!selectedDenuncias.EsActualizacion)
{
CloseModal();
Navigation.NavigateTo("/Pendientes");
return;
}
try
{
isUploading = true;
operationError = string.Empty;
operationNotice = string.Empty;
selectedGroup = NormalizeUpdateGroup(selectedGroup);
using var busy = Busy.Show(
"Enviando actualizacion",
"Preparando expediente, carpeta de actualizacion y documentos.");
StateHasChanged();
await Task.Yield();
// 1) Ficheros a subir
Busy.Update(message: "Cargando ficheros pendientes de esta actualizacion.", detail: "Paso 1 de 8");
var existentesF = await DenunciaStore.GetFicherosByDenunciaAsync(selectedDenuncias.Id_Denuncia);
var fDenuncia = GetPendingUpdateFilesForComplaint(selectedDenuncias, existentesF);
var ficherosNoSeleccionados = GetExcludedUploadFileNames(selectedDenuncias.Id_Denuncia, fDenuncia);
var ficherosSeleccionados = GetSelectedUploadFiles(selectedDenuncias.Id_Denuncia, fDenuncia);
var todos = new List<(string FileName, byte[] Content)>();
foreach (var f in ficherosSeleccionados)
{
if (f.Fichero == null) continue;
todos.Add((f.NombreFichero ?? string.Empty, f.Fichero));
}
var ficherosVacios = todos
.Where(t => t.Content.Length == 0)
.Select(t => string.IsNullOrWhiteSpace(t.FileName) ? "(sin nombre)" : t.FileName)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
todos = todos
.Where(t => t.Content.Length > 0)
.ToList();
if (!todos.Any())
{
operationError = ficherosVacios.Count == 0
? $"La denuncia #{selectedDenuncias.Id_Denuncia} no tiene ficheros pendientes para subir como actualización."
: $"La denuncia #{selectedDenuncias.Id_Denuncia} solo tiene ficheros vacíos y no se ha subido nada. Ficheros omitidos: {string.Join(", ", ficherosVacios)}.";
return;
}
// 2) Determinar expediente destino
var expedienteCreadoEnGestiona = false;
string fileUrl;
if (selectedDenuncias.EnGestiona && !string.IsNullOrWhiteSpace(selectedDenuncias.Expediente_Gestiona))
{
fileUrl = selectedDenuncias.Expediente_Gestiona!;
}
else if (!string.IsNullOrWhiteSpace(autoFoundFileUrl) && useAutoFoundExpediente)
{
fileUrl = autoFoundFileUrl!;
selectedDenuncias.Expediente_Gestiona = fileUrl;
selectedDenuncias.EnGestiona = true;
}
else
{
Busy.Update(message: "Creando expediente nuevo en Gestiona.", detail: "Paso 2 de 8");
var createdFile = await ApiDenuncias.CreateGestionaFileAsync(
nuevoAsunto,
"RQ2ZLC - Expediente de Denuncias",
"3109963"
);
fileUrl = createdFile.FileUrl;
Busy.Update(message: "Abriendo expediente y asignando el grupo elegido.", detail: "Paso 2 de 8");
await ApiDenuncias.OpenGestionaFileAsync(
fileUrl,
createdFile.FileOpenUrl,
assignedGroupCode: selectedGroup,
confidential: selectedDenuncias.Confidencial,
freeTitle: nuevoAsunto
);
selectedDenuncias.Expediente_Gestiona = fileUrl;
selectedDenuncias.EnGestiona = true;
expedienteCreadoEnGestiona = true;
}
Busy.Update(message: "Guardando referencia local del expediente.", detail: "Paso 3 de 8");
selectedDenuncias.Expediente_Gestiona = fileUrl;
selectedDenuncias.EnGestiona = true;
Busy.Update(message: "Asignando el expediente al grupo elegido.", detail: "Paso 3 de 8");
await ApiDenuncias.AssignGestionaFileAsync(fileUrl, selectedGroup);
var thirdParty = selectedThirdParty ?? ThirdPartyIdentityData.FromComplaint(selectedDenuncias);
Busy.Update(message: "Resolviendo tercero y enlazandolo al expediente.", detail: "Paso 4 de 8");
var thirdResult = await ApiDenuncias.EnsureGestionaThirdAndLinkAsync(fileUrl, thirdParty);
var ahoraUtc = DateTime.UtcNow;
var carpetaActualizacion = FixFileName($"Actualizacion {DateTime.Now:yyyy-MM-dd HH-mm}");
Busy.Update(message: "Creando carpeta de actualizacion en Gestiona.", detail: "Paso 5 de 8");
var carpetaActualizacionGestiona = await ApiDenuncias.CreateGestionaFolderAsync(fileUrl, carpetaActualizacion);
var documentsTargetUrl = carpetaActualizacionGestiona.DocumentsTargetUrl;
var nombresOriginalesSubidos = new List<string>();
var nombresFinalesSubidos = new List<string>();
string? documentoParaTramitar = null;
var report = todos.FirstOrDefault(t =>
IsReportFileName(t.FileName));
if (!string.IsNullOrWhiteSpace(report.FileName))
{
Busy.Update(message: "Preparando y subiendo el documento principal.", detail: "Paso 6 de 8");
var reportPdfBytes = PdfHelper.MergeReportToPdf(
new (string FileName, byte[] Content)[] { (report.FileName, report.Content) });
var reportFinalName = FixFileName("Denuncia.pdf");
documentoParaTramitar = await ApiDenuncias.UploadGestionaDocumentAsync(
documentsTargetUrl,
reportPdfBytes,
reportFinalName);
nombresOriginalesSubidos.Add(report.FileName);
nombresFinalesSubidos.Add($"{carpetaActualizacion}/{reportFinalName}");
}
var adjuntos = todos
.Where(t => !IsReportFileName(t.FileName))
.ToList();
if (adjuntos.Count > 0 && uploadMode == "merge")
{
Busy.Update(message: "Uniendo adjuntos nuevos en un unico PDF y subiendolo.", detail: "Paso 7 de 8");
var pdfBytes = PdfHelper.MergeFilesToPdf(adjuntos);
var pdfName = BuildMergedAttachmentsFileName(selectedDenuncias.Id_Denuncia, ahoraUtc);
var docUrl = await ApiDenuncias.UploadGestionaDocumentAsync(documentsTargetUrl, pdfBytes, pdfName);
if (string.IsNullOrWhiteSpace(documentoParaTramitar))
{
documentoParaTramitar = docUrl;
}
foreach (var adjunto in adjuntos)
{
nombresOriginalesSubidos.Add(adjunto.FileName);
}
nombresFinalesSubidos.Add($"{carpetaActualizacion}/{pdfName}");
}
else
{
int i = 1;
string baseNombre = string.IsNullOrWhiteSpace(nombreDocumentos) ? "" : $" {nombreDocumentos}";
foreach (var t in adjuntos)
{
Busy.Update(
message: $"Subiendo adjunto nuevo {i} de {adjuntos.Count}.",
detail: "Paso 7 de 8",
current: i,
total: adjuntos.Count);
var origName = t.FileName;
var content = t.Content;
var ext = Path.GetExtension(origName).ToLowerInvariant();
byte[] bytesParaSubir = content;
string finalName;
if (ext == ".txt")
{
bytesParaSubir = PdfHelper.MergeFilesToPdf(
new (string FileName, byte[] Content)[] { (origName, content) });
finalName = FixFileName($"Documento Adjunto {i}{baseNombre}.pdf");
}
else
{
finalName = FixFileName($"Documento Adjunto {i}{baseNombre}{ext}");
}
var docUrl = await ApiDenuncias.UploadGestionaDocumentAsync(documentsTargetUrl, bytesParaSubir, finalName);
if (string.IsNullOrWhiteSpace(documentoParaTramitar))
{
documentoParaTramitar = docUrl;
}
nombresOriginalesSubidos.Add(origName);
nombresFinalesSubidos.Add($"{carpetaActualizacion}/{finalName}");
i++;
}
}
selectedDenuncias.ArchivoElegido = string.Join(";", nombresFinalesSubidos);
if (!string.IsNullOrWhiteSpace(documentoParaTramitar))
{
Busy.Update(message: "Finalizando el documento principal en Gestiona.", detail: "Paso 8 de 8");
selectedGroup = NormalizeUpdateGroup(selectedGroup);
await ApiDenuncias.TramitarGestionaDocumentAsync(
documentoParaTramitar,
selectedGroup,
selectedDenuncias.Id_Denuncia,
isUpdate: true);
}
foreach (var orig in nombresOriginalesSubidos)
{
var f = existentesF.FirstOrDefault(x => x.NombreFichero == orig);
if (f != null)
{
f.Subido = true;
f.FechaSubida = ahoraUtc;
}
}
Busy.Update(message: "Actualizando trazabilidad en la base de datos.", detail: "Finalizando");
await DenunciaStore.MarkFicherosAsUploadedAsync(
selectedDenuncias.Id_Denuncia,
nombresOriginalesSubidos,
ahoraUtc);
selectedDenuncias.EsActualizacion = false;
selectedDenuncias.NombreDenuncia = nuevoAsunto;
selectedDenuncias.FechaSubidaAGestiona = ahoraUtc;
selectedDenuncias.UltimaSubidaGestionaTipo = "Actualización";
selectedDenuncias.UltimoGrupoAsignadoGestiona = GetGestionaGroupDisplay(selectedGroup);
await SincronizarExpedienteGestionaAsync(selectedDenuncias, fileUrl);
await ActualizarDenunciaAsync(selectedDenuncias);
var historialAviso = await RegistrarHistorialGestionaAsync(
selectedDenuncias,
"Actualización",
selectedGroup,
ahoraUtc,
string.Join("; ", nombresFinalesSubidos));
var denunciaProcesadaId = selectedDenuncias.Id_Denuncia;
actualizaciones.RemoveAll(x => x.Id_Denuncia == denunciaProcesadaId);
excludedUploadFiles.Remove(denunciaProcesadaId);
CloseModal();
var avisos = new List<string>();
if (ficherosVacios.Count > 0)
{
avisos.Add($"Se omitieron ficheros vacíos: {string.Join(", ", ficherosVacios)}.");
}
if (ficherosNoSeleccionados.Count > 0)
{
avisos.Add($"No se subieron por selección del usuario: {string.Join(", ", ficherosNoSeleccionados)}.");
}
if (thirdResult.Warnings.Count > 0)
{
avisos.AddRange(thirdResult.Warnings);
}
if (!string.IsNullOrWhiteSpace(historialAviso))
{
avisos.Add(historialAviso);
}
var expedienteInfo = string.IsNullOrWhiteSpace(selectedDenuncias.ExpedienteGestionaMostrable)
? string.Empty
: $" Nº expediente Gestiona: {selectedDenuncias.ExpedienteGestionaMostrable}.";
var resumen = expedienteCreadoEnGestiona
? $"Actualización #{denunciaProcesadaId}: se ha creado el expediente en Gestiona y se han subido los documentos.{expedienteInfo}"
: $"Actualización #{denunciaProcesadaId}: se han añadido los documentos al expediente de Gestiona.{expedienteInfo}";
operationNotice = avisos.Count > 0
? $"{resumen} {string.Join(" ", avisos)}"
: resumen;
StateHasChanged();
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error al confirmar envío (Actualizaciones): {ex}");
operationError = $"No se ha podido completar la actualización #{selectedDenuncias?.Id_Denuncia}: {ex.Message}";
}
finally
{
isUploading = false;
StateHasChanged();
}
}
private bool IsFileSelectedForUpload(int denunciaId, string? fileName)
{
if (IsReportFileName(fileName))
{
return true;
}
return string.IsNullOrWhiteSpace(fileName) ||
!excludedUploadFiles.TryGetValue(denunciaId, out var excluded) ||
!excluded.Contains(fileName);
}
private void ToggleFileSelection(int denunciaId, string? fileName, bool selected)
{
if (string.IsNullOrWhiteSpace(fileName) || IsReportFileName(fileName))
{
return;
}
if (!excludedUploadFiles.TryGetValue(denunciaId, out var excluded))
{
excluded = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
excludedUploadFiles[denunciaId] = excluded;
}
if (selected)
{
excluded.Remove(fileName);
if (excluded.Count == 0)
{
excludedUploadFiles.Remove(denunciaId);
}
}
else
{
excluded.Add(fileName);
}
}
private List<FicherosDenuncias> GetSelectedUploadFiles(int denunciaId, IEnumerable<FicherosDenuncias> files)
{
return files
.Where(file => IsReportFileName(file.NombreFichero) || IsFileSelectedForUpload(denunciaId, file.NombreFichero))
.ToList();
}
private List<string> GetExcludedUploadFileNames(int denunciaId, IEnumerable<FicherosDenuncias> files)
{
return files
.Where(file => !IsReportFileName(file.NombreFichero) && !IsFileSelectedForUpload(denunciaId, file.NombreFichero))
.Select(file => string.IsNullOrWhiteSpace(file.NombreFichero) ? "(sin nombre)" : file.NombreFichero)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static string BuildAttachmentContentUrl(int denunciaId, string? fileName)
{
return $"/api/denuncias/{denunciaId}/ficheros/content?fileName={Uri.EscapeDataString(fileName ?? string.Empty)}";
}
private static string FormatFileDate(DateTime date)
=> date == DateTime.MinValue
? "-"
: date.ToLocalTime().ToString("dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture);
private string GetContentType(string fileName)
{
return Path.GetExtension(fileName).ToLowerInvariant() switch
{
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".pdf" => "application/pdf",
".txt" => "text/plain",
_ => "application/octet-stream",
};
}
private static string GetDocumentoLabel(DenunciasGestiona denuncia)
{
return string.IsNullOrWhiteSpace(denuncia.TipoDocumentoIdentificativo)
? "Documento identificativo"
: denuncia.TipoDocumentoIdentificativo;
}
private static string GetReadOnlyValue(string? value) =>
string.IsNullOrWhiteSpace(value) ? "—" : value;
private static bool IsExternalGestionaUpdate(DenunciasGestiona denuncia)
{
return denuncia.EsActualizacion &&
denuncia.EnGestiona &&
denuncia.FechaSubidaAGestiona == DateTime.MinValue;
}
private static bool HasPostalAddress(DenunciasGestiona denuncia)
{
return !string.IsNullOrWhiteSpace(denuncia.Direccion) ||
!string.IsNullOrWhiteSpace(denuncia.DireccionNumero) ||
!string.IsNullOrWhiteSpace(denuncia.Municipio) ||
!string.IsNullOrWhiteSpace(denuncia.Provincia) ||
!string.IsNullOrWhiteSpace(denuncia.CodigoPostal);
}
private static string BuildPostalAddressSummary(DenunciasGestiona denuncia)
{
var parts = new List<string>();
var street = string.Join(
' ',
new[]
{
denuncia.DireccionTipoVia,
denuncia.Direccion,
denuncia.DireccionNumero
}.Where(value => !string.IsNullOrWhiteSpace(value)));
if (!string.IsNullOrWhiteSpace(street))
{
parts.Add(street);
}
var extras = string.Join(
", ",
new[]
{
string.IsNullOrWhiteSpace(denuncia.DireccionBloque) ? null : $"Bloque {denuncia.DireccionBloque}",
string.IsNullOrWhiteSpace(denuncia.DireccionEscalera) ? null : $"Esc. {denuncia.DireccionEscalera}",
string.IsNullOrWhiteSpace(denuncia.DireccionPiso) ? null : $"Planta {denuncia.DireccionPiso}",
string.IsNullOrWhiteSpace(denuncia.DireccionPuerta) ? null : $"Puerta {denuncia.DireccionPuerta}",
denuncia.DireccionExtra
}.Where(value => !string.IsNullOrWhiteSpace(value)));
if (!string.IsNullOrWhiteSpace(extras))
{
parts.Add(extras);
}
var location = string.Join(
", ",
new[]
{
denuncia.CodigoPostal,
denuncia.Municipio,
denuncia.Provincia
}.Where(value => !string.IsNullOrWhiteSpace(value)));
if (!string.IsNullOrWhiteSpace(location))
{
parts.Add(location);
}
return string.Join(" | ", parts);
}
private List<FicherosDenuncias> GetPendingUpdateFilesForComplaint(DenunciasGestiona denuncia, List<FicherosDenuncias> files)
{
var cutoffDate = IsExternalGestionaUpdate(denuncia)
? externalUpdateCutoffDate
: null;
return GetPendingUpdateFiles(files, cutoffDate);
}
private static List<FicherosDenuncias> GetPendingUpdateFiles(List<FicherosDenuncias> files, DateOnly? externalUpdateCutoffDate = null)
{
var uploadedHashes = files
.Where(file => file.Subido && !string.IsNullOrWhiteSpace(file.ContentSha256))
.Select(file => file.ContentSha256)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var plannedHashes = new HashSet<string>(uploadedHashes, StringComparer.OrdinalIgnoreCase);
var pending = new List<FicherosDenuncias>();
var report = files
.Where(file => file.EsReport)
.OrderByDescending(file => file.Fecha)
.FirstOrDefault();
if (report is not null && !report.Subido)
{
if (string.IsNullOrWhiteSpace(report.ContentSha256) || plannedHashes.Add(report.ContentSha256))
{
pending.Add(report);
}
}
foreach (var file in files
.Where(file => !file.EsReport && !file.Subido)
.OrderBy(file => file.NombreFichero, StringComparer.OrdinalIgnoreCase))
{
if (externalUpdateCutoffDate.HasValue && !IsFileAfterCutoff(file, externalUpdateCutoffDate.Value))
{
continue;
}
if (string.IsNullOrWhiteSpace(file.ContentSha256))
{
pending.Add(file);
continue;
}
if (plannedHashes.Add(file.ContentSha256))
{
pending.Add(file);
}
}
return pending;
}
private static bool IsFileAfterCutoff(FicherosDenuncias file, DateOnly cutoffDate)
{
if (file.Fecha == DateTime.MinValue)
{
return true;
}
return DateOnly.FromDateTime(file.Fecha) > cutoffDate;
}
private static DateOnly? ParseConfiguredCutoffDate(string? value)
{
return DateOnly.TryParseExact(
value,
"yyyy-MM-dd",
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var parsed)
? parsed
: null;
}
private string GetExternalUpdateWarningText()
{
const string prefix = "Esta actualizacion corresponde a una denuncia que ya existia en Gestiona, pero no consta como subida desde esta app.";
return externalUpdateCutoffDate.HasValue
? $"{prefix} Como no tenemos hashes previos, solo se propondran como nuevos los adjuntos con fecha posterior a {externalUpdateCutoffDate.Value:dd/MM/yyyy}."
: $"{prefix} Configura una fecha de corte para que los adjuntos antiguos no aparezcan como nuevos.";
}
private string GetExternalUpdateFileReason()
{
return externalUpdateCutoffDate.HasValue
? $"Posterior al corte {externalUpdateCutoffDate.Value:dd/MM/yyyy}"
: "Sin historico local";
}
private static string NormalizeUpdateGroup(string? groupCode)
=> groupCode == "510" ? "510" : "600";
private static string GetGestionaGroupDisplay(string? groupCode)
{
return string.IsNullOrWhiteSpace(groupCode) ? string.Empty : groupCode.Trim();
}
private async Task<string?> RegistrarHistorialGestionaAsync(
DenunciasGestiona denuncia,
string tipoSubida,
string grupo,
DateTime uploadedAtUtc,
string documentos)
{
try
{
await ApiDenuncias.RegisterGestionaUploadHistoryAsync(
new GestionaUploadHistoryCreateRequest(
denuncia.Id_Denuncia,
denuncia.Expediente_Gestiona ?? string.Empty,
denuncia.ExpedienteGestionaMostrable,
tipoSubida,
grupo,
uploadedAtUtc,
denuncia.NombreDenuncia ?? string.Empty,
documentos));
return null;
}
catch (Exception ex)
{
Console.Error.WriteLine($"No se ha podido registrar el historico de subida a Gestiona: {ex}");
return "La subida se ha completado, pero no se ha podido registrar el histórico operativo en la aplicación.";
}
}
private async Task SincronizarExpedienteGestionaAsync(DenunciasGestiona denuncia, string fileUrl)
{
if (string.IsNullOrWhiteSpace(fileUrl))
{
return;
}
try
{
var expediente = await ApiDenuncias.GetGestionaExpedienteAsync(fileUrl);
if (expediente is null)
{
return;
}
denuncia.Expediente_Gestiona = expediente.FileUrl;
if (!string.IsNullOrWhiteSpace(expediente.CodigoExpediente))
{
denuncia.CodigoExpedienteGestiona = expediente.CodigoExpediente;
}
if (!string.IsNullOrWhiteSpace(expediente.FreeTitle))
{
denuncia.NombreDenuncia = expediente.FreeTitle;
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"No se ha podido sincronizar el expediente de Gestiona: {ex}");
}
}
}