Add support for Comercio Exterior 2.0

This commit is contained in:
El Mau 2024-01-24 15:07:26 -06:00
commit f0c4423fc8
14 changed files with 1006 additions and 85 deletions

View File

@ -1,3 +1,9 @@
v 2.2.0 [24-Ene-2024]
- Mejora: Soporte para complemento Comercio Exterior 2.0
- **IMPORTANTE**: Aunque no lo uses, esto afecta al JS de facturación, por
lo que tienes que forzar el refresco (CTRL+F5) si tienes algún problema.
v 2.1.0 [26-Dic-2023]
- Mejora: Se agrega filtro por día en facturas.

View File

@ -1 +1 @@
2.1.0
2.2.0

View File

@ -117,10 +117,10 @@ SAT = {
'schema': ' http://www.sat.gob.mx/CartaPorte20 http://www.sat.gob.mx/sitio_internet/cfd/CartaPorte/CartaPorte20.xsd',
},
'comercioe': {
'version': '1.1',
'prefix': 'cce11',
'xmlns': 'http://www.sat.gob.mx/ComercioExterior11',
'schema': ' http://www.sat.gob.mx/ComercioExterior11 http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior11/ComercioExterior11.xsd',
'version': '2.0',
'prefix': 'cce20',
'xmlns': 'http://www.sat.gob.mx/ComercioExterior20',
'schema': ' http://www.sat.gob.mx/ComercioExterior20 http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior20/ComercioExterior20.xsd',
}
}
@ -144,6 +144,7 @@ class CFDI(object):
self._comercio_exterior = False
self._divisas = ''
self._tipo_de_comprobante = ''
self._exportacion = DEFAULT['exportacion']
self.error = ''
def _now(self):
@ -168,7 +169,9 @@ class CFDI(object):
if 'nomina' in datos:
self._nomina(datos['nomina'])
return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
xml = self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
return xml
def add_sello(self, sello, cert_txt):
self._cfdi.attrib['Sello'] = sello
@ -198,6 +201,8 @@ class CFDI(object):
self._leyendas = bool(datos['complementos'].get('leyendas', False))
self._carta_porte = bool(datos['complementos'].get('cartaporte', False))
self._comercio_exterior = bool(datos['complementos'].get('comercioe', False))
if self._comercio_exterior:
self._exportacion = datos['complementos']['comercioe'].pop('Exportacion')
self._divisas = datos['comprobante'].pop('divisas', '')
@ -288,7 +293,7 @@ class CFDI(object):
# ~ cfdi4
if not 'Exportacion' in attributes:
attributes['Exportacion'] = DEFAULT['exportacion']
attributes['Exportacion'] = self._exportacion
self._tipo_de_comprobante = attributes['TipoDeComprobante']
@ -515,6 +520,47 @@ class CFDI(object):
return
def _complemento_comercio_exterior(self, datos):
prefix = SAT['comercioe']['prefix']
emisor = datos.pop('emisor')
propietarios = datos.pop('propietarios', {})
receptor = datos.pop('receptor')
destinatario = datos.pop('destinatario', {})
mercancias = datos.pop('mercancias')
attr = {'Version': SAT['comercioe']['version']}
attr.update(datos)
ce = ET.SubElement(
self._complemento, f'{prefix}:ComercioExterior', attr)
attributes = {}
if 'Curp' in emisor:
attributes = {'Curp': emisor.pop('Curp')}
node = ET.SubElement(ce, '{}:Emisor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), emisor)
attributes = {}
if 'NumRegIdTrib' in receptor:
attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
node = ET.SubElement(ce, '{}:Receptor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), receptor)
node = ET.SubElement(ce, '{}:Mercancias'.format(prefix))
fields = ('Marca', 'Modelo', 'SubModelo', 'NumeroSerie')
for row in mercancias:
detalle = {}
for f in fields:
if f in row and row[f]:
detalle[f] = row[f]
row.pop(f)
concepto = ET.SubElement(node, '{}:Mercancia'.format(prefix), row)
if detalle:
ET.SubElement(
concepto, '{}:DescripcionesEspecificas'.format(prefix), detalle)
return
def _complementos(self, datos):
if not datos:
return
@ -639,58 +685,7 @@ class CFDI(object):
ET.SubElement(node_leyend, '{}:Leyenda'.format(pre), leyend)
if self._comercio_exterior:
prefix = SAT['comercioe']['prefix']
datos = datos.pop('comercioe')
emisor = datos.pop('emisor')
# ~ propietario = datos.pop('propietario')
receptor = datos.pop('receptor')
destinatario = datos.pop('destinatario')
conceptos = datos.pop('mercancias')
self._complemento_comercio_exterior(datos)
# ~ attributes = {}
# ~ attributes['xmlns:{}'.format(pre)] = \
# ~ 'http://www.sat.gob.mx/ComercioExterior11'
# ~ attributes['xsi:schemaLocation'] = \
# ~ 'http://www.sat.gob.mx/ComercioExterior11 ' \
# ~ 'http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior11/ComercioExterior11.xsd'
attr = {'Version': SAT['comercioe']['version']}
attr.update(datos)
ce = ET.SubElement(
self._complemento, f'{prefix}:ComercioExterior', attr)
attributes = {}
if 'Curp' in emisor:
attributes = {'Curp': emisor.pop('Curp')}
node = ET.SubElement(ce, '{}:Emisor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), emisor)
# ~ if propietario:
# ~ ET.SubElement(ce, '{}:Propietario'.format(prefix), propietario)
attributes = {}
if 'NumRegIdTrib' in receptor:
attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
node = ET.SubElement(ce, '{}:Receptor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), receptor)
attributes = {}
if 'NumRegIdTrib' in destinatario:
attributes = {'NumRegIdTrib': destinatario.pop('NumRegIdTrib')}
if 'Nombre' in destinatario:
attributes.update({'Nombre': destinatario.pop('Nombre')})
node = ET.SubElement(ce, '{}:Destinatario'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), destinatario)
node = ET.SubElement(ce, '{}:Mercancias'.format(prefix))
fields = ('Marca', 'Modelo', 'SubModelo', 'NumeroSerie')
for row in conceptos:
detalle = {}
for f in fields:
if f in row:
detalle[f] = row.pop(f)
concepto = ET.SubElement(node, '{}:Mercancia'.format(prefix), row)
if detalle:
ET.SubElement(
concepto, '{}:DescripcionesEspecificas'.format(prefix), detalle)
return

View File

@ -1022,6 +1022,51 @@ class LIBO(object):
return
def _comercio_exterior(self, data):
if not data:
return
emisor = data.pop('emisor')
receptor = data.pop('receptor')
mercancias = data.pop('mercancias')
for k, v in data.items():
self._set_cell(f'{{cce.{k}}}', v)
for k, v in emisor.items():
self._set_cell(f'{{cce.emisor.{k}}}', v)
for k, v in receptor.items():
self._set_cell(f'{{cce.receptor.{k}}}', v)
first = True
count = len(mercancias) - 1
for i, mercancia in enumerate(mercancias):
no_identificacion = mercancia['NoIdentificacion']
fraccion = mercancia['FraccionArancelaria']
unidad = mercancia['UnidadAduana']
cantidad = mercancia['CantidadAduana']
valor_unitario = mercancia['ValorUnitarioAduana']
valor_dolares = mercancia['ValorDolares']
if first:
first = False
cell_1 = self._set_cell('{cce.mercancia.noidentificacion}', no_identificacion)
cell_2 = self._set_cell('{cce.mercancia.fraccionarancelaria}', fraccion)
cell_3 = self._set_cell('{cce.mercancia.unidadaduana}', unidad)
cell_4 = self._set_cell('{cce.mercancia.cantidadaduana}', cantidad)
cell_5 = self._set_cell('{cce.mercancia.valorunitarioaduana}', valor_unitario)
cell_6 = self._set_cell('{cce.mercancia.valordolares}', valor_dolares)
if count > 0:
row = cell_1.CellAddress.Row + 1
self._sheet.getRows().insertByIndex(row, count)
self._copy_paste_rows(cell_1, count)
else:
cell_1 = self._set_cell(v=no_identificacion, cell=cell_1)
cell_2 = self._set_cell(v=fraccion, cell=cell_2)
cell_3 = self._set_cell(v=unidad, cell=cell_3)
cell_4 = self._set_cell(v=cantidad, cell=cell_4)
cell_5 = self._set_cell(v=valor_unitario, cell=cell_5)
cell_6 = self._set_cell(v=valor_dolares, cell=cell_6)
return
def _nomina(self, data):
if not data:
return
@ -1258,6 +1303,7 @@ class LIBO(object):
self._divisas(data.get('divisas', {}))
self._leyendas(data.get('leyendas', ''))
self._carta_porte(data.get('carta_porte', {}))
self._comercio_exterior(data.get('comercio_exterior', {}))
self._timbre(data['timbre'])
@ -1571,6 +1617,59 @@ class LIBO(object):
rows = tuple(data[1:])
return rows, ''
def _data_to_dict(self, rows):
data = {k: v for k, v in rows if v}
return data
def _current_region_to_tuple(self, cursor):
data = []
cursor.collapseToCurrentRegion()
rows = cursor.getDataArray()[1:]
if len(rows) == 1:
return data
keys = rows[0]
data = [dict(zip(keys, values)) for values in rows[1:]]
return data
def _get_data_ce(self, doc):
msg = ''
data = {}
try:
sheet = doc.Sheets[0]
rango = sheet['A2:B10']
data = self._data_to_dict(rango.DataArray)
rango = sheet['A13:B23']
data['emisor'] = self._data_to_dict(rango.DataArray)
rango = sheet['A26:B36']
data['receptor'] = self._data_to_dict(rango.DataArray)
rango = sheet['A39:B50']
data['destinatario'] = self._data_to_dict(rango.DataArray)
cursor = sheet.createCursorByRange(sheet['E12'])
data['propietarios'] = self._current_region_to_tuple(cursor)
cursor = sheet.createCursorByRange(sheet['A53'])
data['mercancias'] = self._current_region_to_tuple(cursor)
except Exception as e:
msg = str(e)
return data, msg
def get_ce(self, path):
options = {'AsTemplate': True, 'Hidden': True}
doc = self._doc_open(path, options)
if doc is None:
return (), 'No se pudo abrir la plantilla'
data, msg = self._get_data_ce(doc)
doc.close(True)
if len(data) == 1:
msg = 'Sin datos para importar'
return (), msg
return data, ''
def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'):
rfc = data['emisor']['rfc']
@ -1602,8 +1701,13 @@ def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'):
default = f'plantilla_donatarias_{version}_{version_donatarias}.ods'
version = f'{version}_cd_{version_donatarias}'
if 'comercio_exterior' in data:
version_cce = data['comercio_exterior']['version']
default = f'plantilla_cce_{version}_{version_cce}.ods'
version = f'{version}_cce_{version_cce}'
template_name = f'{rfc.lower()}_{version}.ods'
# ~ print('T', template_name, default)
# ~ print('\nT', template_name, default)
if APP_LIBO:
app = LIBO()
@ -2317,6 +2421,7 @@ def upload_file(rfc, opt, file_obj):
'_4.0_cp_2.0.ods',
'_4.0_ccp_2.0.ods',
'_4.0_cd_1.1.ods',
'_4.0_cce_2.0.ods',
'_4.0.json',
)
if opt in versions:
@ -2438,6 +2543,15 @@ def upload_file(rfc, opt, file_obj):
name = '{}_nomina.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'tmp', name)
elif opt == 'ceods':
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
if ext != 'ods':
msg = 'Extensión de archivo incorrecta, selecciona un archivo ODS'
return {'status': 'server', 'name': msg, 'ok': False}
name = '{}_ce.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'tmp', name)
if save_file(path, file_obj.file.read()):
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
@ -2915,6 +3029,20 @@ def import_invoice(rfc):
return (), 'No se encontro LibreOffice'
def import_ceods(rfc):
name = '{}_ce.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'tmp', name)
if not is_file(path):
return (), 'No se encontró la plantilla'
if APP_LIBO:
app = LIBO()
if app.is_running:
return app.get_ce(path)
return (), 'No se encontro LibreOffice'
def calc_to_date(value):
return datetime.date.fromordinal(int(value) + 693594)

View File

@ -265,6 +265,7 @@ class CfdiToDict(object):
'leyendasFisc': 'http://www.sat.gob.mx/leyendasFiscales',
'cartaporte20': 'http://www.sat.gob.mx/CartaPorte20',
'nomina12': 'http://www.sat.gob.mx/nomina12',
'cce20': 'http://www.sat.gob.mx/ComercioExterior20',
}
tipo_figura = {
'01': '[01] Operador',
@ -513,6 +514,29 @@ class CfdiToDict(object):
self._values['carta_porte'] = values
self._complemento_comercio_exterior(complemento)
return
def _complemento_comercio_exterior(self, complemento):
path = '//cce20:ComercioExterior'
comercio_exterior = complemento.xpath(path, namespaces=self.NS)
if comercio_exterior:
values = CaseInsensitiveDict(comercio_exterior[0].attrib)
for node in comercio_exterior[0]:
if 'Emisor' in node.tag:
values['emisor'] = CaseInsensitiveDict(node.attrib)
values['emisor'].update(CaseInsensitiveDict(node[0].attrib))
elif 'Receptor' in node.tag:
values['receptor'] = CaseInsensitiveDict(node.attrib)
values['receptor'].update(CaseInsensitiveDict(node[0].attrib))
elif 'Mercancias' in node.tag:
mercancias = [
CaseInsensitiveDict(m.attrib) for m in node]
values['mercancias'] = mercancias
self._values['comercio_exterior'] = values
return
@ -861,9 +885,6 @@ def get_cert(args):
def make_xml(data, certificado):
cert = SATCertificate(certificado.cer, certificado.key_enc.encode())
# ~ if DEBUG:
# ~ data['emisor']['Rfc'] = certificado.rfc
# ~ data['emisor']['RegimenFiscal'] = '603'
cfdi = CFDI()
xml = ET.parse(BytesIO(cfdi.get_xml(data).encode()))
@ -1087,6 +1108,7 @@ def _save_template(rfc, name, file_obj):
rfc = rfc.lower()
path = _join(PATHS['USER'], f'{rfc}{name}')
if save_file(path, file_obj.file.read()):
result['ok'] = True

View File

@ -70,6 +70,9 @@ class StorageEngine(object):
def _get_importinvoice(self, values):
return main.import_invoice()
def _get_importceods(self, values):
return main.import_ceods()
def _get_main(self, values, session):
return main.config_main(session['userobj'])

View File

@ -157,7 +157,7 @@ def upload_file(rfc, opt, file_obj):
result = util.upload_file(rfc, opt, file_obj)
if result['ok']:
names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods')
names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods', 'ceods')
if not opt in names:
Configuracion.add({opt: file_obj.filename})
return result
@ -268,6 +268,18 @@ def import_invoice():
return {'ok': True, 'rows': tuple(products)}
def import_ceods():
log.info('Importando plantilla...')
emisor = Emisor.select()[0]
data, msg = util.import_ceods(emisor.rfc)
if not data:
return {'ok': False, 'msg': msg}
log.info('Plantilla importada...')
return {'ok': True, 'data': data}
def get_doc(type_doc, id, rfc):
types = {
'xml': 'application/xml',
@ -5611,12 +5623,9 @@ class Facturas(BaseModel):
if not valores:
return
# ~ values = utils.loads(valores)
data = {
'factura': invoice,
'nombre': 'comercioe',
# ~ 'valores': utils.dumps(values),
'valores': valores,
}
FacturasComplementos.create(**data)

View File

@ -39,7 +39,7 @@ except ImportError:
DEBUG = DEBUG
VERSION = '2.1.0'
VERSION = '2.2.0'
EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)

View File

@ -22,7 +22,7 @@ var tipo_relacion = ''
var anticipo = false
var donativo = false
var cfg_invoice = new Object()
var values_comercioe = null
//~ var values_comercioe = null
var values_global = ''
@ -102,7 +102,15 @@ var invoices_controllers = {
$$('cmd_carta_add_product').attachEvent('onItemClick', cmd_carta_add_product_click)
$$('cmd_carta_copy_from_invoice').attachEvent('onItemClick', cmd_carta_copy_from_invoice_click)
$$('cmd_carta_import_json').attachEvent('onItemClick', cmd_carta_import_json_click)
$$('cmd_import_json_comercioe').attachEvent('onItemClick', cmd_import_json_comercioe_click)
$$('cmd_ce_import_ods').attachEvent('onItemClick', cmd_ce_import_ods_click)
$$('cmd_ce_tipo_cambio').attachEvent('onItemClick', cmd_ce_tipo_cambio_click)
$$('cmd_ce_add_propietario').attachEvent('onItemClick', cmd_ce_add_propietario_click)
$$('cmd_ce_add_mercancia').attachEvent('onItemClick', cmd_ce_add_mercancia_click)
$$('grid_ce_propietarios').attachEvent('onItemClick', grid_ce_propietarios_click)
$$('grid_ce_mercancias').attachEvent('onItemClick', grid_ce_mercancias_click)
$$('cmd_show_global_information').attachEvent('onItemClick', cmd_show_global_information_click)
webix.extend($$('grid_invoices'), webix.ProgressBar)
@ -239,6 +247,7 @@ function default_config(){
$$('tv_invoice').getTabbar().hideOption('Comercio Exterior')
}else{
$$('tv_invoice').getTabbar().showOption('Comercio Exterior')
_set_default_comercio_exterior()
}
cfg_invoice['leyendasfiscales'] = values.cfdi_leyendasfiscales
cfg_invoice['edu'] = values.cfdi_edu
@ -574,6 +583,12 @@ function validate_invoice(values){
}
}
//~ validate comercio exterior
var usar_comercioe = $$('chk_cfdi_usar_comercioe').getValue()
if(usar_comercioe){
var values = _get_values_comercio_exterior()
}
return true
}
@ -848,14 +863,14 @@ function guardar_y_timbrar(values){
var usar_comercioe = $$('chk_cfdi_usar_comercioe').getValue()
if(usar_comercioe){
data['comercioe'] = values_comercioe
data['comercioe'] = _get_values_comercio_exterior()
}
if(!save_invoice(data)){
return
}
values_comercioe = null
//~ values_comercioe = null
values_global = ''
$$('chk_cfdi_usar_comercioe').setValue(false)
@ -907,16 +922,11 @@ function cmd_timbrar_click(id, e, node){
usar_ine = $$('chk_cfdi_usar_ine').getValue()
if(usar_ine){
msg += 'Estas usando el complemento INE<BR><BR>'
msg += 'Estas usando el complemento: INE<BR><BR>'
}
if($$('chk_cfdi_usar_comercioe').getValue()){
msg += 'Estas usando el complemento Comercio Exterior<BR><BR>'
if(values_comercioe === null){
msg = 'El complemento de Comercio Exterior esta vacío'
msg_error(msg)
return
}
msg += 'Estas usando el complemento:<BR>Comercio Exterior<BR><BR>'
}
if(tipo_comprobante == 'T'){
@ -2952,9 +2962,54 @@ function up_invoice_json_on_after_file_add(obj){
}
function _set_from_json_comercioe(data){
function _set_default_comercio_exterior(){
const controls = {
lst_ce_exportacion: '02',
lst_ce_motivo_traslado: '',
lst_ce_clave_pedimento: 'A1',
lst_ce_certificado_origen: '0',
txt_ce_numero_certificado: '',
txt_ce_numero_exportador: '',
lst_ce_incoterm: 'CFR',
txt_ce_observaciones: '',
txt_ce_tipo_cambio_usd: '',
txt_ce_total_usd: '',
};
Object.keys(controls).forEach(key => {
$$(key).setValue(controls[key])
});
var grid = $$('grid_ce_emisor')
grid.clearAll()
grid.add({id: 0})
var grid = $$('grid_ce_receptor')
grid.clearAll()
grid.add({id: 0})
var grid = $$('grid_ce_destinatario')
grid.clearAll()
grid.add({id: 0})
var grid = $$('grid_ce_propietarios')
grid.clearAll()
var grid = $$('grid_ce_mercancias')
grid.clearAll()
}
function _set_from_json_comercioe(data, json){
_set_default_comercio_exterior()
try{
values_comercioe = JSON.parse(data)
if(json){
values = JSON.parse(data)
}else{
values = data
}
}catch(e){
msg_error('Revisa el archivo JSON')
webix.alert({
@ -2964,6 +3019,66 @@ function _set_from_json_comercioe(data){
})
return
}
const controls = {
Exportacion: 'lst_ce_exportacion',
MotivoTraslado: 'lst_ce_motivo_traslado',
ClaveDePedimento: 'lst_ce_clave_pedimento',
CertificadoOrigen: 'lst_ce_certificado_origen',
NumCertificadoOrigen: 'txt_ce_numero_certificado',
NumeroExportadorConfiable: 'txt_ce_numero_exportador',
Incoterm: 'lst_ce_incoterm',
Observaciones: 'txt_ce_observaciones',
TipoCambioUSD: 'txt_ce_tipo_cambio_usd',
TotalUSD: 'txt_ce_total_usd',
};
Object.keys(controls).forEach(key => {
if(key in values){
$$(controls[key]).setValue(values[key])
}
});
var grid = $$('grid_ce_emisor')
grid.clearAll()
if ('emisor' in values) {
grid.add(values['emisor'])
} else {
grid.add({id: 0})
}
var grid = $$('grid_ce_receptor')
grid.clearAll()
if ('receptor' in values) {
grid.add(values['receptor'])
} else {
grid.add({id: 0})
}
var grid = $$('grid_ce_destinatario')
grid.clearAll()
if ('destinatario' in values) {
grid.add(values['destinatario'])
} else {
grid.add({id: 0})
}
var grid = $$('grid_ce_propietarios')
grid.clearAll()
if ('propietarios' in values) {
values['propietarios'].forEach(function(row, index){
row['delete'] = '-'
grid.add(row)
})
}
var grid = $$('grid_ce_mercancias')
grid.clearAll()
values['mercancias'].forEach(function(row, index){
row['delete'] = '-'
grid.add(row)
})
msg = 'Valores cargados correctamente'
msg_ok(msg)
}
@ -2979,7 +3094,7 @@ function up_invoice_json_comercioe_on_after_file_add(obj){
let reader = new FileReader()
reader.readAsText(obj.file)
reader.onload = function(){
_set_from_json_comercioe(reader.result)
_set_from_json_comercioe(reader.result, true)
}
$$('win_import_json_comercioe').close()
}
@ -3000,3 +3115,175 @@ function cmd_save_global_information_click(){
function cmd_win_global_close_click(){
$$('win_global_information').close()
}
function cmd_ce_tipo_cambio_click(){
window.open('https://www.banxico.org.mx/tipcamb/tipCamMIAction.do?idioma=sp', '_blank')
}
function cmd_ce_add_propietario_click(){
var g = $$('grid_ce_propietarios')
g.add({delete: '-'})
}
function grid_ce_propietarios_click(id, e, node){
if(id.column != 'delete'){
return
}
this.remove(id.row)
}
function cmd_ce_add_mercancia_click(){
var g = $$('grid_ce_mercancias')
g.add({delete: '-'})
}
function grid_ce_mercancias_click(id, e, node){
if(id.column != 'delete'){
return
}
this.remove(id.row)
}
function _get_values_comercio_exterior(){
var form = $$('form_comercio_exterior')
if(!form.validate()) {
msg_error('Valores inválidos en Comercio Exterior')
return
}
const controls = {
Exportacion: 'lst_ce_exportacion',
MotivoTraslado: 'lst_ce_motivo_traslado',
ClaveDePedimento: 'lst_ce_clave_pedimento',
CertificadoOrigen: 'lst_ce_certificado_origen',
NumCertificadoOrigen: 'txt_ce_numero_certificado',
NumeroExportadorConfiable: 'txt_ce_numero_exportador',
Incoterm: 'lst_ce_incoterm',
Observaciones: 'txt_ce_observaciones',
TipoCambioUSD: 'txt_ce_tipo_cambio_usd',
TotalUSD: 'txt_ce_total_usd',
};
var values = new Object()
Object.keys(controls).forEach(key => {
var value = $$(controls[key]).getValue().trim()
if(value){
values[key] = value
}
});
var propietarios = $$('grid_ce_propietarios').data.getRange()
propietarios.forEach(function(row, index){
delete row['id']
delete row['delete']
})
var mercancias = $$('grid_ce_mercancias').data.getRange()
mercancias.forEach(function(row, index){
delete row['id']
delete row['delete']
})
var emisor = $$('grid_ce_emisor').data.getRange()[0]
delete emisor['id']
var receptor = $$('grid_ce_receptor').data.getRange()[0]
delete receptor['id']
var destinatario = $$('grid_ce_destinatario').data.getRange()[0]
delete destinatario['id']
values['emisor'] = emisor
values['propietarios'] = propietarios
values['receptor'] = receptor
values['destinatario'] = destinatario
values['mercancias'] = mercancias
return values
}
function cmd_ce_import_ods_click(){
win_ce_import_ods.init()
$$('win_ce_import_ods').show()
}
function cmd_ce_upload_ods_click(){
var form = $$('form_ce_import_ods')
var values = form.getValues()
if(!$$('lst_ce_up_template').count()){
$$('win_ce_import_ods').close()
return
}
if($$('lst_ce_up_template').count() > 1){
msg = 'Selecciona solo un archivo'
msg_error(msg)
return
}
var template = $$('ce_up_template').files.getItem($$('ce_up_template').files.getFirstId())
if(template.type.toLowerCase() != 'ods'){
msg = 'Archivo inválido.\n\nSe requiere un archivo ODS'
msg_error(msg)
return
}
msg = '¿Estás seguro de importar este archivo?'
webix.confirm({
title: 'Importar datos de plantilla',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
$$('ce_up_template').send()
}
}
})
}
function ce_up_template_complete(response){
if(response.status != 'server'){
msg = 'Ocurrio un error al subir el archivo'
msg_error(msg)
return
}
msg = 'Archivo subido correctamente.\n\nComenzando importación.'
msg_ok(msg)
$$('win_ce_import_ods').close()
webix.ajax().get('/values/importceods', {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr){
var values = data.json()
if (values.ok){
_set_from_json_comercioe(values.data, false)
//~ msg_ok('Plantilla importada correctamente...')
}else{
webix.alert({
title: 'Error al importar',
text: values.msg,
type: 'alert-error',
})
}
}
})
}

View File

@ -605,6 +605,7 @@ var opt_templates_cfdi = [
{id: '_4.0_cp_2.0.ods', value: 'CFDI v4.0 - Pagos v2.0'},
{id: '_4.0_ccp_2.0.ods', value: 'CFDI v4.0 - Carta Porte v2.0'},
{id: '_4.0_cd_1.1.ods', value: 'CFDI v4.0 - Donativos v1.1'},
{id: '_4.0_cce_2.0.ods', value: 'CFDI v4.0 - Comercio Exterior v2.0'},
{id: '_4.0.json', value: 'CFDI v4.0 - JSON'},
{id: '_3.3.ods', value: 'CFDI v3.3'},
{id: '_3.3_cn_1.2.ods', value: 'CFDI v3.3 - Nómina v1.2'},

View File

@ -1210,13 +1210,268 @@ var controls_carta_porte = [
]
var opt_ce_exportacion = [
{id: '02', value: '[02] Definitiva con clave A1'},
{id: '03', value: '[03] Temporal'},
{id: '04', value: '[04] Definitiva con clave distinta a A1'},
]
var opt_ce_motivo_traslado = [
{id: '', value: ''},
{id: '01', value: '[01] Envío de mercancias facturadas con anterioridad'},
{id: '02', value: '[02] Reubicación de mercancías propias'},
{id: '03', value: '[03] Envío de mercancías objeto de contrato de consignación'},
{id: '04', value: '[04] Envío de mercancías para posterior enajenación'},
{id: '05', value: '[05] Envío de mercancías propiedad de terceros'},
{id: '99', value: '[99] Otros'},
]
var opt_ce_clave_pedimento = [
{id: 'A1', value: '[A1] Definitiva'},
]
var opt_ce_certificado_origen = [
{id: '0', value: '[0] No funge'},
{id: '1', value: '[1] Funge'},
]
var opt_ce_incoterm = [
{id: 'CFR', value: '[CFR] Coste y flete (puerto de destino convenido)'},
{id: 'CIF', value: '[CIF] Coste, seguro y flete (puerto de destino convenido)'},
{id: 'CPT', value: '[CPT] Trasnporte pagado hasta el lugar de destino convenido'},
{id: 'CIP', value: '[CIP] Trasnporte y seguro pagado hasta el lugar de destino convenido'},
{id: 'DAP', value: '[DAP] Entregada en lugar'},
{id: 'DDP', value: '[DDP] Entregada derechos pagados en lugar de destino convenido'},
{id: 'DPU', value: '[DPU] Entregada y descargada en lugar acordado'},
{id: 'EXW', value: '[EXW] En fábrica (lugar convenido)'},
{id: 'FCA', value: '[FCA] Franco transportista (lugar designado)'},
{id: 'FAS', value: '[FAS] Franco al costado del buque (puerto de carga convenido)'},
{id: 'FOB', value: '[FOB] Franco a bordo (puerto de carga convenido)'},
]
var body_ce_datos_generales = {rows:[
{cols: [{maxWidth: 15},
{view: 'richselect', id: 'lst_ce_exportacion', label: 'Exportación: ',
value: '02', labelWidth: 130, minWidth: 400, options: opt_ce_exportacion},
{view: 'richselect', id: 'lst_ce_motivo_traslado', label: 'Motivo Traslado: ',
labelWidth: 130, minWidth: 500, options: opt_ce_motivo_traslado},
{},
{maxWidth: 15}]},
{cols: [{maxWidth: 15},
{view: 'richselect', id: 'lst_ce_clave_pedimento', label: 'Clave de Pedimento: ',
value: 'A1', labelWidth: 130, minWidth: 400, maxWidth: 400, options: opt_ce_clave_pedimento},
{view: 'richselect', id: 'lst_ce_certificado_origen', label: 'Certificado Origen: ',
value: '0', labelWidth: 130, maxWidth: 300, options: opt_ce_certificado_origen},
{view: 'text', id: 'txt_ce_numero_certificado', label: 'Nº Certificado:', labelWidth: 100},
{maxWidth: 15}]},
{cols: [{maxWidth: 15},
{view: 'text', id: 'txt_ce_numero_exportador', label: 'Nº Exportador: ',
labelWidth: 130, minWidth: 400, maxWidth: 500},
{view: 'richselect', id: 'lst_ce_incoterm', label: 'Incoterm: ', value: 'CFR',
labelWidth: 130, minWidth: 500, maxWidth: 500, options: opt_ce_incoterm},
{view: 'text', id: 'txt_ce_observaciones', label: 'Observaciones:', labelWidth: 100},
{maxWidth: 15}]},
{cols: [{maxWidth: 15},
{view: 'text', id: 'txt_ce_total_usd', label: 'Total USD: ',
labelWidth: 130, minWidth: 400, inputAlign: 'right'},
{view: 'text', id: 'txt_ce_tipo_cambio_usd', label: 'Tipo Cambio USD:', labelWidth: 130,
inputAlign: 'right'},
{},
{view: 'button', id: 'cmd_ce_tipo_cambio', label: 'TC Banxico',
type: 'iconButton', autowidth: true, icon: ''},
{maxWidth: 15}]},
]}
var grid_cols_ce_emisor = [
{id: 'id', header: 'ID', hidden: true},
{id: 'Curp', header: 'Curp', editor: 'text', fillspace: 1},
{id: 'Calle', header: 'Calle *', editor: 'text', fillspace: 1},
{id: 'NumeroExterior', header: 'Numero Exterior', editor: 'text', fillspace: 1},
{id: 'NumeroInterior', header: 'Número Interior', editor: 'text', fillspace: 1},
{id: 'Colonia', header: 'Colonia', editor: 'text', fillspace: 1},
{id: 'Municipio', header: 'Municipio', editor: 'text', fillspace: 1},
{id: 'Estado', header: 'Estado *', editor: 'text', fillspace: 1},
{id: 'Pais', header: 'País *', editor: 'text', fillspace: 1},
{id: 'CodigoPostal', header: 'C.P. *', editor: 'text', fillspace: 1},
]
var grid_ce_emisor = {
view: 'datatable',
id: 'grid_ce_emisor',
multiselect: false,
adjust: true,
autoheight: true,
headermenu: true,
editable: true,
columns: grid_cols_ce_emisor,
data: [{id: 0}],
}
var body_ce_emisor = {rows:[
grid_ce_emisor
]}
var grid_cols_ce_propietarios = [
{id: 'id', header: 'ID', hidden: true},
{id: 'delete', header: '', width: 30, css: 'delete'},
{id: 'NumRegIdTrib', header: 'Registro Fiscal', editor: 'text', fillspace: 1},
{id: 'ResidenciaFiscal', header: 'Residencia Fiscal', editor: 'text', fillspace: 1},
]
var grid_ce_propietarios = {
view: 'datatable',
id: 'grid_ce_propietarios',
multiselect: false,
adjust: true,
autoheight: true,
headermenu: true,
editable: true,
footer: false,
columns: grid_cols_ce_propietarios,
}
var body_ce_propietarios = {rows:[
{cols: [
{view: 'button', id: 'cmd_ce_add_propietario', label: 'Agregar Propietario',
icon: 'plus', type: 'iconButton', autowidth: true, align: 'center'},
{},
]},
{maxHeight: 10},
grid_ce_propietarios
]}
var grid_cols_ce_receptor = [
{id: 'id', header: 'ID', hidden: true},
{id: 'NumRegIdTrib', header: 'Registro Fiscal', editor: 'text', fillspace: 1},
{id: 'Calle', header: 'Calle *', editor: 'text', fillspace: 1},
{id: 'NumeroExterior', header: 'Numero Exterior', editor: 'text', fillspace: 1},
{id: 'NumeroInterior', header: 'Número Interior', editor: 'text', fillspace: 1},
{id: 'Colonia', header: 'Colonia', editor: 'text', fillspace: 1},
{id: 'Municipio', header: 'Municipio', editor: 'text', fillspace: 1},
{id: 'Estado', header: 'Estado *', editor: 'text', fillspace: 1},
{id: 'Pais', header: 'País *', editor: 'text', fillspace: 1},
{id: 'CodigoPostal', header: 'C.P. *', editor: 'text', fillspace: 1},
]
var grid_ce_receptor = {
view: 'datatable',
id: 'grid_ce_receptor',
multiselect: false,
adjust: true,
autoheight: true,
headermenu: true,
editable: true,
columns: grid_cols_ce_receptor,
data: [{id: 0}],
}
var body_ce_receptor = {rows:[
grid_ce_receptor
]}
var grid_cols_ce_destinatario = [
{id: 'id', header: 'ID', hidden: true},
{id: 'NumRegIdTrib', header: 'Registro Fiscal', editor: 'text', fillspace: 1},
{id: 'Nombre', header: 'Nombre', editor: 'text', fillspace: 1},
{id: 'Calle', header: 'Calle *', editor: 'text', fillspace: 1},
{id: 'NumeroExterior', header: 'No Exterior', editor: 'text', fillspace: 1},
{id: 'NumeroInterior', header: 'No Interior', editor: 'text', fillspace: 1},
{id: 'Colonia', header: 'Colonia', editor: 'text', fillspace: 1},
{id: 'Municipio', header: 'Municipio', editor: 'text', fillspace: 1},
{id: 'Estado', header: 'Estado *', editor: 'text', fillspace: 1},
{id: 'Pais', header: 'País *', editor: 'text', fillspace: 1},
{id: 'CodigoPostal', header: 'C.P. *', editor: 'text', fillspace: 1},
]
var grid_ce_destinatario = {
view: 'datatable',
id: 'grid_ce_destinatario',
multiselect: false,
adjust: true,
autoheight: true,
headermenu: true,
editable: true,
columns: grid_cols_ce_destinatario,
data: [{id: 0}],
}
var body_ce_destinatario = {rows:[
grid_ce_destinatario
]}
var grid_cols_ce_mercancias = [
{id: 'id', header: 'ID', hidden: true},
{id: 'delete', header: '', width: 30, css: 'delete'},
{id: 'NoIdentificacion', header: 'Clave', editor: 'text', fillspace: 1},
{id: 'FraccionArancelaria', header: 'Fraccion Arancelaria', editor: 'text', fillspace: 1},
{id: 'CantidadAduana', header: 'Cantidad Aduana', editor: 'text', fillspace: 1},
{id: 'UnidadAduana', header: 'Unidad Aduana', editor: 'text', fillspace: 1},
{id: 'ValorUnitarioAduana', header: 'PU Aduana', editor: 'text', fillspace: 1},
{id: 'ValorDolares', header: 'Valor USD', editor: 'text', fillspace: 1},
{id: 'Marca', header: 'Marca', editor: 'text', hidden: true, fillspace: 1},
{id: 'Modelo', header: 'Modelo', editor: 'text', hidden: true, fillspace: 1},
{id: 'SubModelo', header: 'SubModelo', editor: 'text', hidden: true, fillspace: 1},
{id: 'NumeroSerie', header: 'Serie', editor: 'text', hidden: true, fillspace: 1},
]
var grid_ce_mercancias = {
view: 'datatable',
id: 'grid_ce_mercancias',
multiselect: false,
adjust: true,
autoheight: true,
headermenu: true,
editable: true,
footer: true,
columns: grid_cols_ce_mercancias,
}
var body_ce_mercancias = {rows:[
{cols: [
{view: 'button', id: 'cmd_ce_add_mercancia', label: 'Agregar Mercancía',
icon: 'plus', type: 'iconButton', autowidth: true, align: 'center'},
{},
]},
{maxHeight: 10},
grid_ce_mercancias
]}
var controls_comercio_exterior = [
{cols: [{maxWidth: 15},
{view: 'checkbox', id: 'chk_cfdi_usar_comercioe', labelWidth: 0,
labelRight: 'Usar el complemento Comercio Exterior'}, {},
labelRight: 'Usar el complemento Comercio Exterior'},
{},
{view: 'button', id: 'cmd_import_json_comercioe', label: 'Importar JSON',
icon: 'upload', type: 'iconButton', autowidth: true, align: 'center'},
{maxWidth: 15}]}
{view: 'button', id: 'cmd_ce_import_ods', label: 'Importar ODS',
icon: 'upload', type: 'iconButton', autowidth: true, align: 'center'},
{maxWidth: 15}]},
{view: 'fieldset', label: 'Datos generales', body: body_ce_datos_generales},
{view: 'fieldset', label: 'Emisor', body: body_ce_emisor},
{view: 'fieldset', label: 'Propietario', body: body_ce_propietarios},
{view: 'fieldset', label: 'Receptor', body: body_ce_receptor},
{view: 'fieldset', label: 'Destinatario', body: body_ce_destinatario},
{view: 'fieldset', label: 'Mercancías', body: body_ce_mercancias},
]
@ -1324,7 +1579,13 @@ var win_import_invoice = {
width: 400,
modal: true,
position: 'center',
head: 'Importar Factura de Plantilla',
head: {view: 'toolbar',
elements: [
{view: 'label', label: 'Importar Factura en Lote'},
{view: 'icon', icon: 'times-circle',
click: '$$("win_import_invoice").close()'},
]
},
body: body_upload_invoice,
})
$$('cmd_upload_invoice').attachEvent('onItemClick', cmd_upload_invoice_click)
@ -1473,3 +1734,41 @@ var win_global_information = {
$$('cmd_win_global_close').attachEvent('onItemClick', cmd_win_global_close_click)
}
}
var body_win_ce_import_ods = {rows: [
{view: 'form', id: 'form_ce_import_ods', rows: [
{cols: [{},
{view: 'uploader', id: 'ce_up_template', autosend: false,
link: 'lst_ce_up_template', value: 'Seleccionar Archivo',
upload: '/files/ceods'}, {}]},
{cols: [
{view: 'list', id: 'lst_ce_up_template',
type: 'uploader', autoheight: true, borderless: true}]},
{cols: [{}, {view: 'button', id: 'cmd_ce_upload_ods',
label: 'Importar Plantilla'}, {}]},
]},
]}
var win_ce_import_ods = {
init: function(){
webix.ui({
view: 'window',
id: 'win_ce_import_ods',
width: 400,
modal: true,
position: 'center',
head: {view: 'toolbar',
elements: [
{view: 'label', label: 'Importar desde archivo ODS'},
{view: 'icon', icon: 'times-circle',
click: '$$("win_ce_import_ods").close()'},
]
},
body: body_win_ce_import_ods,
})
$$('cmd_ce_upload_ods').attachEvent('onItemClick', cmd_ce_upload_ods_click)
$$('ce_up_template').attachEvent('onUploadComplete', ce_up_template_complete)
}
}

Binary file not shown.

View File

@ -11,7 +11,7 @@
<xsl:include href="iedu.xslt"/>
<xsl:include href="leyendasFisc.xslt"/>
<xsl:include href="cartaporte20.xslt"/>
<xsl:include href="comercioexterior11.xslt"/>
<xsl:include href="comercioexterior20.xslt"/>
<xsl:include href="donat11.xslt"/>
<xsl:include href="pagos20.xslt"/>
<xsl:include href="implocal.xslt"/>

View File

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:cce20="http://www.sat.gob.mx/ComercioExterior20">
<xsl:template match="cce20:ComercioExterior">
<!--Manejador de nodos tipo ComercioExterior-->
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Version" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@MotivoTraslado" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@ClaveDePedimento" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@CertificadoOrigen" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumCertificadoOrigen" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumeroExportadorConfiable" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Incoterm" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Observaciones" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@TipoCambioUSD" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@TotalUSD" />
</xsl:call-template>
<!-- Iniciamos el manejo de los elementos hijo en la secuencia -->
<xsl:apply-templates select="./cce20:Emisor" />
<xsl:apply-templates select="./cce20:Receptor" />
<xsl:for-each select="./cce20:Destinatario">
<xsl:apply-templates select="."/>
</xsl:for-each>
<xsl:apply-templates select="./cce20:Mercancias" />
</xsl:template>
<xsl:template match="cce20:Emisor">
<!-- Iniciamos el tratamiento de los atributos de cce20:Emisor-->
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Curp" />
</xsl:call-template>
<xsl:apply-templates select="./cce20:Domicilio" />
</xsl:template>
<xsl:template match="cce20:Propietario">
<!-- Iniciamos el tratamiento de los atributos de cce20:Propietario-->
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@NumRegIdTrib" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@ResidenciaFiscal" />
</xsl:call-template>
</xsl:template>
<xsl:template match="cce20:Receptor">
<!-- Tratamiento de los atributos de cce20:Receptor-->
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumRegIdTrib" />
</xsl:call-template>
<xsl:apply-templates select="./cce20:Domicilio" />
</xsl:template>
<xsl:template match="cce20:Destinatario">
<!-- Tratamiento de los atributos de cce20:Destinatario-->
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumRegIdTrib" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Nombre" />
</xsl:call-template>
<!-- Manejo de los nodos dependientes -->
<xsl:for-each select="./cce20:Domicilio">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="cce20:Mercancias">
<!-- Iniciamos el manejo de los nodos dependientes -->
<xsl:for-each select="./cce20:Mercancia">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="cce20:Domicilio">
<!-- Iniciamos el tratamiento de los atributos de cce20:Domicilio-->
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Calle" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumeroExterior" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumeroInterior" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Colonia" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Localidad" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Referencia" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Municipio" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Estado" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Pais" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@CodigoPostal" />
</xsl:call-template>
</xsl:template>
<xsl:template match="cce20:Mercancia">
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@NoIdentificacion" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@FraccionArancelaria" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@CantidadAduana" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@UnidadAduana" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@ValorUnitarioAduana" />
</xsl:call-template>
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@ValorDolares" />
</xsl:call-template>
<xsl:for-each select="./cce20:DescripcionesEspecificas">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="cce20:DescripcionesEspecificas">
<xsl:call-template name="Requerido">
<xsl:with-param name="valor" select="./@Marca" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@Modelo" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@SubModelo" />
</xsl:call-template>
<xsl:call-template name="Opcional">
<xsl:with-param name="valor" select="./@NumeroSerie" />
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>