feature/alembic-daf

This commit is contained in:
2026-05-12 09:00:32 -06:00
parent b0ce6c8a5a
commit ee9ac4f64f
19 changed files with 1203 additions and 717 deletions

View File

@@ -117,13 +117,13 @@
}
async function handleDownloadScanErrorsCsv() {
const errors = scanResults?.errors;
if (!Array.isArray(errors) || errors.length === 0) return;
const jobId = scanResults?.job_id;
const errorCount = Number(scanResults?.error_count) || 0;
const errors = scanResults?.errors;
// Try backend download (sin límite). If it fails, fallback to preview CSV.
if (jobId) {
// Descarga completa desde el backend (JSONL → CSV) siempre que el scan reportó errores y hay job_id.
// No depende del preview acotado en la respuesta JSON.
if (jobId && errorCount > 0) {
try {
let blob: Blob;
switch (scanErrorsDownloadType) {
@@ -178,17 +178,28 @@
document.body.removeChild(a);
URL.revokeObjectURL(url);
return;
} catch (e) {
// fallback below
} catch {
// Si falla la API, intentar CSV desde preview si existe.
}
}
if (!Array.isArray(errors) || errors.length === 0) return;
const filename = `errores_${Date.now()}.csv`;
const rows = errors.map((e: Record<string, unknown>) => ({
line: e.line,
col: e.col,
msg: e.msg,
solution: e.solution,
advertencia: e.warning ? 'si' : 'no',
codigo: e.code ?? '',
not_found_reason: e.not_found_reason ?? ''
}));
downloadCsv(
filename,
['linea', 'columna', 'mensaje', 'solucion'],
errors,
['line', 'col', 'msg', 'solution']
['linea', 'columna', 'mensaje', 'solucion', 'advertencia', 'codigo', 'not_found_reason'],
rows,
['line', 'col', 'msg', 'solution', 'advertencia', 'codigo', 'not_found_reason']
);
}
@@ -334,18 +345,19 @@
</p>
</div>
</div>
{#if scanResults.errors && scanResults.errors.length > 0}
<div class="border rounded-lg overflow-hidden shadow-sm">
<div class="bg-muted/50 px-4 py-2 border-b flex justify-between items-center">
<h5 class="text-xs font-bold text-foreground uppercase tracking-wide">
{csvMsg('modal.errors_heading')}
</h5>
<div class="flex items-center gap-2">
<span
class="text-[10px] bg-secondary text-secondary-foreground px-2 py-0.5 rounded-full border"
>
{csvFmt('modal.errors_badge', { shown: scanErrorsShown, total: scanErrorsTotal })}
</span>
{#if scanResults.errors && scanResults.errors.length > 0}
<span
class="text-[10px] bg-secondary text-secondary-foreground px-2 py-0.5 rounded-full border"
>
{csvFmt('modal.errors_badge', { shown: scanErrorsShown, total: scanErrorsTotal })}
</span>
{/if}
<Button
variant="ghost"
onclick={handleDownloadScanErrorsCsv}
@@ -356,41 +368,42 @@
</Button>
</div>
</div>
<div class="max-h-60 overflow-y-auto bg-card relative">
<table class="w-full text-xs text-left">
<thead
class="text-muted-foreground font-medium bg-muted/30 sticky top-0 z-10 shadow-sm backdrop-blur-sm"
>
<tr>
<th class="px-4 py-2 w-16">{csvMsg('modal.th_line')}</th>
<th class="px-4 py-2 w-40">{csvMsg('modal.th_column')}</th>
<th class="px-4 py-2">{csvMsg('modal.th_message')}</th>
<th class="px-4 py-2 min-w-[240px]">{csvMsg('modal.th_solution')}</th>
</tr>
</thead>
<tbody class="divide-y">
{#each scanResults.errors as err}
<tr class="hover:bg-muted/30 transition-colors {err.warning ? 'opacity-70' : ''}">
<td class="px-4 py-2 font-mono text-muted-foreground">{err.line}</td>
<td class="px-4 py-2 font-mono font-medium text-foreground">{err.col || '-'}</td>
<td class="px-4 py-2 {err.warning ? 'text-amber-600 dark:text-amber-400' : 'text-destructive'}">{err.msg || '-'}</td>
<td class="px-4 py-2 text-muted-foreground whitespace-pre-wrap">{err.solution || '-'}</td>
{#if scanResults.errors && scanResults.errors.length > 0}
<div class="max-h-60 overflow-y-auto bg-card relative">
<table class="w-full text-xs text-left">
<thead
class="text-muted-foreground font-medium bg-muted/30 sticky top-0 z-10 shadow-sm backdrop-blur-sm"
>
<tr>
<th class="px-4 py-2 w-16">{csvMsg('modal.th_line')}</th>
<th class="px-4 py-2 w-40">{csvMsg('modal.th_column')}</th>
<th class="px-4 py-2">{csvMsg('modal.th_message')}</th>
<th class="px-4 py-2 min-w-[240px]">{csvMsg('modal.th_solution')}</th>
</tr>
{/each}
</tbody>
</table>
</div>
{#if scanErrorsTruncated}
<p class="text-xs text-muted-foreground px-4 py-2">
{csvMsg('modal.errors_truncated')}
</thead>
<tbody class="divide-y">
{#each scanResults.errors as err}
<tr class="hover:bg-muted/30 transition-colors {err.warning ? 'opacity-70' : ''}">
<td class="px-4 py-2 font-mono text-muted-foreground">{err.line}</td>
<td class="px-4 py-2 font-mono font-medium text-foreground">{err.col || '-'}</td>
<td class="px-4 py-2 {err.warning ? 'text-amber-600 dark:text-amber-400' : 'text-destructive'}">{err.msg || '-'}</td>
<td class="px-4 py-2 text-muted-foreground whitespace-pre-wrap">{err.solution || '-'}</td>
</tr>
{/each}
</tbody>
</table>
</div>
{#if scanErrorsTruncated}
<p class="text-xs text-muted-foreground px-4 py-2">
{csvMsg('modal.errors_truncated')}
</p>
{/if}
{:else}
<p class="text-sm text-muted-foreground px-4 py-3">
{csvFmt('modal.errors_missing_detail', { count: scanResults.error_count })}
</p>
{/if}
</div>
{:else}
<p class="text-sm text-muted-foreground mt-1">
{csvFmt('modal.errors_missing_detail', { count: scanResults.error_count })}
</p>
{/if}
{:else}
<div class="rounded-md bg-primary/5 border border-primary/10 p-4 flex items-start gap-3">
<CheckCircle2 class="w-5 h-5 text-primary mt-0.5 shrink-0" />