From 855afe66704a16c390a41d258c4439b3892d2074 Mon Sep 17 00:00:00 2001 From: hreyes Date: Sun, 26 Apr 2026 08:24:57 -0600 Subject: [PATCH 01/10] fix/empresa-cert-duracion --- .../7c8d9e0f1a2b_add_annex30_duration_days.py | 29 +++++++++++++ .../a76/general_catalogs/company/dto.py | 2 + .../a76/general_catalogs/company/service.py | 4 +- .../company/submodels/certification.py | 1 + .../edit/[[id]]/+page.svelte | 43 +++++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 backend/alembic/versions/7c8d9e0f1a2b_add_annex30_duration_days.py diff --git a/backend/alembic/versions/7c8d9e0f1a2b_add_annex30_duration_days.py b/backend/alembic/versions/7c8d9e0f1a2b_add_annex30_duration_days.py new file mode 100644 index 00000000..ec565c31 --- /dev/null +++ b/backend/alembic/versions/7c8d9e0f1a2b_add_annex30_duration_days.py @@ -0,0 +1,29 @@ +"""add annex30_duration_days to company_certification + +Revision ID: 7c8d9e0f1a2b +Revises: c8d9e0f1a2b3 +Create Date: 2026-04-24 16:50:00.000000 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7c8d9e0f1a2b' +down_revision = 'c8d9e0f1a2b3' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "company_certification", + sa.Column("annex30_duration_days", sa.Integer(), nullable=True), + schema="a76", + ) + + +def downgrade() -> None: + op.drop_column("company_certification", "annex30_duration_days", schema="a76") diff --git a/backend/api/v1/modules/a76/general_catalogs/company/dto.py b/backend/api/v1/modules/a76/general_catalogs/company/dto.py index 97421872..39f71b25 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/dto.py @@ -99,6 +99,7 @@ class CompanyCreateDTO(BaseModel): annex30_certification_date: Optional[date] = None annex30_certification_number: Optional[str] = Field(None, max_length=50) annex30_modality: Optional[str] = Field(None, max_length=3) + annex30_duration_days: Optional[int] = Field(None, description="Modality duration in days") annex30_company_type: Optional[str] = Field(None, max_length=50) annex30_renewal_date: Optional[date] = None annex30_final_certification_date: Optional[date] = None @@ -328,6 +329,7 @@ class CompanyResponseDTO(BaseModel): annex30_certification_date: Optional[date] = None annex30_certification_number: Optional[str] = None annex30_modality: Optional[str] = None + annex30_duration_days: Optional[int] = None annex30_company_type: Optional[str] = None annex30_renewal_date: Optional[date] = None annex30_final_certification_date: Optional[date] = None diff --git a/backend/api/v1/modules/a76/general_catalogs/company/service.py b/backend/api/v1/modules/a76/general_catalogs/company/service.py index 962292d8..0c55a9d5 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/service.py @@ -136,7 +136,7 @@ class CompanyService: "is_certified_company", "certified_company_registration", "certified_company_start_date", "certified_company_end_date", "annex30_certification_date", "annex30_certification_number", - "annex30_modality", "annex30_company_type", "annex30_renewal_date", + "annex30_modality", "annex30_duration_days", "annex30_company_type", "annex30_renewal_date", "annex30_final_certification_date", "is_seciit_company", "is_oea_company", "ctpat_svi", "trusted_exporter_number", "neec_company" ] @@ -251,7 +251,7 @@ class CompanyService: "is_certified_company", "certified_company_registration", "certified_company_start_date", "certified_company_end_date", "annex30_certification_date", "annex30_certification_number", - "annex30_modality", "annex30_company_type", "annex30_renewal_date", + "annex30_modality", "annex30_duration_days", "annex30_company_type", "annex30_renewal_date", "annex30_final_certification_date", "is_seciit_company", "is_oea_company", "ctpat_svi", "trusted_exporter_number", "neec_company" ] diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py index ee70040a..7ca28af4 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py @@ -37,6 +37,7 @@ class CompanyCertification(Base, TimestampMixin): annex30_certification_date: Mapped[Optional[date]] = mapped_column(Date) annex30_certification_number: Mapped[Optional[str]] = mapped_column(String(50)) annex30_modality: Mapped[Optional[str]] = mapped_column(String(3)) + annex30_duration_days: Mapped[Optional[int]] = mapped_column(Integer) annex30_company_type: Mapped[Optional[str]] = mapped_column(String(50)) annex30_renewal_date: Mapped[Optional[date]] = mapped_column(Date) annex30_final_certification_date: Mapped[Optional[date]] = mapped_column(Date) diff --git a/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte index b2faeef1..61b19a4d 100644 --- a/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte @@ -112,6 +112,7 @@ annex30_certification_date: '', annex30_certification_number: '', annex30_modality: '', + annex30_duration_days: 0, annex30_company_type: '', annex30_renewal_date: '', annex30_final_certification_date: '', @@ -300,6 +301,7 @@ annex30_certification_date: toInputDate(item.annex30_certification_date), annex30_certification_number: item.annex30_certification_number || '', annex30_modality: item.annex30_modality || '', + annex30_duration_days: item.annex30_duration_days || 0, annex30_company_type: item.annex30_company_type || '', annex30_renewal_date: toInputDate(item.annex30_renewal_date), annex30_final_certification_date: toInputDate(item.annex30_final_certification_date), @@ -531,6 +533,36 @@ loading = false; } } + + // Auto-cálculo de duración y fecha de renovación del Anexo 30 + $effect(() => { + const modality = formData.annex30_modality; + const certDateStr = formData.annex30_certification_date; + + // 1. Mapear modalidad a días + const modalityDaysMap: Record = { + 'A': 365, + 'AA': 730, + 'AAA': 1095 + }; + + if (modality && modalityDaysMap[modality]) { + formData.annex30_duration_days = modalityDaysMap[modality]; + } else if (!modality) { + formData.annex30_duration_days = 0; + } + + // 2. Calcular fecha de renovación + if (certDateStr && formData.annex30_duration_days > 0) { + // Usamos el formato ISO para evitar problemas de zona horaria al crear el objeto Date + const certDate = new Date(certDateStr + 'T00:00:00'); + if (!isNaN(certDate.getTime())) { + const renewalDate = new Date(certDate); + renewalDate.setDate(certDate.getDate() + formData.annex30_duration_days); + formData.annex30_renewal_date = renewalDate.toISOString().split('T')[0]; + } + } + }); async function handleSubmit() { error = null; @@ -598,6 +630,7 @@ annex30_certification_date: toDbDate(formData.annex30_certification_date), annex30_certification_number: clean(formData.annex30_certification_number), annex30_modality: clean(formData.annex30_modality), + annex30_duration_days: Number(formData.annex30_duration_days) || 0, annex30_company_type: clean(formData.annex30_company_type), annex30_renewal_date: toDbDate(formData.annex30_renewal_date), annex30_final_certification_date: toDbDate(formData.annex30_final_certification_date), @@ -1035,6 +1068,16 @@ +
+ + +
(formData.status = (e.target as HTMLInputElement).value || null)} + placeholder="pending / success / failed" + disabled={loading} + /> +
+
+ + (formData.message = (e.target as HTMLInputElement).value || null)} + placeholder="Mensaje del servicio externo" + disabled={loading} + /> +
+ {#if item?.result_json} +
+ +
{(() => {
+								try { return JSON.stringify(JSON.parse(item.result_json || '{}'), null, 2); }
+								catch { return item.result_json || ''; }
+							})()}
+
+ {/if} + + + + + + +
+ + {#if isEdit} + + {/if} +
+
+ + diff --git a/frontend/src/lib/components/dashboard/despacho/doda/doda-export-excel-dialog.svelte b/frontend/src/lib/components/dashboard/despacho/doda/doda-export-excel-dialog.svelte new file mode 100644 index 00000000..bd0b6b3c --- /dev/null +++ b/frontend/src/lib/components/dashboard/despacho/doda/doda-export-excel-dialog.svelte @@ -0,0 +1,178 @@ + + + + + + {m['sidebar.doda_alta.export_excel_badge']()} — {m['sidebar.doda_alta.export_report_heading']()} + + +
+ {m['sidebar.doda_alta.export_excel_badge']()} +
+ +
+

+ {m['sidebar.doda_alta.export_report_heading']()} +

+ +
+
+ + +
+
+ + +
+
+ +
+ +
+ +
+ + +
+ +
+
+ + { + if (v === 'csv' || v === 'xls' || v === 'txt') fileFormat = v; + }} + > + + .{fileFormat} + + + .csv (coma) + .xls (tabulador, Excel) + .txt (|) + + +
+
+
+ + + + +
+
diff --git a/frontend/src/lib/components/dashboard/despacho/doda/doda-progress-dialog.svelte b/frontend/src/lib/components/dashboard/despacho/doda/doda-progress-dialog.svelte new file mode 100644 index 00000000..6580d455 --- /dev/null +++ b/frontend/src/lib/components/dashboard/despacho/doda/doda-progress-dialog.svelte @@ -0,0 +1,217 @@ + + + + + + {m['sidebar.doda_alta.progress_title']()} + Alta {variantLabel} — Task ID: {taskId} + + +
+ {#if state === 'SUCCESS'} +
+ +

{m['sidebar.doda_alta.progress_success']()}

+
+ {#if result} +
+ {#each Object.entries(result) as [key, value]} + {#if key !== 'state' && value && typeof value === 'string'} +
+
{key.replace(/_/g, ' ')}:
+
{value}
+
+ {/if} + {/each} +
+ {/if} + + {:else if state === 'FAILURE'} +
+ +
+

{m['sidebar.doda_alta.progress_error']()}

+ {#if errorMsg} +

{errorMsg}

+ {/if} +
+
+ + {:else} +
+
+ +

{currentStep}

+
+ {#if progress > 0} + +

{progress}%

+ {/if} +
+ {/if} + + {#if taskId} +
+

Task ID:

+

{taskId}

+
+ {/if} +
+ + + {#if isTerminal} + + {:else} + + {/if} + +
+
diff --git a/frontend/src/lib/components/dashboard/general_catalogs/doda/child-detail-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/doda/child-detail-table.svelte index b660ea7f..e825c8ed 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/doda/child-detail-table.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/child-detail-table.svelte @@ -1,7 +1,7 @@ -
-
+
+
{#if title} -

{title}

+

{title}

{/if}
-
+
- - + + {#each columns as col} - {col.header} + {col.header} + {/each} {#if data.length === 0} - + - No hay registros. +
+ + Sin filas. «Nuevo» para añadir. +
{:else} {#each data as row, i} - + { + selectedIndex = i; + onRowSelect?.(row, i); + }} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + selectedIndex = i; + onRowSelect?.(row, i); + } + }} + > {#each columns as col} - + {#if col.render} {col.render(row[col.key])} {:else} @@ -76,17 +130,21 @@
-
-