#!/usr/bin/python
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.

"M�dulo para generar PDF de facturas electr�nicas"

__author__ = "Mariano Reingart <reingart@gmail.com>"
__copyright__ = "Copyright (C) 2011-2015 Mariano Reingart"
__license__ = "GPL 3.0"
__version__ = "1.07x"

DEBUG = False
HOMO = False
CONFIG_FILE = "rece.ini"

LICENCIA = """
pyfepdf.py: Interfaz para generar Facturas Electr�nica en formato PDF
Copyright (C) 2011-2015 Mariano Reingart reingart@gmail.com

Este progarma es software libre, se entrega ABSOLUTAMENTE SIN GARANTIA
y es bienvenido a redistribuirlo bajo la licencia GPLv3.

Para informaci�n adicional sobre garant�a, soporte t�cnico comercial
e incorporaci�n/distribuci�n en programas propietarios ver PyAfipWs:
http://www.sistemasagiles.com.ar/trac/wiki/PyAfipWs
"""

AYUDA="""
Opciones:
  --ayuda: este mensaje
  --licencia: muestra la licencia del programa

  --debug: modo depuraci�n (detalla y confirma las operaciones)
  --formato: muestra el formato de los archivos de entrada/salida
  --prueba: genera y autoriza una factura de prueba (no usar en producci�n!)
  --cargar: carga un archivo de entrada (txt) a la base de datos
  --grabar: graba un archivo de salida (txt) con los datos de los comprobantes procesados
  --pdf: genera la im�gen de factura en PDF
  --dbf: utiliza tablas DBF en lugar del archivo de entrada TXT
  --json: utiliza el formato JSON para el archivo de entrada

Ver rece.ini para par�metros de configuraci�n "
"""
import datetime
import decimal
import os
import sys
import tempfile
import traceback
from io import StringIO
from decimal import Decimal
from fpdf import Template
from . import utils


class FEPDF:
    "Interfaz para generar PDF de Factura Electr�nica"
    _public_methods_ = ['CrearFactura',
                        'AgregarDetalleItem', 'AgregarIva', 'AgregarTributo',
                        'AgregarCmpAsoc', 'AgregarPermiso',
                        'AgregarDato', 'EstablecerParametro',
                        'CargarFormato', 'AgregarCampo',
                        'CrearPlantilla', 'ProcesarPlantilla', 'GenerarPDF',
                        'MostrarPDF',
                        ]
    _public_attrs_ = ['Version', 'Excepcion', 'Traceback', 'InstallDir',
                      'Locale', 'FmtCantidad', 'FmtPrecio', 'CUIT',
                      'LanzarExcepciones',
                     ]

    _reg_progid_ = "PyFEPDF"
    _reg_clsid_ = "{C9B5D7BB-0388-4A5E-87D5-0B4376C7A336}"

    tipos_doc = {80:'CUIT',86:'CUIL',96:'DNI',99: '', 87: "CDI",
                 89: "LE", 90: "LC", 91: "CI Extranjera",
                 92: "en tr�mite", 93: "Acta Nacimiento", 94: "Pasaporte",
                 95: "CI Bs. As. RNP",
                 0: "CI Polic�a Federal", 1: "CI Buenos Aires",
                 2: "CI Catamarca", 3: "CI C�rdoba", 4: "CI Corrientes",
                 5: "CI Entre R�os", 6: "CI Jujuy", 7: "CI Mendoza",
                 8: "CI La Rioja", 9: "CI Salta", 10: "CI San Juan",
                 11: "CI San Luis", 12: "CI Santa Fe",
                 13: "CI Santiago del Estero", 14: "CI Tucum�n",
                 16: "CI Chaco", 17: "CI Chubut", 18: "CI Formosa",
                 19: "CI Misiones", 20: "CI Neuqu�n", 21: "CI La Pampa",
                 22: "CI R�o Negro", 23: "CI Santa Cruz",
                 24: "CI Tierra del Fuego",
                }

    umeds_ds = {0: ' ', 1: 'kg', 2: 'm', 3: 'm2', 4: 'm3', 5: 'l',
             6: '1000 kWh', 7: 'u',
             8: 'pares', 9: 'docenas', 10: 'quilates', 11: 'millares',
            14: 'g', 15: 'mm', 16: 'mm3', 17: 'km', 18: 'hl', 20: 'cm',
            25: 'jgo. pqt. mazo naipes', 27: 'cm3', 29: 'tn',
            30: 'dam3', 31: 'hm3', 32: 'km3', 33: 'ug', 34: 'ng', 35: 'pg', 41: 'mg', 47: 'mm',
            48: 'curie', 49: 'milicurie', 50: 'microcurie', 51: 'uiacthor', 52: 'muiacthor',
            53: 'kg base', 54: 'gruesa', 61: 'kg bruto',
            62: 'uiactant', 63: 'muiactant', 64: 'uiactig', 65: 'muiactig', 66: 'kg activo',
            67: 'gramo activo', 68: 'gramo base', 96: 'packs', 97: 'hormas',
            96: 'packs', 97: 'se�a/anticipo',
            99: 'bonificaci\xf3n', 98: 'otras unidades'}

    ivas_ds = {3: 0, 4: 10.5, 5: 21, 6: 27, 8: 5, 9: 2.5}

    paises = {
        512: 'FIJI, ISLAS',
        513: 'PAPUA NUEVA GUINEA',
        514: 'KIRIBATI, ISLAS',
        515: 'MICRONESIA,EST.FEDER',
        516: 'PALAU',
        517: 'TUVALU',
        518: 'SALOMON,ISLAS',
        519: 'TONGA',
        520: 'MARSHALL,ISLAS',
        521: 'MARIANAS,ISLAS',
        597: 'RESTO OCEANIA',
        598: 'INDET.(OCEANIA)',
        101: 'BURKINA FASO',
        102: 'ARGELIA',
        103: 'BOTSWANA',
        104: 'BURUNDI',
        105: 'CAMERUN',
        107: 'REP. CENTROAFRICANA.',
        108: 'CONGO',
        109: 'REP.DEMOCRAT.DEL CONGO EX ZAIRE',
        110: 'COSTA DE MARFIL',
        111: 'CHAD',
        112: 'BENIN',
        113: 'EGIPTO',
        115: 'GABON',
        116: 'GAMBIA',
        117: 'GHANA',
        118: 'GUINEA',
        119: 'GUINEA ECUATORIAL',
        120: 'KENYA',
        121: 'LESOTHO',
        122: 'LIBERIA',
        123: 'LIBIA',
        124: 'MADAGASCAR',
        125: 'MALAWI',
        126: 'MALI',
        127: 'MARRUECOS',
        128: 'MAURICIO,ISLAS',
        129: 'MAURITANIA',
        130: 'NIGER',
        131: 'NIGERIA',
        132: 'ZIMBABWE',
        133: 'RWANDA',
        134: 'SENEGAL',
        135: 'SIERRA LEONA',
        136: 'SOMALIA',
        137: 'SWAZILANDIA',
        138: 'SUDAN',
        139: 'TANZANIA',
        140: 'TOGO',
        141: 'TUNEZ',
        142: 'UGANDA',
        144: 'ZAMBIA',
        145: 'TERRIT.VINCULADOS AL R UNIDO',
        146: 'TERRIT.VINCULADOS A ESPA\xd1A',
        147: 'TERRIT.VINCULADOS A FRANCIA',
        149: 'ANGOLA',
        150: 'CABO VERDE',
        151: 'MOZAMBIQUE',
        152: 'SEYCHELLES',
        153: 'DJIBOUTI',
        155: 'COMORAS',
        156: 'GUINEA BISSAU',
        157: 'STO.TOME Y PRINCIPE',
        158: 'NAMIBIA',
        159: 'SUDAFRICA',
        160: 'ERITREA',
        161: 'ETIOPIA',
        197: 'RESTO (AFRICA)',
        198: 'INDETERMINADO (AFRICA)',
        200: 'ARGENTINA',
        201: 'BARBADOS',
        202: 'BOLIVIA',
        203: 'BRASIL',
        204: 'CANADA',
        205: 'COLOMBIA',
        206: 'COSTA RICA',
        207: 'CUBA',
        208: 'CHILE',
        209: 'REP\xdaBLICA DOMINICANA',
        210: 'ECUADOR',
        211: 'EL SALVADOR',
        212: 'ESTADOS UNIDOS',
        213: 'GUATEMALA',
        214: 'GUYANA',
        215: 'HAITI',
        216: 'HONDURAS',
        217: 'JAMAICA',
        218: 'MEXICO',
        219: 'NICARAGUA',
        220: 'PANAMA',
        221: 'PARAGUAY',
        222: 'PERU',
        223: 'PUERTO RICO',
        224: 'TRINIDAD Y TOBAGO',
        225: 'URUGUAY',
        226: 'VENEZUELA',
        227: 'TERRIT.VINCULADO AL R.UNIDO',
        228: 'TER.VINCULADOS A DINAMARCA',
        229: 'TERRIT.VINCULADOS A FRANCIA AMERIC.',
        230: 'TERRIT. HOLANDESES',
        231: 'TER.VINCULADOS A ESTADOS UNIDOS',
        232: 'SURINAME',
        233: 'DOMINICA',
        234: 'SANTA LUCIA',
        235: 'SAN VICENTE Y LAS GRANADINAS',
        236: 'BELICE',
        237: 'ANTIGUA Y BARBUDA',
        238: 'S.CRISTOBAL Y NEVIS',
        239: 'BAHAMAS',
        240: 'GRENADA',
        241: 'ANTILLAS HOLANDESAS',
        250: 'AAE Tierra del Fuego - ARGENTINA',
        251: 'ZF La Plata - ARGENTINA',
        252: 'ZF Justo Daract - ARGENTINA',
        253: 'ZF R\xedo Gallegos - ARGENTINA',
        254: 'Islas Malvinas - ARGENTINA',
        255: 'ZF Tucum\xe1n - ARGENTINA',
        256: 'ZF C\xf3rdoba - ARGENTINA',
        257: 'ZF Mendoza - ARGENTINA',
        258: 'ZF General Pico - ARGENTINA',
        259: 'ZF Comodoro Rivadavia - ARGENTINA',
        260: 'ZF Iquique',
        261: 'ZF Punta Arenas',
        262: 'ZF Salta - ARGENTINA',
        263: 'ZF Paso de los Libres - ARGENTINA',
        264: 'ZF Puerto Iguaz\xfa - ARGENTINA',
        265: 'SECTOR ANTARTICO ARG.',
        270: 'ZF Col\xf3n - REP\xdaBLICA DE PANAM\xc1',
        271: 'ZF Winner (Sta. C. de la Sierra) - BOLIVIA',
        280: 'ZF Colonia - URUGUAY',
        281: 'ZF Florida - URUGUAY',
        282: 'ZF Libertad - URUGUAY',
        283: 'ZF Zonamerica - URUGUAY',
        284: 'ZF Nueva Helvecia - URUGUAY',
        285: 'ZF Nueva Palmira - URUGUAY',
        286: 'ZF R\xedo Negro - URUGUAY',
        287: 'ZF Rivera - URUGUAY',
        288: 'ZF San Jos\xe9 - URUGUAY',
        291: 'ZF Manaos - BRASIL',
        295: 'MAR ARG ZONA ECO.EX',
        296: 'RIOS ARG NAVEG INTER',
        297: 'RESTO AMERICA',
        298: 'INDETERMINADO (AMERICA)',
        301: 'AFGANISTAN',
        302: 'ARABIA SAUDITA',
        303: 'BAHREIN',
        304: 'MYANMAR (EX-BIRMANIA)',
        305: 'BUTAN',
        306: 'CAMBODYA (EX-KAMPUCHE)',
        307: 'SRI LANKA',
        308: 'COREA DEMOCRATICA',
        309: 'COREA REPUBLICANA',
        310: 'CHINA',
        312: 'FILIPINAS',
        313: 'TAIWAN',
        315: 'INDIA',
        316: 'INDONESIA',
        317: 'IRAK',
        318: 'IRAN',
        319: 'ISRAEL',
        320: 'JAPON',
        321: 'JORDANIA',
        322: 'QATAR',
        323: 'KUWAIT',
        324: 'LAOS',
        325: 'LIBANO',
        326: 'MALASIA',
        327: 'MALDIVAS ISLAS',
        328: 'OMAN',
        329: 'MONGOLIA',
        330: 'NEPAL',
        331: 'EMIRATOS ARABES UNIDOS',
        332: 'PAKIST\xc1N',
        333: 'SINGAPUR',
        334: 'SIRIA',
        335: 'THAILANDIA',
        337: 'VIETNAM',
        341: 'HONG KONG',
        344: 'MACAO',
        345: 'BANGLADESH',
        346: 'BRUNEI',
        348: 'REPUBLICA DE YEMEN',
        349: 'ARMENIA',
        350: 'AZERBAIJAN',
        351: 'GEORGIA',
        352: 'KAZAJSTAN',
        353: 'KIRGUIZISTAN',
        354: 'TAYIKISTAN',
        355: 'TURKMENISTAN',
        356: 'UZBEKISTAN',
        357: 'TERR. AU. PALESTINOS',
        397: 'RESTO DE ASIA',
        398: 'INDET.(ASIA)',
        401: 'ALBANIA',
        404: 'ANDORRA',
        405: 'AUSTRIA',
        406: 'BELGICA',
        407: 'BULGARIA',
        409: 'DINAMARCA',
        410: 'ESPA\xd1A',
        411: 'FINLANDIA',
        412: 'FRANCIA',
        413: 'GRECIA',
        414: 'HUNGRIA',
        415: 'IRLANDA',
        416: 'ISLANDIA',
        417: 'ITALIA',
        418: 'LIECHTENSTEIN',
        419: 'LUXEMBURGO',
        420: 'MALTA',
        421: 'MONACO',
        422: 'NORUEGA',
        423: 'PAISES BAJOS',
        424: 'POLONIA',
        425: 'PORTUGAL',
        426: 'REINO UNIDO',
        427: 'RUMANIA',
        428: 'SAN MARINO',
        429: 'SUECIA',
        430: 'SUIZA',
        431: 'VATICANO(SANTA SEDE)',
        433: 'POS.BRIT.(EUROPA)',
        435: 'CHIPRE',
        436: 'TURQUIA',
        438: 'ALEMANIA,REP.FED.',
        439: 'BIELORRUSIA',
        440: 'ESTONIA',
        441: 'LETONIA',
        442: 'LITUANIA',
        443: 'MOLDAVIA',
        444: 'RUSIA',
        445: 'UCRANIA',
        446: 'BOSNIA HERZEGOVINA',
        447: 'CROACIA',
        448: 'ESLOVAQUIA',
        449: 'ESLOVENIA',
        450: 'MACEDONIA',
        451: 'REP. CHECA',
        453: 'MONTENEGRO',
        454: 'SERBIA',
        997: 'RESTO CONTINENTE',
        998: 'INDET.(CONTINENTE)',
        497: 'RESTO EUROPA',
        498: 'INDET.(EUROPA)',
        501: 'AUSTRALIA',
        503: 'NAURU',
        504: 'NUEVA ZELANDIA',
        505: 'VANATU',
        506: 'SAMOA OCCIDENTAL',
        507: 'TERRITORIO VINCULADOS A AUSTRALIA',
        508: 'TERRITORIOS VINCULADOS AL R. UNIDO',
        509: 'TERRITORIOS VINCULADOS A FRANCIA',
        510: 'TER VINCULADOS A NUEVA. ZELANDA',
        511: 'TER. VINCULADOS A ESTADOS UNIDOS'
    }

    monedas_ds = {'DOL': 'USD: D�lar', 'PES': 'ARS: Pesos', '010': 'MXN: Pesos Mejicanos', '011': 'UYU: Pesos Uruguayos', '012': 'BRL: Real', '014': 'Coronas Danesas', '015': 'Coronas Noruegas', '016': 'Coronas Suecas', '019': 'JPY: Yens', '018': 'CAD: D\xf3lar Canadiense', '033': 'CLP: Peso Chileno', '056': 'Forint (Hungr\xeda)', '031': 'BOV: Peso Boliviano', '036': 'Sucre Ecuatoriano', '051': 'D\xf3lar de Hong Kong', '034': 'Rand Sudafricano', '053': 'D\xf3lar de Jamaica', '057': 'Baht (Tailandia)', '043': 'Balboas Paname\xf1as', '042': 'Peso Dominicano', '052': 'D\xf3lar de Singapur', '032': 'Peso Colombiano', '035': 'Nuevo Sol Peruano', '061': 'Zloty Polaco', '060': 'EUR: Euro', '063': 'Lempira Hondure\xf1a', '062': 'Rupia Hind\xfa', '064': 'Yuan (Rep. Pop. China)', '009': 'Franco Suizo', '025': 'Dinar Yugoslavo', '002': 'USD: D\xf3lar Libre EEUU', '027': 'Dracma Griego', '026': 'D\xf3lar Australiano', '007': 'Florines Holandeses', '023': 'VEB: Bol\xedvar Venezolano', '047': 'Riyal Saudita', '046': 'Libra Egipcia', '045': 'Dirham Marroqu\xed', '044': 'C\xf3rdoba Nicarag\xfcense', '029': 'G\xfcaran\xed', '028': 'Flor\xedn (Antillas Holandesas)', '054': 'D\xf3lar de Taiwan', '040': 'Lei Rumano', '024': 'Corona Checa', '030': 'Shekel (Israel)', '021': 'Libra Esterlina', '055': 'Quetzal Guatemalteco', '059': 'Dinar Kuwaiti'}

    tributos_ds = {1: 'Impuestos nacionales', 2: 'Impuestos provinciales', 3: 'Impuestos municipales', 4: 'Impuestos Internos', 99: 'Otro'}

    tipos_fact = {
        (1, 6, 11, 19, 51): 'Factura',
        (2, 7, 12, 20, 52): 'Nota de D�bito',
        (3, 8, 13, 21, 53): 'Nota de Cr�dito',
        (4, 9, 15, 54): 'Recibo',
        (10, 5): 'Nota de Venta al contado',
        (60, 61): 'Cuenta de Venta y L�quido producto',
        (63, 64): 'Liquidaci�n',
        (91, ): 'Remito',
        (39, 40): '???? (R.G. N� 3419)'}

    letras_fact = {(1, 2, 3, 4, 5, 39, 60, 63): 'A',
                   (6, 7, 8, 9, 10, 40, 61, 64): 'B',
                   (11, 12, 13, 15): 'C',
                   (51, 52, 53, 54): 'M',
                   (19, 20, 21): 'E',
                   (91, ): 'R',
                  }

    def __init__(self):
        self.Version = __version__
        self.factura = None
        self.Exception = self.Traceback = ""
        self.InstallDir = INSTALL_DIR
        if sys.platform == "win32":
            self.Locale = "Spanish_Argentina.1252"
        elif sys.platform == "linux2":
            self.Locale = "es_AR.utf8"
        else:
            # plataforma no soportada aun (jython?), emular
            self.Locale = None
        self.FmtCantidad = self.FmtPrecio = "0.2"
        self.CUIT = ''
        self.factura = {}
        self.datos = []
        self.elements = []
        self.pdf = {}
        self.log = StringIO()
        #sys.stdout = self.log
        #sys.stderr = self.log
        self.LanzarExcepciones = True

    def DebugLog(self):
        "Devolver bit�cora de depuraci�n"
        msg = self.log.getvalue()
        return msg

    def inicializar(self):
        self.Excepcion = self.Traceback = ""

    @utils.inicializar_y_capturar_excepciones_simple
    def CrearFactura(self, concepto=1, tipo_doc=80, nro_doc="", tipo_cbte=1, punto_vta=0,
            cbte_nro=0, imp_total=0.00, imp_tot_conc=0.00, imp_neto=0.00,
            imp_iva=0.00, imp_trib=0.00, imp_op_ex=0.00, fecha_cbte="", fecha_venc_pago="",
            fecha_serv_desde=None, fecha_serv_hasta=None,
            moneda_id="PES", moneda_ctz="1.0000", cae="", fch_venc_cae="", id_impositivo='',
            nombre_cliente="", domicilio_cliente="", pais_dst_cmp=None,
            obs_comerciales="", obs_generales="", forma_pago="", incoterms="",
            idioma_cbte=7, motivos_obs="", descuento=0.0,
            **kwargs
            ):
        "Creo un objeto factura (internamente)"
        fact = {'tipo_doc': tipo_doc, 'nro_doc':  nro_doc,
                'tipo_cbte': tipo_cbte, 'punto_vta': punto_vta,
                'cbte_nro': cbte_nro,
                'imp_total': imp_total, 'imp_tot_conc': imp_tot_conc,
                'imp_neto': imp_neto, 'imp_iva': imp_iva,
                'imp_trib': imp_trib, 'imp_op_ex': imp_op_ex,
                'fecha_cbte': fecha_cbte,
                'fecha_venc_pago': fecha_venc_pago,
                'moneda_id': moneda_id, 'moneda_ctz': moneda_ctz,
                'concepto': concepto,
                'nombre_cliente': nombre_cliente,
                'domicilio_cliente': domicilio_cliente,
                'pais_dst_cmp': pais_dst_cmp,
                'obs_comerciales': obs_comerciales,
                'obs_generales': obs_generales,
                'id_impositivo': id_impositivo,
                'forma_pago': forma_pago, 'incoterms': incoterms,
                'cae': cae, 'fecha_vto': fch_venc_cae,
                'motivos_obs': motivos_obs,
                'descuento': descuento,
                'cbtes_asoc': [],
                'tributos': [],
                'ivas': [],
                'permisos': [],
                'detalles': [],
            }
        if fecha_serv_desde: fact['fecha_serv_desde'] = fecha_serv_desde
        if fecha_serv_hasta: fact['fecha_serv_hasta'] = fecha_serv_hasta

        self.factura = fact
        return True

    def EstablecerParametro(self, parametro, valor):
        "Modifico un parametro general a la factura (internamente)"
        self.factura[parametro] = valor
        return True


    def AgregarDato(self, campo, valor, pagina='T'):
        "Agrego un dato a la factura (internamente)"
        self.datos.append({'campo': campo, 'valor': valor, 'pagina': pagina})
        return True

    def AgregarDetalleItem(self, u_mtx, cod_mtx, codigo, ds, qty, umed, precio,
                    bonif, iva_id, imp_iva, importe, despacho,
                    dato_a=None, dato_b=None, dato_c=None, dato_d=None, dato_e=None):
        "Agrego un item a una factura (internamente)"
        ##ds = unicode(ds, "latin1") # convierto a latin1
        # Nota: no se calcula neto, iva, etc (deben venir calculados!)
        item = {
                'u_mtx': u_mtx,
                'cod_mtx': cod_mtx,
                'codigo': codigo,
                'ds': ds,
                'qty': qty,
                'umed': umed,
                'precio': precio,
                'bonif': bonif,
                'iva_id': iva_id,
                'imp_iva': imp_iva,
                'importe': importe,
                'despacho': despacho,
                'dato_a': dato_a,
                'dato_b': dato_b,
                'dato_c': dato_c,
                'dato_d': dato_d,
                'dato_e': dato_e,
                }
        self.factura['detalles'].append(item)
        return True

    def AgregarCmpAsoc(self, tipo=1, pto_vta=0, nro=0, **kwarg):
        "Agrego un comprobante asociado a una factura (interna)"
        cmp_asoc = {'cbte_tipo': tipo, 'cbte_punto_vta': pto_vta, 'cbte_nro': nro}
        self.factura['cbtes_asoc'].append(cmp_asoc)
        return True

    def AgregarTributo(self, tributo_id=0, desc="", base_imp=0.00, alic=0, importe=0.00, **kwarg):
        "Agrego un tributo a una factura (interna)"
        tributo = { 'tributo_id': tributo_id, 'desc': desc, 'base_imp': base_imp,
                    'alic': alic, 'importe': importe}
        self.factura['tributos'].append(tributo)
        return True

    def AgregarIva(self, iva_id=0, base_imp=0.0, importe=0.0, **kwarg):
        "Agrego un tributo a una factura (interna)"
        iva = { 'iva_id': iva_id, 'base_imp': base_imp, 'importe': importe }
        self.factura['ivas'].append(iva)
        return True

    # funciones de formateo de strings:

    def fmt_date(self, d):
        "Formatear una fecha"
        if not d or len(d)!=8:
            return d or ''
        else:
            return "%s/%s/%s" % (d[6:8], d[4:6], d[0:4])

    def fmt_num(self, i, fmt="%0.2f", monetary=True):
        "Formatear un n�mero"
        if i is not None and str(i) and not isinstance(i, bool):
            loc = self.Locale
            if loc:
                import locale
                locale.setlocale(locale.LC_ALL, loc)
                return locale.format(fmt, Decimal(str(i).replace(",",".")), grouping=True, monetary=monetary)
            else:
                return (fmt % Decimal(str(i).replace(",","."))).replace(".",",")
        else:
            return ''

    fmt_imp = lambda self, i: self.fmt_num(i, "%0.2f")
    fmt_qty = lambda self, i: self.fmt_num(i, "%" + self.FmtCantidad + "f", False)
    fmt_pre = lambda self, i: self.fmt_num(i, "%" + self.FmtPrecio + "f")

    def fmt_iva(self, i):
        if int(i) in self.ivas_ds:
            p = self.ivas_ds[int(i)]
            if p == int(p):
                return self.fmt_num(p, "%d") + "%"
            else:
                return self.fmt_num(p, "%.1f") + "%"
        else:
            return ""

    def fmt_cuit(self, c):
        if c is not None and str(c):
            c=str(c)
            return len(c)==11 and "%s-%s-%s" % (c[0:2], c[2:10], c[10:]) or c
        return ''

    def fmt_fact(self, tipo_cbte, punto_vta, cbte_nro):
        "Formatear tipo, letra y punto de venta y n�mero de factura"
        n = "%04d-%08d" % (int(punto_vta), int(cbte_nro))
        t, l = tipo_cbte, ''
        for k,v in list(self.tipos_fact.items()):
            if int(tipo_cbte) in k:
                t = v
        for k,v in list(self.letras_fact.items()):
            if int(int(tipo_cbte)) in k:
                l = v
        return t, l, n

    def digito_verificador_modulo10(self, codigo):
        "Rutina para el c�lculo del d�gito verificador 'm�dulo 10'"
        # http://www.consejo.org.ar/Bib_elect/diciembre04_CT/documentos/rafip1702.htm
        # Etapa 1: comenzar desde la izquierda, sumar todos los caracteres ubicados en las posiciones impares.
        codigo = codigo.strip()
        if not codigo or not codigo.isdigit():
            return ''
        etapa1 = sum([int(c) for i,c in enumerate(codigo) if not i%2])
        # Etapa 2: multiplicar la suma obtenida en la etapa 1 por el n�mero 3
        etapa2 = etapa1 * 3
        # Etapa 3: comenzar desde la izquierda, sumar todos los caracteres que est�n ubicados en las posiciones pares.
        etapa3 = sum([int(c) for i,c in enumerate(codigo) if i%2])
        # Etapa 4: sumar los resultados obtenidos en las etapas 2 y 3.
        etapa4 = etapa2 + etapa3
        # Etapa 5: buscar el menor n�mero que sumado al resultado obtenido en la etapa 4 d� un n�mero m�ltiplo de 10. Este ser� el valor del d�gito verificador del m�dulo 10.
        digito = 10 - (etapa4 - (int(etapa4 / 10) * 10))
        if digito == 10:
            digito = 0
        return str(digito)


    # Funciones p�blicas:


    @utils.inicializar_y_capturar_excepciones_simple
    def CargarFormato(self, archivo="factura.csv"):
        "Cargo el formato de campos a generar desde una planilla CSV"

        # si no encuentro archivo, lo busco en el directorio predeterminado:
        if not os.path.exists(archivo):
            archivo = os.path.join(self.InstallDir, "plantillas", os.path.basename(archivo))

        if DEBUG: print("abriendo archivo ", archivo)

        for lno, linea in enumerate(open(archivo.encode('latin1')).readlines()):
            if DEBUG: print("procesando linea ", lno, linea)
            args = []
            for i,v in enumerate(linea.split(";")):
                if not v.startswith("'"):
                    v = v.replace(",",".")
                else:
                    v = v#.decode('latin1')
                if v.strip()=='':
                    v = None
                else:
                    v = eval(v.strip())
                args.append(v)
            self.AgregarCampo(*args)
        return True


    @utils.inicializar_y_capturar_excepciones_simple
    def AgregarCampo(self, nombre, tipo, x1, y1, x2, y2,
                           font="Arial", size=12,
                           bold=False, italic=False, underline=False,
                           foreground= 0x000000, background=0xFFFFFF,
                           align="L", text="", priority=0, **kwargs):
        "Agrego un campo a la plantilla"
        # convierto colores de string (en hexadecimal)
        if isinstance(foreground, str): foreground = int(foreground, 16)
        if isinstance(background, str): background = int(background, 16)
        if isinstance(text, str): text = text.encode("latin1")
        field = {
                'name': nombre,
                'type': tipo,
                'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2,
                'font': font, 'size': size,
                'bold': bold, 'italic': italic, 'underline': underline,
                'foreground': foreground, 'background': background,
                'align': align, 'text': text, 'priority': priority}
        field.update(kwargs)
        self.elements.append(field)
        return True


    @utils.inicializar_y_capturar_excepciones_simple
    def CrearPlantilla(self, papel="A4", orientacion="portrait"):
        "Iniciar la creaci�n del archivo PDF"

        fact = self.factura
        tipo, letra, nro = self.fmt_fact(fact['tipo_cbte'], fact['punto_vta'], fact['cbte_nro'])

        if HOMO:
            self.AgregarCampo("homo", 'T', 100, 250, 0, 0,
                              size=70, rotate=45, foreground=0x808080, priority=-1)

        # sanity check:
        for field in self.elements:
            # si la imagen no existe, eliminar nombre para que no falle fpdf
            if field['type'] == 'I' and not os.path.exists(field["text"]):
                # ajustar rutas relativas a las im�genes predeterminadas:
                if os.path.exists(os.path.join(self.InstallDir, field["text"])):
                    field['text'] = os.path.join(self.InstallDir, field["text"])
                else:
                    field['text'] = ""
                    ##field['type'] = "T"
                    ##field['font'] = ""
                    ##field['foreground'] = 0xff0000

        # genero el renderizador con propiedades del PDF
        t = Template(elements=self.elements,
                 format=papel, orientation=orientacion,
                 title="%s %s %s" % (tipo.encode("latin1", "ignore"), letra, nro),
                 author="CUIT %s" % self.CUIT,
                 subject="CAE %s" % fact['cae'],
                 keywords="AFIP Factura Electr�nica",
                 creator='PyFEPDF %s (http://www.PyAfipWs.com.ar)' % __version__,)
        self.template = t
        return True


    @utils.inicializar_y_capturar_excepciones_simple
    def ProcesarPlantilla(self, num_copias=3, lineas_max=36, qty_pos='izq'):
        "Generar el PDF seg�n la factura creada y plantilla cargada"

        ret = False
        try:
            if isinstance(num_copias, str):
                num_copias = int(num_copias)
            if isinstance(lineas_max, str):
                lineas_max = int(lineas_max)

            f = self.template
            fact = self.factura

            tipo_fact, letra_fact, numero_fact = self.fmt_fact(fact['tipo_cbte'], fact['punto_vta'], fact['cbte_nro'])
            fact['_fmt_fact'] = tipo_fact, letra_fact, numero_fact
            if fact['tipo_cbte'] in (19,20,21):
                tipo_fact_ex = tipo_fact + " de Exportaci�n"
            else:
                tipo_fact_ex = tipo_fact

            # dividir y contar l�neas:
            lineas = 0
            li_items = []
            for it in fact['detalles']:
                qty = qty_pos=='izq' and it['qty'] or None
                codigo = it['codigo']
                umed = it['umed']
                ds = it['ds'] or ""
                if '\x00' in ds:
                    # limpiar descripci�n (campos dbf):
                    ds = ds.replace('\x00', '')
                if '<br/>' in ds:
                    # reemplazar saltos de linea:
                    ds = ds.replace('<br/>', '\n')
                if DEBUG: print("dividiendo", ds)
                # divido la descripci�n (simil c�lda m�ltiple de PDF)
                n_li = 0
                for ds in f.split_multicell(ds, 'Item.Descripcion01'):
                    if DEBUG: print("multicell", ds)
                    # agrego un item por linea (sin precio ni importe):
                    li_items.append(dict(codigo=codigo, ds=ds, qty=qty, umed=umed, precio=None, importe=None))
                    # limpio cantidad y c�digo (solo en el primero)
                    umed = qty = codigo = None
                    n_li += 1
                # asigno el precio a la �ltima l�nea del item
                li_items[-1].update(importe = it['importe'],
                                    despacho = it.get('despacho'),
                                    precio = it['precio'],
                                    qty = (n_li==1 or qty_pos=='der') and it['qty'] or None,
                                    bonif = it.get('bonif'),
                                    iva_id = it.get('iva_id'), imp_iva = it.get('imp_iva'),
                                    dato_a = it.get('dato_a'), dato_b = it.get('dato_b'),
                                    dato_c = it.get('dato_c'), dato_d= it.get('dato_d'),
                                    dato_e = it.get('dato_e'),
                                    u_mtx = it.get('u_mtx'),
                                    cod_mtx = it.get('cod_mtx'),
                                    )

            # divido las observaciones por linea:
            if fact.get('obs_generales') and 'obs' not in f and 'ObservacionesGenerales1' not in f:
                obs="\n<U>Observaciones:</U>\n\n" + fact['obs_generales']
                # limpiar texto (campos dbf) y reemplazar saltos de linea:
                obs = obs.replace('\x00', '').replace('<br/>', '\n')
                for ds in f.split_multicell(obs, 'Item.Descripcion01'):
                    li_items.append(dict(codigo=None, ds=ds, qty=None, umed=None, precio=None, importe=None))
            if fact.get('obs_comerciales') and 'obs_comerciales' not in f:
                obs="\n<U>Observaciones Comerciales:</U>\n\n" + fact['obs_comerciales']
                # limpiar texto (campos dbf) y reemplazar saltos de linea:
                obs = obs.replace('\x00', '').replace('<br/>', '\n')
                for ds in f.split_multicell(obs, 'Item.Descripcion01'):
                    li_items.append(dict(codigo=None, ds=ds, qty=None, umed=None, precio=None, importe=None))

            # agrego permisos a descripciones (si corresponde)
            permisos =  ['Codigo de Despacho %s - Destino de la mercader�a: %s' % (
                         p['Permiso']['id_permiso'], self.paises.get(p['dst_merc'], p['dst_merc']))
                         for p in fact.get('permisos',[])]
            if 'permisos' not in f and permisos:
                obs="\n<U>Permisos de Embarque:</U>\n\n" + '\n'.join(permisos)
                for ds in f.split_multicell(obs, 'Item.Descripcion01'):
                    li_items.append(dict(codigo=None, ds=ds, qty=None, umed=None, precio=None, importe=None))
            permisos_ds = ', '.join(permisos)

            # agrego comprobantes asociados
            cmps_asoc = ['%s %s %s' % self.fmt_fact(c['cbte_tipo'], c['cbte_punto_vta'], c['cbte_nro'])
                          for c in fact.get('cbtes_asoc',[])]
            if 'cmps_asoc' not in f and cmps_asoc:
                obs="\n<U>Comprobantes Asociados:</U>\n\n" + '\n'.join(cmps_asoc)
                for ds in f.split_multicell(obs, 'Item.Descripcion01'):
                    li_items.append(dict(codigo=None, ds=ds, qty=None, umed=None, precio=None, importe=None))
            cmps_asoc_ds = ', '.join(permisos)

            # calcular cantidad de p�ginas:
            lineas = len(li_items)
            if lineas_max>0:
                hojas = lineas / (lineas_max - 1)
                if lineas % (lineas_max - 1): hojas = hojas + 1
                if not hojas:
                    hojas = 1
            else:
                hojas = 1

            if HOMO:
                self.AgregarDato("homo", "HOMOLOGACI�N")

            # mostrar las validaciones no excluyentes de AFIP (observaciones)

            if fact.get('motivos_obs') and fact['motivos_obs']!='00':
                if 'motivos_ds.L' not in f:
                    motivos_ds = "Irregularidades observadas por AFIP (F136): %s" % fact['motivos_obs']
                else:
                    motivos_ds = "%s" % fact['motivos_obs']
            elif HOMO:
                motivos_ds = "Ejemplo Sin validez fiscal - Homologaci�n - Testing"
            else:
                motivos_ds = ""

            if letra_fact in ('A', 'M'):
                msg_no_iva = "\nEl IVA discriminado no puede computarse como Cr�dito Fiscal (RG2485/08 Art. 30 inc. c)."
                if 'leyenda_credito_fiscal' not in f and motivos_ds:
                    motivos_ds += msg_no_iva

            copias = {1: 'Original', 2: 'Duplicado', 3: 'Triplicado'}

            for copia in range(1, num_copias+1):

                # completo campos y hojas
                for hoja in range(1, hojas+1):
                    f.add_page()
                    f.set('copia', copias.get(copia, "Adicional %s" % copia))
                    f.set('hoja', str(hoja))
                    f.set('hojas', str(hojas))
                    f.set('pagina', 'Pagina %s de %s' % (hoja, hojas))
                    if hojas>1 and hoja<hojas:
                        s = 'Contin�a en hoja %s' % (hoja+1)
                    else:
                        s = ''
                    f.set('continua', s)
                    f.set('Item.Descripcion%02d' % (lineas_max+1), s)

                    if hoja>1:
                        s = 'Contin�a de hoja %s' % (hoja-1)
                    else:
                        s = ''
                    f.set('continua_de', s)
                    f.set('Item.Descripcion%02d' % (0), s)

                    if DEBUG: print("generando pagina %s de %s" % (hoja, hojas)

                    # establezco datos seg�n configuraci�n:
                    for d in self.datos:
                        if d['pagina'] == 'P' and hoja != 1:
                            continue
                        if d['pagina'] == 'U' and hojas != hoja:
                            # no es la �ltima hoja
                            continue
                        f.set(d['campo'], d['valor'])

                    # establezco campos seg�n tabla encabezado:
                    for k,v in list(fact.items()):
                        f.set(k,v)

                    f.set('Numero', numero_fact)
                    f.set('Fecha', self.fmt_date(fact['fecha_cbte']))
                    f.set('Vencimiento', self.fmt_date(fact['fecha_venc_pago']))

                    f.set('LETRA', letra_fact)
                    f.set('TipoCBTE', "COD.%02d" % int(fact['tipo_cbte']))

                    f.set('Comprobante.L', tipo_fact)
                    f.set('ComprobanteEx.L', tipo_fact_ex)

                    if fact.get('fecha_serv_desde'):
                        f.set('Periodo.Desde', self.fmt_date(fact['fecha_serv_desde']))
                        f.set('Periodo.Hasta', self.fmt_date(fact['fecha_serv_hasta']))
                    else:
                        for k in 'Periodo.Desde', 'Periodo.Hasta', 'PeriodoFacturadoL':
                            f.set(k, '')

                    f.set('Cliente.Nombre', fact.get('nombre', fact.get('nombre_cliente')))
                    f.set('Cliente.Domicilio', fact.get('domicilio', fact.get('domicilio_cliente')))
                    f.set('Cliente.Localidad', fact.get('localidad', fact.get('localidad_cliente')))
                    f.set('Cliente.Provincia', fact.get('provincia', fact.get('provincia_cliente')))
                    f.set('Cliente.Telefono', fact.get('telefono', fact.get('telefono_cliente')))
                    f.set('Cliente.IVA', fact.get('categoria', fact.get('id_impositivo')))
                    f.set('Cliente.CUIT', self.fmt_cuit(str(fact['nro_doc'])))
                    f.set('Cliente.TipoDoc', "%s:" % self.tipos_doc[int(str(fact['tipo_doc']))])
                    f.set('Cliente.Observaciones', fact.get('obs_comerciales'))
                    f.set('Cliente.PaisDestino', self.paises.get(fact.get('pais_dst_cmp'), fact.get('pais_dst_cmp')) or '')

                    if fact['moneda_id']:
                        f.set('moneda_ds', self.monedas_ds.get(fact['moneda_id'],''))
                    else:
                        for k in 'moneda.L', 'moneda_id', 'moneda_ds', 'moneda_ctz.L', 'moneda_ctz':
                            f.set(k, '')

                    if not fact.get('incoterms'):
                        for k in 'incoterms.L', 'incoterms', 'incoterms_ds':
                            f.set(k, '')

                    li = 0
                    k = 0
                    subtotal = Decimal("0.00")
                    for it in li_items:
                        k = k + 1
                        if k > hoja * (lineas_max - 1):
                            break
                        # acumular subtotal (sin IVA facturas A):
                        if it['importe']:
                            subtotal += Decimal("%.6f" % float(it['importe']))
                            if letra_fact in ('A', 'M') and it['imp_iva']:
                                subtotal -= Decimal("%.6f" % float(it['imp_iva']))
                        # agregar el item si encuadra en la hoja especificada:
                        if k > (hoja - 1) * (lineas_max - 1):
                            if DEBUG: print("it", it)
                            li += 1
                            if it['qty'] is not None:
                                f.set('Item.Cantidad%02d' % li, self.fmt_qty(it['qty']))
                            if it['codigo'] is not None:
                                f.set('Item.Codigo%02d' % li, it['codigo'])
                            if it['umed'] is not None:
                                if it['umed']:
                                    # recortar descripci�n:
                                    umed_ds = self.umeds_ds.get(int(it['umed']))
                                    s = f.split_multicell(umed_ds, 'Item.Umed_ds01')
                                    f.set('Item.Umed_ds%02d' % li, s[0])
                            # solo discriminar IVA en A/M (mostrar tasa en B)
                            if letra_fact in ('A', 'M', 'B'):
                                if it.get('iva_id') is not None:
                                    f.set('Item.IvaId%02d' % li, it['iva_id'])
                                    if it['iva_id']:
                                        f.set('Item.AlicuotaIva%02d' % li, self.fmt_iva(it['iva_id']))
                            if letra_fact in ('A', 'M'):
                                if it.get('imp_iva') is not None:
                                    f.set('Item.ImporteIva%02d' % li, self.fmt_pre(it['imp_iva']))
                            if it.get('despacho') is not None:
                                f.set('Item.Numero_Despacho%02d' % li, it['despacho'])
                            if it.get('bonif') is not None:
                                f.set('Item.Bonif%02d' % li, self.fmt_pre(it['bonif']))
                            f.set('Item.Descripcion%02d' % li, it['ds'])
                            if it['precio'] is not None:
                                f.set('Item.Precio%02d' % li, self.fmt_pre(it['precio']))
                            if it['importe'] is not None:
                                f.set('Item.Importe%02d' % li, self.fmt_num(it['importe']))

                            # Datos MTX
                            if it.get('u_mtx') is not None:
                                f.set('Item.U_MTX%02d' % li, it['u_mtx'])
                            if it.get('cod_mtx') is not None:
                                f.set('Item.COD_MTX%02d' % li, it['cod_mtx'])

                            # datos adicionales de items
                            for adic in ['dato_a', 'dato_b', 'dato_c', 'dato_d', 'dato_e']:
                                if adic in it:
                                    f.set('Item.%s%02d' % (adic, li), it[adic])

                    if hojas == hoja:
                        # �ltima hoja, imprimo los totales
                        li += 1

                        # agrego otros tributos
                        lit = 0
                        for it in fact['tributos']:
                            lit += 1
                            if it['desc']:
                                f.set('Tributo.Descripcion%02d' % lit, it['desc'])
                            else:
                                f.set('Tributo.Descripcion%02d' % lit, self.tributos_ds[it['tributo_id']])
                            if it['base_imp'] is not None:
                                f.set('Tributo.BaseImp%02d' % lit, self.fmt_num(it['base_imp']))
                            if it['alic'] is not None:
                                f.set('Tributo.Alicuota%02d' % lit, self.fmt_num(it['alic']) + "%")
                            if it['importe'] is not None:
                                f.set('Tributo.Importe%02d' % lit, self.fmt_imp(it['importe']))

                        # reiniciar el subtotal neto, independiente de detalles:
                        subtotal = Decimal(0)
                        if fact['imp_neto']:
                            subtotal += Decimal("%.6f" % float(fact['imp_neto']))
                        # agregar IVA al subtotal si no es factura A
                        if not letra_fact in ('A', 'M') and fact['imp_iva']:
                            subtotal += Decimal("%.6f" % float(fact['imp_iva']))
                        # mostrar descuento general solo si se utiliza:
                        if 'descuento' in fact and fact['descuento']:
                            descuento = Decimal("%.6f" % float(fact['descuento']))
                            f.set('descuento', self.fmt_imp(descuento))
                            subtotal -= descuento
                        # al subtotal neto sumo exento y no gravado:
                        if fact['imp_tot_conc']:
                            subtotal += Decimal("%.6f" % float(fact['imp_tot_conc']))
                        if fact['imp_op_ex']:
                            subtotal += Decimal("%.6f" % float(fact['imp_op_ex']))
                        # si no se envia subtotal, usar el calculado:
                        if fact.get('imp_subtotal'):
                            f.set('subtotal', self.fmt_imp(fact.get('imp_subtotal')))
                        else:
                            f.set('subtotal', self.fmt_imp(subtotal))

                        # importes generales de IVA y netos gravado / no gravado
                        f.set('imp_neto', self.fmt_imp(fact['imp_neto']))
                        f.set('impto_liq', self.fmt_imp(fact.get('impto_liq')))
                        f.set('impto_liq_nri', self.fmt_imp(fact.get('impto_liq_nri')))
                        f.set('imp_iva', self.fmt_imp(fact.get('imp_iva')))
                        f.set('imp_trib', self.fmt_imp(fact.get('imp_trib')))
                        f.set('imp_total', self.fmt_imp(fact['imp_total']))
                        f.set('imp_subtotal', self.fmt_imp(fact.get('imp_subtotal')))
                        f.set('imp_tot_conc', self.fmt_imp(fact['imp_tot_conc']))
                        f.set('imp_op_ex', self.fmt_imp(fact['imp_op_ex']))

                        # campos antiguos (por compatibilidad hacia atr�s)
                        f.set('IMPTO_PERC', self.fmt_imp(fact.get('impto_perc')))
                        f.set('IMP_OP_EX', self.fmt_imp(fact.get('imp_op_ex')))
                        f.set('IMP_IIBB', self.fmt_imp(fact.get('imp_iibb')))
                        f.set('IMPTO_PERC_MUN', self.fmt_imp(fact.get('impto_perc_mun')))
                        f.set('IMP_INTERNOS', self.fmt_imp(fact.get('imp_internos')))

                        # mostrar u ocultar el IVA discriminado si es clase A/B:
                        if letra_fact in ('A', 'M'):
                            f.set('NETO', self.fmt_imp(fact['imp_neto']))
                            f.set('IVALIQ', self.fmt_imp(fact.get('impto_liq', fact.get('imp_iva'))))
                            f.set('LeyendaIVA',"")

                            # limpio etiquetas y establezco subtotal de iva liq.
                            for p in list(self.ivas_ds.values()):
                                f.set('IVA%s.L' % p, "")
                            for iva in fact['ivas']:
                                p = self.ivas_ds[int(iva['iva_id'])]
                                f.set('IVA%s' % p, self.fmt_imp(iva['importe']))
                                f.set('IVA%s.L' % p, "IVA %s" % self.fmt_iva(iva['iva_id']))
                        else:
                            # Factura C y E no llevan columna IVA (B solo tasa)
                            if letra_fact in ('C', 'E'):
                                f.set('Item.AlicuotaIVA',"")
                            f.set('NETO.L',"")
                            f.set('IVA.L',"")
                            f.set('LeyendaIVA', "")
                            for p in list(self.ivas_ds.values()):
                                f.set('IVA%s.L' % p, "")
                        f.set('Total.L', 'Total:')
                        f.set('TOTAL', self.fmt_imp(fact['imp_total']))
                    else:
                        # limpio todas las etiquetas (no es la �ltima hoja)
                        for k in ('imp_neto', 'impto_liq', 'imp_total', 'impto_perc',
                                  'imp_iva', 'impto_liq_nri', 'imp_trib', 'imp_op_ex', 'imp_tot_conc',
                                  'imp_op_ex', 'IMP_IIBB', 'imp_iibb', 'impto_perc_mun', 'imp_internos',
                                  'NGRA.L', 'EXENTO.L', 'descuento.L', 'descuento', 'subtotal.L',
                                  'NETO.L', 'IVA21.L', 'IVA10.5.L', 'IVA27.L', 'IVA5.L', 'IVA9.L', 'IVA2.5.L',
                                  'NETO', 'IVA21', 'IVA10.5', 'IVA27', 'IVA5', 'IVA9', 'IVA2.5'):
                            f.set(k,"")
                        f.set('LeyendaIVA', "")
                        f.set('Total.L', 'Subtotal:')
                        f.set('TOTAL', self.fmt_imp(subtotal))

                    f.set('cmps_asoc_ds', cmps_asoc_ds)
                    f.set('permisos_ds', permisos_ds)

                    # Datos del pie de factura (obtenidos desde AFIP):
                    f.set('motivos_ds', motivos_ds)
                    if 'motivos_ds1' in f and motivos_ds:
                        if letra_fact in ('A', 'M'):
                            if 'leyenda_credito_fiscal' in f:
                                f.set('leyenda_credito_fiscal', msg_no_iva)
                        for i, txt in enumerate(f.split_multicell(motivos_ds, 'motivos_ds1')):
                            f.set('motivos_ds%d' % (i+1), txt)
                    if not motivos_ds:
                        f.set("motivos_ds.L", "")

                    f.set('CAE', fact['cae'])
                    f.set('CAE.Vencimiento', self.fmt_date(fact['fecha_vto']))
                    if fact['cae']!="NULL" and str(fact['cae']).isdigit() and str(fact['fecha_vto']).isdigit() and self.CUIT:
                        cuit = ''.join([x for x in str(self.CUIT) if x.isdigit()])
                        barras = ''.join([cuit, "%02d" % int(fact['tipo_cbte']), "%04d" % int(fact['punto_vta']),
                            str(fact['cae']), fact['fecha_vto']])
                        barras = barras + self.digito_verificador_modulo10(barras)
                    else:
                        barras = ""

                    f.set('CodigoBarras', barras)
                    f.set('CodigoBarrasLegible', barras)

                    if not HOMO and barras and fact.get("resultado") == 'A':
                        f.set('estado', "Comprobante Autorizado")
                    elif fact.get("resultado") == 'R':
                        f.set('estado', "Comprobante Rechazado")
                    elif fact.get("resultado") == 'O':
                        f.set('estado', "Comprobante Observado")
                    elif fact.get("resultado"):
                        f.set('estado', "Comprobante No Autorizado")
                    else:
                        f.set('estado', "") # compatibilidad hacia atras

                    if 'observacionesgenerales1' in f and 'obs_generales' in fact:
                        for i, txt in enumerate(f.split_multicell(fact['obs_generales'], 'ObservacionesGenerales1')):
                            f.set('ObservacionesGenerales%d' % (i+1), txt)

                    # evaluo f�rmulas (expresiones python)
                    for field in f.keys:
                        if field.startswith("="):
                            formula = f.elements[field]['text']
                            if DEBUG: print("**** formula: %s %s" % (field, formula)
                            try:
                                value = eval(formula,dict(fact=fact))
                                f.set(field, value)
                                if DEBUG: print("set(%s,%s)" % (field, value)
                            except Exception as e:
                                raise RuntimeError("Error al evaluar %s formula '%s': %s" % (field, formula, e))
            ret = True
        except Exception as e:
            # capturar la excepci�n manualmente, para imprimirla en el PDF:
            ex = utils.exception_info()
            if DEBUG:
                print(self.Excepcion)
                print(self.Traceback)

            # guardar la traza de la excepci�n en un archivo temporal:
            fname = os.path.join(tempfile.gettempdir(), "traceback.txt")
            self.template.add_page()
            # agregar el texto de la excepci�n y ubicaci�n de la traza al PDF:
            self.AgregarCampo("traceback", 'T', 25, 270, 0, 0,
                  size=10, rotate=0, foreground=0xF00000, priority=-1,
                  text="Traceback %s" % (fname, ))
            self.AgregarCampo("excepcion", 'T', 25, 250, 0, 0,
                  size=10, rotate=0, foreground=0xF00000, priority=-1,
                  text="Excepcion %(name)s:%(lineno)s" % ex)
            if DEBUG:
                print("grabando...", fname, self.Excepcion, self.Traceback,ex)
            f = open(fname, "w")
            try:
                f.write(str(ex))
            except Exception as e:
                f.write("imposible grabar")
            finally:
                f.close()
            # guardar la info de la excepcion a lo �ltimo, para que no sea
            # limpiada por el decorador de otros m�todos (AgregarCampo) ...
            self.Excepcion = ex['msg']
            self.Traceback = ex['tb']
        finally:
            return ret

    @utils.inicializar_y_capturar_excepciones_simple
    def GenerarPDF(self, archivo=""):
        "Generar archivo de salida en formato PDF"
        if not archivo:
            dest = "S"  # devolver buffer (string)
        else:
            dest = "F"  # guardar en archivo
        return self.template.render(archivo, dest)

    @utils.inicializar_y_capturar_excepciones_simple
    def MostrarPDF(self, archivo, imprimir=False):
        if sys.platform.startswith("linux2", 'java')):
            os.system("evince ""%s""" % archivo)
        else:
            operation = imprimir and "print" or ""
            os.startfile(archivo, operation)
        return True



# busco el directorio de instalaci�n (global para que no cambie si usan otra dll)
if not hasattr(sys, "frozen"):
    basepath = __file__
elif sys.frozen=='dll':
    import win32api
    basepath = win32api.GetModuleFileName(sys.frozendllhandle)
else:
    basepath = sys.executable
INSTALL_DIR = os.path.dirname(os.path.abspath(basepath))


if __name__ == '__main__':

    if "--register" in sys.argv or "--unregister" in sys.argv:
        import win32com.server.register
        win32com.server.register.UseCommandLine(FEPDF)
    elif "/Automate" in sys.argv:
        # MS seems to like /automate to run the class factories.
        import win32com.server.localserver
        #win32com.server.localserver.main()
        # start the server.
        win32com.server.localserver.serve([FEPDF._reg_clsid_])
    else:
        from configparser import SafeConfigParser

        DEBUG = '--debug' in sys.argv
        utils.safe_console()

        # leeo configuraci�n (primer argumento o rece.ini por defecto)
        if len(sys.argv)>1 and not sys.argv[1].startswith("--"):
            CONFIG_FILE = sys.argv.pop(1)
        if DEBUG: print("CONFIG_FILE:", CONFIG_FILE)

        config = SafeConfigParser()
        config.read(CONFIG_FILE)
        conf_fact = dict(config.items('FACTURA'))
        conf_pdf = dict(config.items('PDF'))

        if '--ayuda' in sys.argv:
            print(AYUDA)
            sys.exit(0)

        if '--licencia' in sys.argv:
            print(LICENCIA)
            sys.exit(0)

        if '--formato' in sys.argv:
            if '--dbf' in sys.argv:
                from .formatos import formato_dbf
                formato_dbf.ayuda()
            else:
                from .formatos import formato_txt
                formato_txt.ayuda()
            sys.exit(0)


        fepdf = FEPDF()

        # cargo el formato CSV por defecto (factura.csv)
        fepdf.CargarFormato(conf_fact.get("formato", "factura.csv"))

        # establezco formatos (cantidad de decimales) seg�n configuraci�n:
        fepdf.FmtCantidad = conf_fact.get("fmt_cantidad", "0.2")
        fepdf.FmtPrecio = conf_fact.get("fmt_precio", "0.2")

        if '--cargar' in sys.argv:
            if '--dbf' in sys.argv:
                from .formatos import formato_dbf
                conf_dbf = dict(config.items('DBF'))
                if DEBUG: print("conf_dbf", conf_dbf)
                regs = list(formato_dbf.leer(conf_dbf).values())
            elif '--json' in sys.argv:
                from .formatos import formato_json
                entrada = conf_fact.get("entrada", "entrada.txt")
                if DEBUG: print("entrada", entrada)
                regs = formato_json.leer(entrada)
            else:
                from .formatos import formato_txt
                entrada = conf_fact.get("entrada", "entrada.txt")
                if DEBUG: print("entrada", entrada)
                regs = formato_txt.leer(entrada)
            if DEBUG:
                print(regs)
                input("continuar...")
            fepdf.factura = regs[0]
            for d in regs[0]['datos']:
                fepdf.AgregarDato(d['campo'], d['valor'], d['pagina'])


        if '--prueba' in sys.argv:
            # creo una factura de ejemplo
            HOMO = True

            # datos generales del encabezado:
            tipo_cbte = 1
            punto_vta = 4000
            fecha = datetime.datetime.now().strftime("%Y%m%d")
            concepto = 3
            tipo_doc = 80; nro_doc = "30000000007"
            cbte_nro = 12345678
            imp_total = "127.00"; imp_tot_conc = "3.00"
            imp_neto = "100.00"; imp_iva = "21.00"
            imp_trib = "1.00"; imp_op_ex = "2.00"; imp_subtotal = "105.00"
            fecha_cbte = fecha; fecha_venc_pago = fecha
            # Fechas del per�odo del servicio facturado (solo si concepto = 1?)
            fecha_serv_desde = fecha; fecha_serv_hasta = fecha
            moneda_id = 'PES'                   # p/exportaci�n (ej): DOL
            moneda_ctz = 1                      # p/exportaci�n (ej): 8.98
            incoterms = 'FOB'                   # solo exportaci�n
            idioma_cbte = 1                     # 1: es, 2: en, 3: pt

            # datos adicionales del encabezado:
            nombre_cliente = 'Joao Da Silva'
            domicilio_cliente = 'Rua 76 km 34.5 Alagoas'
            pais_dst_cmp = 16                   # 200: Argentina, ver tabla
            id_impositivo = 'PJ54482221-l'      # cat. iva (mercado interno)
            forma_pago = '30 dias'

            obs_generales = "Observaciones Generales<br/>texto libre"
            obs_comerciales = "Observaciones Comerciales<br/>texto libre"

            # datos devueltos por el webservice (WSFEv1, WSMTXCA, etc.):
            motivo_obs = "Factura individual, DocTipo: 80, DocNro 30000000007 no se encuentra registrado en los padrones de AFIP."
            cae = "61123022925855"
            fch_venc_cae = "20110320"

            fepdf.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
                cbte_nro, imp_total, imp_tot_conc, imp_neto,
                imp_iva, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago,
                fecha_serv_desde, fecha_serv_hasta,
                moneda_id, moneda_ctz, cae, fch_venc_cae, id_impositivo,
                nombre_cliente, domicilio_cliente, pais_dst_cmp,
                obs_comerciales, obs_generales, forma_pago, incoterms,
                idioma_cbte, motivo_obs)

            # completo campos extra del encabezado:
            ok = fepdf.EstablecerParametro("localidad_cliente", "Hurlingham")
            ok = fepdf.EstablecerParametro("provincia_cliente", "Buenos Aires")

            # imprimir leyenda "Comprobante Autorizado" (constatar con WSCDC!)
            ok = fepdf.EstablecerParametro("resultado", "A")

            # agrego remitos y otros comprobantes asociados:
            for i in range(3):
                tipo = 91
                pto_vta = 2
                nro = 1234 + i
                fepdf.AgregarCmpAsoc(tipo, pto_vta, nro)
            tipo = 5
            pto_vta = 2
            nro = 1234
            fepdf.AgregarCmpAsoc(tipo, pto_vta, nro)

            # tributos adicionales:
            tributo_id = 99
            desc = 'Impuesto Municipal Matanza'
            base_imp = "100.00"
            alic = "1.00"
            importe = "1.00"
            fepdf.AgregarTributo(tributo_id, desc, base_imp, alic, importe)

            tributo_id = 4
            desc = 'Impuestos Internos'
            base_imp = None
            alic = None
            importe = "0.00"
            fepdf.AgregarTributo(tributo_id, desc, base_imp, alic, importe)

            # subtotales por al�cuota de IVA:
            iva_id = 5 # 21%
            base_imp = 100
            importe = 21
            fepdf.AgregarIva(iva_id, base_imp, importe)

            for id in (4, 6):
                fepdf.AgregarIva(iva_id=id, base_imp=0.00, importe=0.00)

            # detalle de art�culos:
            u_mtx = 123456
            cod_mtx = 1234567890123
            codigo = "P0001"
            ds = "Descripcion del producto P0001\n" + "Lorem ipsum sit amet " * 10
            qty = 1.00
            umed = 7
            if tipo_cbte in (1, 2, 3, 4, 5, 34, 39, 51, 52, 53, 54, 60, 64):
                # discriminar IVA si es clase A / M
                precio = 110.00
                imp_iva = 23.10
            else:
                # no discriminar IVA si es clase B (importe final iva incluido)
                precio = 133.10
                imp_iva = None
            bonif = 0.00
            iva_id = 5
            importe = 133.10
            despacho = 'N� 123456'
            dato_a = "Dato A"
            fepdf.AgregarDetalleItem(u_mtx, cod_mtx, codigo, ds, qty, umed,
                    precio, bonif, iva_id, imp_iva, importe, despacho, dato_a)

            # descuento general (a tasa 21%):
            u_mtx = cod_mtx = codigo = None
            ds = "Bonificaci�n/Descuento 10%"
            qty = precio = bonif = None
            umed = 99
            iva_id = 5
            if tipo_cbte in (1, 2, 3, 4, 5, 34, 39, 51, 52, 53, 54, 60, 64):
                # discriminar IVA si es clase A / M
                imp_iva = -2.21
            else:
                imp_iva = None
            importe = -12.10
            fepdf.AgregarDetalleItem(u_mtx, cod_mtx, codigo, ds, qty, umed,
                    precio, bonif, iva_id, imp_iva, importe, "")

            # completo campos personalizados de la plantilla:
            fepdf.AgregarDato("custom-nro-cli", "Cod.123")
            fepdf.AgregarDato("custom-pedido", "1234")
            fepdf.AgregarDato("custom-remito", "12345")
            fepdf.AgregarDato("custom-transporte", "Camiones Ej.")
            print("Prueba!")

        # grabar muestra en dbf:
        if '--grabar' in sys.argv:
            reg = fepdf.factura.copy()
            reg['id'] = 0
            reg['datos'] = fepdf.datos
            reg['err_code'] = 'OK'
            if '--dbf' in sys.argv:
                from .formatos import formato_dbf
                conf_dbf = dict(config.items('DBF'))
                if DEBUG: print("conf_dbf", conf_dbf)
                regs = formato_dbf.escribir([reg], conf_dbf)
            elif '--json' in sys.argv:
                from .formatos import formato_json
                archivo =  conf_fact.get("entrada", "entrada.txt")
                if DEBUG: print("Escribiendo", archivo)
                regs = formato_json.escribir([reg], archivo)
            else:
                from .formatos import formato_txt
                archivo =  conf_fact.get("entrada", "entrada.txt")
                if DEBUG: print("Escribiendo", archivo)
                regs = formato_txt.escribir([reg], archivo)


        # datos fijos:
        for k, v in list(conf_pdf.items()):
            fepdf.AgregarDato(k, v)
            if k.upper() == 'CUIT':
                fepdf.CUIT = v  # CUIT del emisor para c�digo de barras

        fepdf.CrearPlantilla(papel=conf_fact.get("papel", "legal"),
                             orientacion=conf_fact.get("orientacion", "portrait"))
        fepdf.ProcesarPlantilla(num_copias=int(conf_fact.get("copias", 1)),
                                lineas_max=int(conf_fact.get("lineas_max", 24)),
                                qty_pos=conf_fact.get("cant_pos") or 'izq')
        salida = conf_fact.get("salida", "")
        fact = fepdf.factura
        if salida:
            pass
        elif 'pdf' in fact and fact['pdf']:
            salida = fact['pdf']
        else:
            # genero el nombre de archivo seg�n datos de factura
            d = conf_fact.get('directorio', ".")
            clave_subdir = conf_fact.get('subdirectorio','fecha_cbte')
            if clave_subdir:
                d = os.path.join(d, fact[clave_subdir])
            if not os.path.isdir(d):
                os.makedirs(d)
            fs = conf_fact.get('archivo','numero').split(",")
            it = fact.copy()
            tipo_fact, letra_fact, numero_fact = fact['_fmt_fact']
            it['tipo'] = tipo_fact.replace(" ", "_")
            it['letra'] = letra_fact
            it['numero'] = numero_fact
            it['mes'] = fact['fecha_cbte'][4:6]
            it['a�o'] = fact['fecha_cbte'][0:4]
            fn = ''.join([str(it.get(ff,ff)) for ff in fs])
            fn = fn.encode('ascii', 'replace').replace('?','_')
            salida = os.path.join(d, "%s.pdf" % fn)
        if DEBUG:
            print("archivo generado", salida)
        fepdf.GenerarPDF(archivo=salida)
        if '--mostrar' in sys.argv:
            fepdf.MostrarPDF(archivo=salida,imprimir='--imprimir' in sys.argv)
