Refactor pedimento-related interfaces and forms to standardize date and payment fields

- Updated `PedimentoDates`, `PedimentoPayments`, `PedimentoTransportMeans`, and `PedimentoValidation` interfaces to include new fields and rename existing ones for consistency.
- Modified forms in Svelte components to reflect the updated interfaces, including new input fields for various dates and payment details.
- Enhanced status handling in the dashboard to accommodate new status options and improve user feedback.
- Implemented token validation on the server-side to ensure secure access to the dashboard.
- Cleaned up login logic to prevent redirection loops when tokens are present but potentially expired.
This commit is contained in:
2025-11-07 10:08:55 -06:00
parent e6d7d23328
commit 29f637a5ee
24 changed files with 727 additions and 185 deletions

View File

@@ -1,11 +1,10 @@
"""
Modelos ORM para gestión de clientes y proveedores
"""
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, SmallInteger, Numeric, ForeignKey
from sqlalchemy import Column, Integer, String, SmallInteger, Numeric, ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from core.database import Base
import enum
class ClientProvider(Base):
@@ -17,7 +16,7 @@ class ClientProvider(Base):
# Primary key
client_id = Column(String(8), primary_key=True, nullable=False)
# Basic information
type_nat_foreign = Column(String(1), nullable=True) # TIPO NACIONAL/EXTRANJERO
name = Column(String(256), nullable=True)
@@ -36,15 +35,15 @@ class ClientProvider(Base):
enabled_disabled = Column(SmallInteger, nullable=True)
# Relationships
address = relationship("GClientProviderAddress", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
programs = relationship("GClientProviderPrograms", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
address = relationship("ClientProviderAddress", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
programs = relationship("ClientProviderPrograms", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
class GClientProviderAddress(Base):
class ClientProviderAddress(Base):
"""
Modelo para la tabla GClientesPro_Direccion - Dirección de clientes y proveedores
"""
__tablename__ = "gclient_provider_address"
__tablename__ = "client_provider_address"
__table_args__ = {"schema": "a76"}
# Primary key (foreign key)
@@ -70,11 +69,11 @@ class GClientProviderAddress(Base):
client_provider = relationship("ClientProvider", back_populates="address")
class GClientProviderPrograms(Base):
class ClientProviderPrograms(Base):
"""
Modelo para la tabla GClientesPro_Programas - Programas de clientes y proveedores
"""
__tablename__ = "gclient_provider_programs"
__tablename__ = "client_provider_programs"
__table_args__ = {"schema": "a76"}
# Primary key (foreign key)

View File

@@ -8,7 +8,7 @@ from fastapi import HTTPException
from typing import List, Optional
import logging
from .models import ClientProvider, GClientProviderAddress, GClientProviderPrograms
from .models import ClientProvider, ClientProviderAddress, ClientProviderPrograms
from .dto import (
ClientProviderCreateDTO,
ClientProviderUpdateDTO,
@@ -72,7 +72,7 @@ class ClientProviderService:
# Crear dirección si se proporciona
if client_data.address:
db_address = GClientProviderAddress(
db_address = ClientProviderAddress(
client_id=client_data.client_id,
**client_data.address.model_dump(exclude_unset=True)
)
@@ -80,7 +80,7 @@ class ClientProviderService:
# Crear programas si se proporciona
if client_data.programs:
db_programs = GClientProviderPrograms(
db_programs = ClientProviderPrograms(
client_id=client_data.client_id,
**client_data.programs.model_dump(exclude_unset=True)
)
@@ -207,7 +207,7 @@ class ClientProviderService:
# Actualizar dirección
if client_data.address:
address = self.db.query(GClientProviderAddress).filter(GClientProviderAddress.client_id == client_id).first()
address = self.db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == client_id).first()
if address:
# Actualizar dirección existente
address_data = client_data.address.model_dump(exclude_unset=True)
@@ -215,7 +215,7 @@ class ClientProviderService:
setattr(address, field, value)
else:
# Crear nueva dirección
address = GClientProviderAddress(
address = ClientProviderAddress(
client_id=client_id,
**client_data.address.model_dump(exclude_unset=True)
)
@@ -223,7 +223,7 @@ class ClientProviderService:
# Actualizar programas
if client_data.programs:
programs = self.db.query(GClientProviderPrograms).filter(GClientProviderPrograms.client_id == client_id).first()
programs = self.db.query(ClientProviderPrograms).filter(ClientProviderPrograms.client_id == client_id).first()
if programs:
# Actualizar programas existentes
programs_data = client_data.programs.model_dump(exclude_unset=True)
@@ -231,7 +231,7 @@ class ClientProviderService:
setattr(programs, field, value)
else:
# Crear nuevos programas
programs = GClientProviderPrograms(
programs = ClientProviderPrograms(
client_id=client_id,
**client_data.programs.model_dump(exclude_unset=True)
)

View File

@@ -7,11 +7,11 @@ from core.database import Base
import enum
class GCompany(Base):
class Company(Base):
"""
Modelo para la tabla GCompany - Información de la empresa
Modelo para la tabla Company - Información de la empresa
"""
__tablename__ = "gcompany"
__tablename__ = "company"
__table_args__ = {"schema": "a76"}
# Primary key

View File

@@ -7,7 +7,7 @@ from fastapi import HTTPException
from typing import List, Optional
import logging
from .models import GCompany
from .models import Company
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
logger = logging.getLogger(__name__)
@@ -34,12 +34,12 @@ class CompanyService:
"""
try:
# Verificar que no exista ya una empresa (solo puede haber una por el consecutivo único)
existing = self.db.query(GCompany).filter(GCompany.consecutive == True).first()
existing = self.db.query(Company).filter(Company.consecutive == True).first()
if existing:
raise HTTPException(status_code=400, detail="A company is already registered in the system")
# Crear empresa
db_company = GCompany(
db_company = Company(
id=company_data.id,
consecutive=company_data.consecutive,
name=company_data.name,
@@ -98,7 +98,7 @@ class CompanyService:
Returns:
CompanyResponseDTO o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.consecutive == True).first()
company = self.db.query(Company).filter(Company.consecutive == True).first()
if not company:
return None
return CompanyResponseDTO.model_validate(company)
@@ -113,7 +113,7 @@ class CompanyService:
Returns:
CompanyResponseDTO o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
company = self.db.query(Company).filter(Company.id == company_id).first()
if not company:
return None
return CompanyResponseDTO.model_validate(company)
@@ -129,7 +129,7 @@ class CompanyService:
Returns:
CompanyResponseDTO actualizada o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
company = self.db.query(Company).filter(Company.id == company_id).first()
if not company:
return None
@@ -158,7 +158,7 @@ class CompanyService:
Returns:
True si se eliminó, False si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
company = self.db.query(Company).filter(Company.id == company_id).first()
if not company:
return False
@@ -179,6 +179,6 @@ class CompanyService:
Returns:
True si existe una empresa, False en caso contrario
"""
return self.db.query(GCompany).filter(GCompany.consecutive == True).first() is not None
return self.db.query(Company).filter(Company.consecutive == True).first() is not None

View File

@@ -5,8 +5,6 @@ from datetime import datetime, time
class PedimentoDatesBase(BaseModel):
"""Base schema for Pedimento Dates"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
entry_date: Optional[datetime] = Field(None, description="Entry date")
pedimento_date: Optional[datetime] = Field(None, description="Pedimento date")
payment_date: Optional[datetime] = Field(None, description="Payment date")
@@ -45,6 +43,8 @@ class PedimentoDatesUpdate(BaseModel):
class PedimentoDatesResponse(PedimentoDatesBase):
"""Schema for Pedimento Dates response"""
id: int
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,6 +1,7 @@
"""
Routes for PedimentoDates CRUD operations
"""
import logging
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
@@ -15,7 +16,7 @@ from ..dtos.pedimento_dates import (
router = APIRouter(prefix="/{pedimento_id}/dates")
logger = logging.getLogger(__name__)
@router.get("/", response_model=PedimentoDatesResponse)
async def get_dates(
@@ -36,8 +37,7 @@ async def get_dates(
@router.post("/", response_model=PedimentoDatesResponse, status_code=201)
async def create_dates(
pedimento_id: int,
async def create_dates(
data: PedimentoDatesCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
@@ -46,11 +46,7 @@ async def create_dates(
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
dates = PedimentoDatesService.create(db, data, tenant_id)
return dates

View File

@@ -1,12 +1,14 @@
"""
Service layer for PedimentoDates CRUD operations
"""
import logging
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_dates import PedimentoDates
from ..dtos.pedimento_dates import PedimentoDatesCreate, PedimentoDatesUpdate
logger = logging.getLogger(__name__)
class PedimentoDatesService:
"""Service class for PedimentoDates business logic"""

View File

@@ -12,10 +12,10 @@ Se han actualizado todos los modelos en `api/v1/modules/a76/` para usar el schem
| Módulo | Tabla | Schema | Estado |
|--------|-------|---------|---------|
| **Company** | `gcompany` | `a76` | ✅ Actualizada |
| **Company** | `company` | `a76` | ✅ Actualizada |
| **Client & Provider** | `client_provider` | `a76` | ✅ Actualizada |
| **Client & Provider** | `gclient_provider_address` | `a76` | ✅ Actualizada |
| **Client & Provider** | `gclient_provider_programs` | `a76` | ✅ Actualizada |
| **Client & Provider** | `client_provider_address` | `a76` | ✅ Actualizada |
| **Client & Provider** | `client_provider_programs` | `a76` | ✅ Actualizada |
| **GParts** | `parts` | `a76` | ✅ Actualizada |
| **Class** | `classes` | `a76` | ✅ Actualizada |
| **Licenses** | `licenses` | `a76` | ✅ Ya estaba |
@@ -27,12 +27,12 @@ Se han actualizado todos los modelos en `api/v1/modules/a76/` para usar el schem
#### 1. Configuración de Schema
```python
# ANTES
class GCompany(Base):
__tablename__ = "gcompany"
class Company(Base):
__tablename__ = "company"
# DESPUÉS
class GCompany(Base):
__tablename__ = "gcompany"
class Company(Base):
__tablename__ = "company"
__table_args__ = {"schema": "a76"}
```
@@ -59,10 +59,10 @@ PostgreSQL Database
├── tenants
├── licenses
├── license_usage
├── gcompany
├── company
├── client_provider
├── gclient_provider_address
├── gclient_provider_programs
├── client_provider_address
├── client_provider_programs
├── parts
└── classes
```

View File

@@ -8,22 +8,48 @@ export interface PedimentoDates {
pedimento_id: number;
tenant_id: number;
entry_date?: string | null;
presentation_date?: string | null;
pedimento_date?: string | null;
payment_date?: string | null;
rectification_payment_date?: string | null;
extraction_date?: string | null;
submission_date?: string | null;
eucan_date?: string | null;
original_date?: string | null;
start_date?: string | null;
end_date?: string | null;
capture_date?: string | null;
capture_time?: string | null;
created_at: string;
updated_at: string;
}
export interface CreatePedimentoDatesData {
entry_date?: string | null;
presentation_date?: string | null;
pedimento_date?: string | null;
payment_date?: string | null;
rectification_payment_date?: string | null;
extraction_date?: string | null;
submission_date?: string | null;
eucan_date?: string | null;
original_date?: string | null;
start_date?: string | null;
end_date?: string | null;
capture_date?: string | null;
capture_time?: string | null;
}
export interface UpdatePedimentoDatesData {
entry_date?: string | null;
presentation_date?: string | null;
pedimento_date?: string | null;
payment_date?: string | null;
rectification_payment_date?: string | null;
extraction_date?: string | null;
submission_date?: string | null;
eucan_date?: string | null;
original_date?: string | null;
start_date?: string | null;
end_date?: string | null;
capture_date?: string | null;
capture_time?: string | null;
}
export const pedimentoDatesApi = {

View File

@@ -7,20 +7,49 @@ export interface PedimentoPayments {
id: number;
pedimento_id: number;
tenant_id: number;
payment_form?: string | null;
bank_identifier?: string | null;
acknowledgment?: string | null;
operation_number?: string | null;
bank_code?: number | null;
cashier?: string | null;
date?: string | null;
time?: string | null;
shift?: string | null;
total_cash_paid?: number | null;
total_contributions?: number | null;
counter_payment?: number | null;
pece_code?: string | null;
payment_id?: number | null;
created_at: string;
updated_at: string;
}
export interface CreatePedimentoPaymentsData {
payment_form?: string | null;
bank_identifier?: string | null;
acknowledgment?: string | null;
operation_number?: string | null;
bank_code?: number | null;
cashier?: string | null;
date?: string | null;
time?: string | null;
shift?: string | null;
total_cash_paid?: number | null;
total_contributions?: number | null;
counter_payment?: number | null;
pece_code?: string | null;
payment_id?: number | null;
}
export interface UpdatePedimentoPaymentsData {
payment_form?: string | null;
bank_identifier?: string | null;
acknowledgment?: string | null;
operation_number?: string | null;
bank_code?: number | null;
cashier?: string | null;
date?: string | null;
time?: string | null;
shift?: string | null;
total_cash_paid?: number | null;
total_contributions?: number | null;
counter_payment?: number | null;
pece_code?: string | null;
payment_id?: number | null;
}
export const pedimentoPaymentsApi = {

View File

@@ -7,26 +7,25 @@ export interface PedimentoTransportMeans {
id: number;
pedimento_id: number;
tenant_id: number;
arrival_key?: string | null;
arrival_data?: string | null;
departure_key?: string | null;
departure_data?: string | null;
destination?: number | null;
entry_exit?: string | null;
arrival?: string | null;
departure?: string | null;
created_at: string;
updated_at: string;
}
export interface CreatePedimentoTransportMeansData {
arrival_key?: string | null;
arrival_data?: string | null;
departure_key?: string | null;
departure_data?: string | null;
destination?: number | null;
entry_exit?: string | null;
arrival?: string | null;
departure?: string | null;
}
export interface UpdatePedimentoTransportMeansData {
arrival_key?: string | null;
arrival_data?: string | null;
departure_key?: string | null;
departure_data?: string | null;
destination?: number | null;
entry_exit?: string | null;
arrival?: string | null;
departure?: string | null;
}
export const pedimentoTransportApi = {

View File

@@ -7,17 +7,37 @@ export interface PedimentoValidation {
id: number;
pedimento_id: number;
tenant_id: number;
document?: string | null;
validator?: string | null;
validation_ack?: string | null;
pre_ack?: string | null;
line_signature?: string | null;
electronic_signature?: string | null;
certificate_number?: string | null;
validator_id?: number | null;
responsible_id?: number | null;
created_at: string;
updated_at: string;
}
export interface CreatePedimentoValidationData {
document?: string | null;
validator?: string | null;
validation_ack?: string | null;
pre_ack?: string | null;
line_signature?: string | null;
electronic_signature?: string | null;
certificate_number?: string | null;
validator_id?: number | null;
responsible_id?: number | null;
}
export interface UpdatePedimentoValidationData {
document?: string | null;
validator?: string | null;
validation_ack?: string | null;
pre_ack?: string | null;
line_signature?: string | null;
electronic_signature?: string | null;
certificate_number?: string | null;
validator_id?: number | null;
responsible_id?: number | null;
}
export const pedimentoValidationApi = {

View File

@@ -67,15 +67,40 @@ function formatDate(date?: string | null): string {
function getStatusColor(status?: string | null): string {
if (!status) return 'bg-gray-100 text-gray-800';
const statusLower = status.toLowerCase();
if (statusLower.includes('activo') || statusLower.includes('completado')) {
const statusUpper = status.toUpperCase();
// Estados completados/exitosos - Verde
if (statusUpper === 'VALIDADO' || statusUpper === 'PAGADO' || statusUpper === 'CARTA CUPO') {
return 'bg-green-100 text-green-800';
} else if (statusLower.includes('pendiente') || statusLower.includes('proceso')) {
}
// Estados en espera/proceso - Amarillo
if (statusUpper.startsWith('ESPERA')) {
return 'bg-yellow-100 text-yellow-800';
} else if (statusLower.includes('cancelado') || statusLower.includes('rechazado')) {
}
// Estados con firma - Azul
if (statusUpper === 'CON FIRMA DE PREVIO') {
return 'bg-blue-100 text-blue-800';
}
// Estados modificables/editables - Índigo
if (statusUpper === 'MODIFICABLE') {
return 'bg-indigo-100 text-indigo-800';
}
// Estados de borrado - Naranja
if (statusUpper.includes('BORRADA')) {
return 'bg-orange-100 text-orange-800';
}
// Estados cancelados/desistidos - Rojo
if (statusUpper === 'DESISTIO') {
return 'bg-red-100 text-red-800';
}
return 'bg-blue-100 text-blue-800';
// Default - Gris
return 'bg-gray-100 text-gray-800';
}
export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
@@ -131,17 +156,20 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Pedimento>[] {
cell: ({ row }) => {
const status = row.original.status;
const colorClass = getStatusColor(status);
const formattedStatus = status
? status.charAt(0).toUpperCase() + status.slice(1).toLowerCase()
: 'N/A';
const statusSnippet = createRawSnippet<[{ status?: string | null; colorClass: string }]>((getStatus) => {
const statusSnippet = createRawSnippet<[{ status: string; colorClass: string }]>((getStatus) => {
const { status, colorClass } = getStatus();
return {
render: () =>
`<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">
${status || 'N/A'}
${status}
</span>`
};
});
return renderSnippet(statusSnippet, { status, colorClass });
return renderSnippet(statusSnippet, { status: formattedStatus, colorClass });
}
},
{

View File

@@ -27,8 +27,17 @@
if (!pedimentoId) {
formData = {
entry_date: '',
presentation_date: '',
payment_date: ''
pedimento_date: '',
payment_date: '',
rectification_payment_date: '',
extraction_date: '',
submission_date: '',
eucan_date: '',
original_date: '',
start_date: '',
end_date: '',
capture_date: '',
capture_time: ''
};
exists = false;
loading = false;
@@ -44,15 +53,33 @@
exists = false;
formData = {
entry_date: '',
presentation_date: '',
payment_date: ''
pedimento_date: '',
payment_date: '',
rectification_payment_date: '',
extraction_date: '',
submission_date: '',
eucan_date: '',
original_date: '',
start_date: '',
end_date: '',
capture_date: '',
capture_time: ''
};
} else if (response.data) {
exists = true;
formData = {
entry_date: response.data.entry_date ? response.data.entry_date.substring(0, 10) : '',
presentation_date: response.data.presentation_date ? response.data.presentation_date.substring(0, 10) : '',
payment_date: response.data.payment_date ? response.data.payment_date.substring(0, 10) : ''
pedimento_date: response.data.pedimento_date ? response.data.pedimento_date.substring(0, 10) : '',
payment_date: response.data.payment_date ? response.data.payment_date.substring(0, 10) : '',
rectification_payment_date: response.data.rectification_payment_date ? response.data.rectification_payment_date.substring(0, 10) : '',
extraction_date: response.data.extraction_date ? response.data.extraction_date.substring(0, 10) : '',
submission_date: response.data.submission_date ? response.data.submission_date.substring(0, 10) : '',
eucan_date: response.data.eucan_date ? response.data.eucan_date.substring(0, 10) : '',
original_date: response.data.original_date ? response.data.original_date.substring(0, 10) : '',
start_date: response.data.start_date ? response.data.start_date.substring(0, 10) : '',
end_date: response.data.end_date ? response.data.end_date.substring(0, 10) : '',
capture_date: response.data.capture_date ? response.data.capture_date.substring(0, 10) : '',
capture_time: response.data.capture_time || ''
};
}
} catch (e) {
@@ -60,8 +87,17 @@
exists = false;
formData = {
entry_date: '',
presentation_date: '',
payment_date: ''
pedimento_date: '',
payment_date: '',
rectification_payment_date: '',
extraction_date: '',
submission_date: '',
eucan_date: '',
original_date: '',
start_date: '',
end_date: '',
capture_date: '',
capture_time: ''
};
} finally {
loading = false;
@@ -85,7 +121,7 @@
</div>
{:else}
<div class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Fecha de Entrada -->
<div class="space-y-2">
<Label for="entry_date">Fecha de Entrada</Label>
@@ -98,11 +134,11 @@
<!-- Fecha de Presentación -->
<div class="space-y-2">
<Label for="presentation_date">Fecha de Presentación</Label>
<Label for="pedimento_date">Fecha de Presentación</Label>
<Input
id="presentation_date"
id="pedimento_date"
type="date"
bind:value={formData.presentation_date}
bind:value={formData.pedimento_date}
/>
</div>
@@ -115,6 +151,96 @@
bind:value={formData.payment_date}
/>
</div>
<!-- Fecha de Pago Rectificación -->
<div class="space-y-2">
<Label for="rectification_payment_date">Fecha de Pago Rectificación</Label>
<Input
id="rectification_payment_date"
type="date"
bind:value={formData.rectification_payment_date}
/>
</div>
<!-- Fecha de Extracción -->
<div class="space-y-2">
<Label for="extraction_date">Fecha de Extracción</Label>
<Input
id="extraction_date"
type="date"
bind:value={formData.extraction_date}
/>
</div>
<!-- Fecha de Presentación (Submission) -->
<div class="space-y-2">
<Label for="submission_date">Fecha de Envío</Label>
<Input
id="submission_date"
type="date"
bind:value={formData.submission_date}
/>
</div>
<!-- Fecha EUCAN -->
<div class="space-y-2">
<Label for="eucan_date">Fecha EUCAN</Label>
<Input
id="eucan_date"
type="date"
bind:value={formData.eucan_date}
/>
</div>
<!-- Fecha Original -->
<div class="space-y-2">
<Label for="original_date">Fecha Original</Label>
<Input
id="original_date"
type="date"
bind:value={formData.original_date}
/>
</div>
<!-- Fecha de Inicio -->
<div class="space-y-2">
<Label for="start_date">Fecha de Inicio</Label>
<Input
id="start_date"
type="date"
bind:value={formData.start_date}
/>
</div>
<!-- Fecha de Fin -->
<div class="space-y-2">
<Label for="end_date">Fecha de Fin</Label>
<Input
id="end_date"
type="date"
bind:value={formData.end_date}
/>
</div>
<!-- Fecha de Captura -->
<div class="space-y-2">
<Label for="capture_date">Fecha de Captura</Label>
<Input
id="capture_date"
type="date"
bind:value={formData.capture_date}
/>
</div>
<!-- Hora de Captura -->
<div class="space-y-2">
<Label for="capture_time">Hora de Captura</Label>
<Input
id="capture_time"
type="time"
bind:value={formData.capture_time}
/>
</div>
</div>
</div>
{/if}

View File

@@ -33,11 +33,21 @@
}
const statusOptions = [
{ value: 'Activo', label: 'Activo' },
{ value: 'Pendiente', label: 'Pendiente' },
{ value: 'En Proceso', label: 'En Proceso' },
{ value: 'Completado', label: 'Completado' },
{ value: 'Cancelado', label: 'Cancelado' }
{ value: 'MODIFICABLE', label: 'Modificable' },
{ value: 'ESPERA FIRMA PREVIO', label: 'Espera de firma previo' },
{ value: 'ESPERA VALIDACION', label: 'Espera validacion' },
{ value: 'VALIDADO', label: 'Validado' },
{ value: 'ESPERA BORRAR FIRMA PREVIO', label: 'Espera borrar firma previo' },
{ value: 'ESPERA BORRAR FIRMA VALIDACION', label: 'Espera borrar firma validacion ' },
{ value: 'ESPERA PAGO', label: 'Espera pago' },
{ value: 'CON FIRMA DE PREVIO', label: 'Con firma de previo' },
{ value: 'F DE PREVIO BORRADA', label: 'Forma de previo borrada' },
{ value: 'F VALIDACION BORRADA', label: 'Forma validacion borrada' },
{ value: 'PAGADO', label: 'Pagado' },
{ value: 'DESISTIO', label: 'Desistio' },
{ value: 'ESPERA CARTA CUPO', label: 'Espera carta cupo' },
{ value: 'CARTA CUPO', label: 'Carta cupo' },
{ value: 'ESPERA CANCELAR CARTA CUPO', label: 'Espera cancelar carta cupo' }
];
</script>
@@ -160,7 +170,7 @@
<Input
id="regime"
bind:value={formData.regime}
placeholder="Ej: IMP"
placeholder="Ej: IMD"
/>
</div>

View File

@@ -26,8 +26,18 @@
// Si no hay pedimentoId (modo creación), inicializar vacío
if (!pedimentoId) {
formData = {
payment_form: '',
bank_identifier: ''
acknowledgment: '',
operation_number: '',
bank_code: null,
cashier: '',
date: '',
time: '',
shift: '',
total_cash_paid: null,
total_contributions: null,
counter_payment: null,
pece_code: '',
payment_id: null
};
exists = false;
loading = false;
@@ -42,22 +52,52 @@
// No existe o hay error - inicializar vacío
exists = false;
formData = {
payment_form: '',
bank_identifier: ''
acknowledgment: '',
operation_number: '',
bank_code: null,
cashier: '',
date: '',
time: '',
shift: '',
total_cash_paid: null,
total_contributions: null,
counter_payment: null,
pece_code: '',
payment_id: null
};
} else if (response.data) {
exists = true;
formData = {
payment_form: response.data.payment_form || '',
bank_identifier: response.data.bank_identifier || ''
acknowledgment: response.data.acknowledgment || '',
operation_number: response.data.operation_number || '',
bank_code: response.data.bank_code || null,
cashier: response.data.cashier || '',
date: response.data.date || '',
time: response.data.time || '',
shift: response.data.shift || '',
total_cash_paid: response.data.total_cash_paid || null,
total_contributions: response.data.total_contributions || null,
counter_payment: response.data.counter_payment || null,
pece_code: response.data.pece_code || '',
payment_id: response.data.payment_id || null
};
}
} catch (e) {
console.error('Error loading payments:', e);
exists = false;
formData = {
payment_form: '',
bank_identifier: ''
acknowledgment: '',
operation_number: '',
bank_code: null,
cashier: '',
date: '',
time: '',
shift: '',
total_cash_paid: null,
total_contributions: null,
counter_payment: null,
pece_code: '',
payment_id: null
};
} finally {
loading = false;
@@ -80,24 +120,134 @@
</div>
{:else}
<div class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Forma de Pago -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Acuse -->
<div class="space-y-2">
<Label for="payment_form">Forma de Pago</Label>
<Label for="acknowledgment">Acuse</Label>
<Input
id="payment_form"
bind:value={formData.payment_form}
placeholder="Ej: Efectivo, Transferencia"
id="acknowledgment"
bind:value={formData.acknowledgment}
placeholder="Máx. 20 caracteres"
maxlength={20}
/>
</div>
<!-- Identificador Bancario -->
<!-- Número de Operación -->
<div class="space-y-2">
<Label for="bank_identifier">Identificador Bancario</Label>
<Label for="operation_number">Número de Operación</Label>
<Input
id="bank_identifier"
bind:value={formData.bank_identifier}
placeholder="Ej: 012345678901234567"
id="operation_number"
bind:value={formData.operation_number}
placeholder="Máx. 14 caracteres"
maxlength={14}
/>
</div>
<!-- Código de Banco -->
<div class="space-y-2">
<Label for="bank_code">Código de Banco</Label>
<Input
id="bank_code"
type="number"
bind:value={formData.bank_code}
placeholder="Código numérico"
/>
</div>
<!-- Cajero -->
<div class="space-y-2">
<Label for="cashier">Cajero</Label>
<Input
id="cashier"
bind:value={formData.cashier}
placeholder="Máx. 2 caracteres"
maxlength={2}
/>
</div>
<!-- Fecha -->
<div class="space-y-2">
<Label for="date">Fecha</Label>
<Input
id="date"
type="date"
bind:value={formData.date}
/>
</div>
<!-- Hora -->
<div class="space-y-2">
<Label for="time">Hora</Label>
<Input
id="time"
type="time"
bind:value={formData.time}
/>
</div>
<!-- Turno -->
<div class="space-y-2">
<Label for="shift">Turno</Label>
<Input
id="shift"
bind:value={formData.shift}
placeholder="1 carácter"
maxlength={1}
/>
</div>
<!-- Total Efectivo Pagado -->
<div class="space-y-2">
<Label for="total_cash_paid">Total Efectivo Pagado</Label>
<Input
id="total_cash_paid"
type="number"
bind:value={formData.total_cash_paid}
placeholder="Monto en efectivo"
/>
</div>
<!-- Total Contribuciones -->
<div class="space-y-2">
<Label for="total_contributions">Total Contribuciones</Label>
<Input
id="total_contributions"
type="number"
bind:value={formData.total_contributions}
placeholder="Monto total"
/>
</div>
<!-- Pago en Ventanilla -->
<div class="space-y-2">
<Label for="counter_payment">Pago en Ventanilla</Label>
<Input
id="counter_payment"
type="number"
bind:value={formData.counter_payment}
placeholder="Monto"
/>
</div>
<!-- Código PECE -->
<div class="space-y-2">
<Label for="pece_code">Código PECE</Label>
<Input
id="pece_code"
bind:value={formData.pece_code}
placeholder="Máx. 5 caracteres"
maxlength={5}
/>
</div>
<!-- ID de Pago -->
<div class="space-y-2">
<Label for="payment_id">ID de Pago</Label>
<Input
id="payment_id"
type="number"
bind:value={formData.payment_id}
placeholder="Identificador"
/>
</div>
</div>

View File

@@ -26,10 +26,10 @@
// Si no hay pedimentoId (modo creación), inicializar vacío
if (!pedimentoId) {
formData = {
arrival_key: '',
arrival_data: '',
departure_key: '',
departure_data: ''
destination: null,
entry_exit: '',
arrival: '',
departure: ''
};
exists = false;
loading = false;
@@ -44,28 +44,28 @@
// No existe o hay error - inicializar vacío
exists = false;
formData = {
arrival_key: '',
arrival_data: '',
departure_key: '',
departure_data: ''
destination: null,
entry_exit: '',
arrival: '',
departure: ''
};
} else if (response.data) {
exists = true;
formData = {
arrival_key: response.data.arrival_key || '',
arrival_data: response.data.arrival_data || '',
departure_key: response.data.departure_key || '',
departure_data: response.data.departure_data || ''
destination: response.data.destination || null,
entry_exit: response.data.entry_exit || '',
arrival: response.data.arrival || '',
departure: response.data.departure || ''
};
}
} catch (e) {
console.error('Error loading transport:', e);
exists = false;
formData = {
arrival_key: '',
arrival_data: '',
departure_key: '',
departure_data: ''
destination: null,
entry_exit: '',
arrival: '',
departure: ''
};
} finally {
loading = false;
@@ -91,43 +91,47 @@
{:else}
<div class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Clave de Llegada -->
<!-- Destino -->
<div class="space-y-2">
<Label for="arrival_key">Clave de Llegada</Label>
<Label for="destination">Destino</Label>
<Input
id="arrival_key"
bind:value={formData.arrival_key}
placeholder="Ej: 01"
id="destination"
type="number"
bind:value={formData.destination}
placeholder="Código de destino"
/>
</div>
<!-- Datos de Llegada -->
<!-- Entrada/Salida -->
<div class="space-y-2">
<Label for="arrival_data">Datos de Llegada</Label>
<Label for="entry_exit">Entrada/Salida</Label>
<Input
id="arrival_data"
bind:value={formData.arrival_data}
placeholder="Ej: Información adicional"
id="entry_exit"
bind:value={formData.entry_exit}
placeholder="Máx. 2 caracteres"
maxlength={2}
/>
</div>
<!-- Clave de Salida -->
<!-- Llegada -->
<div class="space-y-2">
<Label for="departure_key">Clave de Salida</Label>
<Label for="arrival">Llegada</Label>
<Input
id="departure_key"
bind:value={formData.departure_key}
placeholder="Ej: 02"
id="arrival"
bind:value={formData.arrival}
placeholder="Máx. 2 caracteres"
maxlength={2}
/>
</div>
<!-- Datos de Salida -->
<!-- Salida -->
<div class="space-y-2">
<Label for="departure_data">Datos de Salida</Label>
<Label for="departure">Salida</Label>
<Input
id="departure_data"
bind:value={formData.departure_data}
placeholder="Ej: Información adicional"
id="departure"
bind:value={formData.departure}
placeholder="Máx. 2 caracteres"
maxlength={2}
/>
</div>
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import * as Card from '$lib/components/ui/card';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Textarea } from '$lib/components/ui/textarea';
import { Skeleton } from '$lib/components/ui/skeleton';
@@ -26,7 +27,14 @@
// Si no hay pedimentoId (modo creación), inicializar vacío
if (!pedimentoId) {
formData = {
document: ''
validator: '',
validation_ack: '',
pre_ack: '',
line_signature: '',
electronic_signature: '',
certificate_number: '',
validator_id: null,
responsible_id: null
};
exists = false;
loading = false;
@@ -41,19 +49,40 @@
// No existe o hay error - inicializar vacío
exists = false;
formData = {
document: ''
validator: '',
validation_ack: '',
pre_ack: '',
line_signature: '',
electronic_signature: '',
certificate_number: '',
validator_id: null,
responsible_id: null
};
} else if (response.data) {
exists = true;
formData = {
document: response.data.document || ''
validator: response.data.validator || '',
validation_ack: response.data.validation_ack || '',
pre_ack: response.data.pre_ack || '',
line_signature: response.data.line_signature || '',
electronic_signature: response.data.electronic_signature || '',
certificate_number: response.data.certificate_number || '',
validator_id: response.data.validator_id || null,
responsible_id: response.data.responsible_id || null
};
}
} catch (e) {
console.error('Error loading validation:', e);
exists = false;
formData = {
document: ''
validator: '',
validation_ack: '',
pre_ack: '',
line_signature: '',
electronic_signature: '',
certificate_number: '',
validator_id: null,
responsible_id: null
};
} finally {
loading = false;
@@ -71,21 +100,105 @@
<Card.Content>
{#if loading}
<div class="space-y-4">
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-32 w-full" />
<Skeleton class="h-32 w-full" />
</div>
{:else}
<div class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Validador -->
<div class="space-y-2">
<Label for="validator">Validador</Label>
<Input
id="validator"
bind:value={formData.validator}
placeholder="Máx. 3 caracteres"
maxlength={3}
/>
</div>
<!-- Acuse de Validación -->
<div class="space-y-2">
<Label for="validation_ack">Acuse de Validación</Label>
<Input
id="validation_ack"
bind:value={formData.validation_ack}
placeholder="Máx. 8 caracteres"
maxlength={8}
/>
</div>
<!-- Pre-acuse -->
<div class="space-y-2">
<Label for="pre_ack">Pre-acuse</Label>
<Input
id="pre_ack"
bind:value={formData.pre_ack}
placeholder="Máx. 8 caracteres"
maxlength={8}
/>
</div>
<!-- Número de Certificado -->
<div class="space-y-2">
<Label for="certificate_number">Número de Certificado</Label>
<Input
id="certificate_number"
bind:value={formData.certificate_number}
placeholder="Máx. 99 caracteres"
maxlength={99}
/>
</div>
<!-- ID de Validador -->
<div class="space-y-2">
<Label for="validator_id">ID de Validador</Label>
<Input
id="validator_id"
type="number"
bind:value={formData.validator_id}
placeholder="Identificador numérico"
/>
</div>
<!-- ID de Responsable -->
<div class="space-y-2">
<Label for="responsible_id">ID de Responsable</Label>
<Input
id="responsible_id"
type="number"
bind:value={formData.responsible_id}
placeholder="Identificador numérico"
/>
</div>
</div>
<!-- Firma de Línea -->
<div class="space-y-2">
<Label for="document">Documento de Validación</Label>
<Label for="line_signature">Firma de Línea</Label>
<Input
id="line_signature"
bind:value={formData.line_signature}
placeholder="Máx. 50 caracteres"
maxlength={50}
/>
</div>
<!-- Firma Electrónica -->
<div class="space-y-2">
<Label for="electronic_signature">Firma Electrónica</Label>
<Textarea
id="document"
bind:value={formData.document}
placeholder="Ingresa el documento o referencia de validación..."
id="electronic_signature"
bind:value={formData.electronic_signature}
placeholder="Ingresa la firma electrónica..."
rows={6}
class="resize-none"
class="resize-none font-mono text-sm"
maxlength={999}
/>
<p class="text-sm text-muted-foreground">
Ingresa el número de documento, referencia o cualquier información relevante para la validación del pedimento.
Firma electrónica del pedimento (máximo 999 caracteres).
</p>
</div>
</div>

View File

@@ -26,7 +26,7 @@
<div
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"
>
<activeTeam.logo class="size-4" />
<activeTeam.logo class="size-4 text-white" />
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">

View File

@@ -1,12 +1,45 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ cookies }) => {
export const load: PageServerLoad = async ({ cookies, fetch }) => {
const token = cookies.get('access_token');
// Si está autenticado, redirigir al dashboard
// Si hay token, validar que sea válido antes de redirigir
if (token) {
throw redirect(303, '/dashboard');
try {
// Configurar la URL de la API
let apiUrl = process.env.INTERNAL_API_URL;
if (!apiUrl) {
apiUrl = import.meta.env.VITE_API_URL;
apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend');
}
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
// Verificar si el token es válido
const response = await fetch(`${baseUrl}v1/auth/me`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
// Solo redirigir al dashboard si el token es válido
if (response.ok) {
throw redirect(303, '/dashboard');
} else {
// Token inválido, limpiar cookies y mostrar la página pública
cookies.delete('access_token', { path: '/' });
cookies.delete('refresh_token', { path: '/' });
}
} catch (error) {
// Si es un redirect, re-lanzarlo
if (error && typeof error === 'object' && 'status' in error && 'location' in error) {
throw error;
}
// Para otros errores, limpiar cookies y continuar
cookies.delete('access_token', { path: '/' });
cookies.delete('refresh_token', { path: '/' });
}
}
// Si no está autenticado, mostrar la página principal pública

View File

@@ -21,6 +21,7 @@
<div class="flex items-center gap-2 px-4">
<Sidebar.Trigger class="-ml-1" />
<Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" />
<!--
<Breadcrumb.Root>
<Breadcrumb.List>
<Breadcrumb.Item class="hidden md:block">
@@ -32,6 +33,7 @@
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb.Root>
-->
</div>
</header>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">

View File

@@ -171,11 +171,21 @@
// Opciones de status para el filtro
const statusOptions = [
{ value: "", label: "Todos" },
{ value: "Activo", label: "Activo" },
{ value: "Pendiente", label: "Pendiente" },
{ value: "En Proceso", label: "En Proceso" },
{ value: "Completado", label: "Completado" },
{ value: "Cancelado", label: "Cancelado" }
{ value: 'MODIFICABLE', label: 'Modificable' },
{ value: 'ESPERA FIRMA PREVIO', label: 'Espera de firma previo' },
{ value: 'ESPERA VALIDACION', label: 'Espera validacion' },
{ value: 'VALIDADO', label: 'Validado' },
{ value: 'ESPERA BORRAR FIRMA PREVIO', label: 'Espera borrar firma previo' },
{ value: 'ESPERA BORRAR FIRMA VALIDACION', label: 'Espera borrar firma validacion ' },
{ value: 'ESPERA PAGO', label: 'Espera pago' },
{ value: 'CON FIRMA DE PREVIO', label: 'Con firma de previo' },
{ value: 'F DE PREVIO BORRADA', label: 'Forma de previo borrada' },
{ value: 'F VALIDACION BORRADA', label: 'Forma validacion borrada' },
{ value: 'PAGADO', label: 'Pagado' },
{ value: 'DESISTIO', label: 'Desistio' },
{ value: 'ESPERA CARTA CUPO', label: 'Espera carta cupo' },
{ value: 'CARTA CUPO', label: 'Carta cupo' },
{ value: 'ESPERA CANCELAR CARTA CUPO', label: 'Espera cancelar carta cupo' }
];
// Crear columnas con el callback onSuccess

View File

@@ -105,10 +105,10 @@
}
// 2. Guardar fechas
if (datesFormData && (datesFormData.entry_date || datesFormData.presentation_date || datesFormData.payment_date)) {
if (datesFormData && (datesFormData.entry_date || datesFormData.pedimento_date || datesFormData.payment_date)) {
const payload = {
entry_date: datesFormData.entry_date || null,
presentation_date: datesFormData.presentation_date || null,
pedimento_date: datesFormData.pedimento_date || null,
payment_date: datesFormData.payment_date || null
};

View File

@@ -2,20 +2,15 @@ import { redirect, fail } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ cookies, url }) => {
const token = cookies.get('access_token');
// Si hay un parámetro 'logout' en la URL, limpiar las cookies
if (url.searchParams.has('logout')) {
cookies.delete('access_token', { path: '/' });
cookies.delete('refresh_token', { path: '/' });
return {};
}
// Si está autenticado, redirigir al dashboard
if (token) {
throw redirect(303, '/dashboard');
}
// Si no está autenticado, permitir acceso al login
// Permitir acceso al login sin redirigir automáticamente
// Esto evita bucles de redirección cuando el token existe pero puede estar expirado
return {};
};