Merge pull request 'feature/seed-digitalizacion' (#449) from feature/seed-digitalizacion into development

Reviewed-on: ADUANASOFT/anexo76#449
This commit is contained in:
2026-05-25 17:05:41 +00:00
11 changed files with 116 additions and 66 deletions

View File

@@ -0,0 +1,57 @@
"""seed document_types_digitization
Revision ID: d2e3f4a5b6c7
Revises: f1a2b3c4d5e6
Create Date: 2026-05-25
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
from sqlalchemy import text
from sqlalchemy.orm import Session
from api.v1.modules.a76.doc_types_dig.seed import seed as doc_types_dig_seed
revision: str = "d2e3f4a5b6c7"
down_revision: Union[str, None] = "f1a2b3c4d5e6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Quitar columna active (no se usa en ningún flujo)
op.drop_column('document_types_digitization', 'active', schema='a76')
def format_value(val):
if val is None or str(val).strip() == "" or str(val).upper() == "NONE":
return "NULL"
return f"'{str(val).replace(chr(39), chr(39)*2)}'"
bind = op.get_bind()
session = Session(bind=bind)
companies = session.execute(
text("SELECT tenant_id, id FROM a76.company WHERE deleted_at IS NULL")
).fetchall()
for tenant_id, company_id in companies:
values = ", ".join([
f"({format_value(code)}, {format_value(description)}, {tenant_id}, {company_id})"
for code, description in doc_types_dig_seed
])
if values:
session.execute(text(f"""
INSERT INTO a76.document_types_digitization (code, description, tenant_id, company_id)
VALUES {values}
ON CONFLICT (tenant_id, company_id, code) DO NOTHING;
"""))
session.commit()
def downgrade() -> None:
op.add_column(
'document_types_digitization',
sa.Column('active', sa.Boolean(), nullable=False, server_default=sa.text('true')),
schema='a76',
)

View File

@@ -6,7 +6,6 @@ class DocumentTypeDigitizationBase(BaseModel):
code: str = Field(..., max_length=10, description="Código del tipo de documento")
description: str = Field(..., description="Descripción del tipo de documento")
active: bool = Field(default=True, description="Indica si el tipo está activo")
class DocumentTypeDigitizationCreate(DocumentTypeDigitizationBase):
@@ -20,7 +19,6 @@ class DocumentTypeDigitizationUpdate(BaseModel):
code: str | None = Field(None, max_length=10)
description: str | None = None
active: bool | None = None
class DocumentTypeDigitizationResponse(DocumentTypeDigitizationBase):

View File

@@ -1,7 +1,6 @@
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import (
Boolean,
Integer,
PrimaryKeyConstraint,
String,
@@ -29,4 +28,3 @@ class DocumentTypeDigitization(Base, TenantScopedMixin, TimestampMixin):
id: Mapped[int] = mapped_column(Integer)
code: Mapped[str] = mapped_column(String(10), nullable=False, index=True)
description: Mapped[str] = mapped_column(Text, nullable=False)
active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")

View File

@@ -19,7 +19,6 @@ async def list_document_types(
page: int = Query(1, ge=1, description='Page number'),
page_size: int = Query(50, ge=1, le=2000, description='Page size'),
search: Optional[str] = Query(None, description='Search by code or description'),
active_only: bool = Query(False, description='Only active records'),
sort_by: Optional[str] = Query('code', description='Column to sort by'),
sort_order: str = Query('asc', pattern='^(asc|desc)$', description='Sort order'),
db: Session = Depends(get_core_db),
@@ -31,8 +30,6 @@ async def list_document_types(
if search:
filters['search'] = search
if active_only:
filters['active_only'] = True
items, total = DocumentTypeDigitizationService.get_all(
db,

View File

@@ -0,0 +1,40 @@
# Catálogo SAT — tipos de documentos para digitalización (Ventanilla Única)
seed = [
('168', 'Calca o fotografía digital del NIV del vehículo.'),
('169', 'Aviso.'),
('170', 'Factura.'),
('171', 'Documento con el que se acredite la propiedad de la mercancía.'),
('172', 'Contratos.'),
('176', 'Documentación relacionada con la garantía otorgada en términos de los artículos 84.'),
('177', 'Identificación Oficial.'),
('179', 'Comprobante de domicilio.'),
('184', 'Documento que ampara el avaluó de las mercancías.'),
('185', 'Documentos de adjudicación judicial de las mercancías.'),
('187', 'Solicitud de retiro de mercancías que causaron abandono.'),
('189', 'Actas.'),
('192', 'Escritos.'),
('420', 'Certificado de peso o volumen.'),
('421', 'Comprobante de la importación temporal de la embarcación debidamente formalizado.'),
('422', 'Comprobante expedido por donataria.'),
('423', 'Consulta en la que conste que el vehículo no se encuentra reportado como robado,'),
('424', 'Clave Unica del Registro de Población.'),
('425', 'Declaración de internación o extracción de cantidades en efectivo y/o documentos p'),
('426', 'Declaración de operaciones que no confieren origen en países no parte de acuerdo'),
('427', 'Declaración en la que se señalen los motivos por los que efectúa la devolución de m'),
('428', 'Documentación con información que permita la identificación, análisis y control en tér'),
('429', 'Documentación que acredite que acepta y subsana la irregularidad.'),
('430', 'Documentación que ampara la importación temporal del vehículo de que se trate.'),
('431', 'Documentación que compruebe que la adquisición de las mercancías fue efectuada '),
('433', 'Documento con base en el cual se determine la procedencia y el origen de las merca'),
('434', 'Documento con que se acredite el reintegro del IVA, en caso de que el contribuyente '),
('435', 'Documentos previstos en la regla 8.7., fracciones I a IV de la Resolución del TLCAN.'),
('436', 'El Documento que compruebe el cumplimiento de las regulaciones y restricciones no '),
('438', 'Guía aérea, conocimiento de embarque o carta de porte.'),
('439', 'Hoja con los datos de la matrícula y nombre del barco, el lugar donde se localiza y se '),
('440', 'Manifiesto de carga.'),
('441', 'Oficios emitidos por autoridad.'),
('442', 'Pedimentos.'),
('443', 'Programa IMMEX.'),
('444', 'Relación de candados.'),
('445', 'Relación de certificados de origen.'),
]

View File

@@ -18,14 +18,6 @@ class DocumentTypeDigitizationService:
def _normalize_description(description: str) -> str:
return (description or '').strip()
@staticmethod
def _is_truthy_filter(value: Any, default: bool = False) -> bool:
if value is None:
return default
if isinstance(value, bool):
return value
return str(value).strip().lower() in {'1', 'true', 'yes', 'si'}
@staticmethod
def get_all(
db: Session,
@@ -45,15 +37,8 @@ class DocumentTypeDigitizationService:
query = query.filter(DocumentTypeDigitization.company_id == company_id)
filters = filters or {}
active_only = DocumentTypeDigitizationService._is_truthy_filter(
filters.get('active_only'),
default=False,
)
search = (filters.get('search') or '').strip()
if active_only:
query = query.filter(DocumentTypeDigitization.active.is_(True))
if search:
like = f'%{search}%'
query = query.filter(
@@ -69,7 +54,6 @@ class DocumentTypeDigitizationService:
'id': DocumentTypeDigitization.id,
'code': DocumentTypeDigitization.code,
'description': DocumentTypeDigitization.description,
'active': DocumentTypeDigitization.active,
}.get(sort_by or 'code', DocumentTypeDigitization.code)
if sort_order == 'desc':
@@ -195,17 +179,3 @@ class DocumentTypeDigitizationService:
db.refresh(db_obj)
return db_obj
@staticmethod
def delete(
db: Session,
id: int,
tenant_id: int,
company_id: int,
) -> bool:
db_obj = DocumentTypeDigitizationService.get_by_id(db, id, tenant_id, company_id)
if not db_obj:
return False
db_obj.active = False
db.commit()
return True

View File

@@ -21,6 +21,7 @@ from ..units_of_measure.seed import seed as units_of_measure_seed
from ..fractions.historical_tariff_fractions.seed import seed as historical_tariff_fractions_seed
from ..fractions.warning_fractions.seed import seed as warning_fractions_seed
from ..sectors.seed import seed as sectors_seed
from ...doc_types_dig.seed import seed as doc_types_dig_seed
from core.context import get_user_context
from sqlalchemy import text
@@ -812,6 +813,20 @@ class CompanyService:
ON CONFLICT (key, tenant_id, company_id) DO NOTHING;
"""))
# 5. Document Types for Digitization
values_doc_types = ", ".join(
[
f"({format_value(code)}, {format_value(description)}, {tenant_id}, {company_id})"
for code, description in doc_types_dig_seed
]
)
if values_doc_types:
db.execute(text(f"""
INSERT INTO a76.document_types_digitization (code, description, tenant_id, company_id)
VALUES {values_doc_types}
ON CONFLICT (tenant_id, company_id, code) DO NOTHING;
"""))
def get_companies_by_tenant(self, tenant_id: int) -> List[Company]:
"""Get all companies for a tenant"""
return (

View File

@@ -4,7 +4,6 @@ export interface DocumentTypeDigitization {
id: number;
code: string;
description: string;
active: boolean;
}
export interface DocumentTypeDigitizationListResponse {
@@ -25,7 +24,6 @@ export const documentTypesDigitizationApi = {
pageSize = 50,
companyId: number,
search?: string,
activeOnly = false
) => {
const params = new URLSearchParams({
page: page.toString(),
@@ -37,15 +35,11 @@ export const documentTypesDigitizationApi = {
params.append('search', search);
}
if (activeOnly) {
params.append('active_only', 'true');
}
return api.get<DocumentTypeDigitizationListResponse>(`${BASE_URL}/?${params.toString()}`);
},
getAll: (companyId: number, activeOnly = true, search?: string) => {
return documentTypesDigitizationApi.list(1, 2000, companyId, search, activeOnly);
getAll: (companyId: number, search?: string) => {
return documentTypesDigitizationApi.list(1, 2000, companyId, search);
},
getById: (id: number, companyId: number) => {

View File

@@ -119,7 +119,7 @@
if (docTypes.length === 0) {
docTypesLoading = true;
documentTypesDigitizationApi
.getAll(companyId, true)
.getAll(companyId)
.then((res) => {
docTypes = res.data?.items || [];
})

View File

@@ -81,7 +81,7 @@
tiposDocumentos = [];
return;
}
const response = await documentTypesDigitizationApi.getAll(companyId, true);
const response = await documentTypesDigitizationApi.getAll(companyId);
tiposDocumentos = response.data?.items || [];
} catch (error) {
console.error('Error al cargar tipos de documentos:', error);

View File

@@ -34,25 +34,6 @@ export function createColumns(): ColumnDef<DocumentTypeDigitization>[] {
return renderSnippet(descriptionSnippet, { description: row.original.description });
}
},
{
accessorKey: 'active',
header: 'Estatus',
cell: ({ row }) => {
const activeSnippet = createRawSnippet<[{ active: boolean }]>((getActive) => {
const { active } = getActive();
const className = active
? 'border-emerald-200 bg-emerald-50 text-emerald-700'
: 'border-slate-200 bg-slate-50 text-slate-600';
const label = active ? 'Activo' : 'Inactivo';
return {
render: () =>
`<span class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium ${className}">${label}</span>`
};
});
return renderSnippet(activeSnippet, { active: row.original.active });
}
}
];
}