Soporte basico para Comercio Exterior

This commit is contained in:
El Mau 2022-03-10 20:29:50 -06:00
parent bebcd29710
commit 1991c68b3b
14 changed files with 257 additions and 30 deletions

View File

@ -1,3 +1,9 @@
v 2.0.0 [31-Mar-2022]
----------------------
- Primera versión de timbrado con CFDI4
- **IMPORTANTE** NO intentes timbrar si **antes** no has validado en nuestro demo que puedes timbrar tus CFDIs habituales.
v 1.47.0 [28-Mar-2022]
----------------------
- Mejora: Soporte basico para complemento Comercio Exterior.

View File

@ -1 +1 @@
1.47.0
2.0.0

View File

@ -25,16 +25,21 @@ from logbook import Logger
log = Logger('XML')
CFDI_ACTUAL = 'cfdi33'
CFDI_ACTUAL = 'cfdi40'
NOMINA_ACTUAL = 'nomina12'
DEFAULT = {
'exportacion': '01',
}
SAT = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'cfdi32': {
'version': '3.2',
'cfdi40': {
'version': '4.0',
'prefix': 'cfdi',
'xmlns': 'http://www.sat.gob.mx/cfd/3',
'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd',
'xmlns': 'http://www.sat.gob.mx/cfd/4',
'schema': 'http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd',
},
'cfdi33': {
'version': '3.3',
@ -42,6 +47,12 @@ SAT = {
'xmlns': 'http://www.sat.gob.mx/cfd/3',
'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd',
},
'cfdi32': {
'version': '3.2',
'prefix': 'cfdi',
'xmlns': 'http://www.sat.gob.mx/cfd/3',
'schema': 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd',
},
'nomina11': {
'version': '1.1',
'prefix': 'nomina',
@ -270,6 +281,10 @@ class CFDI(object):
if not 'Fecha' in attributes:
attributes['Fecha'] = self._now()
# ~ cfdi4
if not 'Exportacion' in attributes:
attributes['Exportacion'] = DEFAULT['exportacion']
self._cfdi = ET.Element('{}:Comprobante'.format(self._pre), attributes)
return

View File

@ -797,3 +797,15 @@ class AppSATUnidadesPeso(object):
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.sat_unidades_peso_post(values, user)
resp.status = falcon.HTTP_200
class AppSATRegimenes(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.sat_regimenes_get(values, user)
resp.status = falcon.HTTP_200

View File

@ -78,6 +78,7 @@ import segno
from .pacs.cfdi_cert import SATCertificate
from settings import (
CFDI_VERSIONS,
EXT,
MXN,
PATHS,
@ -1754,7 +1755,7 @@ def _comprobante(doc, options):
data['tiporelacion'] = options.get('tiporelacion', '')
return data
if data['version'] == '3.3':
if data['version'] in CFDI_VERSIONS:
tipos = {
'I': 'ingreso',
'E': 'egreso',
@ -1857,7 +1858,7 @@ def _conceptos(doc, version, options):
data.append(values)
continue
if version == '3.3':
if version in CFDI_VERSIONS:
if 'noidentificacion' in values:
values['noidentificacion'] = '{}\n(SAT {})'.format(
values['noidentificacion'], values['ClaveProdServ'])
@ -1921,7 +1922,7 @@ def _totales(doc, cfdi, version):
# ~ for n in node.getchildren():
for n in list(node):
tmp = CaseInsensitiveDict(n.attrib.copy())
if version == '3.3':
if version in CFDI_VERSIONS:
tasa = round(float(tmp['tasaocuota']), DECIMALES)
title = 'Traslado {} {}'.format(tn.get(tmp['impuesto']), tasa)
else:
@ -1933,7 +1934,7 @@ def _totales(doc, cfdi, version):
# ~ for n in node.getchildren():
for n in list(node):
tmp = CaseInsensitiveDict(n.attrib.copy())
if version == '3.3':
if version in CFDI_VERSIONS:
title = 'Retención {} {}'.format(
tn.get(tmp['impuesto']), '')
else:
@ -1965,7 +1966,7 @@ def _totales(doc, cfdi, version):
def _timbre(doc, version, values, pdf_from='1'):
CADENA = '||{version}|{UUID}|{FechaTimbrado}|{selloCFD}|{noCertificadoSAT}||'
if version == '3.3':
if version in CFDI_VERSIONS:
CADENA = '||{Version}|{UUID}|{FechaTimbrado}|{SelloCFD}|{NoCertificadoSAT}||'
node = doc.find('{}Complemento/{}TimbreFiscalDigital'.format(
PRE[version], PRE['TIMBRE']))

View File

@ -255,8 +255,11 @@ class SendMail(object):
class CfdiToDict(object):
NS_VERSION = {
'cfdi3.3': 'http://www.sat.gob.mx/cfd/3',
'cfdi4.0': 'http://www.sat.gob.mx/cfd/4',
}
NS = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'divisas': 'http://www.sat.gob.mx/divisas',
'leyendasFisc': 'http://www.sat.gob.mx/leyendasFiscales',
'cartaporte20': 'http://www.sat.gob.mx/CartaPorte20',
@ -318,6 +321,9 @@ class CfdiToDict(object):
return self._values
def _get_values(self):
version = self._root.attrib['Version']
ns = f'cfdi{version}'
self.NS['cfdi'] = self.NS_VERSION[ns]
self._complementos()
return

View File

@ -25,6 +25,7 @@ from controllers.main import (AppEmpresas,
AppWareHouse,
AppWareHouseProduct,
AppSATUnidadesPeso,
AppSATRegimenes,
)
@ -78,6 +79,7 @@ api.add_route('/warehouseproduct', AppWareHouseProduct(db))
api.add_route('/ticketsdetails', AppTicketsDetails(db))
api.add_route('/users', AppUsers(db))
api.add_route('/satunidadespeso', AppSATUnidadesPeso(db))
api.add_route('/satregimenes', AppSATRegimenes(db))
session_options = {

View File

@ -526,6 +526,9 @@ class StorageEngine(object):
def sat_unidades_peso_post(self, args, user):
return main.SATUnidadesPeso.post(args, user)
def sat_regimenes_get(self, filters, user):
return main.SATRegimenes.get_data(filters, user)
# Companies only in MV
def _get_empresas(self, values):
return main.companies_get()

View File

@ -939,6 +939,26 @@ class SATRegimenes(BaseModel):
)
return tuple(rows)
@classmethod
def _get_actives(cls, filters, user):
where = ((SATRegimenes.activo==True) & (SATRegimenes.fisica==True))
if (filters['morales']=='true'):
where = ((SATRegimenes.activo==True) & (SATRegimenes.moral==True))
rows = (SATRegimenes
.select(
SATRegimenes.id,
SATRegimenes.name.alias('value'))
.where(where)
.dicts()
)
return tuple(rows)
@classmethod
def get_data(cls, filters, user):
opt = filters['opt']
return getattr(cls, f'_get_{opt}')(filters, user)
class Emisor(BaseModel):
rfc = TextField(unique=True)
@ -1027,8 +1047,6 @@ class Emisor(BaseModel):
'ong_autorizacion': obj.autorizacion,
'ong_fecha': obj.fecha_autorizacion,
'ong_fecha_dof': obj.fecha_dof,
# ~ 'correo_timbrado': obj.correo_timbrado,
# ~ 'token_timbrado': obj.token_timbrado,
'token_soporte': obj.token_soporte,
'emisor_registro_patronal': obj.registro_patronal,
'regimenes': [row.id for row in obj.regimenes]
@ -2787,6 +2805,13 @@ class Socios(BaseModel):
if fields['pais'] != 'México':
fields['pais'] = fields['pais'].upper()
if 'regimenes' in fields:
fields['regimenes'] = utils.loads(fields['regimenes'])
if isinstance(fields['regimenes'], list):
fields['regimenes'] = tuple(map(int, fields['regimenes']))
else:
fields['regimenes'] = (fields['regimenes'],)
return fields
@classmethod
@ -2802,18 +2827,9 @@ class Socios(BaseModel):
str(CondicionesPago.get(id=row['condicion_pago']))
row['partner_balance'] = row.pop('saldo_cliente')
row['partner_email_fp'] = row.pop('correo_facturasp')
row['regimenes'] = SociosRegimenes.get_by_socio(row['id'])
return row
#~ return {'data': data['rows'][:100], 'pos':0, 'total_count': 1300}
#~ start = 0
#~ count = 0
#~ end = 100
#~ if values:
#~ {'start': '100', 'count': '100', 'continue': 'true'}
#~ start = int(values['start'])
#~ cont = int(values['count'])
#~ end = start + count
total = Socios.select().count()
rows = (Socios
@ -2829,19 +2845,23 @@ class Socios(BaseModel):
@classmethod
def get_by_client(cls, values):
id = int(values.get('id', 0))
if id:
row = (Socios
.select(
Socios.id, Socios.nombre, Socios.rfc,
SATFormaPago.key.alias('forma_pago'),
SATUsoCfdi.key.alias('uso_cfdi'))
SATUsoCfdi.key.alias('uso_cfdi'),
Socios.codigo_postal)
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
.where((Socios.id==id) & (Socios.es_cliente==True))
.dicts()
)
if len(row):
return {'ok': True, 'row': row[0]}
client = row[0]
client['regimenes'] = SociosRegimenes.get_by_key(client['id'])
return {'ok': True, 'row': client}
return {'ok': False}
name = values.get('name', '')
@ -2849,7 +2869,8 @@ class Socios(BaseModel):
rows = (Socios
.select(Socios.id, Socios.nombre, Socios.rfc,
SATFormaPago.key.alias('forma_pago'),
SATUsoCfdi.key.alias('uso_cfdi'))
SATUsoCfdi.key.alias('uso_cfdi'),
Socios.codigo_postal)
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
.where((Socios.es_cliente==True & Socios.es_activo==True) &
@ -2863,6 +2884,7 @@ class Socios(BaseModel):
def add(cls, values):
accounts = util.loads(values.pop('accounts', '[]'))
fields = cls._clean(cls, values)
regimenes = fields.pop('regimenes', ())
w = ((Socios.rfc==fields['rfc']) & (Socios.slug==fields['slug']))
if Socios.select().where(w).exists():
@ -2892,6 +2914,16 @@ class Socios(BaseModel):
except IntegrityError:
pass
for regimen in regimenes:
try:
fields = {
'socio': obj,
'regimen': regimen,
}
SociosRegimenes.create(**fields)
except IntegrityError:
pass
row = {
'id': obj.id,
'rfc': obj.rfc,
@ -2905,6 +2937,8 @@ class Socios(BaseModel):
def actualizar(cls, values, id):
fields = cls._clean(cls, values)
fields.pop('accounts', '')
regimenes = fields.pop('regimenes', ())
try:
q = Socios.update(**fields).where(Socios.id==id)
q.execute()
@ -2913,6 +2947,19 @@ class Socios(BaseModel):
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
return data
obj = Socios.get(Socios.id==id)
q = SociosRegimenes.delete().where(SociosRegimenes.socio==id)
q.execute()
for regimen in regimenes:
try:
fields = {
'socio': obj,
'regimen': regimen,
}
SociosRegimenes.create(**fields)
except IntegrityError:
pass
obj = Socios.get(Socios.id==id)
row = {
'id': id,
@ -2934,6 +2981,8 @@ class Socios(BaseModel):
q = SociosCuentasBanco.delete().where(SociosCuentasBanco.socio==id)
q.execute()
q = SociosRegimenes.delete().where(SociosRegimenes.socio==id)
q.execute()
q = Socios.delete().where(Socios.id==id)
return bool(q.execute())
@ -3040,6 +3089,41 @@ class SociosCuentasBanco(BaseModel):
return account.socio == invoice.cliente
class SociosRegimenes(BaseModel):
socio = ForeignKeyField(Socios)
regimen = ForeignKeyField(SATRegimenes)
class Meta:
indexes = (
(('socio', 'regimen'), True),
)
@classmethod
def get_by_key(self, socio):
fields = (SATRegimenes.key.alias('id'), SATRegimenes.name.alias('value'))
where = (SociosRegimenes.socio == socio)
regimenes = (SociosRegimenes
.select(*fields)
.where(where)
.join(SATRegimenes).switch(SociosRegimenes)
.dicts()
)
return tuple(regimenes)
@classmethod
def get_by_socio(self, socio):
fields = (SATRegimenes.id,)
where = (SociosRegimenes.socio == socio)
regimenes = (SociosRegimenes
.select(*fields)
.where(where)
.join(SATRegimenes).switch(SociosRegimenes)
.tuples()
)
regimenes = [r[0] for r in regimenes]
return regimenes
class Contactos(BaseModel):
socio = ForeignKeyField(Socios)
titulo = ForeignKeyField(TipoTitulo)
@ -3806,6 +3890,7 @@ class Productos(BaseModel):
cantidad_empaque = DecimalField(default=0.0, max_digits=14, decimal_places=4,
auto_round=True)
is_discontinued = BooleanField(default=False)
objeto_impuesto = TextField(default='02')
class Meta:
order_by = ('descripcion',)
@ -4389,6 +4474,8 @@ class Facturas(BaseModel):
egreso_anticipo = BooleanField(default=False)
tipo_relacion = TextField(default='')
error = TextField(default='')
exportacion = TextField(default='01')
receptor_regimen = TextField(default='')
class Meta:
order_by = ('fecha',)
@ -5590,6 +5677,8 @@ class Facturas(BaseModel):
'Rfc': invoice.cliente.rfc,
'Nombre': invoice.cliente.nombre,
'UsoCFDI': invoice.uso_cfdi,
'DomicilioFiscalReceptor': invoice.cliente.codigo_postal,
'RegimenFiscalReceptor': invoice.receptor_regimen
}
if invoice.cliente.tipo_persona == 4:
if invoice.cliente.pais:
@ -5701,6 +5790,16 @@ class Facturas(BaseModel):
taxes['retenciones'] = retenciones
concepto['impuestos'] = taxes
# cfdi4
if row.producto.objeto_impuesto:
concepto['ObjetoImp'] = row.producto.objeto_impuesto
else:
if taxes:
concepto['ObjetoImp'] = '02'
else:
concepto['ObjetoImp'] = '01'
conceptos.append(concepto)
impuestos = {}
@ -5749,11 +5848,14 @@ class Facturas(BaseModel):
if tax_decimals:
xml_importe = FORMAT_TAX.format(tax.importe)
xml_tax_base = FORMAT_TAX.format(tax.base)
else:
xml_importe = FORMAT.format(tax.importe)
xml_tax_base = FORMAT.format(tax.base)
if tax.impuesto.tipo == 'T':
traslado = {
"Base": xml_tax_base,
"Impuesto": tax.impuesto.key,
"TipoFactor": tipo_factor,
"TasaOCuota": str(tax.impuesto.tasa),
@ -10546,6 +10648,7 @@ def _crear_tablas(rfc):
PartnerInvoices,
WareHouseProduct,
SATUnidadesPeso,
SociosRegimenes,
]
log.info('Creando tablas...')
database_proxy.create_tables(tablas, True)
@ -10604,6 +10707,7 @@ def _migrate_tables(rfc=''):
PartnerInvoices,
WareHouseProduct,
SATUnidadesPeso,
SociosRegimenes,
]
log.info('Creando tablas nuevas...')
database_proxy.create_tables(tablas, True)
@ -10729,6 +10833,10 @@ def _migrate_tables(rfc=''):
is_discontinued = BooleanField(default=False)
migrations.append(migrator.add_column(
table, 'is_discontinued', is_discontinued))
if not 'objeto_impuesto' in columns:
objeto_impuesto = TextField(default='02')
migrations.append(migrator.add_column(table, 'objeto_impuesto', objeto_impuesto))
if 'almacen_id' in columns:
migrations.append(migrator.drop_column(table, 'almacen_id'))
@ -10745,6 +10853,12 @@ def _migrate_tables(rfc=''):
if not 'divisas' in columns:
divisas = TextField(default='')
migrations.append(migrator.add_column(table, 'divisas', divisas))
if not 'exportacion' in columns:
new_field = TextField(default='01')
migrations.append(migrator.add_column(table, 'exportacion', new_field))
if not 'receptor_regimen' in columns:
receptor_regimen = TextField(default='')
migrations.append(migrator.add_column(table, 'receptor_regimen', receptor_regimen))
table = 'almacenes'
columns = [c.name for c in database_proxy.get_columns(table)]

View File

@ -42,7 +42,8 @@ except ImportError:
DEBUG = DEBUG
VERSION = '1.47.0'
VERSION = '2.0.0'
EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
@ -133,6 +134,7 @@ PRE = {
'3.0': '{http://www.sat.gob.mx/cfd/3}',
'3.2': '{http://www.sat.gob.mx/cfd/3}',
'3.3': '{http://www.sat.gob.mx/cfd/3}',
'4.0': '{http://www.sat.gob.mx/cfd/4}',
'TIMBRE': '{http://www.sat.gob.mx/TimbreFiscalDigital}',
'DONATARIA': '{http://www.sat.gob.mx/donat}',
'INE': '{http://www.sat.gob.mx/ine}',
@ -193,6 +195,7 @@ CURRENCY_MN = 'MXN'
# ~ v2
CANCEL_VERSION = ('3.3', '4.0')
CFDI_VERSIONS = CANCEL_VERSION
IS_MV = MV
DB_COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))

View File

@ -685,6 +685,7 @@ function guardar_y_timbrar(values){
data['metodo_pago'] = $$('lst_metodo_pago').getValue()
data['uso_cfdi'] = $$('lst_uso_cfdi').getValue()
data['regimen_fiscal'] = $$('lst_regimen_fiscal').getValue()
data['receptor_regimen'] = $$('lst_invoice_client_regimen').getValue()
data['relacionados'] = ids
data['tipo_relacion'] = tipo_relacion
data['anticipo'] = anticipo
@ -904,6 +905,12 @@ function search_client_by_id(id){
function set_client(row){
if(!row.codigo_postal){
msg = 'El cliente no tiene capturado su Código Postal, es obligatorio.'
msg_error(msg)
return
}
var form = $$('form_invoice')
var html = '<span class="webix_icon fa-user"></span><span class="lbl_partner">'
form.setValues({
@ -913,6 +920,12 @@ function set_client(row){
html += row.nombre + ' (' + row.rfc + ')</span>'
$$('lbl_client').setValue(html)
$$('cmd_cfdi_relacionados').enable()
var lst = $$('lst_invoice_client_regimen')
lst.getList().clearAll()
lst.getList().parse(row.regimenes)
lst.setValue(lst.getPopup().getList().getFirstId())
form.focus('search_product_id')
}

View File

@ -95,6 +95,7 @@ function cmd_new_partner_click(id, e, node){
$$('partner_balance').define('readonly', !cfg_partners['chk_config_change_balance_partner'])
get_partner_banks()
get_partner_accounts_bank(0)
get_sat_regimenes()
}
@ -108,6 +109,8 @@ function cmd_edit_partner_click(){
var msg = ''
var row = $$('grid_partners').getSelectedItem()
get_sat_regimenes()
$$('form_partner_account_bank').clearValidation()
if (row == undefined){
@ -145,6 +148,7 @@ function cmd_edit_partner_click(){
$$('cuenta_proveedor').enable()
}
get_partner_accounts_bank(row['id'])
$$('lst_receptor_regimenes_fiscales').select(values.regimenes)
}
})
@ -239,7 +243,17 @@ function cmd_save_partner_click(id, e, node){
}
}
var ids_regimenes = $$('lst_receptor_regimenes_fiscales').getSelectedId()
if(values.tipo_persona < 3){
if(!ids_regimenes){
msg = 'Selecciona al menos un Regimen Fiscal'
msg_error(msg)
return
}
}
values['accounts'] = $$('grid_partner_account_bank').data.getRange()
values['regimenes'] = ids_regimenes
webix.ajax().post('/partners', values, {
error:function(text, data, XmlHttpRequest){
@ -343,9 +357,16 @@ function opt_tipo_change(new_value, old_value){
$$('id_fiscal').define('value', '')
show('id_fiscal', new_value == 4)
$$('lst_receptor_regimenes_fiscales').clearAll()
if (new_value == 1 || new_value == 2){
$$("rfc").define("value", "")
$$("rfc").define("readonly", false)
moral = false
if(new_value == 2){
moral = true
}
get_sat_regimenes(moral)
} else if (new_value == 3) {
$$("rfc").define("value", RFC_PUBLICO)
$$("nombre").define("value", PUBLICO)
@ -376,6 +397,8 @@ function opt_tipo_change(new_value, old_value){
}
$$('lst_uso_cfdi_socio').getList().parse(query)
$$('lst_uso_cfdi_socio').refresh()
}
@ -619,3 +642,21 @@ function partner_delete_account_bank(row){
}
})
}
function get_sat_regimenes(morales=false){
var data = {opt: 'actives', morales: morales}
webix.ajax().get('/satregimenes', data, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
$$('lst_receptor_regimenes_fiscales').clearAll()
$$('lst_receptor_regimenes_fiscales').parse(values)
}
})
}

View File

@ -418,6 +418,7 @@ var suggest_partners = {
{id: 'rfc', adjust: 'data'},
{id: 'forma_pago', hidden: true},
{id: 'uso_cfdi', hidden: true},
{id: 'codigo_postal', hidden: true},
],
dataFeed:function(text){
if (text.length > 2){
@ -596,6 +597,10 @@ var controls_generate = [
autowidth:true},
{view: 'label', id: 'lbl_client', name: 'lbl_client',
label: 'Ninguno'},
]},
{cols: [{
view: 'richselect', id: 'lst_invoice_client_regimen',
label: 'Regimen Fiscal: ', labelWidth: 150, options: []}
]}
]}},
{view: 'fieldset', label: 'Buscar Producto', body: {rows: [

View File

@ -96,7 +96,7 @@ var controls_fiscales = [
{cols: [{view: 'text', id: 'no_interior', name: 'no_interior', width: 300,
label: 'No Interior: '},{}]},
{cols: [{view: 'search', id: 'codigo_postal', name: 'codigo_postal',
width: 300, label: 'C.P.: ', attributes: {maxlength: 5}},{}]},
width: 300, label: 'C.P.: ', attributes: {maxlength: 5}, required: true},{}]},
{view: 'text', id: 'colonia', name: 'colonia', label: 'Colonia: '},
{view: 'text', id: 'municipio', name: 'municipio', label: 'Municipio: '},
{view: 'text', id: 'estado', name: 'estado', label: 'Estado: '},
@ -122,6 +122,12 @@ var controls_fiscales = [
{view: 'richselect', id: 'lst_uso_cfdi_socio', name: 'uso_cfdi_socio',
label: 'Uso del CFDI', options: []},
{},
]},
{template: 'Regimenes Fiscales', type: 'section'},
{cols: [
{view: 'list', id: 'lst_receptor_regimenes_fiscales', data: [],
select: 'multiselect', width: 600, height: 125, required: true},
{},
]}
]
@ -159,7 +165,7 @@ var controls_others = [
label: 'Cuenta Proveedor: ', disabled: true}, {}]
},
{view: 'checkbox', name: 'es_ong', label: 'Es ONG: ', value: false},
{view: 'text', name: 'tags', label: 'Etiquetas',
{view: 'text', name: 'tags', label: 'Etiquetas', disabled: true,
tooltip: 'Utiles para filtrados rápidos. Separa por comas.'},
{view: 'textarea' , height: 200, name: 'notas', label: 'Notas'},
]