diff --git a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/create.py b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/create.py
index b52049e7..79a6f0cb 100644
--- a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/create.py
+++ b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/create.py
@@ -14,7 +14,7 @@ def validate_create(db: Session, invoice: InvoiceHeaderCreate, tenant_id: int, c
if not invoice.invoice_type:
errors.add_required_error("invoice_type")
- if not invoice.document_type:
+ if not invoice.document_type and invoice.invoice_type != "MEX":
errors.add_required_error("document_type")
if not invoice.invoice_number:
diff --git a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py
index da4cc970..97ee1843 100644
--- a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py
+++ b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/update.py
@@ -224,9 +224,10 @@ def validate_update(
else:
invoice_data.compliance_mx.aduana = existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None
- # Validar que aduana sea obligatorio
- if not invoice_data.compliance_mx or not invoice_data.compliance_mx.aduana:
- errors.add_required_error("aduana")
+ # Validar que aduana sea obligatorio (excepto para MEX)
+ if existing_invoice.invoice_type != "MEX":
+ if not invoice_data.compliance_mx or not invoice_data.compliance_mx.aduana:
+ errors.add_required_error("aduana")
# Columna AC: Sección de Despacho / Puerto de Entrada (Opcional)
if invoice_data.compliance_mx and invoice_data.compliance_mx.port_of_entry:
diff --git a/backend/api/v1/modules/a76/invoices/models.py b/backend/api/v1/modules/a76/invoices/models.py
index 402054a1..f3f9e0c7 100644
--- a/backend/api/v1/modules/a76/invoices/models.py
+++ b/backend/api/v1/modules/a76/invoices/models.py
@@ -58,7 +58,7 @@ class TransportType(str, Enum):
PLATES = "licence plates"
TRUCK = "truck"
VESSEL = "vessel"
- BARGE = "rail barge"
+ BARGE = "rail_barge"
CONTAINER = "container"
AIRPLANE = "airplane"
GONDOLA = "gondola"
@@ -82,9 +82,10 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
invoice_type: Mapped[str] = mapped_column(
ForeignKey("public.invoice_types.key")
) # TIPOFACTURA / TIPODOC
- document_type: Mapped[str] = mapped_column(
- ForeignKey("public.pedimento_regimens.code")
- ) # CLAVEDOCUMENTO / Clave de documento
+ document_type: Mapped[Optional[str]] = mapped_column(
+ ForeignKey("public.pedimento_regimens.code"), nullable=True
+ )
+ # CLAVEDOCUMENTO / Clave de documento
invoice_number: Mapped[str] = mapped_column(
String(100)
) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION/FACTURAENVIO/FACTURASALIDA
@@ -299,6 +300,9 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
act_value: Mapped[Optional[str]] = mapped_column(
String(5)
) # ACTVALOR / Actualizar valor
+ rule_3121_parties_ii: Mapped[Optional[bool]] = mapped_column(
+ Boolean, default=False, server_default="false"
+ ) # REGLA3121PARTESII / Regla 3.1.21 Partes II
is_pedimento_pending: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False, server_default="false"
) # PED_PENDIENTE_ASIGNAR (Mapear 1 -> True, 0 -> False)
@@ -375,7 +379,7 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
String(1)
) # RAZONEXPORTACION / Razón de exportación
signature_key: Mapped[Optional[str]] = mapped_column(
- String(10)
+ String(100)
) # CLAVEFIRMA / Clave de firma
# SM specific
diff --git a/backend/api/v1/modules/a76/invoices/schemas.py b/backend/api/v1/modules/a76/invoices/schemas.py
index 1c3995df..1ac300c9 100644
--- a/backend/api/v1/modules/a76/invoices/schemas.py
+++ b/backend/api/v1/modules/a76/invoices/schemas.py
@@ -22,8 +22,8 @@ class InvoiceHeaderBase(BaseModel):
invoice_type: Optional[str] = Field(
None, max_length=5, description="Invoice type key"
)
- document_type: str = Field(
- ..., max_length=3, description="Document type (Regimen Aduanero)"
+ document_type: Optional[str] = Field(
+ None, max_length=3, description="Document type (Regimen Aduanero)"
)
invoice_number: Optional[str] = Field(
None, max_length=100, description="Invoice number"
@@ -139,6 +139,9 @@ class InvoiceComplianceMxBase(BaseModel):
which_exchange_rate: Optional[str] = Field(
None, max_length=5, description="Which exchange rate"
)
+ rule_3121_parties_ii: Optional[bool] = Field(
+ False, description="Is Rule 3.1.21 Parties II"
+ )
value_method: Optional[str] = Field(None, max_length=2, description="Value method")
act_value: Optional[str] = Field(None, max_length=5, description="Act value")
is_pedimento_pending: bool = Field(..., description="Is pedimento pending")
@@ -187,7 +190,7 @@ class InvoiceComplianceMxBase(BaseModel):
None, max_length=1, description="Reason for export"
)
signature_key: Optional[str] = Field(
- None, max_length=10, description="Signature key"
+ None, max_length=100, description="Signature key"
)
sem_id: Optional[int] = Field(None, description="SEM ID")
diff --git a/backend/api/v1/modules/a76/invoices/services.py b/backend/api/v1/modules/a76/invoices/services.py
index 273049af..f3b0681b 100644
--- a/backend/api/v1/modules/a76/invoices/services.py
+++ b/backend/api/v1/modules/a76/invoices/services.py
@@ -128,6 +128,10 @@ class InvoiceService:
invoice_dict = clean_dict(raw_invoice_dict)
invoice_dict["tenant_id"] = tenant_id
invoice_dict["company_id"] = company_id
+
+ # Ensure document_type respects DB constraints for MEX invoices (bypass clean_dict)
+ if invoice_dict.get("invoice_type") == "MEX" and not invoice_dict.get("document_type"):
+ invoice_dict["document_type"] = None
new_invoice = models.InvoiceHeader(**invoice_dict)
diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/seed.py b/backend/api/v1/modules/public/reference_data/invoice_types/seed.py
index 151163dd..1b42742a 100644
--- a/backend/api/v1/modules/public/reference_data/invoice_types/seed.py
+++ b/backend/api/v1/modules/public/reference_data/invoice_types/seed.py
@@ -28,6 +28,13 @@ seed = [
"both",
"imp",
),
+ (
+ "REP",
+ "REPARACION",
+ "REPARACION DE ACTIVOS FIJOS",
+ "fixed asset",
+ "imp",
+ ),
# === TIPOS DE EXPORTACION ===
("DONAC", "DONACION", "", "both", "exp"),
diff --git a/backend/sync_invoice_types.py b/backend/sync_invoice_types.py
new file mode 100644
index 00000000..cdcf23b5
--- /dev/null
+++ b/backend/sync_invoice_types.py
@@ -0,0 +1,35 @@
+from core.database import CoreSessionLocal
+from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType
+from api.v1.modules.public.reference_data.invoice_types.seed import seed
+
+def sync_invoice_types():
+ db = CoreSessionLocal()
+ try:
+ for s in seed:
+ key, description, note, type_, operation = s
+ obj = db.query(InvoiceType).filter(InvoiceType.key == key).first()
+ if obj:
+ print(f"Updating {key}...")
+ obj.description = description
+ obj.note = note
+ obj.type = type_
+ obj.operation = operation
+ else:
+ print(f"Creating {key}...")
+ obj = InvoiceType(
+ key=key,
+ description=description,
+ note=note,
+ type=type_,
+ operation=operation
+ )
+ db.add(obj)
+ db.commit()
+ except Exception as e:
+ print(f"Error: {e}")
+ db.rollback()
+ finally:
+ db.close()
+
+if __name__ == "__main__":
+ sync_invoice_types()
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index e2563ba5..8abbec08 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -82,7 +82,8 @@
"temporary": "Temporary",
"definitive": "Definitive",
"mexican_purchases": "Mexican Purchases",
- "regime_change": "Regime Change"
+ "regime_change": "Regime Change",
+ "repair": "Repair"
},
"export_invoices": {
"title": "Export Invoices",
diff --git a/frontend/messages/es.json b/frontend/messages/es.json
index 1caa7503..3be2f6be 100644
--- a/frontend/messages/es.json
+++ b/frontend/messages/es.json
@@ -82,7 +82,8 @@
"temporary": "Temporal",
"definitive": "Definitiva",
"mexican_purchases": "Compras mexicanas",
- "regime_change": "Cambio de régimen"
+ "regime_change": "Cambio de régimen",
+ "repair": "Reparación"
},
"export_invoices": {
"title": "Facturas de exportación",
diff --git a/frontend/src/lib/api/dashboard/a76/invoices.ts b/frontend/src/lib/api/dashboard/a76/invoices.ts
index 6b00d38b..99973e13 100644
--- a/frontend/src/lib/api/dashboard/a76/invoices.ts
+++ b/frontend/src/lib/api/dashboard/a76/invoices.ts
@@ -73,6 +73,7 @@ export interface InvoiceComplianceMx {
reason_export?: string | null;
signature_key?: string | null;
sem_id?: number | null;
+ rule_3121_parties_ii?: boolean | null;
}
export interface InvoiceFinancials {
diff --git a/frontend/src/lib/components/dashboard/export/manifest/manifest-form.svelte b/frontend/src/lib/components/dashboard/export/manifest/manifest-form.svelte
index 3bb3f9b4..9a57d055 100644
--- a/frontend/src/lib/components/dashboard/export/manifest/manifest-form.svelte
+++ b/frontend/src/lib/components/dashboard/export/manifest/manifest-form.svelte
@@ -269,7 +269,7 @@
// Helper to check if Repar request
const isRepar = (t: string | undefined) => {
- return t && t.toUpperCase().includes('REPAR');
+ return t === 'REPAR';
};
// Categorize
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/PortSelectorModal.svelte b/frontend/src/lib/components/dashboard/invoices/edit/PortSelectorModal.svelte
new file mode 100644
index 00000000..73308ed6
--- /dev/null
+++ b/frontend/src/lib/components/dashboard/invoices/edit/PortSelectorModal.svelte
@@ -0,0 +1,108 @@
+
+
+
+
+
+ Seleccionar Puerto (Aduana/Sección)
+ Busca y selecciona una sección aduanera de la lista.
+
+
+
+
+
+
+
+
+
+ {#if loading}
+
+
+
+ {:else if filteredSections.length > 0}
+
+
+
+ | Código |
+ Nombre / Sección |
+ |
+
+
+
+ {#each filteredSections as section}
+ handleSelect(section)}>
+ | {section.customs_code} |
+ {section.section_name} |
+
+
+ |
+
+ {/each}
+
+
+ {:else}
+
No se encontraron resultados
+ {/if}
+
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte
index 0df763c5..1f89d8fc 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte
@@ -4,18 +4,22 @@
import { Checkbox } from '$lib/components/ui/checkbox';
import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
import { Button } from '$lib/components/ui/button';
+ import { Search } from 'lucide-svelte';
+ import PortSelectorModal from './PortSelectorModal.svelte';
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
let {
invoice,
formData = $bindable(),
exists = $bindable(),
- operationType = undefined
+ operationType = undefined,
+ invoiceType = undefined
}: {
invoice: Invoice | null;
formData?: any;
exists?: boolean;
operationType?: number;
+ invoiceType?: string;
} = $props();
if (!formData && invoice) {
@@ -28,6 +32,7 @@
cantidad_guias_embarque: invoice.logistics?.guide_number || null,
destino_origen: invoice.logistics?.destination_location || '',
puerto_entrada: invoice.logistics?.origin_location || '',
+ vehicle_data: invoice.logistics?.vehicle_data || '',
// Checkboxes
fue_revisado_equipo: invoice.logistics?.equipment_reviewed || false,
sub_division: invoice.logistics?.is_subdivision || false,
@@ -60,6 +65,7 @@
cantidad_guias_embarque: null,
destino_origen: '',
puerto_entrada: '',
+ vehicle_data: '',
// Checkboxes
fue_revisado_equipo: false,
sub_division: false,
@@ -84,6 +90,11 @@
};
exists = false;
}
+ let showPortModal = $state(false);
+
+ function handlePortSelect(section: any) {
+ formData.puerto_entrada = section.customs_code;
+ }
@@ -104,7 +115,8 @@
-
+
+
@@ -147,7 +159,7 @@
/>
- {#if operationType === 1}
+ {#if operationType === 1 || invoiceType === 'CR' || invoiceType === 'REP'}
@@ -164,14 +176,28 @@
{/if}
- {#if operationType !== 1}
+ {#if operationType !== 1 && invoiceType !== 'CR'}
{/if}
- {#if operationType === 1}
+ {#if operationType === 1 || invoiceType === 'CR'}
@@ -216,25 +242,25 @@
- {#if operationType !== 1}
+ {#if operationType !== 1 && invoiceType !== 'CR'}
-
-
-
-
-
-
-
-
- {/if}
- {#if operationType !== 1}
-
-
-
-
+ {#if invoiceType !== 'REP' && invoiceType !== 'REPAR'}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/if}
{/if}
@@ -305,7 +331,7 @@
- {#if operationType === 1}
+ {#if operationType === 1 || invoiceType === 'CR'}
@@ -327,3 +353,5 @@
{/if}
+
+
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte
index c79b530c..626a106d 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte
@@ -4,7 +4,7 @@
import * as Select from '$lib/components/ui/select';
import * as RadioGroup from '$lib/components/ui/radio-group';
import { Button } from '$lib/components/ui/button';
- import { Search } from 'lucide-svelte';
+ import { Search, Upload } from 'lucide-svelte';
import ManifestSelectorModal from './ManifestSelectorModal.svelte';
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
import type { InvoiceType } from '$lib/api/dashboard/reference_data/invoice_types';
@@ -32,7 +32,8 @@
codePedimentoRegimens = [],
operationType = undefined,
defaultOperationType = undefined,
- exchangeRate = undefined
+ exchangeRate = undefined,
+ invoiceType = undefined
}: {
invoice: Invoice | null;
formData?: any;
@@ -52,6 +53,7 @@
defaultInvoiceType?: string | null;
operationType?: number | null;
exchangeRate?: number | null;
+ invoiceType?: string;
} = $props();
let showManifestModal = $state(false);
@@ -96,7 +98,9 @@
transport_num: invoice.logistics?.vehicle_num || '',
aduana: invoice.compliance_mx?.aduana || '',
document_type: invoice.document_type || '',
- manifest_number: invoice.compliance_mx?.manifest_number || ''
+ manifest_number: invoice.compliance_mx?.manifest_number || '',
+ code_signature: invoice.compliance_mx?.signature_key || '',
+ electronic_signature: invoice.compliance_mx?.electronic_signature || ''
};
} else {
// Creando una nueva factura
@@ -126,7 +130,9 @@
transport_num: '',
aduana: '',
document_type: '',
- manifest_number: ''
+ manifest_number: '',
+ code_signature: '',
+ electronic_signature: ''
};
}
} else {
@@ -176,7 +182,7 @@
];
const soldToHeaderOptions = $derived(
- operationType === 1
+ operationType === 1 || invoiceType === 'CR'
? [
{ value: 'consignado_a', label: 'Consignado a' },
{ value: 'vendido_a', label: 'Vendido a' },
@@ -190,7 +196,7 @@
);
const shippedToHeaderOptions = $derived(
- operationType === 1
+ operationType === 1 || invoiceType === 'CR'
? [
{ value: 'enviado_a', label: 'Enviado a' },
{ value: 'transferido_a', label: 'Transferido a' },
@@ -204,7 +210,7 @@
);
const shippedByHeaderOptions = $derived(
- operationType === 1
+ operationType === 1 || invoiceType === 'CR'
? [
{ value: 'enviado_por', label: 'Enviado Por' },
{ value: 'vendido_por', label: 'Vendido Por' },
@@ -296,25 +302,27 @@
-
Datos del pedimento
-
-
-
Fecha del:
-
{formData.fecha_pedimento_del || '-'}
+ {#if invoiceType !== 'MEX'}
+
Datos del pedimento
+
+
+
Fecha del:
+
{formData.fecha_pedimento_del || '-'}
+
+
+
Fecha al:
+
{formData.fecha_pedimento_al || '-'}
+
+
+
Clave:
+
{formData.clave_pedimento || '-'}
+
+
+
Régimen:
+
{formData.regimen_pedimento || '-'}
+
-
-
Fecha al:
-
{formData.fecha_pedimento_al || '-'}
-
-
-
Clave:
-
{formData.clave_pedimento || '-'}
-
-
-
Régimen:
-
{formData.regimen_pedimento || '-'}
-
-
+ {/if}
@@ -471,7 +479,7 @@
*
- {#if operationType === 1}
+ {#if operationType === 1 || invoiceType === 'CR'}
- {#if operationType !== 1}
+ {#if operationType !== 1 && invoiceType !== 'MEX' && invoiceType !== 'CR' && invoiceType !== 'REP' && invoiceType !== 'REPAR'}
{/if}
- {#if operationType === 1}
+ {#if operationType === 1 || invoiceType === 'CR'}
@@ -719,37 +727,39 @@
Transportista
-
-
- {
- formData.carrier_id = v || null;
- }}
- >
-
-
- {#if formData.carrier_id}
- {transporters.find(
- (t) => String(t.transporter_key) === String(formData.carrier_id)
- )?.name || formData.carrier_id}
- {:else if transporters.length > 0}
- Selecciona transportista...
- {:else}
- Sin datos
- {/if}
-
-
-
- {#each transporters as transporter}
-
- {transporter.transporter_key}
-
- {/each}
-
-
-
+ {#if invoiceType !== 'MEX'}
+
+
+ {
+ formData.carrier_id = v || null;
+ }}
+ >
+
+
+ {#if formData.carrier_id}
+ {transporters.find(
+ (t) => String(t.transporter_key) === String(formData.carrier_id)
+ )?.name || formData.carrier_id}
+ {:else if transporters.length > 0}
+ Selecciona transportista...
+ {:else}
+ Sin datos
+ {/if}
+
+
+
+ {#each transporters as transporter}
+
+ {transporter.transporter_key}
+
+ {/each}
+
+
+
+ {/if}
@@ -872,71 +882,91 @@
-
-
- {
- formData.aduana = v ?? '';
- }}
- >
-
-
- {#if formData.aduana}
- {customsSections.find((cs) => cs.customs_code === formData.aduana)?.section_name ||
- formData.aduana}
- {:else if customsSections.length > 0}
- Selecciona aduana...
- {:else}
- Sin datos
- {/if}
-
-
-
- {#each customsSections as section}
-
- {section.customs_code} - {section.section_name}
-
- {/each}
-
-
-
+ {#if invoiceType !== 'MEX'}
+
+
+ {
+ formData.aduana = v ?? '';
+ }}
+ >
+
+
+ {#if formData.aduana}
+ {customsSections.find((cs) => cs.customs_code === formData.aduana)
+ ?.section_name || formData.aduana}
+ {:else if customsSections.length > 0}
+ Selecciona aduana...
+ {:else}
+ Sin datos
+ {/if}
+
+
+
+ {#each customsSections as section}
+
+ {section.customs_code} - {section.section_name}
+
+ {/each}
+
+
+
+ {/if}
-
-
- {
- formData.document_type = v ?? '';
- }}
- >
-
-
- {#if formData.document_type}
- {codePedimentoRegimens.find((r) => r.regimen_code === formData.document_type)
- ?.regimen_code || formData.document_type}
- {:else if filteredRegimens.length > 0}
- Selecciona régimen...
- {:else if operationType}
- Sin regímenes para tipo {operationType}
- {:else}
- Selecciona tipo de operación primero
- {/if}
-
-
-
- {#each filteredRegimens as regimen}
-
- {regimen.regimen_code}
-
- {/each}
-
-
-
+ {#if invoiceType !== 'MEX'}
+
+
+ {
+ formData.document_type = v ?? '';
+ }}
+ >
+
+
+ {#if formData.document_type}
+ {codePedimentoRegimens.find((r) => r.regimen_code === formData.document_type)
+ ?.regimen_code || formData.document_type}
+ {:else if filteredRegimens.length > 0}
+ Selecciona régimen...
+ {:else if operationType}
+ Sin regímenes para tipo {operationType}
+ {:else}
+ Selecciona tipo de operación primero
+ {/if}
+
+
+
+ {#each filteredRegimens as regimen}
+
+ {regimen.regimen_code}
+
+ {/each}
+
+
+
+ {/if}
+
+ {#if invoiceType === 'MEX'}
+
+
+
+
+
+
+
+ {/if}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte b/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte
index cbbad85e..82adf098 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte
@@ -13,7 +13,8 @@
invoiceTypes = [],
pedimentos = [],
defaultOperationType = undefined,
- defaultInvoiceType = undefined
+ defaultInvoiceType = undefined,
+ invoiceType = undefined
}: {
invoice: Invoice | null;
formData?: any;
@@ -21,6 +22,7 @@
pedimentos?: Pedimento[];
defaultOperationType?: string | null;
defaultInvoiceType?: string | null;
+ invoiceType?: string;
} = $props();
function handlePedimentoChange(pedimentoId: string) {
@@ -81,7 +83,10 @@
fecha_pedimento_del: '',
fecha_pedimento_al: '',
clave_pedimento: '',
- regimen_pedimento: ''
+ regimen_pedimento: '',
+ // Campos específicos para MEX
+ iva_factor: invoice?.financials?.iva_factor || '',
+ alternate_invoice: invoice?.alternate_invoice || ''
};
} else {
// Si formData ya existe pero operation_type está vacío, usar defaultOperationType
@@ -92,7 +97,29 @@
) {
formData.operation_type = defaultOperationType;
}
+ // Ensure new fields exist if formData was created before
+ if (formData.iva_factor === undefined)
+ formData.iva_factor = invoice?.financials?.iva_factor || '';
+ if (formData.alternate_invoice === undefined)
+ formData.alternate_invoice = invoice?.alternate_invoice || '';
}
+ // Filter invoice types based on operation type
+ let filteredInvoiceTypes = $derived(
+ invoiceTypes.filter((type) => {
+ if (!formData.operation_type) return true;
+ return type.operation === 'both' || type.operation === formData.operation_type;
+ })
+ );
+
+ // Reset invoice_type if not valid for new operation_type
+ $effect(() => {
+ if (formData.operation_type && formData.invoice_type) {
+ const isValid = filteredInvoiceTypes.some((t) => t.key === formData.invoice_type);
+ if (!isValid) {
+ formData.invoice_type = '';
+ }
+ }
+ });
@@ -136,7 +163,7 @@
- {#each invoiceTypes as type}
+ {#each filteredInvoiceTypes as type}
{type.key} - {type.description}
@@ -145,47 +172,52 @@
-
-
- {
- formData.is_pedimento_pending = checked;
- }}
- />
-
-
-
- {
- formData.pedimento_id = v ? parseInt(v) : null;
- if (v) {
- handlePedimentoChange(v);
- }
- }}
- >
-
-
- {formData.pedimento || 'Selecciona pedimento...'}
-
-
-
- {#each pedimentos as pedimento}
-
- {pedimento.customs_office?.slice(0, 2)}-{pedimento.license}-{pedimento.pedimento_number}
-
- {/each}
-
-
-
+ {#if invoiceType !== 'MEX'}
+
+
+ {
+ formData.is_pedimento_pending = checked;
+ }}
+ />
+
+
+
+ {
+ formData.pedimento_id = v ? parseInt(v) : null;
+ if (v) {
+ handlePedimentoChange(v);
+ }
+ }}
+ >
+
+
+ {formData.pedimento || 'Selecciona pedimento...'}
+
+
+
+ {#each pedimentos as pedimento}
+
+ {pedimento.customs_office?.slice(
+ 0,
+ 2
+ )}-{pedimento.license}-{pedimento.pedimento_number}
+
+ {/each}
+
+
+
-
-
-
-
+
+
+
+
+ {/if}
+
+ {#if invoiceType === 'MEX'}
+
+ Factor IVA
+
+
+
+ Factura Alterna
+
+
+ {/if}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte
index 37815876..cce7734a 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte
@@ -16,12 +16,14 @@
invoice,
formData = $bindable(),
exists = $bindable(),
- operationType = undefined
+ operationType = undefined,
+ invoiceType = undefined
}: {
invoice: Invoice | null;
formData?: any;
exists?: boolean;
operationType?: number;
+ invoiceType?: string;
} = $props();
let imported = 0;
@@ -258,10 +260,10 @@
}
// Load part number data
- if (line.part_number_id) {
+ if (line.part_number) {
try {
const response = await fetch(
- `/api-sveltekit/parts/${line.part_number_id}?company_id=${activeCompanyId}`,
+ `/api-sveltekit/parts/${line.part_number}?company_id=${activeCompanyId}`,
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
);
if (response.ok) {
@@ -733,6 +735,30 @@
Descripción Esp.
Subpartidas
Partida Princ.
+ {:else if invoiceType === 'CR'}
+ Línea
+ Factura Impo
+ Lin Impo
+ P/S
+ Cantidad Exportada
+ Clase
+ Número de Parte
+ Descripción en Español
+ Contiene Subpartida
+ Partida Principal
+ {:else if invoiceType === 'REP' || invoiceType === 'REPAR'}
+ Línea
+ No. Factura Expo
+ Línea Expo
+ P/S
+ Clase
+ Num. Parte
+ Descripción en Español
+ Cantidad Importada
+ UM
+ Preferencia
+ Contiene Subpartida
+ Partida Principal
{:else}
Línea
P/S
@@ -782,6 +808,40 @@
{item.fa_data?.contains_subitems ? 'Sí' : 'No'}
{item.warehouse || '-'}
+ {:else if invoiceType === 'CR'}
+ {item.line_number}
+ {item.fa_data?.search_invoice || '-'}
+ {item.fa_data?.search_line || '-'}
+ {item.is_subitem ? 'S' : 'P'}
+ {item.quantity?.quantity || '0'}
+ {item.class_code || '-'}
+ {item.part_number || '-'}
+
+ {item.description?.description_spanish || '-'}
+
+ {item.fa_data?.contains_subitems ? 'Sí' : 'No'}
+ {item.warehouse || '-'}
+ {:else if invoiceType === 'REP' || invoiceType === 'REPAR'}
+ {item.line_number}
+ {item.fa_data?.search_invoice || '-'}
+ {item.fa_data?.search_line || '-'}
+ {item.is_subitem ? 'S' : 'P'}
+ {item.class_code || '-'}
+ {item.part_number || '-'}
+
+ {item.description?.description_spanish || '-'}
+
+ {item.quantity?.quantity || '0'}
+ {item.unit_of_measure_code || '-'}
+ {item.reference_number || '-'}
+ {item.fa_data?.contains_subitems ? 'Sí' : 'No'}
+ {item.warehouse || '-'}
{:else}
{item.line_number}
{item.is_subitem ? 'S' : 'P'}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte
index 7d69bef7..87405e85 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte
@@ -12,7 +12,8 @@
seals = [],
incoterms = [],
enclosure = [],
- operationType = undefined
+ operationType = undefined,
+ invoiceType = undefined
}: {
invoice: Invoice | null;
formData?: any;
@@ -21,6 +22,7 @@
incoterms?: any[];
enclosure?: any[];
operationType?: number;
+ invoiceType?: string;
} = $props();
if (!formData && invoice) {
@@ -53,7 +55,8 @@
complement_1: invoice.logistics?.complement_1 || '',
identifier_2: invoice.logistics?.identifier_2 || '',
complement_2: invoice.logistics?.complement_2 || '',
- office_document: invoice.compliance_mx?.office_document || ''
+ office_document: invoice.compliance_mx?.office_document || '',
+ is_mixed: invoice.compliance_mx?.is_mixed || false
};
exists = true;
} else if (!formData) {
@@ -86,7 +89,8 @@
complement_1: '',
identifier_2: '',
complement_2: '',
- office_document: ''
+ office_document: '',
+ is_mixed: false
};
exists = false;
}
@@ -99,9 +103,11 @@
- {operationType === 1
+ {operationType === 1 || invoiceType === 'CR'
? 'Observaciones de la factura mexicana:'
- : 'Observacion de la factura mexicana y bilingue:'}
+ : invoiceType === 'MEX'
+ ? 'Observacion de la factura mexicana y bilingue:'
+ : 'Observacion de la factura mexicana y bilingue:'}
-
-
- Observaciones de la factura americana:
-
-
-
+ {#if invoiceType !== 'MEX'}
+
+
+ Observaciones de la factura americana:
+
+
+
+ {/if}
{#if operationType === 1}
@@ -395,83 +403,123 @@
{/if}
-
-
-
-
Incoterm:
-
{
- formData.incoterm = v ? parseInt(v) : null;
- }}
- >
-
- {formData.incoterm
- ? incoterms.find((p) => p.id === formData.incoterm)?.name || 'Selecciona...'
- : 'Selecciona...'}
-
-
- {#each incoterms as inco}
- {inco.name}
- {/each}
-
-
+ {#if invoiceType !== 'MEX'}
+
+
+
+ Incoterm:
+ {
+ formData.incoterm = v ? parseInt(v) : null;
+ }}
+ >
+
+ {formData.incoterm
+ ? incoterms.find((p) => p.id === formData.incoterm)?.name || 'Selecciona...'
+ : 'Selecciona...'}
+
+
+ {#each incoterms as inco}
+ {inco.name}
+ {/each}
+
+
+
+
+ Recinto:
+ {
+ formData.enclosure = v ? parseInt(v) : null;
+ }}
+ >
+
+ {formData.enclosure
+ ? enclosure.find((p) => p.id === formData.enclosure)?.name || 'Selecciona...'
+ : 'Selecciona...'}
+
+
+ {#each enclosure as rec}
+ {rec.name}
+ {/each}
+
+
+
-
- Recinto:
- {
- formData.enclosure = v ? parseInt(v) : null;
- }}
- >
-
- {formData.enclosure
- ? enclosure.find((p) => p.id === formData.enclosure)?.name || 'Selecciona...'
- : 'Selecciona...'}
+ Método de Valoración:
+ {
+ formData.valuation_method = v;
+ }}
+ >
+
+ {formData.valuation_method || 'Selecciona...'}
+
+
+ General
+ Devaluado
+ Especial
+
+
+
+ {/if}
+
+ {/if}
+
+ {#if invoiceType === 'MEX'}
+
- {#if operationType === 1}
-
- Método de Valoración:
- {
- formData.valuation_method = v;
- }}
- >
-
- {formData.valuation_method || 'Selecciona...'}
-
-
- General
- Devaluado
- Especial
-
-
-
- {/if}
-
+ {/if}
- {#if operationType !== 1}
+ {#if operationType !== 1 && invoiceType !== 'MEX'}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte
index 4a153cd8..4752a40b 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte
+++ b/frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte
@@ -4,7 +4,7 @@
import { Label } from '$lib/components/ui/label';
import { Button } from '$lib/components/ui/button';
import { Checkbox } from '$lib/components/ui/checkbox';
- import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
+ import * as RadioGroup from '$lib/components/ui/radio-group';
import * as Select from '$lib/components/ui/select';
import { Textarea } from '$lib/components/ui/textarea';
import { Plus, Upload } from 'lucide-svelte';
@@ -15,13 +15,15 @@
formData = $bindable(),
exists = $bindable(),
transportModes = [],
- operationType = undefined
+ operationType = undefined,
+ invoiceType = undefined
}: {
invoice: Invoice | null;
formData?: any;
exists?: boolean;
transportModes?: any[];
operationType?: number;
+ invoiceType?: string;
} = $props();
if (!formData && invoice) {
@@ -30,9 +32,11 @@
comments_status: invoice.comments_status || '',
// Campos que van en diferentes recursos pero se editan aquí
transport_mode: invoice.logistics?.transport_mode || null,
- is_mixed: invoice.compliance_mx?.is_mixed || null,
+ is_mixed: invoice.compliance_mx?.is_mixed ? 'si' : 'no',
print_stamp: invoice.financials?.seal_value_2500 || false,
- rule_3121_parties_ii: false,
+ rule_3121_parties_ii: invoice.compliance_mx?.rule_3121_parties_ii || false,
+ // Errores (Copied from Continuation Tab)
+ errores_facturacion: [],
related_doc_id: invoice.related_doc_id || null,
code_signature: invoice.compliance_mx?.signature_key || '',
electronic_signature: invoice.compliance_mx?.electronic_signature || '',
@@ -64,6 +68,8 @@
is_mixed: null,
print_stamp: false,
rule_3121_parties_ii: false,
+ // Errores
+ errores_facturacion: [],
related_doc_id: null,
code_signature: '',
electronic_signature: '',
@@ -94,231 +100,280 @@
// Función para cargar información
console.log('Cargar información');
}
+
+ $effect(() => {
+ console.log('OthersTabForm props:', {
+ invoiceType,
+ operationType,
+ isMixed: formData.is_mixed,
+ rule: formData.rule_3121_parties_ii
+ });
+ });
-
-
-
-
- Modo de Transporte:
- (formData.transport_mode = value || 'TRUCK')}
- >
-
- {transportModes.find((m) => m.key === formData.transport_mode)?.name ||
- 'Seleccionar modo'}
-
-
- {#each transportModes as mode}
-
- {mode.name}
-
- {/each}
-
-
-
-
- {#if operationType !== 1}
-
+{#if invoiceType !== 'MEX'}
+
+
+
-
Spacer
-
-
-
- Imprimir el Sello por Valor menor a 2500 dlls
-
-
-
-
-
-
-
Spacer
-
Es Mixto?
-
(formData.is_mixed = v === 'yes')}
- class="flex gap-4"
- >
-
-
- Sí
-
-
-
- No
-
-
-
Spacer
-
- {:else}
-
-
- Número Master BOL:
-
-
-
- Número Guía:
-
-
-
- Número Embarque:
-
-
-
- Opción IV 18:
+ Modo de Transporte:
(formData.option_iv18 = v || '')}
+ value={formData.transport_mode}
+ onValueChange={(value: string | undefined) =>
+ (formData.transport_mode = value || 'TRUCK')}
>
-
- {formData.option_iv18 || 'Seleccionar opción'}
+
+ {transportModes.find((m) => m.key === formData.transport_mode)?.name ||
+ 'Seleccionar modo'}
- Opción 1
-
+ {#each transportModes as mode}
+
+ {mode.name}
+
+ {/each}
-
-
-
- Datos Entrega
-
-
-
- Entregado
-
+ {#if operationType !== 1 && invoiceType !== 'MEX'}
+
- Recibido por:
-
-
-
- Fecha Entrega:
-
-
-
- {/if}
-
- {#if operationType !== 1}
-
-
- Regla 3.1.21 Partes II
-
- {/if}
-
-
-
- Spacer
- Comentario Estatus:
-
-
-
-
-
-
-
-
- ID Relación Docs:
-
-
-
-
-
-
Firma Electrónica:
-
-
-
-
-
-
-
-
- Mandatario/Persona Autorizada:
-
-
-
-
-
- RFC: {rfc}
-
-
-
-
- CURP: {curp}
-
-
-
-
-
-
- Modo Contingencia
-
-
-
-
- COVE:
-
-
-
-
-
- Núm Operación:
-
-
-
-
-
- Adenda(s):
-
-
-
-
-
-
Observaciones VU:
-
-
-
+
Spacer
+
+
+
+ Imprimir el Sello por Valor menor a 2500 dlls
+
-
+ {#if invoiceType !== 'CR' && invoiceType !== 'REP' && invoiceType !== 'REPAR'}
+
+
+
Spacer
+
Es Mixto?
+
+
+
+ Sí
+
+
+
+ No
+
+
+
+ {/if}
+ {/if}
+
+ {#if (operationType === 1 || invoiceType === 'CR') && invoiceType !== 'REP' && invoiceType !== 'REPAR'}
+
+
+ Número Master BOL:
+
+
- Número Certificado:
-
+ Número Guía:
+
+
+
+ Número Embarque:
+
+
+
+ Opción IV 18:
+ (formData.option_iv18 = v || '')}
+ >
+
+ {formData.option_iv18 || 'Seleccionar opción'}
+
+
+ Opción 1
+
+
-
+
+
+
+ Datos Entrega
+
+
+
+ Entregado
+
+
+ Recibido por:
+
+
+
+ Fecha Entrega:
+
+
+
+ {/if}
+
+ {#if operationType !== 1 && invoiceType !== 'CR' && invoiceType !== 'REP' && invoiceType !== 'REPAR'}
+
+
+ Regla 3.1.21 Partes II
+
+ {/if}
+
+
+
+ Spacer
+ Comentario Estatus:
+
+
+
+
+
+
+
+
+ ID Relación Docs:
+
+
+
+
-
Firma Electrónica:
-
+
Firma Electrónica:
+
+
+
+
+
+
+
+
+ Mandatario/Persona Autorizada:
+
+
+
+
+
+ RFC: {rfc}
+
+
+
+
+ CURP: {curp}
+
+
+
+
+
+
+ Modo Contingencia
+
+
+
+
+ COVE:
+
+
+
+
+
+ Núm Operación:
+
+
+
+
+
+ Adenda(s):
+
+
+
+
+
+
Observaciones VU:
+
+
+
+
+
+
+
+
+ Número Certificado:
+
+
+
+
+
+ Firma Electrónica:
+
+
-
+{:else}
+
+
Errores de Facturación
+
+
+
+
+
+ | Línea |
+ Clave |
+ Descripción |
+
+
+
+ {#if formData.errores_facturacion?.length}
+ {#each formData.errores_facturacion as error}
+
+ | {error.linea} |
+ {error.clave} |
+ {error.descripcion} |
+
+ {/each}
+ {:else}
+
+ |
+ Sin errores registrados
+ |
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+{/if}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts b/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts
index 59586a2c..aa8070a8 100644
--- a/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts
+++ b/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts
@@ -57,7 +57,7 @@ export async function saveInvoice(options: SaveInvoiceOptions): Promise
{
@@ -611,32 +619,36 @@
currentStatusFunction = null;
}
- // --- AQUÍ PASAMOS LA FUNCIÓN DE DESCARGA A LAS COLUMNAS ---
- const columns = createColumns(handleSuccess);
- async function handleModalConfirm(type: string, format: string, currency: string, uomSource: string, weightUnit: string) {
- if (!selectedInvoice || !companyStore.activeCompany) return;
+ // --- AQUÍ PASAMOS LA FUNCIÓN DE DESCARGA A LAS COLUMNAS ---
+ const columns = createColumns(handleSuccess);
+ async function handleModalConfirm(
+ type: string,
+ format: string,
+ currency: string,
+ uomSource: string,
+ weightUnit: string
+ ) {
+ if (!selectedInvoice || !companyStore.activeCompany) return;
- try {
- // 1. Trigger: Start celery task with selected options
- // Note: uomSource and weightUnit are UI-only for now
- const { task_id } = await invoicesReportsApi.triggerPdfGeneration(
- selectedInvoice.id,
- companyStore.activeCompany.id,
- type,
- currency
- );
-
- // 2. Open Progress Dialog
- currentTaskId = task_id;
- currentStatusFunction = invoicesReportsApi.getTaskStatus;
- showProgressDialog = true;
-
- } catch (error) {
- console.error(error);
- toast.error("No se pudo iniciar la descarga");
- }
- }
+ try {
+ // 1. Trigger: Start celery task with selected options
+ // Note: uomSource and weightUnit are UI-only for now
+ const { task_id } = await invoicesReportsApi.triggerPdfGeneration(
+ selectedInvoice.id,
+ companyStore.activeCompany.id,
+ type,
+ currency
+ );
+ // 2. Open Progress Dialog
+ currentTaskId = task_id;
+ currentStatusFunction = invoicesReportsApi.getTaskStatus;
+ showProgressDialog = true;
+ } catch (error) {
+ console.error(error);
+ toast.error('No se pudo iniciar la descarga');
+ }
+ }
@@ -659,13 +671,13 @@
>
-
+
Tipo de Operación
@@ -521,7 +539,11 @@
-
+
General
@@ -538,10 +560,12 @@
Otros
-
-
- Cont.
-
+ {#if invoiceType !== 'MEX'}
+
+
+ Cont.
+
+ {/if}