Soporte para factura en HTML

This commit is contained in:
Mauricio Baeza 2018-11-19 01:03:48 -06:00
parent 2d045af037
commit 7dc65a2a2b
30 changed files with 8504 additions and 5 deletions

View File

@ -510,7 +510,7 @@ class AppDocumentos(object):
session = req.env['beaker.session']
req.context['result'], file_name, content_type = \
self._db.get_doc(type_doc, id_doc, session['rfc'])
if not type_doc in ('pdf', 'pre', 'tpdf', 'pdfpago'):
if not type_doc in ('pdf', 'pre', 'tpdf', 'pdfpago', 'html'):
resp.append_header('Content-Disposition',
'attachment; filename={}'.format(file_name))
resp.content_type = content_type

View File

@ -52,6 +52,11 @@ except ImportError:
import pyqrcode
from dateutil import parser
from lxml import etree
import mako.runtime
from mako.exceptions import TopLevelLookupException
mako.runtime.UNDEFINED = ''
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice, \
SeaFileAPI, PrintTicket
@ -1576,6 +1581,27 @@ def to_pdf(data, emisor_rfc, ods=False):
return read_file(path)
def format_currency(value, currency, digits=2):
c = {
'MXN': '$',
'USD': '$',
'EUR': '',
}
s = c.get(currency, 'MXN')
return f'{s} {float(value):,.{digits}f}'
def to_html(data):
name = f"{data['rfc']}_{data['version']}.html"
try:
template = template_lookup.get_template(name)
except TopLevelLookupException:
template = template_lookup.get_template('plantilla_factura.html')
data['rfc'] = 'estilos'
return template.render(**data)
def import_employees(rfc):
name = '{}_employees.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'tmp', name)
@ -1621,8 +1647,8 @@ def get_dict(data):
return CaseInsensitiveDict(data)
def to_letters(value, moneda):
return NumLet(value, moneda).letras
def to_letters(value, currency):
return NumLet(value, currency).letras
def get_qr(data):
@ -3648,3 +3674,8 @@ def validate_rfc(value):
return ''
except:
return msg
def parse_xml2(xml_str):
return etree.fromstring(xml_str.encode('utf-8'))

View File

@ -204,12 +204,15 @@ def get_doc(type_doc, id, rfc):
'ods': 'application/octet-stream',
'zip': 'application/octet-stream',
'nomlog': 'application/txt',
'html': 'text/html',
}
content_type = types.get(type_doc, 'application/pdf')
if type_doc == 'xml':
data, file_name = Facturas.get_xml(id)
elif type_doc == 'pdf':
data, file_name = Facturas.get_pdf(id, rfc)
elif type_doc == 'html':
data, file_name = Facturas.get_html(id)
elif type_doc == 'ods':
data, file_name = Facturas.get_ods(id, rfc)
elif type_doc == 'zip':
@ -3671,6 +3674,138 @@ class Facturas(BaseModel):
return doc, name
def _get_description(self, node, data):
return data['descripcion']
def _get_others_values(self, invoice, emisor):
v_cancel = {True: 'inline', False: 'none'}
v_type = {'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado'}
v_method = {
'PUE': 'Pago en una sola exhibición',
'PPD': 'Pago en parcialidades o diferido',
}
v_tax = {
'001': 'ISR',
'002': 'IVA',
'003': 'IEPS',
}
data = {
'rfc': emisor.rfc.lower(),
'version': invoice.version,
'cancelada': v_cancel.get(invoice.cancelada),
'cfdi_notas': invoice.notas,
}
xml = util.parse_xml2(invoice.xml)
d = util.get_dict(xml.attrib)
d.pop('Certificado', '')
currency = d.get('Moneda', 'MXN')
data.update({f'cfdi_{k.lower()}': v for k, v in d.items()})
data['cfdi_tipodecomprobante'] = v_type[data['cfdi_tipodecomprobante']]
if data.get('cfdi_formapago', ''):
obj = SATFormaPago.get(SATFormaPago.key==invoice.forma_pago)
data['cfdi_formapago'] = str(obj)
if data.get('cfdi_metodopago', ''):
data['cfdi_metodopago'] = 'Método de Pago: ({}) {}'.format(
data['cfdi_metodopago'], v_method[data['cfdi_metodopago']])
obj = SATMonedas.get(SATMonedas.key==currency)
data['cfdi_moneda'] = str(obj)
data['cfdi_tipocambio'] = util.format_currency(
data['cfdi_tipocambio'], currency, 4)
data['cfdi_totalenletras'] = util.to_letters(
float(data['cfdi_total']), currency)
data['totales'] = [(
'Subtotal', util.format_currency(data['cfdi_subtotal'], currency))]
for node in xml:
if 'Emisor' in node.tag:
d = util.get_dict(node.attrib)
data.update(
{f'emisor_{k.lower()}': v for k, v in d.items()}
)
elif 'Receptor' in node.tag:
d = util.get_dict(node.attrib)
data.update(
{f'receptor_{k.lower()}': v for k, v in d.items()}
)
elif 'Conceptos' in node.tag:
data['conceptos'] = []
for subnode in node:
d = util.get_dict(subnode.attrib)
concepto = {
'clave': f"{d['noidentificacion']}<BR>({d['claveprodserv']})",
'descripcion': self._get_description(self, subnode, d),
'unidad': f"{d['unidad']}<BR>({d['claveunidad']})",
'cantidad': d['cantidad'],
'valorunitario': util.format_currency(d['valorunitario'], currency),
'importe': util.format_currency(d['importe'], currency),
}
data['conceptos'].append(concepto)
elif 'Impuestos' in node.tag:
for subnode in node:
if 'Traslados' in subnode.tag:
for t in subnode:
d = util.get_dict(t.attrib)
name = v_tax.get(d['impuesto'])
tasa = FORMAT.format(float(d['tasaocuota']))
title = f"Traslado {name} {tasa}"
importe = util.format_currency(d['importe'], currency)
data['totales'].append((title, importe))
elif 'Retenciones' in subnode.tag:
for r in subnode:
d = util.get_dict(r.attrib)
name = v_tax.get(d['impuesto'])
tasa = FORMAT.format(float(d['tasaocuota']))
title = f"Retención {name} {tasa}"
importe = util.format_currency(d['importe'], currency)
data['totales'].append((title, importe))
elif 'Complemento' in node.tag:
for subnode in node:
if 'TimbreFiscalDigital' in subnode.tag:
d = util.get_dict(subnode.attrib)
data.update(
{f'timbre_{k.lower()}': v for k, v in d.items()}
)
obj = SATRegimenes.get(SATRegimenes.key==data['emisor_regimenfiscal'])
data['emisor_regimenfiscal'] = str(obj)
data['emisor_logo'] = data['emisor_rfc'].lower()
obj = SATUsoCfdi.get(SATUsoCfdi.key==data['receptor_usocfdi'])
data['receptor_usocfdi'] = str(obj)
data['totales'].append((
'Total', util.format_currency(data['cfdi_total'], currency)))
data['timbre_cadenaoriginal'] = f"""||{data['timbre_version']}|
{data['timbre_uuid']}|{data['timbre_fechatimbrado']}|
{data['timbre_sellocfd']}|{data['timbre_nocertificadosat']}||"""
# ~ print(data)
return data
@classmethod
def get_html(cls, id):
try:
emisor = Emisor.select()[0]
except IndexError:
return '', 'sin_datos_de_emisor.html'
obj = Facturas.get(Facturas.id==id)
name = '{}{}_{}.html'.format(obj.serie, obj.folio, obj.cliente.rfc)
if obj.uuid is None:
return '', name
data = cls._get_others_values(cls, obj, emisor)
doc = util.to_html(data)
return doc, name
@classmethod
def get_ods(cls, id, rfc):
try:

View File

@ -74,7 +74,10 @@ TEMPLATE_CANCEL = os.path.abspath(os.path.join(PATH_TEMPLATES, CT))
PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt'))
PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin'))
template_lookup = TemplateLookup(directories=[PATH_TEMPLATES],
PATH_TEMPLATES_USER = os.path.abspath(os.path.join(
BASE_DIR, '..', 'docs', 'templates'))
directories=[PATH_TEMPLATES, PATH_TEMPLATES_USER]
template_lookup = TemplateLookup(directories=directories,
input_encoding='utf-8',
output_encoding='utf-8')

View File

@ -0,0 +1,508 @@
.emisor-cintilla{
background-color: #975759;
color: #fff;
}
.fiscales-emisor{
color: #7d1916;
}
.fiscales-emisor .telefono, .fiscales-emisor .correo, .fiscales-emisor .web{
color: #333;
}
header .titulo-vertical{
background-color: #dbc5c6;
color: #000;
}
.receptor .nombre{
color: #000;
}
.receptor .rfc{
color: #000;
}
.receptor .direccion{
color: #000;
}
.receptor .estado{
color: #000;
}
.cfdi .folio span{
color: #ed483d;
}
.cfdi .tipo span{
color: #ed483d;
}
.cfdi .folio-fiscal span{
color: #ed483d;
}
.conceptos th{
background-color: #975759;
}
.conceptos td.clave, .conceptos td.unidad, .conceptos td.valor-unitario{
background-color: #dbc5c6;
}
.conceptos td.descripcion, .conceptos td.cantidad, .conceptos td.importe{
background-color: #f0e7e7;
}
table.subtotal th{
background-color: #dbc5c6;
}
table.subtotal td{
background-color: #f0e7e7;
}
.sello .cadenas-sello .cadena div {
background-color: #dbc5c6;
color: #7d1916;
}
.rfc-pac {
background-color: #dbc5c6;
color: #7d1916;
}
.cancelada{
color: #ed3833;
}
@font-face {
font-family: 'Avenir Next';
src: url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot');
src: url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot?#iefix') format('embedded-opentype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff2') format('woff2'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff') format('woff'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.ttf') format('truetype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.svg#AvenirNextLTPro-Regular') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Avenir Next';
src: url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot');
src: url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot?#iefix') format('embedded-opentype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff2') format('woff2'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff') format('woff'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.ttf') format('truetype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.svg#AvenirNextLTPro-Bold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Avenir Next';
src: url('/static/fonts/avenir_next/AvenirNextLTPro-It.eot');
src: url('/static/fonts/avenir_next/AvenirNextLTPro-It.eot?#iefix') format('embedded-opentype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-It.woff2') format('woff2'),
url('/static/fonts/avenir_next/AvenirNextLTPro-It.woff') format('woff'),
url('/static/fonts/avenir_next/AvenirNextLTPro-It.ttf') format('truetype'),
url('/static/fonts/avenir_next/AvenirNextLTPro-It.svg#AvenirNextLTPro-It') format('svg');
font-weight: normal;
font-style: italic;
}
@page{
size: 8.5in 11in;
margin: 1cm ;
background-color: white !important;
}
@media print {
thead {display: table-header-group;}
body {
background-color: #fff;
}
}
body {
background-color: rgb(204,204,204);
font-family: 'Avenir Next';
}
#plantilla {
background: #fff;
display: block;
margin: 0 auto;
width: 21.5cm;
height: 27.9cm;
position: relative;
}
.cancelada{
-ms-transform: rotate(320deg);
-webkit-transform: rotate(320deg);
transform: rotate(320deg);
color: #ed3833;
font-size: 100px;
font-weight: bold;
left: 18%;
position: absolute;
text-align: center;
top: 30%;
z-index: 1000;
}
.cancelada div{
font-size: 20px;
}
.emisor-cintilla{
background-color: #975759;
color: #fff;
font-size: 7pt;
padding: 5px;
text-align: center;
}
.datos-emisor .logo{
margin-left: 20px;
max-width: 180px;
}
.datos-emisor .fiscales-emisor{
float: right;
}
.fiscales-emisor{
color: #7d1916;
font-weight: bold;
margin-top: 20px;
text-align: right;
}
.fiscales-emisor .nombre{
font-size: 18px;
padding-right: 30px;
}
.fiscales-emisor .rfc{
font-size: 16px;
padding-right: 30px;
}
.fiscales-emisor .regimen{
font-size: 12px;
padding-right: 30px;
}
.fiscales-emisor .telefono, .fiscales-emisor .correo, .fiscales-emisor .web{
color: #333;
font-size: 14px;
line-height: 15px;
padding-right: 10px;
}
.fiscales-emisor img{
margin-left: 10px;
width: 10px;
}
.clear{
clear: both;
}
header .titulo-vertical{
background-color: #dbc5c6;
-ms-transform: rotate(270deg);
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
font-size: 15px;
font-weight: bold;
left: -31px;
line-height: 35px;
position: absolute;
text-align: center;
top: 35px;
width: 105px;
}
header .receptor{
box-sizing: border-box;
float: left;
font-size: 10px;
height: 106px;
margin-top: 10px;
position: relative;
padding-bottom: 5px;
padding-left: 45px;
padding-top: 5px;
width: 65%;
}
.receptor .nombre{
color: #000;
font-size: 17px;
font-weight: bold;
}
.receptor .rfc{
color: #000;
font-size: 15px;
font-weight: bold;
}
.receptor .direccion,
.receptor .estado{
color: #000;
font-size: 10px;
}
.receptor img{
width: 10px;
}
.receptor .correo,
.receptor .telefono{
float: right;
width: 40%;
}
.receptor .uso-cfdi{
color: #000;
font-size: 12px;
font-weight: bold;
float: left;
width: 60%;
}
header .cfdi{
box-sizing: border-box;
float: right;
font-size: 10px;
height: 100px;
margin-top: 10px;
position: relative;
padding-bottom: 5px;
padding-left: 45px;
padding-top: 5px;
width: 35%;
}
.cfdi .folio, .cfdi .tipo{
color: #000;
float: left;
font-size: 11px;
font-weight: bold;
line-height: 15px;
width: 50%;
}
.cfdi .folio span, .cfdi .tipo span{
color: #ed483d;
}
.cfdi .folio span{
font-size: 10px;
}
.cfdi .tipo span{
font-size: 10px;
}
.cfdi .folio-fiscal{
color: #000;
font-size: 10px;
font-weight: bold;
line-height: 15px;
text-align: center;
}
.cfdi .folio-fiscal span{
color: #ed483d;
}
.cfdi .fecha-emision, .cfdi .fecha-certificacion, .cfdi .lugar-expedicion{
color: #333;
font-size: 10px;
font-weight: bold;
line-height: 15px;
}
.conceptos{
margin-bottom: 10px;
margin-top: 20px;
width: 100%;
text-align: center;
}
.conceptos th{
background-color: #975759;
color: #fff;
font-size: 11px;
line-height: 15px;
}
.conceptos .clave{
width: 10%;
}
.conceptos .descripcion{
width: 45%;
}
.conceptos .descripcion div{
text-align: justify;
}
.conceptos .unidad{
width: 8%;
}
.conceptos .cantidad{
width: 9%;
}
.conceptos .valor-unitario{
width: 13%;
}
.conceptos .importe{
width: 15%;
}
.conceptos td{
background-color: #975759;
color: #000;
font-size: 11px;
line-height: 15px;
}
.conceptos td.clave, .conceptos td.unidad, .conceptos td.valor-unitario{
background-color: #dbc5c6;
}
.conceptos td.descripcion, .conceptos td.cantidad, .conceptos td.importe{
background-color: #f0e7e7;
}
.conceptos td.valor-unitario, .conceptos td.importe{
text-align: right;
}
.total-letras{
float: left;
font-size: 12px;
line-height: 15px;
margin: 4px 0;
width: 63%;
}
table.subtotal{
float: right;
font-size: 12px;
line-height: 15px;
text-align: right;
width: 37%;
font-weight: bold;
}
table.subtotal th{
background-color: #f0e7e7;
width: 60%;
padding: 3px 3px 3px 3px;
}
table.subtotal td{
background-color: #dbc5c6;
width: 40%;
padding: 3px 3px 3px 3px;
}
.condiciones-pago{
font-size: 11px;
line-height: 15px;
}
.notas{
font-size: 11px;
margin: 20px 0;
}
.formapago-metodopago, .moneda-tipocambio, .tiporelacion, .relacionados{
float: left;
font-size: 10px;
line-height: 12px;
width: 85%;
}
.tipocomite, .tipoproceso, .idcontabilidad{
float: right;
font-size: 10px;
line-height: 12px;
width: 30%;
}
.sello{
margin: 20px 0;
}
.sello .cbb{
border: 1px solid #000;
height: auto;
margin-top: 20px;
margin-right: 5%;
width: 15%;
}
.sello .cadenas-sello{
color: #000;
float: right;
font-size: 8px;
font-weight: bold;
line-height: 16px;
width: 79%;
margin-left: 2px;
margin-right: 2px;
word-break: break-all;
}
.sello .cadenas-sello .cadena{
margin: 10px 0;
}
.sello .cadenas-sello .cadena div{
font-weight: normal;
background-color: #dbc5c6;
color: #7d1916;
}
.sello .cadena-original{
color: #000;
float: right;
font-size: 8px;
font-weight: bold;
line-height: 16px;
width: 100%;
margin: 10px;
margin-left: 5px;
margin-right: 2px;
word-break: break-all;
}
.sello .cadena-original div{
font-weight: normal;
background-color: #dbc5c6;
color: #7d1916;
}
.rfc-pac{
background-color: #dbc5c6;
color: #7d1916;
font-size: 10px;
line-height: 16px;
text-align: center;
margin-right: 2px;
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
source/static/img/web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1282,11 +1282,15 @@ function enviar_correo(row){
function get_pdf(id){
//~ location = '/doc/pdf/' + id
window.open('/doc/pdf/' + id, '_blank')
}
function get_html(id){
window.open('/doc/html/' + id, '_blank')
}
function grid_invoices_click(id, e, node){
var row = this.getItem(id)
@ -1294,6 +1298,8 @@ function grid_invoices_click(id, e, node){
location = '/doc/xml/' + row.id
}else if(id.column == 'pdf'){
get_pdf(row.id)
}else if(id.column == 'html'){
get_html(row.id)
}else if(id.column == 'ods'){
location = '/doc/ods/' + row.id
}else if(id.column == 'zip'){

View File

@ -59,6 +59,7 @@ function get_icon(tipo){
icons = {
xml: 'fa-file-code-o',
pdf: 'fa-file-pdf-o',
html: 'fa-html5',
zip: 'fa-file-zip-o',
email: 'fa-envelope-o',
print: 'fa-print',

View File

@ -267,6 +267,7 @@ var grid_invoices_cols = [
fillspace: true, sort: 'string', footer: {text: '$ 0.00', colspan: 2}},
{id: 'xml', header: 'XML', adjust: 'data', template: get_icon('xml')},
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
{id: 'html', header: 'HTML', adjust: 'data', template: get_icon('html')},
{id: 'ods', header: 'ODS', adjust: 'data', template: get_icon('table')},
{id: 'zip', header: 'ZIP', adjust: 'data', template: get_icon('zip')},
{id: 'email', header: '', adjust: 'data', template: get_icon('email')}

View File

@ -0,0 +1,191 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="/static/css/${rfc}.css">
<title>Empresa Libre</title>
</head>
<body>
<div id="plantilla">
<div class="cancelada" style="display: ${cancelada}">
Cancelada
<div>Sin valor fiscal</div>
</div>
<header>
<section class="emisor-cintilla">
${emisor_calle} ${emisor_noexterior} ${emisor_nointerior}
${emisor_colonia} ${emisor_codigopostal} ${emisor_municipio}
${emisor_estado} ${emisor_pais}
</section>
<section class="datos-emisor">
<img src="/static/img/${emisor_logo}.png" class="logo"/>
<section class="fiscales-emisor">
<div class="nombre">
${emisor_nombre}
</div>
<div class="rfc">
${emisor_rfc}
</div>
<div class="regimen">
${emisor_regimenfiscal}
</div>
<div class="telefono">
(55) 5810-8088
<img src="/static/img/telefono.png" />
</div>
<div class="correo">
administracion@empresalibre.net
<img src="/static/img/correo.png" />
</div>
<div class="web">
https://empresalibre.net
<img src="/static/img/web.png" />
</div>
</section>
<div class="clear"></div>
<section class="receptor">
<div class="titulo-vertical">Receptor</div>
<div class="nombre">
${receptor_nombre}
</div>
<div class="rfc">
${receptor_rfc}
</div>
<div class="direccion">
${receptor_calle} ${receptor_noexterior} ${receptor_nointerior}
${receptor_colonia} ${receptor_codigopostal}
</div>
<div class="estado">
${receptor_municipio} ${receptor_estado} ${receptor_pais}
</div>
<div class="correo">
<img src="/static/img/correo.png" /> ${receptor_correo_1}
</div>
<div class="clear"></div>
<div class="uso-cfdi">
${receptor_usocfdi}
</div>
<div class="telefono">
<img src="/static/img/telefono.png" /> ${receptor_telefono_1}
</div>
</section>
<section class="cfdi">
<div class="titulo-vertical">Datos CFDI</div>
<div class="folio">Folio: <span>${cfdi_serie}-${cfdi_folio}</span></div>
<div class="tipo">Tipo: <span>${cfdi_tipodecomprobante}</span></div>
<div class="clear"></div>
<div class="folio-fiscal">
Folio Fiscal <br>
<span>${timbre_uuid}</span>
</div>
<div class="fecha-emision">Fecha Emisión: ${cfdi_fecha}</div>
<div class="fecha-certificacion">Fecha Certificacion: ${timbre_fechatimbrado}</div>
<div class="lugar-expedicion">CP de Expedición: ${cfdi_lugarexpedicion}</div>
</section>
</section>
</header>
<div class="clear"></div>
<table class="conceptos">
<thead>
<tr>
<th class="clave">Clave</th>
<th class="descripcion">Descripción</th>
<th class="unidad">Unidad</th>
<th class="cantidad">Cantidad</th>
<th class="valor-unitario">Valor Unitario</th>
<th class="importe">Importe</th>
</tr>
</thead>
<tbody>
% for c in conceptos:
<tr>
<td class="clave">${c['clave']}</td>
<td class="descripcion"><div>${c['descripcion']}</div></td>
<td class="unidad">${c['unidad']}</td>
<td class="cantidad">${c['cantidad']}</td>
<td class="valor-unitario">${c['valorunitario']}</td>
<td class="importe">${c['importe']}</td>
</tr>
% endfor
</tbody>
</table>
<div class="total-letras">
${cfdi_totalenletras}
</div>
<table class="subtotal">
% for total in totales:
<tr>
<th>${total[0]}</th>
<td>${total[1]}</td>
</tr>
% endfor
</table>
<div class="clear"></div>
<div class="condiciones-pago">
${cfdi_condicionespago}
</div>
<div class="notas">
${cfdi_notas}
</div>
<div class="formapago-metodopago">
${cfdi_formapago}<BR>${cfdi_metodopago}
</div>
<div class="moneda-tipocambio">
${cfdi_moneda}, Tipo de Cambio: ${cfdi_tipocambio}
</div>
<div class="tipocomite">
{ine.tipocomite}
</div>
<div class="tiporelacion">
{cfdi.tiporelacion}
</div>
<div class="tipoproceso">
{ine.tipoproceso}
</div>
<div class="relacionados">
{cfdi.relacionados}
</div>
<div class="idcontabilidad">
{ine.idcontabilidad}
</div>
<div class="clear"></div>
<div class="sello">
<img class="cbb" src="img/cbb.png" />
<div class="cadenas-sello">
<div class="cadena">
Sello Digital del CFDI:
<div>
${cfdi_sello}
</div>
</div>
<div class="cadena">
Sello del SAT:
<div>
${timbre_sellosat}
</div>
</div>
</div>
</div>
<div class="sello">
<div class="cadena-original">
Cadena original del complemento de certificación digital del SAT:
<div>
${timbre_cadenaoriginal}
</div>
</div>
</div>
<div class="clear"></div>
<div class="rfc-pac">
RFC del PAC que Certifico: ${timbre_rfcprovcertif}
Serie CSD SAT: ${timbre_nocertificadosat}
Serie CSD Emisor: ${cfdi_nocertificado}
</div>
</div>
</body>
</html>