Compare commits

...

583 Commits

Author SHA1 Message Date
El Mau 34a981a2dd Fix: en mercancias en Carta porte al generar el PDF 2024-04-10 20:54:26 -06:00
El Mau 66549e271b Fix: en mercancias en Carta porte al generar el PDF 2024-04-10 20:54:01 -06:00
El Mau 884b10bd79 Fix in js 2024-04-02 10:59:54 -06:00
El Mau fd050159ba Fix in js 2024-04-02 10:59:23 -06:00
El Mau 8199667188 Fix in Carta Porte 3.0 2024-04-02 10:31:58 -06:00
El Mau 05d3f617eb Fix in Carta Porte 2024-04-02 10:31:28 -06:00
El Mau 0ec4f0c2c9 Add support for Carta Porte 3.0 2024-04-02 00:25:41 -06:00
El Mau 29bfb42abc PDF from Carta Porte v3 2024-04-02 00:20:44 -06:00
El Mau 7f129b84b2 Stamp Carta Porte v3 2024-04-01 14:09:57 -06:00
El Mau 13a3c106c6 UI for Carta Porte v3 2024-03-31 22:28:13 -06:00
El Mau 0c7abf634b Update changelog 2024-03-04 14:07:10 -06:00
El Mau f0c4423fc8 Add support for Comercio Exterior 2.0 2024-01-24 15:07:26 -06:00
El Mau d925587a98 PDF from CE in version 2.0 2024-01-24 13:45:23 -06:00
El Mau 510ebdd1e5 Stamp CE in version 2.0 2024-01-21 23:20:27 -06:00
El Mau d2e92098da Import from ODS 2024-01-18 22:29:49 -06:00
El Mau c1ce206835 Import from json 2024-01-17 23:49:58 -06:00
El Mau 1efa280920 Import from json 2024-01-16 22:51:58 -06:00
El Mau 6fd8f9e2ea Add UI for CE 2024-01-14 22:42:26 -06:00
El Mau e3a44e6400 Add filter by day 2023-12-26 21:38:22 -06:00
El Mau 390ec60fdd Add filter by day 2023-12-26 21:37:45 -06:00
El Mau 957160755a Fix issue #98 and #107 2023-12-20 18:55:18 -06:00
El Mau 070ee31a1b Update changelog 2023-12-20 18:54:25 -06:00
El Mau 2620159408 Fix issue #98 2023-12-20 18:51:38 -06:00
El Mau 4d9bc4a172 Fix issue #107 2023-12-20 18:36:44 -06:00
El Mau 17ed56e9d7 Fix for CFDI egreso for General Public 2023-11-05 22:15:17 -06:00
El Mau 5057ac57ae Fix for CFDI egreso for General Public 2023-11-05 22:13:45 -06:00
El Mau 410a7080da Modify name for Public RFC 2023-10-30 16:51:38 -06:00
El Mau edd8d560bd Update changelog and version 2023-10-30 00:12:40 -06:00
El Mau bd1699d082 Add support for change time zone 2023-10-30 00:02:48 -06:00
El Mau 6d4023bb17 Update SATA catalog 2023-10-29 22:40:05 -06:00
Mauricio beda45690f Fix in retention tax for RESICOs 2023-07-06 11:16:04 -06:00
Mauricio 8feba167af Fix in retention tax for RESICOs 2023-07-06 11:15:15 -06:00
Mauricio 4dbb797d99 Fix tax retention in pay complement 2023-06-14 21:42:17 -06:00
Mauricio cd424b71dc Update changelog 2023-06-14 20:20:30 -06:00
Mauricio ea13d11319 Fix - Retencion tax in pay complement 2023-06-14 20:19:21 -06:00
Mauricio d9c863820a Merge pull request 'Añade información de desarrollo al readme' (#105) from categulario/empresa-libre:improve-readme into develop
Reviewed-on: #105
2023-06-06 15:23:46 -06:00
Categulario 6815d6c64c
improve readme with information on how to run locally 2023-06-05 22:34:09 -06:00
Mauricio 402cb7d70c Fix #103 2023-06-05 16:25:37 -06:00
Mauricio a5f1114774 Update changelog 2023-06-05 16:25:07 -06:00
Mauricio 2588a5f0b8 Fix #103 2023-06-05 16:22:53 -06:00
Mauricio eeae157772 Fix in complement pays 2023-05-25 21:59:13 -06:00
Mauricio 7cad4093d4 Fix in global invoice 2023-04-20 13:21:19 -06:00
Mauricio 7caf6423e1 Fix in global invoice 2023-04-20 13:20:48 -06:00
Mauricio 8a3aea41eb Add index in tickets 2023-04-15 19:23:56 -06:00
Mauricio bd49356d3f Add index in tickets 2023-04-15 18:13:45 -06:00
Mauricio 3b54ef0a36 Fix in global invoice 2023-04-14 17:56:12 -06:00
Mauricio 497a4be392 Update version 2023-04-14 17:55:48 -06:00
Mauricio 7fc6264322 Fix in global invoice 2023-04-14 17:55:00 -06:00
Mauricio c5d0e97212 Fix: al obtener la clave del sat al facturar en lote 2023-04-01 15:10:07 -06:00
Mauricio 5bbad9ef58 Fix: al obtener la clave del sat al facturar en lote 2023-04-01 15:09:27 -06:00
Mauricio 8e7aa23d1b Validate exists partner in edit 2023-03-29 17:36:17 -06:00
Mauricio f3d8b1dae5 Fix issue #97 2023-03-29 13:32:31 -06:00
Mauricio 2c684ae6bd Fix issue #97 2023-03-29 13:31:23 -06:00
el Mau febea3f289 Fix #96 2023-03-21 10:56:55 -06:00
el Mau 5facb96662 Fix #96 2023-03-21 10:55:16 -06:00
el Mau 6411c2e51e Fix in taxes in invoice pay 2023-03-13 17:16:30 -06:00
el Mau eeb128fda6 Fix in taxes in invoice pay 2023-03-13 17:16:15 -06:00
el Mau a078e27703 Fix in taxes in invoice pay 2023-03-13 14:01:26 -06:00
el Mau 28ef53043f Fix in taxes in invoice pay 2023-03-13 13:59:24 -06:00
el Mau e956c243f7 Fix in invoice pay 2023-03-10 14:56:19 -06:00
el Mau 1b33413de2 Fix in invoice pay 2023-03-10 14:55:57 -06:00
el Mau 64541a84b9 Fix in invoice pay with tax IVA 0 2023-03-07 11:53:28 -06:00
el Mau 834c1faae6 Fix in invoice pay with tax IVA 0 2023-03-07 11:53:04 -06:00
el Mau affe57bfcd Add validate for cancel movement bank 2023-03-06 16:10:07 -06:00
el Mau 4dc54ade1e Add validate for cancel movement bank 2023-03-06 16:09:37 -06:00
el Mau e72739a5fe Add cancel invoice pay in admin list 2023-03-06 00:48:01 -06:00
el Mau dd7b9c1680 Add cancel invoice pay in admin list 2023-03-06 00:47:34 -06:00
el Mau 9823bd967f Fix for exempted tax 2023-03-03 01:38:37 -06:00
el Mau aee467d98f Fix for exempted tax 2023-03-03 01:38:19 -06:00
el Mau 64917527f7 Validate tax object 2023-03-01 22:24:26 -06:00
el Mau 5f56179ebb Validate tax object 2023-03-01 22:24:00 -06:00
el Mau 955a0348f8 Can select month in global invoice 2023-03-01 18:45:50 -06:00
el Mau 0826b0cea2 Can select month in global invoice 2023-03-01 18:45:21 -06:00
el Mau 8b0f9fbc64 Can select global options for public invoice 2023-03-01 18:17:56 -06:00
el Mau f3d827364b Can select global options for public invoice 2023-03-01 18:16:52 -06:00
el Mau 50cbb3bae8 Fix in tax IVA retention 2023-03-01 14:32:33 -06:00
el Mau a7c8d80219 Fix in tax IVA retention 2023-03-01 14:32:01 -06:00
el Mau ca9d7a02e3 Update requirements.txt 2023-02-24 14:08:14 -06:00
el Mau 31fcb63e51 Update requirements.txt 2023-02-24 14:07:44 -06:00
el Mau bd7a31ea8a Fix global invoice 2023-02-24 12:45:52 -06:00
el Mau 6a6b6ca395 Fix global invoice 2023-02-23 18:20:10 -06:00
el Mau 3148c5ba82 Fix type change in deposit 2023-02-22 11:07:38 -06:00
el Mau 5f62ad7750 Fix type change in deposit 2023-02-22 11:06:37 -06:00
el Mau ff44162622 Fix type change in deposit 2023-02-21 22:16:25 -06:00
el Mau a37b3a5c23 Fix type change in deposit 2023-02-21 22:09:42 -06:00
el Mau 4fdea6173b Fix invoice pay in others currencies 2023-02-20 19:15:40 -06:00
el Mau 968c2744ec Fix pay invoice in other currencies 2023-02-20 19:10:29 -06:00
el Mau c9d85ca2e7 Fix make PDF from pays 1.0 2023-02-19 14:00:06 -06:00
el Mau 93e64eafd8 Fix make PDF from pays 1.0 2023-02-18 12:56:22 -06:00
el Mau 1984103ff5 Fix make PDF from pays 1.0 2023-02-18 12:40:05 -06:00
el Mau e4ba06c0ff Fix in tax object 04 2023-02-17 14:24:33 -06:00
el Mau 38bb267e55 Fix in tax object 04 2023-02-17 14:24:17 -06:00
el Mau 602467c8e9 Fix in tax object 04 2023-02-17 13:44:42 -06:00
el Mau 4020237f68 Fix in tax object 04 2023-02-17 13:43:43 -06:00
el Mau 933c9820e6 Fix with tax exento 2023-02-16 23:38:03 -06:00
el Mau 51daf0ad5e Stamp with tax exento 2023-02-16 23:09:35 -06:00
el Mau 709c524830 Show message when delete product 2023-02-16 22:30:08 -06:00
el Mau 434e15bb5b Fix in cancel with Finkok 2023-02-08 21:43:38 -06:00
el Mau ae5949c529 Fix in cancel with Finkok 2023-02-08 19:25:53 -06:00
el Mau af5fa3c812 Fix in templates 2023-02-01 22:45:48 -06:00
el Mau 6d399fcb39 Fix in templates 2023-02-01 22:45:24 -06:00
el Mau 8003fc42b9 Add template in json for 4.0 2023-02-01 22:27:13 -06:00
el Mau 7a0fb2b243 Add template in json for 4.0 2023-02-01 22:26:10 -06:00
el Mau 83bebde169 Fix use cfdi P01 2023-02-01 08:56:55 -06:00
el Mau 76d5f51d78 Fix use cfdi P01 2023-02-01 08:56:28 -06:00
el Mau 5f641c632e Get message error in import invoice by lote 2023-01-31 22:07:43 -06:00
el Mau 85eb7cd58c Get message error in import invoice by lote 2023-01-31 22:07:06 -06:00
el Mau 1c11a1b013 Fix get sum by tax ISR 2023-01-30 22:16:07 -06:00
el Mau 701bb68478 Fix sum for ISR tax 2023-01-30 22:10:25 -06:00
el Mau f13491d983 Fix get sum basy pay for IVA 2023-01-30 20:12:09 -06:00
el Mau 4020365616 Fix get base pay 2023-01-30 20:09:57 -06:00
el Mau a90f218c76 Fix node tax in invoice pay 2023-01-27 12:00:36 -06:00
el Mau 37995befd8 Fix node tax in invoice pay 2023-01-27 12:00:09 -06:00
el Mau 746c827492 Validate regimen fiscal in pay invoice 2023-01-23 16:18:11 -06:00
el Mau 3cea54c07c Validate regimen fiscal in pay invoice 2023-01-23 16:17:47 -06:00
el Mau 661eff1dc3 Validate regimen fiscal in pay invoice 2023-01-23 16:05:38 -06:00
el Mau cd11456ec3 Validate regimen fiscal in pay invoice 2023-01-23 16:05:22 -06:00
el Mau 2526a8f5aa Validate regimen fiscal in pay invoice 2023-01-23 15:39:04 -06:00
el Mau 0a13004419 Validate regimen fiscal in pay invoice 2023-01-23 15:38:28 -06:00
el Mau 296874a9b1 Admin regimenes 2023-01-22 12:57:35 -06:00
el Mau 34b1af1319 Admin regimenes 2023-01-21 23:19:44 -06:00
el Mau 5fbf80fb92 Show keys in invoice 2023-01-21 20:19:28 -06:00
el Mau b56fee5f73 Show key by regimen in invoice 2023-01-21 20:06:31 -06:00
el Mau c9ca45b60d Add keys in regimenes fiscales 2023-01-21 19:46:06 -06:00
el Mau 86e7f50621 Fix in validate CP 2023-01-13 22:25:48 -06:00
el Mau 2cef108856 Fix in validate CP 2023-01-13 22:21:03 -06:00
el Mau 260ba62b2e Fix in validate CP 2023-01-12 12:02:02 -06:00
el Mau 76d15e482b Fix in validate CP 2023-01-12 12:01:37 -06:00
el Mau 8e2446e8f5 Fix in complement pay without taxes 2023-01-12 00:33:39 -06:00
el Mau 482a0385cd Fix in complement pay without taxes 2023-01-12 00:33:11 -06:00
el Mau 29e02e649d Fix error in nomina 2023-01-11 00:27:42 -06:00
el Mau 5aece843bf Fix error in nomina 2023-01-11 00:24:35 -06:00
el Mau d47e059d93 CFDI 4.0 2023-01-10 00:07:17 -06:00
el Mau 89f79de9b6 Add template for donatarias 2023-01-10 00:06:40 -06:00
el Mau fbaa85f13a Fix #84 2023-01-09 23:11:52 -06:00
el Mau bed6039fff Fix #84 2023-01-09 17:46:40 -06:00
el Mau acbaecbfc5 Fix in preinvoice 2023-01-09 15:18:08 -06:00
el Mau 8dddb7b293 Update changelog 2023-01-08 19:29:36 -06:00
el Mau 24777f691e CFDI 4.0 2023-01-07 07:37:32 -06:00
el Mau 1d270aa478 Add templates 2023-01-06 21:42:46 -06:00
el Mau 8872af8c50 Fix in unit in invoice 2023-01-06 19:04:50 -06:00
el Mau d57ded161c Proporcional taxes in pays complements 2023-01-04 18:29:30 -06:00
el Mau 0c8d6d3cea Fix #83 2023-01-04 13:02:33 -06:00
el Mau f14c1f74d8 PDF for pays complement 2023-01-02 22:59:39 -06:00
el Mau 210f50a7d7 Add months in global invoice 2023-01-02 14:56:34 -06:00
el Mau 5418b23200 Plantillas 2023-01-01 22:58:58 -06:00
el Mau a13c7c0a1d Factura global 2022-12-31 17:30:01 -06:00
el Mau a6276a33fb Update temaple for Leyendas Fiscales 2022-10-27 22:37:05 -05:00
el Mau e3bd1e8871 Fix - Query SAT 2022-10-25 20:22:48 -05:00
el Mau 2ddb3d6b75 Fix - Cancel v4.0 2022-10-25 14:59:37 -05:00
Mauricio Baeza 4cda9f7f21 Update CP Db 2022-08-02 22:13:05 -05:00
Mauricio Baeza 7c184c9513 Update CP db 2022-08-01 21:31:44 -05:00
Mauricio Baeza aea8aeaecf Fix in import employes 2022-06-17 13:30:21 -05:00
Mauricio Baeza e08e4e038f Set name client to upper case 2022-06-09 13:50:33 -05:00
Mauricio Baeza 496dcf7162 Add some complements 2022-06-05 20:24:08 -05:00
Mauricio Baeza f8cd99084a Stamp Nomina with Cfdi 4.0 2022-06-04 21:52:28 -05:00
Mauricio Baeza 1e824bd841 Fix set current regimen in edit 2022-06-02 22:46:31 -05:00
Mauricio Baeza bc0cd9f3e1 Fix get data foro version 3.3 2022-05-31 22:14:17 -05:00
Mauricio Baeza ae259e461b Add template for CFDI 4.0 2022-05-30 13:30:47 -05:00
Mauricio Baeza 1a507d2eeb Add template for CFDI 4.0 2022-05-29 22:57:26 -05:00
Mauricio Baeza f766f92618 Minor changes 2022-05-25 23:18:21 -05:00
Mauricio Baeza ad56356a6f Quitar referencia a paquetes 2022-05-16 14:44:42 -05:00
El Mau c6cf33c4c4 Obtener datos de periodicidad para factura glbal 2022-04-18 21:45:30 -05:00
El Mau dbc3910297 Guardar periodicidad de factura global 2022-04-17 16:33:50 -05:00
El Mau 6c78a282fe Agregar periodicidad en UI para factura del día 2022-04-17 16:01:05 -05:00
El Mau f8c2c5e2d6 Agregar objeto de impuestos a productos 2022-04-16 21:58:42 -05:00
El Mau 7d40e79f3c Quitar validacion de empaques 2022-04-15 22:13:49 -05:00
El Mau a36034a476 Agregar controles para objeto de impuesto en productos 2022-04-14 23:01:02 -05:00
El Mau de2ab5cedd Obtener regimen fiscal del receptor al facturar desde tickets 2022-04-11 22:47:37 -05:00
El Mau b60e2d6a37 Timbrado con público en general 2022-04-07 21:31:42 -05:00
El Mau d9021cca4a Seleccionar regimen para público en general 2022-04-05 22:05:16 -05:00
El Mau 7bbef31936 Regresar regimenes del cliente al seleccionar 2022-04-03 23:00:02 -05:00
El Mau 74e7f12088 Regresar CP del cliente al refacturar 2022-04-02 23:13:37 -06:00
El Mau 20c0757f04 Regresar productos en orden al refacturar 2022-04-02 23:13:37 -06:00
El Mau 3a5fbc609b Regresar productos en orden al refacturar 2022-04-01 09:50:10 -06:00
El Mau 5e888d2337 Regresar productos en orden al refacturar 2022-04-01 09:49:15 -06:00
El Mau d97fd4867a Cambiar URLs para timbrado con Comercio Digital 2022-03-29 18:27:40 -06:00
El Mau e58b8f90af Cambiar demo de Finkok a https 2022-03-29 17:29:21 -06:00
El Mau 50d4a3f4b7 Soporte inicial para CFDI 4.0 2022-03-29 15:40:14 -06:00
El Mau 1978edfaf0 Tablas para borrar 2022-03-28 22:40:41 -06:00
El Mau 7ca87d1811 Timbrado de Complemento de Pagos 2.0 2022-03-28 22:40:41 -06:00
El Mau a7bc6d6f6c Agregar xlst para pagos 2.0 2022-03-28 22:40:41 -06:00
El Mau 1991c68b3b Soporte basico para Comercio Exterior 2022-03-28 22:39:55 -06:00
El Mau bebcd29710 Agregar cadena para Cfdi 4 2022-03-28 22:37:08 -06:00
El Mau de856f28ab Soporte basico para complemento Comercio Exterior 2022-03-28 21:34:10 -06:00
El Mau e726cda085 Soporte basico para complemento Comercio Exterior 2022-03-28 21:33:39 -06:00
El Mau eee386a97b Soporte basico para complemento Comercio Exterior 2022-03-17 21:22:27 -06:00
El Mau ae21403c59 Error al timbrar nomina 2022-03-10 20:26:27 -06:00
El Mau 110d41cf6e Actualizar VERSION 2022-03-10 20:25:51 -06:00
El Mau bd6fb65a1b Fix - Problema al timbra nómina con Comercio Digital 2022-03-10 19:24:29 -06:00
El Mau b68d79d0ca Fix issue #54 2022-02-18 14:59:01 -06:00
El Mau 670aca3886 Fix - Issue #54 2022-02-18 14:57:13 -06:00
El Mau 68c0203039 Fix issue #53 2022-02-15 23:51:27 -06:00
El Mau b2cdd0efbe Fix - Issue #53 2022-02-15 23:50:02 -06:00
El Mau 51af15f311 Error al generar Carta Porte sin remolque 2022-02-01 21:31:49 -06:00
El Mau edf35aa5ce Error al cancelar con Finkok 2022-02-01 21:31:06 -06:00
El Mau b19d087d10 Error al generar Carta Porte sin remolque 2022-01-31 18:48:43 -06:00
El Mau 64ef75fbf5 Mejoras en Carta Porte 2022-01-30 14:50:05 -06:00
El Mau d6a8c1e3fa Actualizar lista de cambios 2022-01-30 14:48:32 -06:00
El Mau a7080bf954 Agregar validación para distancia en origen de Carta Porte 2022-01-30 14:47:34 -06:00
El Mau 3ddf99075e Reporte #49, arreglado 2022-01-29 20:47:30 -06:00
El Mau bd6ba5d4d0 Mejoras en Carta Porte 2022-01-27 22:18:28 -06:00
El Mau 6a18934b19 Actualizar lista de cambios 2022-01-27 22:17:41 -06:00
El Mau 61548c9135 Buscar Estado y Municipio por CP en Carta Porte 2022-01-27 22:14:54 -06:00
El Mau fe3f743e32 Fix - Al generar PDF de carta porte sin remolque 2022-01-27 18:34:55 -06:00
El Mau c83e6981ae Agregar tipos de persimo SCT para Carta Porte 2022-01-27 13:23:00 -06:00
El Mau 39b9af125c Fix - Issue #45 2022-01-27 13:10:16 -06:00
El Mau dd606be46b Arreglado el error al cancelar con CD 2022-01-26 22:32:38 -06:00
El Mau 34df8882b3 Actualizar lista de cambios 2022-01-26 22:28:15 -06:00
El Mau e992103c57 Fix - Issue #44 2022-01-26 18:12:18 -06:00
El Mau 8f74d24b14 Fix - Issue #42 2022-01-26 18:01:27 -06:00
El Mau 480ec57d1e Error al timbrar Carta Porte 2022-01-25 19:33:05 -06:00
El Mau a38247727c Error al timbrar Carta Porte 2022-01-25 19:12:41 -06:00
El Mau 702aa264d8 Agregar certificado original 2022-01-24 17:05:41 -06:00
El Mau 0422eece40 Prueba de cancelación con UUID 2022-01-24 15:45:40 -06:00
El Mau 12774af7ac Quitar nodo KeyInfo del XML de cancelación 2022-01-24 12:47:57 -06:00
El Mau b16eabd773 Quitar nodo KeyInfo del XML de cancelación 2022-01-24 12:43:28 -06:00
El Mau 1bab1cb23c Quitar todos los saltos de línea del XML de cancelación 2022-01-24 12:37:42 -06:00
El Mau 15a05e38f7 Agregar, de nuevo KeyInfo del XML de cancelación 2022-01-24 12:31:10 -06:00
El Mau a0a8e0ce62 Quitar KeyInfo del XML de cancelación 2022-01-24 12:21:21 -06:00
El Mau 825e23e369 Mostrar el request enviado 2022-01-24 12:15:27 -06:00
El Mau 121831a139 Quitar saltos de línea en XML de cancelación 2022-01-24 12:08:17 -06:00
El Mau c09a5749da Quitar declaración de enconding en XML de cancelación 2022-01-24 12:04:26 -06:00
El Mau 01ccbcabd5 Cambio de NameSpace en XML de cancelación 2022-01-24 11:32:01 -06:00
El Mau cc0eee1443 Fix - Issue #40 2022-01-23 23:11:08 -06:00
El Mau 7a88c4b97f Fix - Issue #40 2022-01-23 23:10:28 -06:00
El Mau 0e7db2b711 Verificar cancelación con certificados en CD 2022-01-22 22:16:52 -06:00
El Mau 65851182c3 Verificar cancelación con certificados en CD 2022-01-22 22:02:02 -06:00
El Mau 2de98b9b92 Verificar cancelación con certificados en CD 2022-01-22 21:54:31 -06:00
El Mau 5a24bc159d Verificar cancelación con certificados en CD 2022-01-22 21:35:53 -06:00
El Mau f84721716c Verificar cancelación con certificados en CD 2022-01-22 21:21:55 -06:00
El Mau 8b91532b82 Verificar cancelación con certificados en CD 2022-01-22 21:11:18 -06:00
El Mau 0a98338fad Fix - Issue #40 2022-01-22 20:24:06 -06:00
El Mau 7bcf6d6e3c Agregar columna método de pago al listado de facturas 2022-01-22 20:17:42 -06:00
El Mau dbc8717575 Verificar cancelación con certificados en CD 2022-01-22 20:04:12 -06:00
El Mau 0adcd7f30f Verificar cancelación con certificados en CD 2022-01-22 20:01:53 -06:00
El Mau d237b33020 Verificar cancelación con certificados en CD 2022-01-22 19:47:41 -06:00
El Mau 5b997f7858 Verificar cancelaion con certificados en CD 2022-01-22 19:34:40 -06:00
El Mau a609c0c6f5 Actualizar cancelación con XML firmado con CD 2022-01-21 17:09:29 -06:00
El Mau d70cae64b7 Actualizar cancelación con XML firmado con CD 2022-01-21 17:00:59 -06:00
El Mau 9daf07693a Corregir error al enviar correo 2022-01-20 17:04:35 -06:00
El Mau e40acd28ba Corregir el error al enviar correo 2022-01-20 17:02:11 -06:00
El Mau 88dd9ca04b Corregir el error al enviar correo 2022-01-20 13:22:51 -06:00
El Mau d2f879c224 Importar carta porte desde json 2022-01-20 00:10:22 -06:00
El Mau 4922010caf Importar carta porte desde json 2022-01-20 00:06:05 -06:00
El Mau ff8bc31f50 Agregar opción STARTTLS para envío de correo 2022-01-19 15:03:26 -06:00
El Mau e795e87461 Agregar opción STARTTLS para envío de correo 2022-01-19 14:58:30 -06:00
El Mau d2c361e174 Representación impresa para Carta Porte 2022-01-19 13:30:22 -06:00
El Mau 6b0ca817a3 Agregar plantilla predeterminada para Carta Porte 2022-01-19 13:26:43 -06:00
El Mau 84fd8f57df Actualizar lista de cambios 2022-01-19 13:23:30 -06:00
El Mau f105f2a0fa Regimen fiscal desde emisor para CFDIs de pago 2022-01-19 13:10:30 -06:00
El Mau ea145c630b Primera versión completa de la representación impresa de la Carta Porte 2022-01-19 00:55:37 -06:00
El Mau 88bb6d9411 Agregar ubicaciones a representación impresa de Carta Porte 2022-01-19 00:17:29 -06:00
El Mau 0498217b65 Iniciar representación impresa de Carta Porte 2022-01-18 23:28:52 -06:00
El Mau 581e8bbdc5 Se agrega nodo para remolques 2022-01-17 21:49:35 -06:00
El Mau c79e8492d3 Copiar mercancias del CFDI 2022-01-16 22:20:45 -06:00
El Mau f9df0d34d0 Agregar eventos para agregar mercancias 2022-01-15 21:52:41 -06:00
El Mau f06022ac69 Agregar botón de comando para agregar mercacías en Carta Porte 2022-01-14 21:54:17 -06:00
El Mau 074cdc475f Agregar nodo para domicilio en Carta Porte 2022-01-12 21:42:02 -06:00
El Mau 0f041ed975 Agregar nodo para domicilio en Carta Porte 2022-01-11 21:49:28 -06:00
El Mau 6de30c3417 Actualizar lista de cambios 2022-01-10 14:28:16 -06:00
El Mau 8bbd2ba62b Actualizar lista de cambios 2022-01-10 14:10:17 -06:00
El Mau 00c58d9b41 Nuevo método para subir las plantillas 2022-01-10 13:39:35 -06:00
El Mau a3d291c893 Primera versión mínima de Carta Porte timbrada 2022-01-09 23:52:05 -06:00
El Mau 86bc032413 Nuevos controles para subir las plantillas 2022-01-08 21:24:19 -06:00
El Mau a91feb15d7 Agregar nuevos regimenes fiscales 2022-01-07 22:59:33 -06:00
El Mau 3ffc54f966 Modificar selector emergente para el calendario 2022-01-06 21:15:11 -06:00
El Mau b3663dfcc6 Modificar selector emergente para el calendario 2022-01-05 09:42:40 -06:00
El Mau 3cc11430d7 Cambio de formato del QR para plantilla json 2022-01-04 22:40:41 -06:00
El Mau 4ae5197ced Cambios en cancelación para Comercio 2022-01-04 14:29:00 -06:00
El Mau bf601dfcef Cambios en cancelación para Finkok 2022-01-03 23:06:55 -06:00
El Mau 481594839d Guardar datos para carta porte 2022-01-03 19:14:20 -06:00
El Mau 7b2467f99a Iniciar validaciones para carta porte 2022-01-01 21:48:02 -06:00
El Mau 2f22ad4cc8 Nuevos controles para carta porte terminada 2021-12-30 19:41:40 -06:00
El Mau 46d6329754 Resuelto: ticket #29 2021-12-30 11:56:22 -06:00
El Mau 2a2689a61b Sincronizar productos para carta porte 2021-12-29 22:02:17 -06:00
El Mau 76663fdd67 Agregar evento al cambiar de pestaña 2021-12-28 22:26:05 -06:00
El Mau 54ab86a089 Ocultar columna temporalmente 2021-12-27 21:33:34 -06:00
El Mau 7fe5251153 Agregar controles para Carta Porte 2021-12-25 23:00:23 -06:00
El Mau fe4829c6b8 Agregar controles para Carta Porte 2021-12-25 17:48:08 -06:00
El Mau bb90b3f6d7 Nuevos controles para carta porte 2021-12-21 23:01:44 -06:00
El Mau f4779dbb99 Validar que se seleccionen los dos archivos de los certificados 2021-12-17 21:14:23 -06:00
El Mau 47ec5ad360 Resolver conflictos 2021-12-12 22:30:15 -06:00
El Mau 80957afb84 Actualizar lista de cambios 2021-12-12 21:51:34 -06:00
El Mau b9af99aad6 Cancelar con certificados solo en modo depuración 2021-12-12 14:56:59 -06:00
El Mau 43e932e445 Consultar estatus del SAT en nómina 2021-12-12 14:46:53 -06:00
El Mau 8a151d42ad Corregir error al cancelar nomina 2021-12-12 13:51:51 -06:00
El Mau db77f6972d Corregir error al generar factura del día por ticket 2021-12-10 14:11:54 -06:00
El Mau ef2314880f Cambiar tipo de campo al campo Folio de la tabla Facturas 2021-12-09 22:56:29 -06:00
El Mau 1a1cb1173e Agregar XLST para Carta Porte 2021-12-04 22:05:20 -06:00
El Mau fe26744a8f Mover solo los tickets seleccionados al facturarlos 2021-12-04 21:56:00 -06:00
El Mau 69402e03bb Agregar archivos requeridos para CartaPorte 2.0 2021-12-03 21:41:37 -06:00
El Mau 4a84654117 Validar existencia por almacen 2021-11-29 21:57:32 -06:00
El Mau 81428c4f8e Cantidad a decimal al ajustar inventario 2021-11-28 15:29:57 -06:00
El Mau 28ef107949 Agregar cancelación con certificados con el pac CD 2021-11-26 13:14:43 -06:00
El Mau e372f064b4 Agregar declaración de encoding XML para cancelación 2021-11-25 16:41:30 -06:00
El Mau 46efb3693c Agregar declaración de encoding XML para cancelación 2021-11-25 14:40:37 -06:00
El Mau d2bacc3b6b Agregar declaración de encoding XML para cancelación 2021-11-25 12:51:49 -06:00
El Mau 264090f2a6 Remover nodo KeyValue en XML para cancelación 2021-11-25 10:22:23 -06:00
El Mau 425cd53df1 Remover nodo KeyValue en XML para cancelación 2021-11-24 16:02:48 -06:00
El Mau b7e8bb3cff Cambio en el formato del número de serie del certificado en el XML de cancelación 2021-11-24 15:53:40 -06:00
El Mau 1df477e0b1 Modificaciones menores a la plantilla XML para cancelación 2021-11-24 14:55:41 -06:00
El Mau 9b09fbbcd1 Resuelto - establecer 0 cuando descuento es None 2021-11-22 19:06:57 -06:00
El Mau f793cd2240 Resuelto - Obtener id de almacen origen 2021-11-22 09:16:28 -06:00
El Mau 54f2a574a3 Agregar método para importar existencias 2021-11-21 22:26:34 -06:00
El Mau 219f7c6712 Comentar lineas al firmar XML 2021-11-20 22:08:05 -06:00
El Mau 342f64a18b Resuelto - Quitar saltos de línea en XML firmado para cancelar 2021-11-18 21:45:54 -06:00
El Mau e880256cde Actualizar requerimientos 2021-11-18 21:35:09 -06:00
El Mau 3a1cb8d142 Actualizar vínculos en main 2021-11-17 19:38:09 -06:00
El Mau 42a840f85c Actualizar vínculos en main 2021-11-17 18:28:43 -06:00
Mauricio Baeza 2e5f4ac27e Get details from ticket 2021-10-30 15:08:32 -05:00
Mauricio Baeza 98f091a347 Start event for ticket from ticket 2021-10-29 22:06:53 -05:00
Mauricio Baeza 82ec2faac5 Fix link to blog 2021-10-28 21:23:06 -05:00
Mauricio Baeza 11c0cc4899 Add button for regenerate ticket 2021-10-28 21:20:58 -05:00
Mauricio Baeza 1c9c829296 Add link to repository 2021-10-26 23:17:01 -05:00
Mauricio Baeza 03f26b65e4 Add link to repository 2021-10-24 21:35:27 -05:00
Mauricio Baeza 00f2b2b126 Start new doc 2021-10-23 22:03:43 -05:00
Mauricio Baeza c77438216d Start new doc 2021-10-23 21:46:04 -05:00
Mauricio Baeza b02b2b0e63 Change label in certificates 2021-10-23 21:14:15 -05:00
Mauricio Baeza 75788474ba Add controller for warehouse movement 2021-10-14 19:20:23 -05:00
Mauricio Baeza 866e8c40b9 Add UI for warehouse movement 2021-10-13 21:13:08 -05:00
Mauricio Baeza c64de2bbb9 Update inventory in cancel tickets 2021-10-05 15:11:27 -05:00
Mauricio Baeza cd35884999 Add warehouse in detail tickets 2021-10-04 22:21:49 -05:00
Mauricio Baeza c4e29e47fa Update inventory in cancel invoice 2021-10-03 22:21:01 -05:00
Mauricio Baeza 00cbe4d557 Save warehouse in product details 2021-10-03 22:12:05 -05:00
Mauricio Baeza a95a6842f9 Save warehouse in product 2021-10-02 22:23:51 -05:00
Mauricio Baeza 30a4a276dd Update inventory in tickets 2021-09-26 21:20:08 -05:00
Mauricio Baeza 416bc65174 Can products without taxes 2021-09-14 21:53:00 -05:00
Mauricio Baeza 3dd88f14d6 Update version 2021-09-14 21:51:58 -05:00
Mauricio Baeza fb24346601 Can add or edit products without taxes 2021-09-14 21:49:21 -05:00
Mauricio Baeza f792b79655 Get exists by warehouse 2021-09-05 20:29:08 -05:00
Mauricio Baeza 2055a904f6 Add dialog for show exists in warehouse 2021-09-05 19:48:05 -05:00
Mauricio Baeza 7a1fd8fcaf Add command button for show details exists in warehouses 2021-09-04 21:59:24 -05:00
Mauricio Baeza cd22a5a24d Add new documentation 2021-09-03 22:13:25 -05:00
Mauricio Baeza 25e86cd28c Remove modifi precition in decimal context 2021-09-01 21:45:21 -05:00
Mauricio Baeza 4aad1a94eb Replace save for update in invoice 2021-08-31 21:47:05 -05:00
Mauricio Baeza 229be3b415 Fix pass id product in update inventory 2021-08-30 23:23:53 -05:00
Mauricio Baeza bc53171969 Update inventory 2021-08-30 22:57:31 -05:00
Mauricio Baeza 61a8937eb3 Add product by warehouse 2021-08-23 15:49:29 -05:00
Mauricio Baeza be75abeb54 Get wharehouse in products 2021-08-22 22:33:04 -05:00
Mauricio Baeza 2c314a3891 Add select warehouse in add product 2021-08-11 22:10:23 -05:00
Mauricio Baeza 7fc1d42381 Set branch by user 2021-08-08 21:51:12 -05:00
Mauricio Baeza 0957cf673f Add dialog for set branch user 2021-08-04 23:00:23 -05:00
Mauricio Baeza f99adaf109 Clean branch for users 2021-08-03 22:13:19 -05:00
Mauricio Baeza 91cfbb0508 Add column 'In branch' for users 2021-08-03 20:08:57 -05:00
Mauricio Baeza 4f18e1e01f Show or hide column for super user 2021-08-01 23:03:32 -05:00
Mauricio Baeza 7e7b08b1c0 Add products without taxes 2021-07-26 22:40:59 -05:00
Mauricio Baeza 4006322f30 Fix issue #12 2021-07-26 22:30:37 -05:00
Mauricio Baeza 935483f7ee Fix issue #11 2021-07-26 22:08:20 -05:00
Mauricio Baeza 80b766783d Save warehouse in sucursal 2021-07-25 21:58:23 -05:00
Mauricio Baeza 8822687308 Show warehouse in sucursal 2021-07-25 21:45:59 -05:00
Mauricio Baeza 0512e0f6dd Add and delete warehouse 2021-07-25 21:18:01 -05:00
Mauricio Baeza 93f0d09f4d Fix name error in warehouse 2021-07-24 22:41:06 -05:00
Mauricio Baeza 870d95cb5a Get wharehouse 2021-07-23 22:29:22 -05:00
Mauricio Baeza 9fa35d5592 Add wharehouse 2021-07-23 22:01:59 -05:00
Mauricio Baeza 2bfe86973a Add new table WhareHouseProduct 2021-07-23 18:43:26 -05:00
Mauricio Baeza 896be35e3b Hide or show controls for warehouses 2021-07-22 22:25:07 -05:00
Mauricio Baeza f2af10c2df Add UI grid for warehouse 2021-07-22 21:45:36 -05:00
Mauricio Baeza 8f7aaac563 Add options for multi stock UI 2021-07-22 21:24:04 -05:00
Mauricio Baeza 8a615144a6 Only admins can add inventory 2021-07-21 22:04:13 -05:00
Mauricio Baeza 227204abec Only admins can add inventory 2021-07-21 21:37:31 -05:00
Mauricio Baeza 637c69b9c7 Update changelog 2021-07-10 13:25:04 -05:00
Mauricio Baeza 4f3bdbda95 Add inventory entries manually 2021-07-08 12:56:40 -05:00
Mauricio Baeza 34c467d26e Add validation for new stock 2021-07-05 15:56:00 -05:00
Mauricio Baeza d61672dac4 Add UI windows for add inventory manually 2021-07-05 15:21:03 -05:00
Mauricio Baeza 1aa6540ac1 Change pyqrcode for segno 2021-06-29 19:07:55 -05:00
Mauricio Baeza 1b87591f62 Add inventory entries 2021-06-17 22:33:06 -05:00
Mauricio Baeza 986020a4a3 Add partner products 2021-06-17 21:45:40 -05:00
Mauricio Baeza 3514944f5d Add products in inventory entries 2021-06-16 23:36:25 -05:00
Mauricio Baeza 9be913c47d Add inventory entries post 2021-06-15 23:45:11 -05:00
Mauricio Baeza b9d87f0343 Validate add products 2021-06-14 23:41:39 -05:00
Mauricio Baeza 585632882d Validate add products 2021-06-13 23:55:32 -05:00
Mauricio Baeza 920ff62e98 Add new table PartnerProducts 2021-06-13 23:12:07 -05:00
Mauricio Baeza dc61d3b010 Parse details from CFDI 2021-06-10 21:39:15 -05:00
Mauricio Baeza 62a0a82699 Refactory class for read and write CFDIs 2021-06-08 22:54:59 -05:00
Mauricio Baeza 702ac88b38 Refactory method for import file 2021-06-07 22:45:00 -05:00
Mauricio Baeza 535cc9d527 Add button for import products from CFDI 2021-06-06 22:33:46 -05:00
Mauricio Baeza cf35cd08cd Validate stock in ticket 2021-06-01 19:45:42 -05:00
Mauricio Baeza e9eeab8c2c Validate stock when invoice 2021-06-01 19:34:34 -05:00
Mauricio Baeza a518278b55 Ticket #5 2021-05-31 13:01:03 -05:00
Mauricio Baeza 0442019ea5 Ticket #5 2021-05-31 12:59:45 -05:00
Mauricio Baeza fa94e36e7f Delete sucursales 2021-05-25 21:37:56 -05:00
Mauricio Baeza 348be949ac Add sucursales 2021-05-25 20:36:27 -05:00
Mauricio Baeza 565f36be3a Option cancel only by admins 2021-05-24 12:54:11 -05:00
Mauricio Baeza 7062151e79 Actualizar archvio de cambios 2021-05-24 12:33:56 -05:00
Mauricio Baeza 5f1edc76ca Add RFC test 2021-05-12 19:43:35 -05:00
Mauricio Baeza 5729a1f83e Cancel by admin 2021-04-20 21:41:08 -05:00
Mauricio Baeza 1a9087b13a Fix ticket #4 2021-04-13 22:29:29 -05:00
Mauricio Baeza a2670cb92c Fix ticket #4 2021-04-13 22:29:04 -05:00
Mauricio Baeza a8667a5d4f Fix in validate cert 2021-02-26 14:32:43 -06:00
Mauricio Baeza 952dcba7ae Fix issue #3 2021-02-12 19:37:11 -06:00
Mauricio Baeza 676ff29b57 Fix - issue #3 2021-02-12 18:19:40 -06:00
Mauricio Baeza 12a24c1117 Fix issue #2 2021-02-12 12:23:34 -06:00
Mauricio Baeza e59941db34 Update version and changelog 2021-02-12 12:23:20 -06:00
Mauricio Baeza 0e881eb17a Fix get version in PDF nomina 2021-02-12 12:04:31 -06:00
Mauricio Baeza fa01e6cf01 Fix issue #1 2021-02-11 18:10:37 -06:00
Mauricio Baeza 51e2464141 Update version and changelog 2021-02-11 18:09:58 -06:00
Mauricio Baeza 3e43cc3b46 Fix - issue #1 2021-02-11 18:07:51 -06:00
Mauricio Baeza b5bdcbbeb3 Import clients form CSV 2021-02-10 23:02:12 -06:00
Mauricio Baeza 4f71dd0de3 Clean review methods 2021-02-10 23:01:30 -06:00
Mauricio Baeza 720c9f0cb1 Update changelog and version 2021-02-10 22:54:16 -06:00
Mauricio Baeza 66d740e8d1 Report log 2021-02-10 22:50:05 -06:00
Mauricio Baeza 892906c46f Import clients 2021-02-10 22:47:00 -06:00
Mauricio Baeza aa68225571 Read csv to dict 2021-02-10 22:34:34 -06:00
Mauricio Baeza 4f40bc3bfa Add option for import clients 2021-02-10 22:01:39 -06:00
Mauricio Baeza b03fbebe09 Fix get status sat, issue #422 2021-02-09 23:04:49 -06:00
Mauricio Baeza 3cdbe14287 Update changelog 2021-02-09 23:04:31 -06:00
Mauricio Baeza 0bbf0b438f Add try except 2021-02-09 23:04:02 -06:00
Mauricio Baeza c98cdd7fff Fix get status sat, issue #422 2021-02-09 23:01:28 -06:00
Mauricio Baeza f0b5fa5ae9 Fix - issue #422 2021-02-09 23:01:08 -06:00
Mauricio Baeza 537cecbb0b Fix get status sat, issue #422 2021-02-09 22:49:57 -06:00
Mauricio Baeza 7f44f7f26d Update changelog 2021-02-09 22:46:36 -06:00
Mauricio Baeza c1e1c5ad7c Fix - issue #422 2021-02-09 22:44:26 -06:00
Mauricio Baeza 3c574425eb Move get status sat 2021-02-09 22:34:15 -06:00
Mauricio Baeza 5b8cd0ecb2 Fix show version of Empresa Libre 2021-02-02 22:10:13 -06:00
Mauricio Baeza 05a51741b6 Fix - show version of EmpresaLibre 2021-02-02 22:09:12 -06:00
Mauricio Baeza b3e2671359 Update template 2021-02-02 21:49:10 -06:00
Mauricio Baeza 5c047ee9d5 Update changelog 2021-01-19 12:16:41 -06:00
Mauricio Baeza b680d728a7 Update changelog 2021-01-18 19:28:15 -06:00
Mauricio Baeza 431422361b Fix passed argument to pac 2021-01-18 19:06:59 -06:00
Mauricio Baeza 85e58c2728 Fix passed argument to pac 2021-01-18 18:41:03 -06:00
Mauricio Baeza 739ac08205 Add migrate auth 2021-01-13 19:15:38 -06:00
Mauricio Baeza 64adba1588 Add migrate auth 2021-01-13 19:14:54 -06:00
Mauricio Baeza de06f706a6 Fix get version in proforma 2021-01-11 14:43:50 -06:00
Mauricio Baeza 6785dfebf7 Fix get version in proforma 2021-01-11 14:43:28 -06:00
Mauricio Baeza aaccece897 Fix get folios 2021-01-10 19:18:48 -06:00
Mauricio Baeza 94f5df0723 Fix get folios 2021-01-10 19:18:13 -06:00
Mauricio Baeza f813fb4dd8 Fix set error in pac Finkok 2021-01-10 15:24:35 -06:00
Mauricio Baeza 49d5838822 Fix set error in oac Finkok 2021-01-10 15:24:11 -06:00
Mauricio Baeza 51b4386b24 Fix add backend in cert 2021-01-10 14:23:58 -06:00
Mauricio Baeza 38f3c125cd Fix add backend in cert 2021-01-10 14:23:39 -06:00
Mauricio Baeza e678083eea Fix add backend in cert 2021-01-10 14:21:51 -06:00
Mauricio Baeza b612991476 Fix add backend in cert 2021-01-10 14:21:32 -06:00
Mauricio Baeza 32679fced3 Fix in function name export cert 2021-01-10 14:17:32 -06:00
Mauricio Baeza 8e4bf56846 Fix in function name export cert 2021-01-10 14:17:01 -06:00
Mauricio Baeza 0084197906 Fix in get year for preinvoices 2021-01-08 20:53:29 -06:00
Mauricio Baeza ace4ef21a7 Fix get years in preinvoices 2021-01-08 19:58:14 -06:00
Mauricio Baeza 17fa99e45b Add migrate certificates 2021-01-06 22:15:30 -06:00
Mauricio Baeza e547ce0113 Change name from migrate cert 2021-01-06 22:14:59 -06:00
Mauricio Baeza 858fd829aa Update changelog 2021-01-06 22:13:02 -06:00
Mauricio Baeza 788a1723b4 Remove class for import from FacturaLibre 2021-01-06 21:44:02 -06:00
Mauricio Baeza 4b9df44dac Add migrate certificates 2021-01-06 21:30:31 -06:00
Mauricio Baeza cca598310b Fix in import conf for pacs 2021-01-05 18:53:13 -06:00
Mauricio Baeza 39071be43b Fix import conf in pacs 2021-01-05 18:43:34 -06:00
Mauricio Baeza e633f8e2bf Fix import conf in pacs 2021-01-05 18:16:18 -06:00
Mauricio Baeza 21fd903943 Refactory stamp and cancel 2021-01-05 17:32:38 -06:00
Mauricio Baeza fec1c8523a Update changelog 2021-01-05 17:32:03 -06:00
Mauricio Baeza 0a04ec6c26 Cancel old with Finkok 2021-01-03 19:44:52 -06:00
Mauricio Baeza a7945dba58 Cancel by rfc of pac 2021-01-02 22:23:33 -06:00
Mauricio Baeza 348a7f6ecb Add pac Finkok refactory 2021-01-02 18:16:15 -06:00
Mauricio Baeza cf189b08fa Add try in cert 2020-12-31 21:55:01 -06:00
Mauricio Baeza 9423aeef6c Cancel cfdi of nomina with xml signed 2020-12-31 21:11:22 -06:00
Mauricio Baeza 90eec635ce Cancel cfdi of pay with xml signed 2020-12-31 21:00:00 -06:00
Mauricio Baeza 8f15961d20 Fix - replace getchildren form list 2020-12-31 20:17:00 -06:00
Mauricio Baeza 8a8f05384b Stamp nomina 2020-12-31 20:10:32 -06:00
Mauricio Baeza a54ad8d760 Cancel sign xml 2020-12-31 15:08:49 -06:00
Mauricio Baeza 0389c0734f Clean methods to import from old FacturaLibre 2020-12-31 12:17:26 -06:00
Mauricio Baeza cb04910a84 Sign in memory 2020-12-31 12:01:41 -06:00
Mauricio Baeza 38c9c676af Remove old class cert 2020-12-31 00:06:50 -06:00
Mauricio Baeza e0d1f40a11 Remove old validate cert 2020-12-31 00:04:19 -06:00
Mauricio Baeza d8ecae2c8f Refactory upload certificates 2020-12-31 00:01:41 -06:00
Mauricio Baeza 85c5a37798 Refactory class certificates 2020-12-30 22:45:57 -06:00
Mauricio Baeza 56e52782f4 Refactory generate files 2020-12-29 22:05:02 -06:00
Mauricio Baeza aae856bb74 New class for Pac Comercio Digital 2020-12-29 21:53:51 -06:00
Mauricio Baeza 95399798f8 Stamp by Pac 2020-12-28 22:28:52 -06:00
Mauricio Baeza 75e4f2e1c0 Fix version in XSLT 2020-12-28 22:22:36 -06:00
Mauricio Baeza f02b6782be Move configuration for PACs 2020-12-28 22:14:30 -06:00
Mauricio Baeza bd7508fe6b Issue #415 2020-12-16 16:04:32 -06:00
Mauricio Baeza 1f3e51861d Update version 2020-09-17 13:53:01 -05:00
Mauricio Baeza 3a51a50c8e Update version 2020-09-17 13:52:31 -05:00
Mauricio Baeza 1c6f9cd5f7 Fix - Namespaces for IEDU 2020-09-17 13:46:41 -05:00
Mauricio Baeza b6c5c72df8 Fix - Namespaces for IEDU 2020-09-17 13:46:23 -05:00
Mauricio Baeza 8d815bf570 Update date in change log 2020-08-25 23:03:12 -05:00
Mauricio Baeza afced92aff Update date in change log 2020-08-25 23:02:54 -05:00
Mauricio Baeza 443b916056 Fix - Validate namespace 2020-08-25 15:17:59 -05:00
Mauricio Baeza 1d18d97b90 Update change log 2020-08-25 15:17:10 -05:00
Mauricio Baeza 43808efc91 Fix namespace in nomina 2020-08-25 14:15:07 -05:00
Mauricio Baeza 3ef195ed48 Mostrar totales 2020-04-16 22:35:41 -05:00
Mauricio Baeza 980a7aafdb Fix: nomina stamp asimilados 2020-03-30 19:04:50 -06:00
Mauricio Baeza 1e95b180e8 Fix: nomina stamp asimilados 2020-03-30 19:04:33 -06:00
Mauricio Baeza 267a96ebbc Global invoice by ticket 2020-03-08 15:27:53 -06:00
Mauricio Baeza 5e8d4d5fd9 Fix make PDF 2020-03-08 15:26:53 -06:00
Mauricio Baeza 8a293110ab Fix make PDF 2020-03-08 15:26:44 -06:00
Mauricio Baeza 4fcd7e18c0 Global invoice by ticket 2020-03-04 22:36:20 -06:00
Mauricio Baeza ed23a3531c Uncancel ticket when cancel invoice 2020-03-03 22:47:40 -06:00
Mauricio Baeza 82af00fedf Global invoice, get max payment type 2020-03-03 22:24:00 -06:00
Mauricio Baeza 763f3c21fb Support for tax legends 2020-03-02 17:27:28 -06:00
Mauricio Baeza 6b4eb36795 Tax legends in PDF 2020-03-02 17:27:02 -06:00
Mauricio Baeza 2877a68b92 Stamp fiscal leyends 2020-03-01 23:18:26 -06:00
Mauricio Baeza cca836bb6a Add UI for fiscal leyends 2020-02-29 23:51:14 -06:00
Mauricio Baeza 26e4ce3d13 Options for show documents 2020-02-25 21:52:17 -06:00
Mauricio Baeza 3166723cdc Options for show documents 2020-02-25 21:51:48 -06:00
Mauricio Baeza a202f12a17 Add filter by sucursal 2020-02-24 22:35:24 -06:00
Mauricio Baeza bf3ed47fac Update changelog 2020-02-24 22:34:54 -06:00
Mauricio Baeza b2f3cdd66e Filter by sucursal 2020-02-23 22:02:46 -06:00
Mauricio Baeza a17345c0fd Fix stamp nomina 2020-02-09 22:34:58 -06:00
Mauricio Baeza 7d7ca4eeba Fix stamp nomina 2020-02-09 22:34:36 -06:00
Mauricio Baeza d509bfb8be Fix new validate nomina SAT 2020-01-29 16:03:20 -06:00
Mauricio Baeza 0a006f4508 Fix new validate nomina SAT 2020-01-29 16:03:02 -06:00
Mauricio Baeza 71bd536cb1 Update catalog SAT 2020-01-27 22:40:57 -06:00
Mauricio Baeza 068c57c018 Add update SAT 2020-01-27 22:39:39 -06:00
Mauricio Baeza e94cda3577 Fix import conf.py 2020-01-24 10:02:24 -06:00
Mauricio Baeza f102cb7c01 Fix import conf.py 2020-01-24 10:01:55 -06:00
Mauricio Baeza 92e6a317ad Get folios by Pac 2020-01-22 22:30:11 -06:00
Mauricio Baeza 77b35f8322 Add new PAC 2020-01-22 15:25:40 -06:00
Mauricio Baeza 3f0daec6ad Change argument user for stamp 2020-01-22 13:21:53 -06:00
Mauricio Baeza a641024a86 Select Pac in admin 2020-01-22 12:48:15 -06:00
Mauricio Baeza f9a73dfd66 Add new PAC 2020-01-21 23:25:25 -06:00
Mauricio Baeza 2cc6e7046a Add new PAC 2020-01-21 23:25:13 -06:00
Mauricio Baeza fe09860922 Update catalogos SAT 2020-01-12 22:40:45 -06:00
Mauricio Baeza aeb045e76a Check unique clients 2020-01-11 22:14:38 -06:00
Mauricio Baeza 1c55fbd7c7 Fix #355 2020-01-07 21:48:57 -06:00
Mauricio Baeza e1b7e525df Fix #355 2020-01-07 00:34:11 -06:00
Mauricio Baeza 6f3d9cc3fa Fix - al crear PDF 2019-10-28 20:04:28 -06:00
Mauricio Baeza cff2294601 Fix - al generar PDF 2019-10-28 19:35:03 -06:00
Mauricio Baeza ce0e960534 Merge branch 'develop'
Fix #369
2019-08-29 09:13:43 -05:00
Mauricio Baeza af6b13fa67 Update version 2019-08-28 14:30:57 -05:00
Mauricio Baeza df901090a2 Fix #369 2019-08-28 14:28:48 -05:00
Mauricio Baeza a87c78fc6b Start changes for uncancel 2019-07-11 13:34:59 -05:00
Mauricio Baeza abbb4cc608 Merge branch 'develop'
Envío de nómina por correo
Actualización de catalogos del SAT
2019-04-23 23:12:25 -05:00
Mauricio Baeza a67d174782 Update Changelog 2019-04-23 23:11:49 -05:00
Mauricio Baeza 63ae4a5127 Modify Middleware 2019-04-17 22:38:51 -05:00
Mauricio Baeza 4574ff744f Envío de nómina por correo 2019-04-02 09:25:40 -06:00
Mauricio Baeza 163e0f50c9 Envío de nómina por correo 2019-04-01 23:09:32 -06:00
Mauricio Baeza 692477f41a Merge branch 'develop'
Actualización de catálogos del SAT en Nómina
2019-03-24 21:49:26 -06:00
Mauricio Baeza ae351b6148 Actualización de catálogos del SAT de Nómina 2019-03-24 20:40:47 -06:00
Mauricio Baeza 519a61e132 Merge branch 'develop'
Permitir editar la descripción en un movimiento bancario
Fix #343
2019-03-08 22:10:19 -06:00
Mauricio Baeza d7260a8a95 Permitir editar la descripción de un movimiento bancario 2019-03-08 22:06:16 -06:00
Mauricio Baeza 485dc4154e Fix #343 2019-03-08 19:40:53 -06:00
Mauricio Baeza 4bff2703a4 Merge branch 'develop'
Fix - Send pay invoice
2019-03-04 19:44:59 -06:00
Mauricio Baeza 2ee2b8be43 Fix - Send pay invoice 2019-03-04 19:44:42 -06:00
Mauricio Baeza 6cef8fe669 Merge branch 'develop'
Fix - In filter invoice by dates
2019-03-04 19:22:56 -06:00
Mauricio Baeza aad48639ee Fix - In filter invoice by dates 2019-03-04 19:22:28 -06:00
Mauricio Baeza 27a29d331c Delete files in render PDF 2019-03-03 23:01:31 -06:00
Mauricio Baeza 7a709f056c Merge branch 'categulario/empresa-libre-feature/virtualenv' into develop
Agregar env a gitignore
2019-02-25 13:18:40 -06:00
Mauricio Baeza 49f1c24df5 Mezclar gitignore 2019-02-25 13:14:52 -06:00
Mauricio Baeza 4a38410e8f Validación para que ISR sea de solo retención 2019-02-22 12:24:52 -06:00
Mauricio Baeza 2cacb786b6 Merge branch 'develop'
Error al enviar prefactura por correo
2019-02-20 23:40:07 -06:00
Mauricio Baeza 7f33252fa1 Error al enviar prefactura por correo 2019-02-20 23:39:47 -06:00
Mauricio Baeza 79cea5b092 Merge branch 'develop'
Soporte para complemento de divisas
2019-02-17 22:12:39 -06:00
Mauricio Baeza 9ea396b133 Cambio al generar un zip 2019-02-17 22:12:01 -06:00
Mauricio Baeza 4c2ea774b4 Descargar nómina en lote 2019-02-17 13:29:10 -06:00
Mauricio Baeza da41bb272e Filtro de facturas incluye cliente 2019-02-16 22:38:58 -06:00
Mauricio Baeza 8d7b42e368 Actulizar lista de cambios 2019-02-16 22:11:20 -06:00
Mauricio Baeza e292ba88b8 Mejora #329 2019-02-16 22:07:19 -06:00
Mauricio Baeza 7e73541c96 Buscar facturas por notas 2019-02-15 17:51:13 -06:00
Mauricio Baeza a0e1f83c25 Complemento divisas en PDF 2019-02-15 14:38:41 -06:00
Mauricio Baeza 8a4021ce49 Complemento divisas 2019-02-14 22:50:17 -06:00
Mauricio Baeza a4997be78d Actualizar vinculos 2019-02-14 20:17:03 -06:00
Mauricio Baeza 95d3345418 Notificación de acceso por correo 2019-02-05 22:12:19 -06:00
Mauricio Baeza f761ab7a5a Mostrar cantidad de empaques en PDF 2019-02-04 22:13:11 -06:00
Mauricio Baeza 5cdbb0aaa7 Agregar funciones para encriptar 2019-02-03 23:32:48 -06:00
Mauricio Baeza c9f9ea526f Cantidad de empaques al facturar 2019-02-03 22:43:27 -06:00
Mauricio Baeza 06170cb054 Cantidad por empaque en productos 2019-02-03 22:20:54 -06:00
Mauricio Baeza 43684b7fe9 Merge branch 'develop'
Fix - Al cancelar nómina
2019-01-23 13:33:32 -06:00
Mauricio Baeza d48122608b Fix - Al cancelar nómina 2019-01-23 13:33:11 -06:00
Mauricio Baeza 3d300146f1 Merge branch 'develop'
Permitir capturar folio manualmente
2019-01-17 22:10:17 -06:00
Mauricio Baeza 5ed281fd9d Permitir capturar folio manualmente 2019-01-17 22:00:07 -06:00
Mauricio Baeza dbf3a342bf Merge branch 'develop'
Guardar logos de emisor en carpeta correcta
2019-01-16 23:38:05 -06:00
Mauricio Baeza 43e643cccd Guardar logos del emisor en carpeta correcta 2019-01-16 23:37:51 -06:00
Mauricio Baeza 454de7bf1e Guardar logos del emisor en carpeta correcta 2019-01-15 23:16:34 -06:00
Mauricio Baeza 997dfe90ba Merge branch 'develop'
Generar PDF desde plantilla JSON
2019-01-15 00:51:05 -06:00
Mauricio Baeza 812e510660 Actualizar CHANGELOG.md 2019-01-15 00:47:15 -06:00
Mauricio Baeza 538a9f39ce Leyenda cancelada en PDF a partir de JSON 2019-01-15 00:45:08 -06:00
Mauricio Baeza cf0c4b8622 PDF a partir de JSON 2019-01-14 02:07:42 -06:00
Mauricio Baeza 7864e236db PDF a partir de JSON 2019-01-10 23:37:37 -06:00
Mauricio Baeza 00bdd2b1a2 Subir plantillas HTML y CSS 2018-11-20 23:47:53 -06:00
Mauricio Baeza 0d52e9b570 Factura HTML v1 2018-11-20 00:03:07 -06:00
Mauricio Baeza 7dc65a2a2b Soporte para factura en HTML 2018-11-19 01:03:48 -06:00
Mauricio Baeza ff0ee73a73 Merge branch 'develop'
Error al importar nómina y factura en lote
2018-11-07 19:15:54 -06:00
Mauricio Baeza 2d045af037 Error al importar nómina y factura en lote 2018-11-07 19:14:54 -06:00
Mauricio Baeza 4660e1d4e0 Merge branch 'develop'
Error: Consulta de  estatus en el SAT
2018-11-06 22:57:06 -06:00
Mauricio Baeza 68ed8ee0db Error: Consulta de estatus en el SAT 2018-11-06 22:56:43 -06:00
Mauricio Baeza 47ed1cb72e Separar documentación 2018-10-31 17:15:53 -06:00
Mauricio Baeza dc8aeaebba Merge branch 'develop'
Mejora: Permitir importar CFDI 3.2
2018-10-30 16:37:15 -06:00
Mauricio Baeza d2dc80a0c9 Actualizar version 1.23.0 2018-10-30 16:25:17 -06:00
Mauricio Baeza e83281bdee Mejora: Se importan CFDI 3.2 2018-10-30 16:21:00 -06:00
Mauricio Baeza 0bddc189b4 Merge branch 'develop'
Mejora al generar factura en lote
2018-10-27 01:15:42 -05:00
Mauricio Baeza c2c8a10dec Mejora al generar factura en lote 2018-10-27 01:15:20 -05:00
Mauricio Baeza 1dddbac0f2 Permitir cambiar descrición al facturar en lote 2018-10-25 14:59:22 -05:00
Mauricio Baeza bbaf709e0e Merge branch 'develop'
Error unicode
2018-10-24 00:02:08 -05:00
Mauricio Baeza c42bc817b7 Actualizar version 2018-10-23 23:57:36 -05:00
Mauricio Baeza ba91d28fbd Error unicode 2018-10-23 23:43:23 -05:00
147 changed files with 26986 additions and 6457 deletions

13
.gitignore vendored
View File

@ -2,6 +2,7 @@
__pycache__/
*.py[cod]
*$py.class
Pipfile*
# Django stuff:
*.log
@ -14,9 +15,17 @@ source/media
# Sphinx documentation
*.xlsx
docs/bk/
bk/
source/docs/
site/
vedev/
# Virtualenv
.env/
virtual/
env
docs/build
cache/
credenciales.conf
*.sqlite
@ -24,4 +33,6 @@ credenciales.conf
*.service
*.orig
rfc.db
Dockerfile
chuletas/

View File

@ -1,3 +1,570 @@
v 2.3.2 [10-Abr-2024]
- Fix: En las mercancias en la Carta Porte al generar el PDF.
v 2.3.1 [02-Abr-2024]
- Fix: En la cantidad de la mercancia en la Carta Porte.
v 2.3.0 [01-Abr-2024]
- Mejora: Soporte para complemento Carta Porte 3.0
- **IMPORTANTE**: Aunque no lo uses, esto afecta al JS de facturación, por
lo que tienes que forzar el refresco (CTRL+F5) si tienes algún problema.
v 2.2.0 [24-Ene-2024]
- Mejora: Soporte para complemento Comercio Exterior 2.0
- **IMPORTANTE**: Aunque no lo uses, esto afecta al JS de facturación, por
lo que tienes que forzar el refresco (CTRL+F5) si tienes algún problema.
v 2.1.0 [26-Dic-2023]
- Mejora: Se agrega filtro por día en facturas.
v 2.0.9 [20-Dic-2023]
- Fix: Issue 98 y 107
v 2.0.8 [30-Oct-2023]
---------------------
- Fix: Permitir generar CFDI de egreso para facturas globales sin datos globales.
- Fix: Permitir cambiar la zona horaria para cuando se usa en servidor.
Es necesario hacer una migración:
```
cd /opt/empresa-libre
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 2.0.7 [06-Ju1-2023]
---------------------
- Fix: En tasa de retención de un Resico a una Persona Moral.
v 2.0.6 [14-Jun-2023]
---------------------
- Fix: Al generar complementos de pago con facturas con retención de impuestos.
v 2.0.5 [05-Jun-2023]
---------------------
- Fix: Al generar complementos de pago con facturas con varios impuestos.
- Fix: ticket #103, hay que usar {receptor.correo_facturas} y {receptor.telefonos}
v 2.0.2 [01-Abr-2023]
---------------------
- Fix: Obtener la clave del sat al facturar por lote.
v 2.0.1 [29-Mar-2023]
---------------------
- Fix ticket #97
Es necesario hacer una migración:
```
cd /opt/empresa-libre
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 2.0.0 [08-Ene-2023]
----------------------
- Liberamos para todos la versión CFDI 4.0
- **IMPORTANTE** NO intentes timbrar si **antes** no has validado en nuestro demo que puedes timbrar tus CFDIs habituales.
* IMPORTANTE:
Es necesario hacer una migración:
```
cd /opt/empresa-libre
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
Y reiniciar todo. IMPORTANTE: dependiendo desde que versión actualices, tal vez debas de hacer algún prodecimiento extra. Siempre revisa este historial.
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.
v 1.46.5 [10-Mar-2022]
----------------------
- Error: Al timbrar nómina con Comercio Digital
v 1.46.4 [18-Feb-2022]
----------------------
- Error: Issue #54
v 1.46.3 [15-Feb-2022]
----------------------
- Error: Issue #53
v 1.46.2 [31-Ene-2022]
----------------------
- Error: Al generar Carta Porte sin remolque.
- Error: Al cancelar con Finkok.
v 1.46.1 [29-Ene-2022]
----------------------
- Error: Issue #49
- Mejora: Agregar validación para distancia en origen de Carta Porte.
v 1.46.0 [27-Ene-2022]
----------------------
- Mejora: Issue #45
- Mejora: Agregar tipos de persimos SCT para Carta Porte
- Mejora: Buscar estado y municipio por CP en Carta Porte
v 1.45.4 [25-Ene-2022]
----------------------
- Error: Al timbrar carta porte.
- Error: Al cancelar con Comercio Digital.
- Error: Issue #42
- Mejora: Issue #44
* IMPORTANTE: Es necesario subir de nuevo tus certificados de sello, **solo** si timbras con Comercio Digital.
v 1.45.3 [23-Ene-2022]
----------------------
- Error: El enviar por correo CFDI de pago. Ticket #40
v 1.45.2 [21-Ene-2022]
----------------------
- Error: Al cancelar un CFDI
v 1.45.1 [20-Ene-2022]
----------------------
- Error: Al enviar correos con la nueva configuración
* IMPORTANTE: Revisa tu configuración de correo para verificar que todo funcione.
v 1.45.0 [20-Ene-2022]
----------------------
- Importar Carta Porte desde archivo JSON
v 1.44.2 [19-Ene-2022]
----------------------
- Agregar opción STARTTLS que requieren algunos servidores de correo
* IMPORTANTE: Revisa tu configuración de correo para verificar si tienes que usar esta opción.
v 1.44.1 [19-Ene-2022]
----------------------
- Correciones en generación de Carta Porte v2.0
- Plantilla para representación impresa de Carta Porte v2.0
v 1.44.0 [10-Ene-2022]
----------------------
- Soporte para Carta Porte v2 con CFDI 3.3
- Soporte para el nuevo esquema de cancelación del SAT
* IMPORTANTE:
Es necesario hacer una migración:
```
cd /opt/empresa-libre
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 1.43.0 [12-Dic-2021]
----------------------
- Soporte para entradas de almacen.
- Soporte para multi almacen.
- Soporte para regenerar un ticket.
- Soporte para movimientos entre almacenes
- Consulta en SAT para estatus de nómina
* IMPORTANTE:
Instalar nuevo requerimiento:
```
pip install segno
```
Es necesario hacer una migración:
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 1.42.2 [14-Sep-2021]
----------------------
- Los productos pueden no llevar ningún impuesto.
v 1.42.1 [01-Jun-2021]
----------------------
- Error - Ticket #5
v 1.42.0 [24-May-2021]
----------------------
- Opción para que solo un admin pueda cancelar.
v 1.41.4 [13-Abr-2021]
----------------------
- Error - Ticket #4
v 1.41.3 [12-Feb-2021]
----------------------
- Error - Ticket #3
v 1.41.2 [12-Feb-2021]
----------------------
- Error - Ticket #2
v 1.41.1 [11-Feb-2021]
----------------------
- Error - Ticket #1
v 1.41.0 [10-Feb-2021]
----------------------
- Importar clientes desde archivo CSV
v 1.40.1 [09-Feb-2021]
----------------------
- Fix #422
v 1.40.0 [05-ene-2021]
----------------------
- Error: Al parsear XML en Python 3.9+
- Mejora: Agregar versión de Empresa Libre a plantilla.
- Mejora: Sellado en memoria
- Mejora: Se agrega un segundo PAC y se refactoriza el timbrado.
* **IMPORTANTE**
Es necesario seguir una serie de pasos **obligatorios** para migrar a esta
versión, **no continues hasta seguir paso a paso** estas instrucciones.
**Antes** de comenzar ten a la mano tus certificados de sello para timbrar, es
necesario subirlos de nuevo. **NO actualices si no tienes tus certificados**
con su respectiva contraseña, te quedarás sin poder timbrar.
Estas instrucciones solamente aplican para la maquina virtual con Ubuntu Server,
la ultima y única versión sobre la que se dará soporte.
1. Entra a la parte administrativa y toma de tus credenciales de timbrado en el
menú "Emisor" ficha "Otros Datos", usuario y token de timbrado.
1. Agregar nuevos requerimientos `sudo apt install pkgconf libxml2-dev libxmlsec1-dev libxmlsec1-openssl`
1. Agregar nuevo requerimiento `pip install --user xmlsec`
1. Entrar a la carpeta del sistema: `/opt/empresa-libre`
1. Actualizar `git pull origin master`
1. Entrar a `source/app/controllers/pacs/comerciodigital` y copiar `conf.py.example` a `conf.py`
1. Entrar a `source/app/controllers/pacs/finkok` y copiar `conf.py.example` a `conf.py`
1. Agregar la variable `TOKEN = ''` al archivo `source/app/conf.py` mira el archivo de ejemplo en el mismo directorio.
1. Reiniciar el servicio: `sudo systemctl restart empresalibre`
1. Si no ves los cambios descritos a continuación, fuerza el refresco de tu navegador, generalmente con **CTRL+F5**
1. Sube de nuevo tus certificados en el menú "Emisor" ficha "Certificado".
1. Ve al menú "Opciones", ficha "Otros".
1. Selecciona tu PAC, si tu usuario es un correo electrónico, invariablemente
debes seleccionar Finkok. Si tienes duda ponte en contacto con nosotros.
1. Establece las credenciales del punto 1.
1. Guarda los datos.
v 1.39.1 [17-sep-2020]
----------------------
- Error: Esquema para complemento IEDU
v 1.39.0 [25-ago-2020]
----------------------
- Mejora: Consulta de las facturas de pago en el SAT
- Mejora: Mostrar totales en nómina
- Mejora: Mostrar totales por cantidad al facturar
- Mejora: Validar líneas con importe cero antes de facturar
- Error: Validaciones de namespace en CFDI de nómina por parte del PAC
v 1.38.1 [30-mar-2020]
----------------------
- Error: En nómina al timbrar asimilados
v 1.38.0 [08-mar-2020]
----------------------
- Mejora: Factura global por ticket o nota
- Error: Al generar algunos PDFs
v 1.37.0 [02-mar-2020]
----------------------
- Mejora: Soporte para complemento Leyendas Fiscales
* IMPORTANTE:
Es necesario hacer una migración, y agregar los campos necesarios para mostrar
las leyendas, mira la carpeta pública con la plantilla de ejemplo.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m -r RFC
```
v 1.36.0 [25-feb-2020]
----------------------
- Mejora: Permitir ver a usuarios todos los documentos
v 1.35.0 [24-feb-2020]
----------------------
- Mejora: Filtrado de documentos por sucursal
v 1.34.1 [09-feb-2020]
----------------------
- Error: Al timbrar nómina de asimilados
v 1.34.0 [29-ene-2020]
----------------------
- Error: Al timbrar nómina
v 1.33.2 [27-ene-2020]
----------------------
- Se actualizan métodos de pago en catálogos del SAT
* IMPORTANTE:
Es necesario actualizar los catálogos del SAT
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -us
```
v 1.33.0 [22-ene-2020]
----------------------
- Mejora: Cambio del mensaje para cuando se intenta dar de alta un cliente ya existente.
- Mejora: Solo los admins pueden ver la nómina.
- Se agrega un segundo PAC
- Se actualizan los catálogos del SAT
* IMPORTANTE:
Es necesario actualizar los catálogos del SAT
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -us
```
v 1.32.0 [05-ene-2020]
----------------------
- Mejora: Recuperar facturas no aceptadas para cancelación por el receptor
v 1.31.2 [28-oct-2019]
----------------------
- Error: Al generar PDF con tags en las series
v 1.31.1 [28-ago-2019]
----------------------
- Error: Al agregar nuevo impuesto #369
v 1.31.0 [23-abr-2019]
----------------------
- Error: Validar cantidad o valor unitario cero en tickets
- Mejora: Envío de nómina por correo al empleado
v 1.30.0 [24-mar-2019]
----------------------
- Mejora: Se actualizan los catálogos de Nómina
* IMPORTANTE:
Es necesario realizar una migración, después de actualizar.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
v 1.29.0 [08-mar-2019]
----------------------
- Error: Al mostrar detalle en facturas importadas #343
- Mejora: Editar la descripción de un movimiento bancario.
v 1.28.2 [04-mar-2019]
----------------------
- Error: Al enviar facturas de pago
v 1.28.1 [04-mar-2019]
----------------------
- Error: Al buscar facturas por fechas
v 1.28.0 [17-feb-2019]
----------------------
- Mejora: Manejo de empaques para mensajeria
- Mejora: Usar concepto personalizado en deducciones de nómina 004 Otros
- Mejora: Búsqueda en notas
- Mejora: Soporte para el complemento de Divisas
- Mejora: Descarga de nómina en lote
* IMPORTANTE:
Es necesario realizar una migración, después de actualizar.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
Es necesario agregar un nuevo requerimiento.
```
sudo pip install cryptography
```
**IMPORTANTE** Si envias tus facturas por correo directamente, es necesario
volver a capturar la contraseña de tu servidor de correo y guardar los datos
nuevamente.
v 1.27.1 [23-ene-2019]
----------------------
- Error: Al cancelar nómina
v 1.27.0 [17-ene-2019]
----------------------
- Mejora: Permitir capturar folio manualmente
v 1.26.1 [16-ene-2019]
----------------------
- Error: Guardar logos de emisor
v 1.26.0 [15-ene-2019]
----------------------
- Mejora: Generar PDF desde plantilla JSON
v 1.25.0 [07-nov-2018]
----------------------
- Fix: Al importar días de pago en nómina
- Fix: Al importar descripción en factura en lote
v 1.24.0 [06-nov-2018]
----------------------
- Fix: Consulta estatus SAT
v 1.23.0 [30-oct-2018]
----------------------
- Mejora: Permitir importar CFDI 3.2
v 1.22.0 [25-oct-2018]
----------------------
- Mejora: Permitir cambiar descripción al facturar en lote
v 1.21.2 [23-oct-2018]
----------------------
- Error unicode
v 1.21.1 [14-oct-2018]
----------------------
- Error #307

View File

@ -13,9 +13,3 @@
* Propon nuevas funcionalidades
* Difunde Empresa Libre
### Prioridades
1. [X] Darle continuidad a la facturación de los clientes
2. Cubrir todas las funcionalidades que cubre ahora Factura Libre
3. Agregar nuevas funcionalidades

View File

@ -10,26 +10,51 @@ Este proyecto está en continuo desarrollo, contratar un esquema de soporte,
nos ayuda a continuar su desarrollo. Ponte en contacto con nosotros para
contratar: administracion ARROBA empresalibre.net
#### Ahora también puede aportar con Bitcoin Cash (BCH):
#### Ahora también puede aportar con criptomonedas:
`pq763fj7kxxf2wtf360lfsy5ydw84yz72q76hanhxq`
G1: `A5DdXxCKPw3QKWVdDVs7CzkNugNUW1sHu5zDJFWxCU2h`
BCH: `qztd3l00xle5tffdqvh2snvadkuau2ml0uqm4n875d`
### Requerimientos:
## Requerimientos:
* Servidor web, recomendado Nginx
* uwsgi
* python3
* python 3.8
* xsltproc
* openssl
* xmlsec
* Ubuntu 20.04
```
apt install pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl xsltproc
```
Debería de funcionar con cualquier combinación servidor-wsgi que soporte
aplicaciones Python.
El sistema tiene soporte para tres bases de datos: SQLite, MySQL y PostgreSQL
(recomendado), debes de instalar el servidor de la base de datos y sus drivers
respectivos, excepto SQLite que es nativo en Python.
El sistema tiene soporte solo para PostgreSQL, debes de instalar el servidor de
la base de datos y su driver respectivo.
## Configuración para desarrollo local
* Crea un entorno virtual con python 3.8 y actívalo. Por ejemplo con virtualenv:
virtualenv .venv
source .venv/bin/activate
* Instala las dependencias
pip install -r requirements.txt
* Copia y ajusta algunos archivos necesarios.
- `source/app/conf.py`
- `source/app/controllers/pacs/comerciodigital/conf.py.example`
- `source/app/controllers/pacs/finkok/conf.py.example`
* Finalmente ejecutamos la aplicación. Para esto vamos a necesitar un servidor
wsgi como uwsgi:
pip install uwsgi
cd source/app
uwsgi main_debug.ini
* Ahora puedes ver la aplicación en http://localhost:8000

3
TODO.md Normal file
View File

@ -0,0 +1,3 @@
[ ] Permitir más de un remolque en la Carta Porte
[ ] Campos de captura de Comercio Exterior
[ ] Representación impresa de Comercio Exterior

View File

@ -1 +1 @@
1.21.1
2.3.2

View File

@ -1,81 +0,0 @@
## Administración del sistema
<BR>
<div class="alert-box notice"><span>TIP: </span>
Se requiere un usuario con derechos de <b>administración</b> para usar esta área
</div>
<BR><BR>
Para acceder al área administrativa, presiona el icono de los engranes en la
esquina superior derecha. Si no lo ves, tu usuario no tiene derechos administrativos.
![Acceso al admin](img/02/admin_001.png)
### Agregar cuenta bancaria
Para agregar una cuenta bancaria, primero hay que seleccionar el banco o bancos
a usar, esto se hace en ***Catalogos SAT***, pestaña ***Bancos***. Por default
veras primero los bancos ya seleccionados. Marca la casilla de verificación de
cada banco que deses tener disponible al agregar las cuentas bancarias. Usa la
barra de desplazamiento de la tabla o la rueda de tu ratón para ver más bancos.
Cada banco que selecciones, también estará disponible en la sección ***Clientes
y Proveedores***.
<BR>
<div class="alert-box notice"><span>TIP: </span>
Si vas a agregar los datos bancarios en las ***Facturas de pago***, es
necesario capturar el RFC de cada banco seleccionado.
</div>
<BR>
Para editar el RFC, solo da clic en la celda correspondiente y captura
correctamente el RFC del banco, para guardar presionas ***Enter***. El sistema
solo válida que sea un RFC válido, por lo que **asegurate de que sea el RFC
correcto para cada banco**.
![Seleccionar bancos](img/02/admin_002.png)
Ahora, desde la sección ***Emisor***, pestaña ***Cuentas de Banco***, captura
los siguiente campos, todos son requeridos:
* **Nombre**: Un nombre para identificar la cuenta, puedes usar cualquier texto
* **Banco**: Solo veras los bancos previamente seleccionados
* **Cuenta**: El número de cuenta
* **CLABE**: La clave interbancaria, se valida que este correcta.
* **Moneda**: Solo veras las monedas seleccionadas del Catalogo del SAT
* **Fecha de apertura**: La fecha de apertura de la cuenta
* **Saldo inicial**: Saldo inicial **a partir de donde se empezará a llevar el control de movimientos**
* **Fecha saldo inicial**: La fecha del saldo inicial. **No puedes capturar movimientos anteriores a esta fecha**
![Agregar cuenta bancaria](img/02/admin_003.png)
Usa el botón de comando **Agregar cuenta** para agregarla al sistema. Si todos
los campos son correctos, deberá agregarse al listado inferior.
<div class="alert-box notice"><span>TIP: </span>
Solo puedes eliminar cuentas bancarias que no tengan movimientos capturados.
</div>
<BR>
### Complementos
Los complementos, son caracteristicas especiales dictadas por el SAT que se
pueden agregar a cada CFDI generado.
<BR>
<div class="alert-box notice"><span>TIP: </span>
Consulta con tu departamente contable los requerimientos y caracteristicas de cada uno.
</div>
<BR>
Todos se encuentran en la sección ***Opciones***, pestaña ***Otros***. Ve a la
subsección ***Complementos*** y activa solo los que requieras.
![Agregar cuenta bancaria](img/02/admin_004.png)
* **INE**: Para CFDI emitidos a partidos politicos
* **EDU**: Solo para escuelas incorporadas a la SEP
* **Pagos**: Para generar facturas de pago.
* La serie predeterminada es **FP**, puedes personalizar la que quieras, capturas y presionas **ENTER** para guardar el valor.
* El folio predeterminado es **1**, puedes personalizar el que quieras, capturas y presionas **ENTER** para guardar el valor.

View File

@ -1,309 +0,0 @@
## Bancos
<BR>
Esta herramienta nos permite llevar el control de ingresos y egresos de una o
más cuentas bancarias. También nos permite generar el CFDI (factura) de pago.
Primero, es necesario [dar de alta una cuenta bancaria][1]
![Bancos Principal](img/03/bancos_001.png)
<div class="alert-box notice"><span>TIP: </span>
Asegurate siempre de agregar solo movimientos conciliados con tu estado de
cuenta bancario y de, en la medida de lo posible, agregarlos en orden.
</div>
<BR>
### Fecha y saldo inicial
Estos valores estarán determinados por los que hayas capturado al [dar de alta
la cuenta bancaria][1] en el área administrativa. Mientras una cuenta no tenga
movimientos, aparte del saldo inicial, es posible eliminarla desde
administración.
![Saldo inicial](img/03/bancos_002.png)
<BR>
### Agregar depósito sin facturas relacionadas
Con el botón de comando ***+ Depósito***, agregamos un nuevo depósito. ![+](img/03/bancos_003.png)
Todos los campos con asterisco rojo son requeridos.
* **Fecha**: La fecha en que fue realizado el depósito.
* **Hora**: La hora en que fue realizado el depósito.
* **Referencia**: Referencia del depósito.
* **Forma de pago**: Selecciona de la lista la forma de pago.
* **Importe**: Captura el importe del depósito.
* **Descripción**: El detalle del concepto de este depósito.
![Agregar depósito](img/03/bancos_004.png)
Guarda con el botón de comando ***+ Guardar Depósito***. Se te preguntara para
confirmar la acción. **Asegurate que todos los datos sean correctos**.
![Confirmar](img/03/bancos_005.png)
Si todo esta bien, el nuevo movimiento será agregado a la cuenta y actualizado
el saldo de la misma.
![Nuevo depósito](img/03/bancos_006.png)
<div class="alert-box warning"><span>TIP: </span>
La hora de los depósitos se usa al generar una Factura de Pago. El SAT, en
caso de no saber la hora, permite usar el medío día para todos (12:00:00).
<B>Resiste la "comodidad" de usar esto y asegurate de usar una hora diferente
para cada movimiento.</B>
</div>
<BR>
### Agregar retiro
Con el botón de comando ***- Retiro***, agregamos un nuevo retiro.
Todos los campos con asterisco rojo son requeridos.
* **Fecha**: La fecha en que fue realizado el retiro.
* **Hora**: La hora en que fue realizado el retiro.
* **Referencia**: Referencia del retiro.
* **Forma de pago**: Selecciona de la lista la forma de pago.
* **Importe**: Captura el importe del retiro.
* **Descripción**: El detalle del concepto de este retiro.
![Agregar retiro](img/03/bancos_007.png)
Guarda con el botón de comando ***- Guardar Retiro***. Se te preguntara para
confirmar la acción. **Asegurate que todos los datos sean correctos**.
![Confirmar retiro](img/03/bancos_008.png)
Si todo esta bien, el nuevo movimiento será agregado a la cuenta y actualizado
el saldo de la misma.
![Nuevo retiro](img/03/bancos_009.png)
<div class="alert-box warning"><span>TIP: </span>
Recuerda: <B>Resiste la "comodidad" de usar la misma hora para todos los
movimientos.</B>
</div>
<BR>
### Pagar factura sin relacionar a un depósito
Usa el botón de comando ***+ Depósito***. En la tabla ***Facturas por pagar***,
veras las facturas pendientes de pago. Selecciona la correcta y da clic en el
botón de comando ***Solo marcar pagada***.
![Nuevo retiro](img/03/bancos_010.png)
* No captures ningún dato del formulario, solo selecciona la factura a marcar como pagada.
* **IMPORTANTE**: Solo marca facturas pagadas que **estes 100% seguro que NO requeriras generarle Factura de pago** y que no esten relacionadas con ningún depósito.
Al dar clic en el botón de comando ***Solo marcar pagada***, debes confirmar la
acción.
![Confirmar pago](img/03/bancos_011.png)
Si todo esta bien, la factura será retirada de la tabla ***Facturas por pagar***,
y recibiras una notificación de confirmación.
![Confirmar pago](img/03/bancos_012.png)
<div class="alert-box notice"><span>TIP: </span>
Marca <B>solo como pagada</b> una factura, al no estar relacionada con
ningún depósito, no afecta al saldo de la cuenta bancaria, pero <b>si a la
cuenta del cliente.</b>
</div>
<BR>
### Agregar depósito relacionando facturas
Con el botón de comando ***+ Depósito***, agregamos un nuevo depósito.
Todos los campos con asterisco rojo son requeridos.
* **Fecha**: La fecha en que fue realizado el depósito.
* **Hora**: La hora en que fue realizado el depósito. **Usa diferente hora para cada movimiento**
* **Referencia**: Referencia del depósito.
* **Forma de pago**: **Es importante selecciones el valor correcto, si vas a generar Factura de Pago.**
* **Importe**: **NO captures este valor**
* **Descripción**: **NO captures este valor**
![Agregar depósito](img/03/bancos_013.png)
Para relacionar las facturas pagadas en este depósito, ubica las facturas
correctas y arrastralas a la tabla ***Facturas a pagar en este depósito***,
también puede usar un doble clic para esto. Puedes agregar una o más facturas,
conforme las vas agregando, el sistema sumara sus respectivos importes y
actualizará la descripción del depósito. La descripción puedes editarla
libremente, pero **no cambies el importe del depósito manualmente**.
![Agregar depósito](img/03/bancos_014.png)
Guarda con el botón de comando ***+ Guardar Depósito***. Se te preguntara para
confirmar la acción. **Asegurate que todos los datos sean correctos** y de que
la cantidad y las facturas relacionadas son correctas.
![Confirmar](img/03/bancos_015.png)
Si todo esta bien, el nuevo movimiento será agregado a la cuenta y actualizado
el saldo de la misma.
![Nuevo depósito](img/03/bancos_016.png)
<div class="alert-box warning"><span>REITERAMOS: </span>
La hora de los depósitos se usa al generar una Factura de Pago. El SAT, en
caso de no saber la hora, permite usar el medío día para todos (12:00:00).
<B>Resiste la "comodidad" de usar esto y asegurate de usar una hora diferente
para cada movimiento.</B>
</div>
<BR>
### Cancelar un movimiento.
En cualquier momento puedes cancelar un movimiento con el botón de comando
***Cancelar***. Es mucho mejor te asegures de capturar movimientos solo
confirmados y conciliados, en vez de estar eliminando movimientos. Cuando se
elimina un movimiento, no importa si es retiro o depósito, el saldo de la cuenta
se actualiza a partir de la fecha del movimiento eliminado y hasta el más
reciente.
* Si es un depósito y tiene facturas relacionadas, las mismas estarán de nuevo
marcadas como **Por pagar** y disponibles para relacionarse con otro depósito.
* Puedes eliminar un depósito aún si este tiene generada una Factura de Pago. La
misma quedará ***huerfana***, pero disponible desde el listado de Facturas de
Pago elaboradas.
![Cancelar movimiento](img/03/bancos_017.png)
<BR>
### Generar Factura de Pago
Para usar esta herramienta, debe estar activado el uso del [complemento de pago][2]
dentro de administración. Si no ves en ***Bancos*** el botón de comando ***Generar
Factura de Pago***, solicita a un administrador que lo active.
![Factura de pago](img/03/bancos_018.png)
<div class="alert-box notice"><span>TIP: </span>
Asegurate de revisar que todos tus movimientos este correctamente capturados
y las facturas correctas relacionadas con cada depósito, antes de usar esta
herramienta.
</div>
<BR>
* Solo puedes generar ***Facturas de pago*** de movimientos de depósito que tengan
facturas relacionadas.
* Selecciona el depósito correcto y presiona el botón de comando ***Generar Factura de Pago***.
En la siguiente pantalla, **verifica una vez más que todos los datos con correctos**.
En este momento puedes cerrar e incluso cancelar el movimiento para hacer cualquier
corrección. Una vez generada la ***Factura de Pago***, no podrás hacer ningún
cambio. Todos los datos mostrados son de solo lectura, tanto los datos del
depósito como las facturas relacionadas para que, reiteramos, verifiques que
todos los datos son correctos.
![Factura de pago](img/03/bancos_019.png)
**En cuanto estes 100% seguro de que todos los datos son correctos**, usa el
botón de comando ***Timbrar***, para enviar a timbrar la ***Factura de Pago***.
Se te pedirá una confirmación de esta acción.
![Factura de pago](img/03/bancos_020.png)
Si todo esta bien, el sistema: guardará, generará y timbrará la nueva ***Factura
de Pago***.
![Factura de pago](img/03/bancos_021.png)
Con un clic en el icono ***PDF***, puedes generar el PDF respectivo de esta
***Factura de Pago***. Como con las plantillas de las facturas, puedes
personalizar completamente esta plantilla.
![Factura de pago](img/03/bancos_022.png)
<BR>
### Facturas de pago con facturas en otras monedas
Cuando el sistema detecta que se están usando más de una moneda, automáticamente
te mostrará las columnas del ***Total*** y la ***Moneda*** respectiva.
![Factura de pago](img/03/bancos_023.png)
Al relacionar la factura, observa que tomará el valor original en la moneda del
documento.
![Factura de pago](img/03/bancos_024.png)
Los siguientes pasos **son importantes y en este orden**, los campos que debes de modificar en la
tabla ***Facturas a pagar en este depósito*** son:
* **Este pago**: Si el valor pagado es el total de la factura, no lo modifiques,
si es parcial, captura el valor pagado **en la moneda del documento**, en este
ejemplo, en UDS. Este valor se usa para el estado de cuenta de la factura.
* **Este Pago MXM**: Captura el valor pagado en moneda nacional de este documento.
En este ejemplo, sería el valor **real** del depósito. Este valor se usará para
la ***Factura de pago***, para el estado de cuenta y para el saldo del cliente.
* **T.C.**: Al capturar correctamente los dos valores anteriores, el sistema te
calculará el tipo de cambio (***T.C***) usado. Este valor se usa para la ***Factura de pago***.
Ahora, nuestros datos deben verse así:
![Factura de pago](img/03/bancos_025.png)
Guarda con el botón de comando ***+ Guardar Depósito***. Se te preguntara para
confirmar la acción. **Asegurate que todos los datos sean correctos** y de que
la cantidad y las facturas relacionadas son correctas.
![Factura de pago](img/03/bancos_026.png)
Si todo esta bien, el nuevo movimiento será agregado a la cuenta y actualizado
el saldo de la misma.
Ahora si, puedes generar la ***Factura de Pago*** de este depósito.
![Factura de pago](img/03/bancos_027.png)
<BR>
### Cuenta de banco y facturas relacionadas en otras monedas
Cuando la cuenta de banco no este en M.N (MXN) y los documentos relacionados
esten en la misma moneda de la cuenta de banco, el procedimiento para generar
la factura de pago es:
Los campos que debes de modificar en la tabla ***Facturas a pagar en este depósito*** son:
* **Este pago**: Si el valor pagado es el total de la factura, no lo modifiques, si es parcial, captura el valor pagado **en la moneda del documento**, en este ejemplo, en UDS. Este valor se usa para el estado de cuenta de la factura.
* **Este Pago USD**: Captura el valor pagado en la moneda de la cuenta bancaria. En este ejemplo, sería el valor **real** del depósito. Este valor se usará para la ***Factura de pago***.
* **T.C.**: Siempre debe ser 1.00 al ser la misma moneda de la cuente bancaria y del documento.
Ahora, el tipo de cambio del movimiento, **si debes de capturarlo**, es el tipo de
cambio de la moneda de la cuenta en relación con la M.N (MXN). Este valor se usará
para la ***Factura de pago***.
![Factura de pago](img/03/bancos_028.png)
Guarda con el botón de comando ***+ Guardar Depósito***. Se te preguntara para
confirmar la acción. **Asegurate que todos los datos sean correctos** y de que
la cantidad y las facturas relacionadas son correctas.
Si todo esta bien, el nuevo movimiento será agregado a la cuenta y actualizado
el saldo de la misma.
Ahora si, puedes generar la ***Factura de Pago*** de este depósito.
![Factura de pago](img/03/bancos_029.png)
[1]: /administracion/#agregar-cuenta-bancaria
[2]: /administracion/#complementos

View File

@ -1,30 +0,0 @@
.alert-box {
color: #555;
border-radius: 10px;
font-family: Tahoma,Geneva,Arial,sans-serif;
font-size: 15px;
padding: 10px 10px 10px 36px;
margin: 10px;
}
.alert-box span {
font-weight: bold;
text-transform: uppercase;
}
.error {
background:#ffecec url('../img/error.png') no-repeat 10px 50%;
border:1px solid #f5aca6;
}
.success {
background:#e9ffd9 url('../img/success.png') no-repeat 10px 50%;
border:1px solid #a6ca8a;
}
.warning {
background:#fff8c4 url('../img/warning.png') no-repeat 10px 50%;
border:1px solid #f2c779;
}
.notice {
background:#e3f7fc url('../img/notice.png') no-repeat 10px 50%;
border:1px solid #8ed9f6;
}

View File

@ -1,9 +0,0 @@
## Guía de Usuario
<BR>
* Cliente y Proveedores
* Productos y Servicios
* [Bancos](bancos.md)
* Facturas

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 B

View File

@ -1,36 +0,0 @@
## Bienvenido a la documentación de Empresa Libre
<div style="text-align: right">
<BR><B>
En cada relación comercial,<BR>
existe una relación humana.</B>
<BR>
<BR>
<BR>
</div>
**Empresa Libre** es un sistema de facturación electrónica especificamente
diseñado para la legislación mexicana. **Empresa Libre** es totalmente
[software libre][1].
### Contenido
1. [Instalación y configuración](instalacion.md)
1. [Administración del sistema](administracion.md)
1. [Guía de usuario](guiadeusuario.md)
1. [Bancos](bancos.md)
1. [Preguntas más frecuentes](preguntas.md)
### Como ayudar
* Usa **Empresa Libre**
* Reportando errores
* Escribe documentación
* Graba videos de uso
* Propon mejoras
* Conviertete en **patrocinador**
[1]: https://www.gnu.org/philosophy/free-sw.es.html

View File

@ -1,250 +0,0 @@
## Instalación de Empresa Libre
<BR>
### Actualización
<BR>
<div class="alert-box notice"><span>TIP: </span>
Es <b>muy importante</b> y es <b>tu responsabilidad</b> mantener siempre actualizado y al día el sistema.
</div>
<BR><BR>
Mantener al día el sistema te permite tener siempre los ultimos cambios realizados
en el, recibir las correcciones de errores y las mejoras que vamos incorporando.
Siempre consulta el [historial de cambios](/notas) para saber si hay algún proceso
extra que seguir al actualizar. Es muy importante que revises cada nota desde
tu versión actual y hasta la más reciente.
<BR>
Al iniciar el sistema, debes de ver la versión actual del mismo. Si no la ves,
es que tienes una versión demasiado desactualizada, es importante actualices a
la brevedad.
![Versión del sistema](img/01/install_001.png)
<BR>
<div class="alert-box notice"><span>TIP: </span>
Para todas las instrucciones siguientes, se asume que son para nuestra
maquina virtual (MV), las rutas pueden cambiar si has personalizado tu
propia MV o estas ejecutando Empresa Libre en un servidor propio.
</div>
<BR>
Ya dentro del sistema, el proceso para actualizar es:
1. Entra a la carpeta del proyecto.
┌─[empresalibre-][empresa]->{~}
└──> cd proyectos/empresa-libre
┌─[empresalibre-][empresa]->{~/proyectos/empresa-libre}
└──>
1. Actualiza la rama `master` del repositorio. El resultado variará, dependiendo desde la versión que estes actualizando, entre más vieja sea, veras más archivos modificados.
└──> git pull origin master
remote: Enumerating objects: 197, done.
remote: Counting objects: 100% (197/197), done.
remote: Compressing objects: 100% (93/93), done.
remote: Total 197 (delta 135), reused 146 (delta 99)
Recibiendo objetos: 100% (197/197), 131.71 KiB | 956.00 KiB/s, listo.
Resolviendo deltas: 100% (135/135), listo.
Desde https://gitlab.com/mauriciobaeza/empresa-libre
* branch master -> FETCH_HEAD
f0ab924..18b1880 master -> origin/master
Actualizando f0ab924..18b1880
Fast-forward
CHANGELOG.md | 44 ++++++++
VERSION | 2 +-
source/app/controllers/main.py | 3 +-
source/app/controllers/util.py | 35 +++++-
source/app/models/db.py | 23 +++-
source/app/models/main.py | 278 ++++++++++++++++++++++++++++++++++++++--------
source/app/settings.py | 3 +-
source/static/css/app.css | 7 ++
source/static/js/controller/admin.js | 44 +++++++-
source/static/js/controller/bancos.js | 89 +++++++++++++--
source/static/js/controller/partners.js | 163 ++++++++++++++++++---------
source/static/js/controller/util.js | 1 +
source/static/js/ui/admin.js | 18 ++-
source/static/js/ui/bancos.js | 9 +-
source/static/js/ui/main.js | 5 +-
source/static/js/ui/partners.js | 28 ++++-
source/templates/base.html | 2 +-
17 files changed, 626 insertions(+), 128 deletions(-)
1. Si las [notas de lanzamiento](/notas) te indican que debes de hacer algún otro proceso, generalmente será que migres la base de datos.
1. ¿Cuando debo migrar la base de datos?
1. Si te lo indica la **ultima** nota de lanzamiento
1. Si te lo indica alguna nota intermedia desde tu versión actual y hasta la ultima.
1. Si **no ves** la versión de **Empresa Libre** al inicio del sistema
1. Si **no** estas seguro desde que versión estas actualizando.
* Para migrar entra a la siguiente carpeta
└──> cd source/app/models/
┌─[empresalibre-][empresa]->{~/proyectos/empresa-libre/source/app/models}
└──>
* **IMPORTANTE:** siempre saca un respaldo de tu base de datos **antes de migrar**
└──> python main.py -bk
[21-Sep-2018 23:34:08] INFO: API: Generando backup de: LAN7008173R5
[21-Sep-2018 23:34:09] INFO: API: Backup generado de LAN7008173R5
[21-Sep-2018 23:34:09] INFO: API: Sin datos para sincronización particular de lan7008173r5.bk
* Si en vez del mensaje anterior, ves un mensaje de error como el siguiente. Mira en [errores más comúnes][1] para arreglarlo primero. Una vez resuelto, vuelve a ejecutar el comando anterior. **Verifica siempre estar en la carpeta correcta**.
File "/usr/lib/python3.7/site-packages/requests/adapters.py", line 513, in send
raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='seafile.empresalibre.net', port=443): Max retries exceeded with url: /api2/auth-token/ (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f2f031a4550>: Failed to establish a new connection: [Errno -2] Name or service not known'))
* **IMPORTANTE:** Si **no sabes** desde que versión estas actualizando, o no has actualizado desde la **versión 1.2.0 del 18 de diciembre del 2017**. Entonces, primero usa el siguente comando.
└──> python main.py -bd
Introduce el RFC: TU_RFC
* Ahora si, si ya **no obtienes ningún error** y estas al día, puedes migrar tu base de datos con el comando.
└──> python main.py -m -r TU_RFC
* Por supuesto, reemplaza **TU_RFC** por el RFC del emisor a actualizar.
└──> python main.py -m -r LAN7008173R5
[21-Sep-2018 23:41:55] INFO: API: Conectado a la BD...
[21-Sep-2018 23:41:55] INFO: API: Creando tablas nuevas...
[21-Sep-2018 23:41:55] INFO: API: Tablas creadas correctamente...
[21-Sep-2018 23:41:55] INFO: API: Iniciando migración de tablas...
[21-Sep-2018 23:41:55] INFO: API: Tablas migradas correctamente...
[21-Sep-2018 23:41:55] INFO: API: Importando datos...
[21-Sep-2018 23:41:55] INFO: API: Importando tabla: Categorias
[21-Sep-2018 23:41:55] INFO: API: Importando tabla: SATImpuestos
[21-Sep-2018 23:41:55] INFO: API: Importando tabla: SATUnidades
[21-Sep-2018 23:41:55] INFO: API: Importando tabla: SATNivelesEducativos
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoRelacion
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATMonedas
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATFormaPago
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATRegimenes
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATBancos
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATUsoCfdi
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATEstados
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATOrigenRecurso
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATPeriodicidadPago
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATRiesgoPuesto
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoContrato
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoDeduccion
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoHoras
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoIncapacidad
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoJornada
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoNomina
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoOtroPago
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoPercepcion
[21-Sep-2018 23:41:56] INFO: API: Importando tabla: SATTipoRegimen
[21-Sep-2018 23:41:56] INFO: API: Importación terminada...
* **IMPORTANTE:** Si tienes varios emisores en el sistema, **debes de migrarlos todos**.
1. Por ultimo reinicia el sistema.
└──> sudo systemctl restart empresalibre
[sudo] password for empresa:
* El proceso anterior debe ser instantaneo, si se tarda más de un minuto, mira en [errores más comúnes][2].
* También debe funcionar que reinicies la maquina virtual o servidor donde tengas el sistema.
1. Cualquier otro problema, usa el [sistema de tickets del proyecto][3]
<BR>
### Errores más comúnes
<BR>
<div class="alert-box error"><span>PRECAUCIÓN: </span>
La modificación incorrecta de cualquier archivo del sistema, puede provocar
que deje de funcionar. Asegurate de seguir todos los pasos correctamente.
Si no estas seguro, es mejor contrates el soporte para que un técnico
especializado realice el procedimiento.
</div>
<BR>
#### Al respaldar la base de datos, muestra el error
File "/usr/lib/python3.7/site-packages/requests/adapters.py", line 513, in send
raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='seafile.empresalibre.net', port=443): Max retries exceeded with url: /api2/auth-token/ (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f2f031a4550>: Failed to establish a new connection: [Errno -2] Name or service not known'))
* Entra a la carpeta
└──> cd proyectos/empresa-libre/source/app/
┌─[empresalibre-][empresa]->{~/proyectos/empresa-libre/source/app}
└──>
* Siempre saca una copia de seguridad.
cp conf.py ~/conf.py
* Edita el archivo
nano conf.py
* Debe de verse exactamente como el siguiente ejemplo, agrega lo que haga falta.
#!/usr/bin/env python
DEBUG = False
MV = True
#~ Establece una ruta accesible para el servidor web
LOG_PATH = '/home/empresa/log/empresa-libre.log'
SEAFILE_SERVER = {
'URL': 'https://seafile.empresalibre.net/api2/',
'USER': '',
'PASS': '',
'REPO': '',
}
SEAFILE_SERVER = {}
* Para guardar los cambios presionas: `CTRL+O`
* Para salir presionas: `CTRL+X`
<BR>
#### Al reinciar el sistema, tarda más de un minuto en terminar.
<BR>
<div class="alert-box warning"><span>CUIDADO: </span>
Se muestra el contenido del archivo de la maquina virtual, puede ser diferente
si lo usas en tu propio servidor o VPS.
</div>
<BR>
* Edita el archivo
sudo nano /etc/systemd/system/empresalibre.service
* Asegurate de que este **exactamente** como en:
[Unit]
Description=uWSGI instance to serve Empresa Libre
[Service]
ExecStart=/usr/bin/uwsgi /home/empresa/empresa-libre/app/main.ini
KillSignal=SIGQUIT
[Install]
WantedBy=multi-user.target
* Para guardar los cambios presionas: `CTRL+O`
* Para salir presionas: `CTRL+X`
* Recarga los cambios.
sudo systemctl daemon-reload
* Asegurate de que ahora reinicia al instante.
sudo systemctl restart empresalibre
[1]: #al-respaldar-la-base-de-datos-muestra-el-error
[2]: #al-reinciar-el-sistema-tarda-mas-de-un-minuto-en-terminar
[3]: https://gitlab.com/mauriciobaeza/empresa-libre/issues

View File

@ -1,680 +0,0 @@
## License
```
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 of the License, 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
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
```

View File

@ -1,240 +0,0 @@
## Notas de lanzamiento
Siempre debe verificar en esta sección, el proceso que debes seguir con cada
actualización del sistema. Recuerda; **es muy importante mantener tu sistema
siempre actualizado.** Solo se da soporte sobre la ultima versión de **Empresa
Libre**.
### 1.21.1 [14-oct-2018]
- Error [#307](https://gitlab.com/mauriciobaeza/empresa-libre/issues/307)
### 1.21.0 [12-oct-2018]
- Error [#287](https://gitlab.com/mauriciobaeza/empresa-libre/issues/287)
- Mejora: Complemento de pago con datos de cuentas
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar.
### 1.20.0 [08-oct-2018]
- Error [#295](https://gitlab.com/mauriciobaeza/empresa-libre/issues/295)
- Mejora - Cuentas de banco para clientes
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar.
### 1.19.1 [03-oct-2018]
- Error [#291](https://gitlab.com/mauriciobaeza/empresa-libre/issues/291)
- Error al generar PDF de factura de pago con relacionados sin serie
- Error al relacionar facturas versión 3.2
### 1.19.0 [28-sep-2018]
- Mejora [#280](https://gitlab.com/mauriciobaeza/empresa-libre/issues/280)
- Mejora [#288](https://gitlab.com/mauriciobaeza/empresa-libre/issues/288)
- Error [#290](https://gitlab.com/mauriciobaeza/empresa-libre/issues/290)
* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
### 1.18.0 [27-sep-2018]
- Fix [#282](https://gitlab.com/mauriciobaeza/empresa-libre/issues/282) Factura de pago en otras monedas
### 1.17.0 [25-sep-2018]
- Fix - Al generar factura de pago con documentos relacionados en otras monedas
- Fix - Al generar factura de pago sin serie en documentos relacionados
- Fix [#278](https://gitlab.com/mauriciobaeza/empresa-libre/issues/278)
- IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
### 1.16.1 [18-sep-2018]
- Error [#268](https://gitlab.com/mauriciobaeza/empresa-libre/issues/268)
- IMPORTANTE: Actualizar si usas cuatro decimales en impuestos
### 1.16.0 [16-sep-2018]
- Se puede editar el saldo de un cliente
- Se muestra la cantidad de facturas de pago en los movimientos
### 1.15.0 [12-sep-2018]
- Se pueden mover las facturas con doble clic en los movimientos de banco.
- Fix - Al sumar las facturas en los depósitos
- Fix - Al importar los pedimentos en facturas por lotes
- Fix - Al guardar los datos del emisor
### 1.14.0 [10-sep-2018]
- Personalizar plantilla para factura de pago
- Fix - Mostrar serie y folio capturado para factura de pago
- Fix - Agregar nueva cuenta de banco
### 1.13.0 [10-sep-2018]
- Cancelar factura de pago
- IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
### 1.12.0 [31-ago-2018]
- Soporte para facturas (complemento) de pago.
- IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bk
python main.py -m
```
### 1.11.1 [21-ago-2018]
- Fix - Quitar columna en tabla facturaspagos.
- IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -m
```
### 1.11.0 [25-jul-2018]
- Se cambia la forma de consultar los folios restantes. Es indispensable actualizar a esta versión para ver tus timbres restantes.
### 1.10.0 [10-jul-2018]
- Ahora se pueden manejar precios con cuatro decimales.
### 1.9.3 [08-jul-2018]
- Fix: Al refacturar conceptos con descuento
### 1.9.2 [05-jul-2018]
- Fix: Al generar el reporte de facturas en PDF
### 1.9.1 [25-jun-2018]
- Fix: Al mostrar el título de la aplicación - Se agrega el registro de acción al borrar una factura
### 1.9.0 [18-jun-2018]
- Se agrega la vista del detalle de facturas
- Fix: Al timbrar nómina
### 1.8.1 [14-jun-2018]
- Fix: Se agrega una barra de desplazamiento al buscar productos o clientes
- Se cambia el servidor de consulta de timbres
### 1.8.0 [03-jun-2018]
- Se permiten 4 decimales en Tipo de cambio
- Se agrega el campo {total_cantidades} al generar el PDF
- Se agrega opción para generar respaldos de la BD en MV
- Fix: Al generar con complemento EDU
### 1.7.0 [23-may-2018]
- Se agrega soporte para truncar impuestos locales, para las estulticias de los "ingenieros" de las dependencias de gobierno
### 1.6.1 [09-abr-2018]
- Fix: Nómina con separación
### 1.6.0 [18-feb-2018]
- Facturacion a extranjeros
- IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -m
```
### 1.5.0 [30-ene-2018]
- Timbrado de Nómina
- IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -m
```
### 1.4.0 [01-ene-2018]
- Impresión de tickets
### 1.3.0 [27-Dic-2017]
- Punto de venta
- IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -m
```
### 1.2.0 [18-Dic-2017]
- IMPORTANTE: Es necesario actualizar la base de datos, despues de actualizar la rama principal.
```
git pull origin master
cd source/app/models
python main.py -bd
```
### 0.1.0 [26-Oct-2017]
- Generar y timbrar con CFDI 3.3

View File

@ -1,105 +0,0 @@
## Preguntas más frecuentes
<div style="text-align: right">
<BR><B><FONT SIZE=+1>Software libre, no gratis</FONT></B>
<BR>
<BR>
<BR>
</div>
<BR>
### ¿Empresa Libre es gratis?
No, **Empresa Libre** no es gratis, es libre y es muy importante tener clara esta
diferencia. **Empresa Libre** es [software libre][1], lo que quiere decir que
puedes usarlo, copiarlo, mejorarlo y compartirlo con quien quieras. **Empresa
Libre** se sostiene de la venta de los folios para el timbrado y del soporte
técnico derivado de su uso, por eso, por favor, **no pidas soporte técnico gratis**.
<BR>
### Si es libre... ¿Por qué cobran el alta inicial?
Recuerda, **libre es diferente de gratis**, y el alta inicial de **Empresa Libre**,
es el costo inicial de 100 timbres fiscales, lo que estas pagando son timbres
fiscales, no el software. Nunca, reiteramos, nunca hemos cobrado el software.
<BR>
### Si el SAT dice que los PACs deben de dar timbrado gratuito... ¿Por que lo cobran Ustedes?
Efectivamente, por ley todos los PACs deben de tener una aplicación gratuita
para el timbrado. Nosotros **NO somos PAC**. Hacemos el timbrado por medio de
Finkok, el mejor PAC de México, puedes [timbrar gratuitamente en su página][2].
<BR>
### ¿Al contratar el servicio se incluye el soporte técnico?
No, no se incluye ningún tipo de soporte técnico. Dado que **Empresa Libre** es
[software libre][1] y se ofrece sin **ningún costo para todos**, el soporte técnico
es parte de los ingresos indispensables para poder seguir desarrollando y
manteniendo el sistema. Esto incluye cualquier estulticia que se le ocurra al SAT.
Por eso, por favor, **no pidas soporte técnico gratis**.
<BR>
### ¿Qué alternativas tienen de soporte?
El ingreso por soporte técnico es una importante fuente de ingreso para sufragar
todas [nuestras actividades altruistas][3]. Cada vez que contratas nuestros
servicios, **estas ayudando a otros**.
1. **Soporte gratuito** en el [sistema de tickets del proyecto][4], un soporte
que hemos dado por años de forma profesional, además, otros entusiastas usuarios
del sistema, también pueden ayudarte con cualquier duda que tengas.
2. **Soporte de pago**, puedes contratar el servicio puntual por hora para un
problema en específico o contratar una póliza de soporte mensual. Ponte en
contacto con nosotros para tener más detalles.
3. Todos nuestros [donantes y patrocinadores][5] tienen todo el soporte técnico
que requieran para el sitema. Todas las [ONGs][6] que apoyamos, también tienen
todo el soporte técnico que requieran.
<BR>
### ¿Puedo probar el sistema antes de contratar el servicio?
Claro, de hecho, **es un requisito indispensable** para contratar, los datos de
acceso al sistema de pruebas son:
* **URL**: https://demo.empresalibre.net/
* **RFC**: LAN7008173R5
* **Usuario**: admin
* **Contraseña**: salgueiro3.3
Toma en cuenta que este sitio esta en constante actualización y desarrollo, los
datos generados se limpian de forma regular.
<BR>
### ¿Dondé descargar los archivos necesarios?
Todos los archivos necesarios para probar localmente **Empresa Libre** en tu
equipo, los puedes descargar desde nuestra carpeta compartida, donde también
puedes descargar todas las plantillas necesarias para el sistema
[Carpeta pública de Empresa Libre][7]
<BR>
### Después de actualizar, no veo los cambios esperados en pantalla
Un alto porcentaje de estos problemas, se arregla **forzando** el refresco de
tu pantalla, en la mayoría de los navegadores con la combinación de teclas
**CTRL+F5**. En raras ocasiones, es preciso limpiar el cache de tu navegador.
[1]: https://www.gnu.org/philosophy/free-sw.es.html
[2]: https://www.finkok.com/
[3]: http://universolibre.org/hacemos/
[4]: https://gitlab.com/mauriciobaeza/empresa-libre/issues
[5]:
[6]: https://universolibre.org/hacemos/
[7]: https://doc.elmau.net/d/dbb11c9186684457beb6/

View File

@ -1,17 +0,0 @@
site_name: Empresa Libre
#~ repo_url: https://gitlab.com/mauriciobaeza/empresa-libre
nav:
- Inicio: index.md
- Contenido:
- Instalación y configuración: instalacion.md
- Administracón del sistema: administracion.md
- Guía de Usuario: guiadeusuario.md
- Preguntas más frecuentes: preguntas.md
- Acerca de :
- Un poco de historia: historia.md
- Notas de lanzamiento: notas.md
- Como ayudar: ayudar.md
- Licencia: licencia.md
extra_css:
- css/main.css

View File

@ -1,15 +1,17 @@
falcon
falcon==1.4.1
falcon-multipart
Beaker
Mako
peewee==2.10.2
Click
logbook
bcrypt
python-dateutil
zeep
chardet
pyqrcode
pypng
reportlab
psycopg2-binary
cryptography==3.4.8
xmlsec
segno
# python-escpos

View File

@ -13,13 +13,9 @@ TITLE_APP = 'Empresa Libre'
#~ Establece una ruta accesible para el servidor web
LOG_PATH = '/var/log/empresalibre/empresa-libre.log'
# ~ Establece los valores para sincronizar los backups de la base de datos
# ~ por ejemplo
# ~ SEAFILE_SERVER = {
# ~ 'URL': 'https://tu_servidor_seafile',
# ~ 'USER': 'tu_usuario',
# ~ 'PASS': 'tu_contraseña',
# ~ 'REPO': 'id_repo',
# ~ }
SEAFILE_SERVER = {}
# ~ Establece un token personalizado para encriptar las claves
# ~ from secrets import token_hex
# ~ token_hex(32)
# ~ IMPORTANTE: Cada vez que cambies este valor, debes de subir de nuevo
# ~ tus certificados
TOKEN = ''

View File

@ -20,20 +20,28 @@ import datetime
from xml.etree import ElementTree as ET
from xml.dom.minidom import parseString
from dateutil import parser
from logbook import Logger
log = Logger('XML')
CFDI_ACTUAL = 'cfdi33'
CFDI_ACTUAL = 'cfdi40'
NOMINA_ACTUAL = 'nomina12'
PUBLIC = 'PUBLICO EN GENERAL'
CFDI_EGRESO = 'E'
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',
@ -41,17 +49,23 @@ 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',
'xmlns': 'http://www.sat.gob.mx/nomina',
'schema': 'http://www.sat.gob.mx/nomina http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina11.xsd',
'schema': ' http://www.sat.gob.mx/nomina http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina11.xsd',
},
'nomina': {
'version': '1.2',
'prefix': 'nomina12',
'xmlns': 'http://www.sat.gob.mx/nomina12',
'schema': 'http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd',
'schema': ' http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd',
},
'locales': {
'version': '1.0',
@ -76,14 +90,38 @@ SAT = {
'version': '1.0',
'prefix': 'iedu',
'xmlns': 'http://www.sat.gob.mx/iedu',
'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/ine/iedu.xsd',
'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd',
},
'pagos': {
'version': '1.0',
'prefix': 'pago10',
'xmlns': 'http://www.sat.gob.mx/Pagos',
'schema': ' http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd',
'version': '2.0',
'prefix': 'pago20',
'xmlns': 'http://www.sat.gob.mx/Pagos20',
'schema': ' http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd',
},
'divisas': {
'version': '1.0',
'prefix': 'divisas',
'xmlns': 'http://www.sat.gob.mx/divisas',
'schema': ' http://www.sat.gob.mx/divisas http://www.sat.gob.mx/sitio_internet/cfd/divisas/divisas.xsd',
},
'leyendas': {
'version': '1.0',
'prefix': 'leyendasFisc',
'xmlns': 'http://www.sat.gob.mx/leyendasFiscales',
'schema': ' http://www.sat.gob.mx/leyendasFiscales http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd',
},
'cartaporte': {
'version': '3.0',
'prefix': 'cartaporte30',
'xmlns': 'http://www.sat.gob.mx/CartaPorte30',
'schema': ' http://www.sat.gob.mx/CartaPorte30 http://www.sat.gob.mx/sitio_internet/cfd/CartaPorte/CartaPorte30.xsd',
},
'comercioe': {
'version': '2.0',
'prefix': 'cce20',
'xmlns': 'http://www.sat.gob.mx/ComercioExterior20',
'schema': ' http://www.sat.gob.mx/ComercioExterior20 http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior20/ComercioExterior20.xsd',
}
}
@ -101,6 +139,12 @@ class CFDI(object):
self._edu = False
self._pagos = False
self._is_nomina = False
self._leyendas = False
self._carta_porte = False
self._comercio_exterior = False
self._divisas = ''
self._tipo_de_comprobante = ''
self._exportacion = DEFAULT['exportacion']
self.error = ''
def _now(self):
@ -111,6 +155,8 @@ class CFDI(object):
return ''
self._comprobante(datos['comprobante'])
if 'global' in datos:
self._informacion_global(datos['global'])
self._relacionados(datos['relacionados'])
self._emisor(datos['emisor'])
self._receptor(datos['receptor'])
@ -122,13 +168,14 @@ class CFDI(object):
if 'nomina' in datos:
self._nomina(datos['nomina'])
#~ if 'complementos' in datos:
#~ self._complementos(datos['complementos'])
return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
xml = self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
def add_sello(self, sello):
return xml
def add_sello(self, sello, cert_txt):
self._cfdi.attrib['Sello'] = sello
self._cfdi.attrib['Certificado'] = cert_txt
return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
def _to_pretty_xml(self, source):
@ -151,6 +198,13 @@ class CFDI(object):
if 'ine' in datos['complementos']:
self._ine = True
self._pagos = bool(datos['complementos'].get('pagos', False))
self._leyendas = bool(datos['complementos'].get('leyendas', False))
self._carta_porte = bool(datos['complementos'].get('cartaporte', False))
self._comercio_exterior = bool(datos['complementos'].get('comercioe', False))
if self._comercio_exterior:
self._exportacion = datos['complementos']['comercioe'].pop('Exportacion')
self._divisas = datos['comprobante'].pop('divisas', '')
if 'nomina' in datos:
self._is_nomina = True
@ -190,6 +244,12 @@ class CFDI(object):
attributes[name] = SAT['edu']['xmlns']
schema_edu = SAT['edu']['schema']
schema_divisas = ''
if self._divisas:
name = 'xmlns:{}'.format(SAT['divisas']['prefix'])
attributes[name] = SAT['divisas']['xmlns']
schema_divisas = SAT['divisas']['schema']
schema_nomina = ''
if self._is_nomina:
name = 'xmlns:{}'.format(SAT['nomina']['prefix'])
@ -202,9 +262,28 @@ class CFDI(object):
attributes[name] = SAT['pagos']['xmlns']
schema_pagos = SAT['pagos']['schema']
schema_leyendas = ''
if self._leyendas:
name = 'xmlns:{}'.format(SAT['leyendas']['prefix'])
attributes[name] = SAT['leyendas']['xmlns']
schema_leyendas = SAT['leyendas']['schema']
schema_carta_porte = ''
if self._carta_porte:
name = 'xmlns:{}'.format(SAT['cartaporte']['prefix'])
attributes[name] = SAT['cartaporte']['xmlns']
schema_carta_porte = SAT['cartaporte']['schema']
schema_comercioe = ''
if self._comercio_exterior:
name = 'xmlns:{}'.format(SAT['comercioe']['prefix'])
attributes[name] = SAT['comercioe']['xmlns']
schema_carta_porte = SAT['comercioe']['schema']
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
schema_locales + schema_donativo + schema_ine + schema_edu + \
schema_nomina + schema_pagos
schema_divisas + schema_nomina + schema_pagos + schema_leyendas + \
schema_carta_porte + schema_comercioe
attributes.update(datos)
if not 'Version' in attributes:
@ -212,9 +291,23 @@ class CFDI(object):
if not 'Fecha' in attributes:
attributes['Fecha'] = self._now()
# ~ cfdi4
if not 'Exportacion' in attributes:
attributes['Exportacion'] = self._exportacion
self._tipo_de_comprobante = attributes['TipoDeComprobante']
self._cfdi = ET.Element('{}:Comprobante'.format(self._pre), attributes)
return
def _informacion_global(self, datos):
if not datos:
return
node_name = '{}:InformacionGlobal'.format(self._pre)
node = ET.SubElement(self._cfdi, node_name, datos)
return
def _relacionados(self, datos):
if not datos or not datos['tipo'] or not datos['cfdis']:
return
@ -234,6 +327,10 @@ class CFDI(object):
return
def _receptor(self, datos):
receptor_name = datos['Nombre'].upper()
if receptor_name == PUBLIC and self._tipo_de_comprobante == CFDI_EGRESO:
receptor_name = datos['Nombre']
datos['Nombre'] = receptor_name
node_name = '{}:Receptor'.format(self._pre)
emisor = ET.SubElement(self._cfdi, node_name, datos)
return
@ -242,7 +339,8 @@ class CFDI(object):
from xml.sax.saxutils import escape, unescape
conceptos = ET.SubElement(self._cfdi, '{}:Conceptos'.format(self._pre))
for row in reversed(datos):
# ~ for row in reversed(datos):
for row in datos:
complemento = {}
if 'complemento' in row:
complemento = row.pop('complemento')
@ -302,6 +400,10 @@ class CFDI(object):
for field in fields:
if field in datos:
attributes[field] = datos[field]
if not attributes:
return
node_name = '{}:Impuestos'.format(self._pre)
impuestos = ET.SubElement(self._cfdi, node_name, attributes)
@ -418,6 +520,47 @@ class CFDI(object):
return
def _complemento_comercio_exterior(self, datos):
prefix = SAT['comercioe']['prefix']
emisor = datos.pop('emisor')
propietarios = datos.pop('propietarios', {})
receptor = datos.pop('receptor')
destinatario = datos.pop('destinatario', {})
mercancias = datos.pop('mercancias')
attr = {'Version': SAT['comercioe']['version']}
attr.update(datos)
ce = ET.SubElement(
self._complemento, f'{prefix}:ComercioExterior', attr)
attributes = {}
if 'Curp' in emisor:
attributes = {'Curp': emisor.pop('Curp')}
node = ET.SubElement(ce, '{}:Emisor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), emisor)
attributes = {}
if 'NumRegIdTrib' in receptor:
attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
node = ET.SubElement(ce, '{}:Receptor'.format(prefix), attributes)
ET.SubElement(node, '{}:Domicilio'.format(prefix), receptor)
node = ET.SubElement(ce, '{}:Mercancias'.format(prefix))
fields = ('Marca', 'Modelo', 'SubModelo', 'NumeroSerie')
for row in mercancias:
detalle = {}
for f in fields:
if f in row and row[f]:
detalle[f] = row[f]
row.pop(f)
concepto = ET.SubElement(node, '{}:Mercancia'.format(prefix), row)
if detalle:
ET.SubElement(
concepto, '{}:DescripcionesEspecificas'.format(prefix), detalle)
return
def _complementos(self, datos):
if not datos:
return
@ -426,73 +569,126 @@ class CFDI(object):
self._complemento = ET.SubElement(
self._cfdi, '{}:Complemento'.format(self._pre))
if self._carta_porte:
datos = datos['cartaporte']
ubicaciones = datos.pop('ubicaciones')
mercancias = datos.pop('mercancias', ())
tiposfigura = datos.pop('tiposfigura', ())
autotransporte = datos.pop('autotransporte', {})
identificacion = autotransporte.pop('identificacion')
seguros = autotransporte.pop('seguros')
remolques = autotransporte.pop('remolques')
atributos = {'Version': SAT['cartaporte']['version']}
atributos.update(datos)
prefix = SAT['cartaporte']['prefix']
node_carta = ET.SubElement(self._complemento, f'{prefix}:CartaPorte', atributos)
node = ET.SubElement(node_carta, f'{prefix}:Ubicaciones')
for ubicacion in ubicaciones:
domicilio = ubicacion.pop('domicilio', {})
dt = parser.parse(ubicacion['FechaHoraSalidaLlegada'])
ubicacion['FechaHoraSalidaLlegada'] = dt.isoformat()[:19]
sub_node = ET.SubElement(node, f'{prefix}:Ubicacion', ubicacion)
if domicilio:
ET.SubElement(sub_node, f'{prefix}:Domicilio', domicilio)
attr = mercancias
mercancias = attr.pop('mercancias')
node = ET.SubElement(node_carta, f'{prefix}:Mercancias', attr)
for mercancia in mercancias:
ET.SubElement(node, f'{prefix}:Mercancia', mercancia)
sub_node = ET.SubElement(node, f'{prefix}:Autotransporte', autotransporte)
ET.SubElement(sub_node, f'{prefix}:IdentificacionVehicular', identificacion)
ET.SubElement(sub_node, f'{prefix}:Seguros', seguros)
if remolques:
node_remolques = ET.SubElement(sub_node, f'{prefix}:Remolques')
for remolque in remolques:
ET.SubElement(node_remolques, f'{prefix}:Remolque', remolque)
if tiposfigura:
sub_node = ET.SubElement(node_carta, f'{prefix}:FiguraTransporte')
for figura in tiposfigura:
ET.SubElement(sub_node, f'{prefix}:TiposFigura', figura)
if self._divisas:
atributos = {
'version': SAT['divisas']['version'],
'tipoOperacion': self._divisas,
}
ET.SubElement(self._complemento, 'divisas:Divisas', atributos)
if 'ine' in datos:
atributos = {'Version': SAT['ine']['version']}
ine_key_entidad = datos['ine'].pop('ClaveEntidad', '')
ine_ambito = datos['ine'].pop('Ambito', '')
if ine_key_entidad:
ine_id_conta = datos['ine'].pop('IdContabilidad', '')
atributos.update(datos['ine'])
ET.SubElement(self._complemento, 'ine:INE', atributos)
node_ine = ET.SubElement(self._complemento, 'ine:INE', atributos)
if ine_key_entidad:
attr = {'ClaveEntidad': ine_key_entidad}
if ine_ambito:
attr['Ambito'] = ine_ambito
node_entidad = ET.SubElement(node_ine, 'ine:Entidad', attr)
attr = {'IdContabilidad': ine_id_conta}
ET.SubElement(node_entidad, 'ine:Contabilidad', attr)
if 'pagos' in datos:
datos = datos.pop('pagos')
totales = datos.pop('totales')
relacionados = datos.pop('relacionados')
taxes_pay = datos.pop('taxes_pay')
pre = SAT['pagos']['prefix']
attributes = {'Version': SAT['pagos']['version']}
pagos = ET.SubElement(
self._complemento, '{}:Pagos'.format(pre), attributes)
ET.SubElement(pagos, '{}:Totales'.format(pre), totales)
node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), datos)
for row in relacionados:
ET.SubElement(node_pago, '{}:DoctoRelacionado'.format(pre), row)
taxes = row.pop('taxes')
node = ET.SubElement(node_pago, f'{pre}:DoctoRelacionado', row)
nodex_tax = None
if taxes['traslados'] or taxes['retenciones']:
node_tax = ET.SubElement(node, f'{pre}:ImpuestosDR')
if taxes['retenciones']:
node = ET.SubElement(node_tax, f'{pre}:RetencionesDR')
for tax in taxes['retenciones']:
ET.SubElement(node, f'{pre}:RetencionDR', tax)
if taxes['traslados']:
node = ET.SubElement(node_tax, f'{pre}:TrasladosDR')
for tax in taxes['traslados']:
ET.SubElement(node, f'{pre}:TrasladoDR', tax)
if 'ce' in datos:
pre = 'cce11'
datos = datos.pop('ce')
emisor = datos.pop('emisor')
propietario = datos.pop('propietario')
receptor = datos.pop('receptor')
destinatario = datos.pop('destinatario')
conceptos = datos.pop('conceptos')
if taxes_pay['traslados'] or taxes_pay['retenciones']:
node_tax = ET.SubElement(node_pago, f'{pre}:ImpuestosP')
if taxes_pay['retenciones']:
node = ET.SubElement(node_tax, f'{pre}:RetencionesP')
for key, importe in taxes_pay['retenciones'].items():
attr = {'ImpuestoP': key, 'ImporteP': importe}
ET.SubElement(node, f'{pre}:RetencionP', attr)
if taxes_pay['traslados']:
node = ET.SubElement(node_tax, f'{pre}:TrasladosP')
for key, tax in taxes_pay['traslados'].items():
ET.SubElement(node, f'{pre}:TrasladoP', tax)
attributes = {}
attributes['xmlns:{}'.format(pre)] = \
'http://www.sat.gob.mx/ComercioExterior11'
attributes['xsi:schemaLocation'] = \
'http://www.sat.gob.mx/ComercioExterior11 ' \
'http://www.sat.gob.mx/sitio_internet/cfd/ComercioExterior11/ComercioExterior11.xsd'
attributes.update(datos)
ce = ET.SubElement(
complemento, '{}:ComercioExterior'.format(pre), attributes)
if 'leyendas' in datos:
pre = SAT['leyendas']['prefix']
attributes = {'version': SAT['leyendas']['version']}
node_leyend = ET.SubElement(
self._complemento, '{}:LeyendasFiscales'.format(pre), attributes)
for leyend in datos['leyendas']:
ET.SubElement(node_leyend, '{}:Leyenda'.format(pre), leyend)
attributes = {}
if 'Curp' in emisor:
attributes = {'Curp': emisor.pop('Curp')}
node = ET.SubElement(ce, '{}:Emisor'.format(pre), attributes)
ET.SubElement(node, '{}:Domicilio'.format(pre), emisor)
if self._comercio_exterior:
datos = datos.pop('comercioe')
self._complemento_comercio_exterior(datos)
if propietario:
ET.SubElement(ce, '{}:Propietario'.format(pre), propietario)
attributes = {}
if 'NumRegIdTrib' in receptor:
attributes = {'NumRegIdTrib': receptor.pop('NumRegIdTrib')}
node = ET.SubElement(ce, '{}:Receptor'.format(pre), attributes)
ET.SubElement(node, '{}:Domicilio'.format(pre), receptor)
attributes = {}
if 'NumRegIdTrib' in destinatario:
attributes = {'NumRegIdTrib': destinatario.pop('NumRegIdTrib')}
if 'Nombre' in destinatario:
attributes.update({'Nombre': destinatario.pop('Nombre')})
node = ET.SubElement(ce, '{}:Destinatario'.format(pre), attributes)
ET.SubElement(node, '{}:Domicilio'.format(pre), destinatario)
node = ET.SubElement(ce, '{}:Mercancias'.format(pre))
fields = ('Marca', 'Modelo', 'SubModelo', 'NumeroSerie')
for row in conceptos:
detalle = {}
for f in fields:
if f in row:
detalle[f] = row.pop(f)
concepto = ET.SubElement(node, '{}:Mercancia'.format(pre), row)
if detalle:
ET.SubElement(
concepto, '{}:DescripcionesEspecificas'.format(pre), detalle)
return

View File

@ -1,13 +0,0 @@
#!/usr/bin/env python
DEBUG = False
#~ Ecodex
ID_INTEGRADOR = ''
#~ Finkok
FINKOK= {
'USER': '',
'PASS': '',
}

View File

@ -1,90 +0,0 @@
#!/usr/bin/env python
from .conf import DEBUG, ID_INTEGRADOR, FINKOK
DEBUG = DEBUG
TIMEOUT = 10
#~ PACs que han proporcionado un entorno de pruebas libre y abierto
#~ ecodex, finkok
PAC = 'finkok'
def ecodex(debug):
NEW_SERVER = True
auth = {'ID': ID_INTEGRADOR}
if debug:
#~ No cambies este ID de pruebas
auth = {'ID': '2b3a8764-d586-4543-9b7e-82834443f219'}
base_url = 'https://servicios.ecodex.com.mx:4043/Servicio{}.svc?wsdl'
if NEW_SERVER:
base_url = 'https://serviciosnominas.ecodex.com.mx:4043/Servicio{}.svc?wsdl'
base_api = 'https://api.ecodex.com.mx/{}'
if debug:
base_url = 'https://wsdev.ecodex.com.mx:2045/Servicio{}.svc?wsdl'
base_api = 'https://pruebasapi.ecodex.com.mx/{}'
url = {
'seguridad': base_url.format('Seguridad'),
'clients': base_url.format('Clientes'),
'timbra': base_url.format('Timbrado'),
'token': base_api.format('token?version=2'),
'docs': base_api.format('api/documentos'),
'hash': base_api.format('api/Documentos/{}'),
'codes': {
'HASH': 'DUPLICIDAD EN HASH',
}
}
return auth, url
#~ IMPORTANTE: Si quieres hacer pruebas, con tu propio correo de usuario y
#~ contraseña, ponte en contacto con Finkok para que te asignen tus datos de
#~ acceso, consulta su documentación para ver las diferentes opciones de acceso.
#~ Si solo estas haciendo pruebas de timbrado y ancelación, con estos datos debería
#~ ser suficiente.
def finkok(debug):
USER = FINKOK['USER']
PASS = FINKOK['PASS']
TOKEN = ''
auth = {
'DEBUG': debug,
'USER': '',
'PASS': TOKEN or PASS,
'RESELLER': {'USER': USER, 'PASS': PASS}
}
if debug:
USER = 'pruebas-finkok@correolibre.net'
PASS = ''
TOKEN = '5c9a88da105bff9a8c430cb713f6d35269f51674bdc5963c1501b7316366'
auth = {
'DEBUG': debug,
'USER': USER,
'PASS': TOKEN or PASS,
'RESELLER': {
'USER': '',
'PASS': ''
}
}
base_url = 'https://facturacion.finkok.com/servicios/soap/{}.wsdl'
if debug:
base_url = 'http://demo-facturacion.finkok.com/servicios/soap/{}.wsdl'
url = {
'timbra': base_url.format('stamp'),
'quick_stamp': False,
'cancel': base_url.format('cancel'),
'client': base_url.format('registration'),
'util': base_url.format('utilities'),
'codes': {
'200': 'Comprobante timbrado satisfactoriamente',
'307': 'Comprobante timbrado previamente',
'205': 'No Encontrado',
}
}
return auth, url
AUTH, URL = globals()[PAC](DEBUG)

View File

@ -1,11 +1,14 @@
#!/usr/bin/env python3
import io
import os
import re
import smtplib
import ssl
import collections
from xml.sax.saxutils import escape
from collections import OrderedDict
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
@ -29,6 +32,9 @@ from reportlab.platypus import Paragraph, Table, TableStyle, Spacer
from reportlab.pdfgen import canvas
CANCEL = False
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
class CaseInsensitiveDict(collections.MutableMapping):
"""A case-insensitive ``dict``-like object.
@ -263,9 +269,7 @@ class SendMail(object):
def _login(self):
try:
if self._config['ssl'] and (
'gmail' in self._config['servidor'] or
'outlook' in self._config['servidor']):
if self._config['ssl'] and self._config['starttls']:
self._server = smtplib.SMTP(
self._config['servidor'],
self._config['puerto'], timeout=10)
@ -287,7 +291,7 @@ class SendMail(object):
if '535' in str(e):
self._error = 'Nombre de usuario o contraseña inválidos'
return False
print (e)
# ~ print (e)
if '534' in str(e) and 'gmail' in self._config['servidor']:
self._error = 'Necesitas activar el acceso a otras ' \
'aplicaciones en tu cuenta de GMail'
@ -340,6 +344,7 @@ class SendMail(object):
class NumberedCanvas(canvas.Canvas):
X = 20.59 * cm
XC = 21.6 * cm / 2 + 1.5 * cm
Y = 1.5 * cm
def __init__(self, *args, **kwargs):
@ -357,19 +362,33 @@ class NumberedCanvas(canvas.Canvas):
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number(page_count)
self.draw_cancel()
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
return
def draw_page_number(self, page_count):
self.setFont('Helvetica', 8)
self.setFillColor(colors.darkred)
text = 'Página {} de {}'.format(self._pageNumber, page_count)
# ~ self.setFillColor(colors.darkred)
text = f'Página {self._pageNumber} de {page_count}'
self.drawRightString(self.X, self.Y, text)
text = 'Factura elaborada con software libre: www.empresalibre.net'
text = 'Este documento es una representación impresa de un CFDI'
self.drawCentredString(self.XC, self.Y, text)
text = 'Factura elaborada con software libre'
self.drawString(1.5 * cm, 1.5 * cm, text)
return
def draw_cancel(self):
global CANCEL
if CANCEL:
self.rotate(45)
self.setFont('Helvetica', 100)
self.setFillColor(colors.red)
text = 'Cancelada'
self.drawCentredString(20 * cm, 3 * cm, text)
return
class TemplateInvoice(BaseDocTemplate):
@ -418,6 +437,9 @@ class TemplateInvoice(BaseDocTemplate):
def _emisor(self, styles, data):
logo_path = data.pop('logo', '')
logo_style = styles.pop('logo', {})
logo_path2 = data.pop('logo2', '')
logo_style2 = styles.pop('logo2', {})
sucursales = styles.pop('sucursales', {})
for k, v in styles.items():
self._set_text(styles[k], data.get(k, ''))
@ -428,6 +450,28 @@ class TemplateInvoice(BaseDocTemplate):
for k in keys:
rect[k] = rect[k] * cm
self.canv.drawImage(logo_path, **rect)
if logo_path2 and logo_style2:
rect = logo_style2['rectangulo']
keys = ('x', 'y', 'width', 'height')
for k in keys:
rect[k] = rect[k] * cm
self.canv.drawImage(logo_path2, **rect)
if sucursales:
for k, sucursal in sucursales.items():
values = sucursal.pop('textos')
x = sucursal['rectangulo']['x']
y = sucursal['rectangulo']['y']
w = sucursal['rectangulo']['width']
h = sucursal['rectangulo']['height']
for v in values:
self._set_text(sucursal, v)
y -= h
sucursal['rectangulo']['x'] = x
sucursal['rectangulo']['y'] = y
sucursal['rectangulo']['width'] = w
sucursal['rectangulo']['height'] = h
return
def _receptor(self, styles, data):
@ -449,6 +493,7 @@ class TemplateInvoice(BaseDocTemplate):
def _comprobante1(self, styles, data):
title = styles.pop('titulo', {})
self.canv.setTitle(f"Factura {data.get('seriefolio', '')}")
for k, v in styles.items():
self._set_text(styles[k], data.get(k, ''))
@ -480,6 +525,12 @@ class TemplateInvoice(BaseDocTemplate):
fields = ('valorunitario', 'importe')
if field in fields:
return self._currency(value)
if field == 'descripcion':
html = '<font color="black" size=7>{}</font>'
style_bt = getSampleStyleSheet()['BodyText']
return Paragraph(html.format(value), style_bt)
return value
def _conceptos(self, conceptos):
@ -515,8 +566,8 @@ class TemplateInvoice(BaseDocTemplate):
('GRID', (0, 0), (-1, -1), 0.05 * cm, colors.white),
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
('FONTSIZE', (0, 0), (-1, -1), 8),
('BACKGROUND', (1, 0), (-1, -1), colors.linen),
('TEXTCOLOR', (1, 0), (-1, -1), colors.darkred),
('BACKGROUND', (1, 0), (-1, -1), colors.lightgrey),
('TEXTCOLOR', (1, 0), (-1, -1), colors.black),
('FACE', (1, 0), (-1, -1), 'Helvetica-Bold'),
]
table = Table(rows, colWidths=widths, spaceBefore=0.25*cm)
@ -524,7 +575,7 @@ class TemplateInvoice(BaseDocTemplate):
return table
def _comprobante2(self, styles, data):
leyenda = styles.pop('leyenda', {})
leyendas = styles.pop('leyendas', {})
ls = []
for k, v in styles.items():
@ -532,17 +583,27 @@ class TemplateInvoice(BaseDocTemplate):
if 'spaceBefore' in v['estilo']:
v['estilo']['spaceBefore'] = v['estilo']['spaceBefore'] * cm
ps = ParagraphStyle(**v['estilo'])
p = Paragraph(data[k], ps)
p = Paragraph(escape(data[k]), ps)
ls.append(p)
elif k=='formametodopago':
ps = ParagraphStyle(**v['estilo'])
v = f"{data['formadepago']} - {data['metododepago']}"
p = Paragraph(v, ps)
ls.append(p)
elif k=='monedatipocambio':
ps = ParagraphStyle(**v['estilo'])
v = f"{data['moneda']} - {data['tipocambio']}"
p = Paragraph(v, ps)
ls.append(p)
cbb = Image(data['path_cbb'])
cbb = Image(data['cbb'])
cbb.drawHeight = 4 * cm
cbb.drawWidth = 4 * cm
style_bt = getSampleStyleSheet()['BodyText']
style_bt.leading = 8
html_t = '<b><font size=6>{}</font></b>'
html = '<font color="darkred" size=5>{}</font>'
html = '<font color="black" size=5>{}</font>'
msg = 'Cadena original del complemento de certificación digital del SAT'
rows = [
(cbb, Paragraph(html_t.format('Sello Digital del CFDI'), style_bt)),
@ -558,13 +619,13 @@ class TemplateInvoice(BaseDocTemplate):
('FONTSIZE', (0, 0), (-1, -1), 6),
('SPAN', (0, 0), (0, -1)),
('FACE', (1, 0), (1, 0), 'Helvetica-Bold'),
('BACKGROUND', (1, 1), (1, 1), colors.linen),
('BACKGROUND', (1, 1), (1, 1), colors.lightgrey),
('TEXTCOLOR', (1, 1), (1, 1), colors.darkred),
('FACE', (1, 2), (1, 2), 'Helvetica-Bold'),
('BACKGROUND', (1, 3), (1, 3), colors.linen),
('BACKGROUND', (1, 3), (1, 3), colors.lightgrey),
('TEXTCOLOR', (1, 3), (1, 3), colors.darkred),
('FACE', (1, 4), (1, 4), 'Helvetica-Bold'),
('BACKGROUND', (1, 5), (1, 5), colors.linen),
('BACKGROUND', (1, 5), (1, 5), colors.lightgrey),
('TEXTCOLOR', (1, 5), (1, 5), colors.darkred),
('ALIGN', (0, 0), (0, 0), 'CENTER'),
('VALIGN', (0, 0), (0, 0), 'MIDDLE'),
@ -573,14 +634,14 @@ class TemplateInvoice(BaseDocTemplate):
table.setStyle(TableStyle(table_styles))
ls.append(table)
if leyenda:
if 'spaceBefore' in leyenda['estilo']:
leyenda['estilo']['spaceBefore'] = \
leyenda['estilo']['spaceBefore'] * cm
msg = 'Este documento es una representación impresa de un CFDI'
ps = ParagraphStyle(**leyenda['estilo'])
p = Paragraph(msg, ps)
ls.append(p)
if leyendas:
if 'spaceBefore' in leyendas['estilo']:
leyendas['estilo']['spaceBefore'] = \
leyendas['estilo']['spaceBefore'] * cm
for t in leyendas['textos']:
ps = ParagraphStyle(**leyendas['estilo'])
p = Paragraph(t, ps)
ls.append(p)
return ls
@ -596,8 +657,10 @@ class TemplateInvoice(BaseDocTemplate):
return self._data
@data.setter
def data(self, values):
#~ print (values)
global CANCEL
# ~ print (values)
self._data = values
CANCEL = self._data['cancelada']
rows = self._conceptos(self._data['conceptos'])
widths = [2 * cm, 9 * cm, 1.5 * cm, 2 * cm, 2 * cm, 3 * cm]
@ -607,13 +670,13 @@ class TemplateInvoice(BaseDocTemplate):
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FACE', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BACKGROUND', (0, 0), (-1, 0), colors.darkred),
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('FONTSIZE', (0, 1), (-1, -1), 7),
('VALIGN', (0, 1), (-1, -1), 'TOP'),
('ALIGN', (0, 1), (0, -1), 'CENTER'),
('ALIGN', (2, 1), (2, -1), 'CENTER'),
('ALIGN', (3, 1), (5, -1), 'RIGHT'),
('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.darkred),
('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.grey),
('LINEBEFORE', (0, 1), (-1, -1), 0.05 * cm, colors.white),
]
table_conceptos = Table(rows, colWidths=widths, repeatRows=1)
@ -623,7 +686,7 @@ class TemplateInvoice(BaseDocTemplate):
comprobante = self._comprobante2(
self._custom_styles['comprobante'], self.data['comprobante'])
self._rows = [Spacer(0, 6*cm), table_conceptos, totales] + comprobante
self._rows = [Spacer(0, 5.7 * cm), table_conceptos, totales] + comprobante
def render(self):
frame = Frame(self.leftMargin, self.bottomMargin,
@ -1054,4 +1117,4 @@ class PrintTicket(object):
self._t(self.LEYENDA)
self._set('center', 'A', 'B')
self._t('empresalibre.net')
return
return

View File

@ -2,6 +2,7 @@
import falcon
from middleware import get_template
from urllib.parse import unquote
class AppEmpresas(object):
@ -47,6 +48,7 @@ class AppLogin(object):
session.invalidate()
values = req.params
values['rfc'] = values['rfc'].upper()
values['ip'] = req.remote_addr
result, user = self._db.authenticate(values)
if result['login']:
session.save()
@ -110,6 +112,14 @@ class AppValues(object):
def on_get(self, req, resp, table):
values = req.params
session = req.env['beaker.session']
if table == 'product':
original = values['name']
try:
values['name'] = unquote(req.query_string.split('=')[1])
except Exception as e:
values['name'] = original
if req.path in ('/values/titlelogin', '/values/empresas'):
req.context['result'] = self._db.get_values(table, values, session)
resp.status = falcon.HTTP_200
@ -278,7 +288,12 @@ class AppProducts(object):
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_products(values)
user = req.env['beaker.session']['userobj']
if 'opt' in values:
req.context['result'] = self._db.products_get(values, user)
else:
req.context['result'] = self._db.get_products(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
@ -301,7 +316,8 @@ class AppInvoices(object):
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_invoices(values)
session = req.env['beaker.session']
req.context['result'] = self._db.get_invoices(values, session['userobj'])
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
@ -310,6 +326,12 @@ class AppInvoices(object):
req.context['result'] = self._db.invoice(values, session['userobj'])
resp.status = falcon.HTTP_200
def on_put(self, req, resp):
values = req.params
session = req.env['beaker.session']
req.context['result'] = self._db.invoice_put(values, session['userobj'])
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
session = req.env['beaker.session']
@ -349,7 +371,8 @@ class AppTickets(object):
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_tickets(values)
session = req.env['beaker.session']
req.context['result'] = self._db.get_tickets(values, session['userobj'])
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
@ -485,12 +508,24 @@ class AppNomina(object):
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_nomina(values)
user = req.env['beaker.session']['userobj']
if 'opt' in values:
req.context['result'] = self._db.nomina_get(values, user)
else:
by = values.get('by', '')
req.context['result'] = self._db.get_nomina(values)
if 'download' in by:
name = req.context['result']['name']
req.context['blob'] = req.context['result']['data']
resp.content_type = 'application/octet-stream'
resp.append_header(
'Content-Disposition', f'attachment; filename={name}')
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.nomina(values)
session = req.env['beaker.session']
req.context['result'] = self._db.nomina(values, session['userobj'])
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
@ -507,12 +542,16 @@ class AppDocumentos(object):
self._db = db
def on_get(self, req, resp, type_doc, id_doc):
# ~ print('TD', type_doc)
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', 'nompdf'):
resp.append_header('Content-Disposition',
'attachment; filename={}'.format(file_name))
if type_doc in ('pdf', 'nompdf', 'pdfpago'):
resp.append_header('Content-Disposition',
'inline; filename={}'.format(file_name))
resp.content_type = content_type
resp.status = falcon.HTTP_200
@ -577,6 +616,29 @@ class AppSATFormaPago(object):
resp.status = falcon.HTTP_200
class AppSATLeyendaFiscales(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.sat_leyendas_fiscales_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.sat_leyendas_fiscales_post(values)
resp.status = falcon.HTTP_200
def on_delete(self, req, resp):
values = req.params
if self._db.sat_leyendas_fiscales_delete(values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204
class AppSociosCuentasBanco(object):
def __init__(self, db):
@ -592,3 +654,174 @@ class AppSociosCuentasBanco(object):
req.context['result'] = self._db.partners_accounts_bank(values)
resp.status = falcon.HTTP_200
class AppCert(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.cert_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.cert_post(values)
resp.status = falcon.HTTP_200
class AppSucursales(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.sucursales_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.sucursales_post(values)
resp.status = falcon.HTTP_200
class AppPartnerProducts(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.partner_products_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.partner_products_post(values)
resp.status = falcon.HTTP_200
class AppInventoryEntries(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.inventory_entries_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.inventory_entries_post(values, user)
resp.status = falcon.HTTP_200
class AppWareHouse(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.warehouse_get(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.warehouse_post(values, user)
resp.status = falcon.HTTP_200
class AppWareHouseProduct(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.warehouseproduct_get(values, user)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.warehouseproduct_post(values, user)
resp.status = falcon.HTTP_200
class AppTicketsDetails(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.ticketsdetails_get(values, user)
resp.status = falcon.HTTP_200
class AppUsers(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.users_get(values, user)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
req.context['result'] = self._db.users_post(values, user)
resp.status = falcon.HTTP_200
class AppSATUnidadesPeso(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_unidades_peso_get(values, user)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
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
class AppSociosRegimenes(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.socios_regimenes_get(values, user)
resp.status = falcon.HTTP_200

View File

@ -1,735 +0,0 @@
#!/usr/bin/env python
#~ import re
#~ from xml.etree import ElementTree as ET
#~ from requests import Request, Session, exceptions
import datetime
import hashlib
import os
import requests
import time
from lxml import etree
from xml.dom.minidom import parseString
from xml.sax.saxutils import escape, unescape
from uuid import UUID
from logbook import Logger
from zeep import Client
from zeep.plugins import HistoryPlugin
from zeep.cache import SqliteCache
from zeep.transports import Transport
from zeep.exceptions import Fault, TransportError
from requests.exceptions import ConnectionError
if __name__ == '__main__':
from configpac import DEBUG, TIMEOUT, AUTH, URL
else:
from .configpac import DEBUG, TIMEOUT, AUTH, URL
log = Logger('PAC')
#~ node = client.create_message(client.service, SERVICE, **args)
#~ print(etree.tostring(node, pretty_print=True).decode())
class Ecodex(object):
def __init__(self, auth, url):
self.auth = auth
self.url = url
self.codes = self.url['codes']
self.error = ''
self.message = ''
self._transport = Transport(cache=SqliteCache(), timeout=TIMEOUT)
self._plugins = None
self._history = None
if DEBUG:
self._history = HistoryPlugin()
self._plugins = [self._history]
def _get_token(self, rfc):
client = Client(self.url['seguridad'],
transport=self._transport, plugins=self._plugins)
try:
result = client.service.ObtenerToken(rfc, self._get_epoch())
except Fault as e:
self.error = str(e)
log.error(self.error)
return ''
s = '{}|{}'.format(self.auth['ID'], result.Token)
return hashlib.sha1(s.encode()).hexdigest()
def _get_token_rest(self, rfc):
data = {
'rfc': rfc,
'grant_type': 'authorization_token',
}
headers = {'Content-type': 'application/x-www-form-urlencoded'}
result = requests.post(URL['token'], data=data, headers=headers)
data = result.json()
s = '{}|{}'.format(AUTH['ID'], data['service_token'])
return hashlib.sha1(s.encode()).hexdigest(), data['access_token']
def _validate_xml(self, xml):
NS_CFDI = {'cfdi': 'http://www.sat.gob.mx/cfd/3'}
if os.path.isfile(xml):
tree = etree.parse(xml).getroot()
else:
tree = etree.fromstring(xml.encode())
fecha = tree.get('Fecha')
rfc = tree.xpath('string(//cfdi:Emisor/@Rfc)', namespaces=NS_CFDI)
data = {
'ComprobanteXML': etree.tostring(tree).decode(),
'RFC': rfc,
'Token': self._get_token(rfc),
'TransaccionID': self._get_epoch(fecha),
}
return data
def _get_by_hash(self, sh, rfc):
token, access_token = self._get_token_rest(rfc)
url = URL['hash'].format(sh)
headers = {
'Authorization': 'Bearer {}'.format(access_token),
'X-Auth-Token': token,
}
result = requests.get(url, headers=headers)
if result.status_code == 200:
print (result.json())
return
def timbra_xml(self, xml):
data = self._validate_xml(xml)
client = Client(self.url['timbra'],
transport=self._transport, plugins=self._plugins)
try:
result = client.service.TimbraXML(**data)
except Fault as e:
error = str(e)
if self.codes['HASH'] in error:
sh = error.split(' ')[3]
return self._get_by_hash(sh[:40], data['RFC'])
self.error = error
return ''
tree = parseString(result.ComprobanteXML.DatosXML)
xml = tree.toprettyxml(encoding='utf-8').decode('utf-8')
return xml
def _get_epoch(self, date=None):
if isinstance(date, str):
f = '%Y-%m-%dT%H:%M:%S'
e = int(time.mktime(time.strptime(date, f)))
else:
date = datetime.datetime.now()
e = int(time.mktime(date.timetuple()))
return e
def estatus_cuenta(self, rfc):
#~ Codigos:
#~ 100 = Cuenta encontrada
#~ 101 = RFC no dado de alta en el sistema ECODEX
token = self._get_token(rfc)
if not token:
return {}
data = {
'RFC': rfc,
'Token': token,
'TransaccionID': self._get_epoch()
}
client = Client(URL['clients'],
transport=self._transport, plugins=self._plugins)
try:
result = client.service.EstatusCuenta(**data)
except Fault as e:
log.error(str(e))
return
#~ print (result)
return result.Estatus
class Finkok(object):
def __init__(self, auth={}):
self.codes = URL['codes']
self.error = ''
self.message = ''
self._transport = Transport(cache=SqliteCache(), timeout=TIMEOUT)
self._plugins = None
self._history = None
self.uuid = ''
self.fecha = None
if DEBUG:
self._history = HistoryPlugin()
self._plugins = [self._history]
self._auth = AUTH
else:
self._auth = auth
def _debug(self):
if not DEBUG:
return
print('SEND', self._history.last_sent)
print('RESULT', self._history.last_received)
return
def _check_result(self, method, result):
# ~ print ('CODE', result.CodEstatus)
# ~ print ('INCIDENCIAS', result.Incidencias)
self.message = ''
MSG = {
'OK': 'Comprobante timbrado satisfactoriamente',
'307': 'Comprobante timbrado previamente',
}
status = result.CodEstatus
if status is None and result.Incidencias:
for i in result.Incidencias['Incidencia']:
self.error += 'Error: {}\n{}\n{}'.format(
i['CodigoError'], i['MensajeIncidencia'], i['ExtraInfo'])
return ''
if method == 'timbra' and status in (MSG['OK'], MSG['307']):
#~ print ('UUID', result.UUID)
#~ print ('FECHA', result.Fecha)
if status == MSG['307']:
self.message = MSG['307']
tree = parseString(result.xml)
response = tree.toprettyxml(encoding='utf-8').decode('utf-8')
self.uuid = result.UUID
self.fecha = result.Fecha
return response
def _load_file(self, path):
try:
with open(path, 'rb') as f:
data = f.read()
except Exception as e:
self.error = str(e)
return
return data
def _validate_xml(self, file_xml):
if os.path.isfile(file_xml):
try:
with open(file_xml, 'rb') as f:
xml = f.read()
except Exception as e:
self.error = str(e)
return False, ''
else:
xml = file_xml.encode('utf-8')
return True, xml
def _validate_uuid(self, uuid):
try:
UUID(uuid)
return True
except ValueError:
self.error = 'UUID no válido: {}'.format(uuid)
return False
def timbra_xml(self, file_xml):
self.error = ''
if not DEBUG and not self._auth:
self.error = 'Sin datos para timbrar'
return
method = 'timbra'
ok, xml = self._validate_xml(file_xml)
if not ok:
return ''
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'xml': xml,
}
if URL['quick_stamp']:
try:
result = client.service.quick_stamp(**args)
except Fault as e:
self.error = str(e)
return
else:
try:
result = client.service.stamp(**args)
except Fault as e:
self.error = str(e)
return
except TransportError as e:
if '413' in str(e):
self.error = '413<BR><BR><b>Documento muy grande para timbrar</b>'
else:
self.error = str(e)
return
except ConnectionError as e:
msg = '502 - Error de conexión'
self.error = msg
return
return self._check_result(method, result)
def _get_xml(self, uuid):
if not self._validate_uuid(uuid):
return ''
method = 'util'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'uuid': uuid,
'taxpayer_id': self.rfc,
'invoice_type': 'I',
}
try:
result = client.service.get_xml(**args)
except Fault as e:
self.error = str(e)
return ''
except TransportError as e:
self.error = str(e)
return ''
if result.error:
self.error = result.error
return ''
tree = parseString(result.xml)
xml = tree.toprettyxml(encoding='utf-8').decode('utf-8')
return xml
def recupera_xml(self, file_xml='', uuid=''):
self.error = ''
if uuid:
return self._get_xml(uuid)
method = 'timbra'
ok, xml = self._validate_xml(file_xml)
if not ok:
return ''
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
try:
result = client.service.stamped(
xml, self._auth['user'], self._auth['pass'])
except Fault as e:
self.error = str(e)
return ''
return self._check_result(method, result)
def estatus_xml(self, uuid):
method = 'timbra'
if not self._validate_uuid(uuid):
return ''
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
try:
result = client.service.query_pending(
self._auth['USER'], self._auth['PASS'], uuid)
return result.status
except Fault as e:
self.error = str(e)
return ''
def cancel_xml(self, rfc, uuid, cer, key):
# ~ for u in uuids:
# ~ if not self._validate_uuid(u):
# ~ return ''
method = 'cancel'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
uuid_type = client.get_type('ns1:UUIDS')
sa = client.get_type('ns0:stringArray')
args = {
'UUIDS': uuid_type(uuids=sa(string=uuid)),
'username': self._auth['USER'],
'password': self._auth['PASS'],
'taxpayer_id': rfc,
'cer': cer,
'key': key,
'store_pending': False,
}
try:
result = client.service.cancel(**args)
except Fault as e:
self.error = str(e)
return ''
if result.CodEstatus and self.codes['205'] in result.CodEstatus:
self.error = result.CodEstatus
return ''
return result
def cancel_signature(self, file_xml):
method = 'cancel'
if os.path.isfile(file_xml):
root = etree.parse(file_xml).getroot()
else:
root = etree.fromstring(file_xml.encode())
xml = etree.tostring(root)
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'xml': xml,
'store_pending': False,
}
try:
result = client.service.cancel_signature(**args)
return result
except Fault as e:
self.error = str(e)
return ''
def get_acuse(self, rfc, uuids, type_acuse='C'):
for u in uuids:
if not self._validate_uuid(u):
return ''
method = 'cancel'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'taxpayer_id': rfc,
'uuid': '',
'type': type_acuse,
}
try:
result = []
for u in uuids:
args['uuid'] = u
r = client.service.get_receipt(**args)
result.append(r)
except Fault as e:
self.error = str(e)
return ''
return result
def estatus_cancel(self, uuids):
for u in uuids:
if not self._validate_uuid(u):
return ''
method = 'cancel'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': self._auth['USER'],
'password': self._auth['PASS'],
'uuid': '',
}
try:
result = []
for u in uuids:
args['uuid'] = u
r = client.service.query_pending_cancellation(**args)
result.append(r)
except Fault as e:
self.error = str(e)
return ''
return result
def add_token(self, rfc, email):
"""Agrega un nuevo token al cliente para timbrado.
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del cliente, ya debe existir
email (str): El correo del cliente, funciona como USER al timbrar
Returns:
dict
'username': 'username',
'status': True or False
'name': 'name',
'success': True or False
'token': 'Token de timbrado',
'message': None
"""
auth = AUTH['RESELLER']
method = 'util'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': auth['USER'],
'password': auth['PASS'],
'name': rfc,
'token_username': email,
'taxpayer_id': rfc,
'status': True,
}
try:
result = client.service.add_token(**args)
except Fault as e:
self.error = str(e)
return ''
return result
def get_date(self):
method = 'util'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
try:
result = client.service.datetime(AUTH['USER'], AUTH['PASS'])
except Fault as e:
self.error = str(e)
return ''
if result.error:
self.error = result.error
return
return result.datetime
def add_client(self, rfc, type_user=False):
"""Agrega un nuevo cliente para timbrado.
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del nuevo cliente
Kwargs:
type_user (bool): False == 'P' == Prepago or True == 'O' == On demand
Returns:
dict
'message':
'Account Created successfully'
'Account Already exists'
'success': True or False
"""
auth = AUTH['RESELLER']
tu = {False: 'P', True: 'O'}
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['USER'],
'reseller_password': auth['PASS'],
'taxpayer_id': rfc,
'type_user': tu[type_user],
'added': datetime.datetime.now().isoformat()[:19],
}
try:
result = client.service.add(**args)
except Fault as e:
self.error = str(e)
return ''
return result
def edit_client(self, rfc, status=True):
"""
Se requiere cuenta de reseller para usar este método
status = 'A' or 'S'
"""
auth = AUTH['RESELLER']
sv = {False: 'S', True: 'A'}
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['USER'],
'reseller_password': auth['PASS'],
'taxpayer_id': rfc,
'status': sv[status],
}
try:
result = client.service.edit(**args)
except Fault as e:
self.error = str(e)
return ''
return result
def get_client(self, rfc):
"""Regresa el estatus del cliente
.
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del emisor
Returns:
dict
'message': None,
'users': {
'ResellerUser': [
{
'status': 'A',
'counter': 0,
'taxpayer_id': '',
'credit': 0
}
]
} or None si no existe
"""
auth = AUTH['RESELLER']
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['USER'],
'reseller_password': auth['PASS'],
'taxpayer_id': rfc,
}
try:
result = client.service.get(**args)
except Fault as e:
self.error = str(e)
return ''
except TransportError as e:
self.error = str(e)
return ''
return result
def assign_client(self, rfc, credit):
"""Agregar credito a un emisor
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del emisor, debe existir
credit (int): Cantidad de folios a agregar
Returns:
dict
'success': True or False,
'credit': nuevo credito despues de agregar or None
'message':
'Success, added {credit} of credit to {RFC}'
'RFC no encontrado'
"""
auth = AUTH['RESELLER']
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': auth['USER'],
'password': auth['PASS'],
'taxpayer_id': rfc,
'credit': credit,
}
try:
result = client.service.assign(**args)
except Fault as e:
self.error = str(e)
return ''
return result
def client_get_timbres(self, rfc):
method = 'client'
client = Client(
URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': self._auth['USER'],
'reseller_password': self._auth['PASS'],
'taxpayer_id': rfc,
}
try:
self.result = client.service.get(**args)
except Fault as e:
self.error = str(e)
return 0
except TransportError as e:
self.error = str(e)
return 0
except ConnectionError:
self.error = 'Verifica la conexión a internet'
return 0
success = bool(self.result.users)
if not success:
self.error = self.result.message or 'RFC no existe'
return 0
return self.result.users.ResellerUser[0].credit
def _get_data_sat(path):
BF = 'string(//*[local-name()="{}"]/@{})'
NS_CFDI = {'cfdi': 'http://www.sat.gob.mx/cfd/3'}
try:
if os.path.isfile(path):
tree = etree.parse(path).getroot()
else:
tree = etree.fromstring(path.encode())
data = {}
emisor = escape(
tree.xpath('string(//cfdi:Emisor/@rfc)', namespaces=NS_CFDI) or
tree.xpath('string(//cfdi:Emisor/@Rfc)', namespaces=NS_CFDI)
)
receptor = escape(
tree.xpath('string(//cfdi:Receptor/@rfc)', namespaces=NS_CFDI) or
tree.xpath('string(//cfdi:Receptor/@Rfc)', namespaces=NS_CFDI)
)
data['total'] = tree.get('total') or tree.get('Total')
data['emisor'] = emisor
data['receptor'] = receptor
data['uuid'] = tree.xpath(BF.format('TimbreFiscalDigital', 'UUID'))
except Exception as e:
print (e)
return {}
return '?re={emisor}&rr={receptor}&tt={total}&id={uuid}'.format(**data)
def get_status_sat(xml):
data = _get_data_sat(xml)
if not data:
return 'XML inválido'
URL = 'https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc?wsdl'
client = Client(URL, transport=Transport(cache=SqliteCache()))
try:
result = client.service.Consulta(expresionImpresa=data)
except Exception as e:
return 'Error: {}'.format(str(e))
return result.Estado
def main():
return
if __name__ == '__main__':
main()

View File

@ -0,0 +1,4 @@
#!/usr/bin/env python
from .comerciodigital import PACComercioDigital
from .finkok import PACFinkok

View File

@ -0,0 +1,350 @@
#!/usr/bin/env python3
import argparse
import base64
import datetime
import getpass
import hashlib
import subprocess
from pathlib import Path
import lxml.etree as ET
import xmlsec
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.x509.oid import ExtensionOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from conf import TOKEN
class SATCertificate(object):
def __init__(self, cer=b'', key=b'', password=''):
self._error = ''
self._init_values()
self._get_data_cer(cer)
self._get_data_key(key, password)
# ~ if not password:
# ~ self._test()
# ~ def _test(self):
# ~ key = self._get_key('')
# ~ self._p = ''
# ~ self._key_der = key.private_bytes(
# ~ encoding=serialization.Encoding.DER,
# ~ format=serialization.PrivateFormat.PKCS8,
# ~ encryption_algorithm=serialization.BestAvailableEncryption(self._p.encode())
# ~ )
# ~ return
def _init_values(self):
self._rfc = ''
self._serial_number = ''
self._serial_number2 = ''
self._subject = ''
self._issuer = ''
self._not_before = None
self._not_after = None
self._is_fiel = False
self._are_couple = False
self._is_valid_time = False
self._key = b''
self._cer = b''
self._cer_pem = ''
self._cer_txt = ''
self._key_enc = b''
self._key_der = b''
self._p12 = b''
self._cer_modulus = 0
self._key_modulus = 0
return
def __str__(self):
msg = '\tRFC: {}\n'.format(self.rfc)
msg += '\tNo de Serie: {}\n'.format(self.serial_number)
msg += '\tVálido desde: {}\n'.format(self.not_before)
msg += '\tVálido hasta: {}\n'.format(self.not_after)
msg += '\tEs vigente: {}\n'.format(self.is_valid_time)
msg += '\tSon pareja: {}\n'.format(self.are_couple)
msg += '\tEs FIEL: {}\n'.format(self.is_fiel)
return msg
def __bool__(self):
return self.is_valid
def _get_hash(self):
digest = hashes.Hash(hashes.SHA512(), default_backend())
digest.update(self._rfc.encode())
digest.update(self._serial_number.encode())
digest.update(TOKEN.encode())
return digest.finalize()
def _get_data_cer(self, cer):
self._cer = cer
obj = x509.load_der_x509_certificate(cer, default_backend())
self._issuer = obj.issuer.rfc4514_string()
self._subject = obj.subject.rfc4514_string()
self._rfc = obj.subject.get_attributes_for_oid(
NameOID.X500_UNIQUE_IDENTIFIER)[0].value.split(' ')[0]
self._serial_number2 = '{0:x}'.format(obj.serial_number)
self._serial_number = self._serial_number2[1::2]
self._not_before = obj.not_valid_before
self._not_after = obj.not_valid_after
now = datetime.datetime.utcnow()
self._is_valid_time = (now > self.not_before) and (now < self.not_after)
if not self._is_valid_time:
msg = 'El certificado no es vigente'
self._error = msg
self._is_fiel = obj.extensions.get_extension_for_oid(
ExtensionOID.KEY_USAGE).value.key_agreement
self._cer_pem = obj.public_bytes(serialization.Encoding.PEM).decode()
self._cer_txt = ''.join(self._cer_pem.split('\n')[1:-2])
self._cer_modulus = obj.public_key().public_numbers().n
return
def _get_data_key(self, key, password):
self._key = key
self._keyp = password
self._key_enc = key
if not key or not password:
return
try:
obj = serialization.load_der_private_key(
key, password.encode(), default_backend())
except ValueError:
msg = 'La contraseña es incorrecta'
self._error = msg
return
p = self._get_hash()
self._key_enc = obj.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(p)
)
self._key_modulus = obj.public_key().public_numbers().n
self._are_couple = self._cer_modulus == self._key_modulus
if not self._are_couple:
msg = 'El CER y el KEY no son pareja'
self._error = msg
return
def _get_key(self, password):
if not password:
password = self._get_hash()
private_key = serialization.load_pem_private_key(
self._key_enc, password=password, backend=default_backend())
return private_key
def _get_key_pem(self):
obj = self._get_key('')
key_pem = obj.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
return key_pem
# Not work
def _get_p12(self):
obj = serialization.pkcs12.serialize_key_and_certificates('test',
self.key_pem, self.cer_pem, None,
encryption_algorithm=serialization.NoEncryption()
)
return obj
def sign(self, data, password=''):
private_key = self._get_key(password)
firma = private_key.sign(data, padding.PKCS1v15(), hashes.SHA256())
return base64.b64encode(firma).decode()
def sign_xml(self, tree):
node = xmlsec.tree.find_node(tree, xmlsec.constants.NodeSignature)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self.key_pem, xmlsec.constants.KeyDataFormatPem)
ctx.key = key
ctx.sign(node)
node = xmlsec.tree.find_node(tree, 'X509Certificate')
node.text = self.cer_txt
node = xmlsec.tree.find_node(tree, 'X509IssuerName')
node.text = self.issuer
node = xmlsec.tree.find_node(tree, 'X509SerialNumber')
node.text = self.serial_number
node = xmlsec.tree.find_node(tree, 'SignatureValue')
node.text = node.text.replace('\n', '')
node = xmlsec.tree.find_node(tree, 'Modulus')
node.text = node.text.replace('\n', '')
# ~ xml_signed = ET.tostring(tree,
# ~ xml_declaration=True, encoding='UTF-8').decode()
xml_signed = ET.tostring(tree, encoding='UTF-8').decode().replace('\n', '')
return xml_signed
@property
def rfc(self):
return self._rfc
@property
def serial_number(self):
return self._serial_number
@property
def serial_number2(self):
return self._serial_number2
@property
def issuer(self):
return self._issuer
@property
def subject(self):
return self._subject
@property
def not_before(self):
return self._not_before
@property
def not_after(self):
return self._not_after
@property
def is_fiel(self):
return self._is_fiel
@property
def are_couple(self):
return self._are_couple
@property
def is_valid(self):
return not bool(self.error)
@property
def is_valid_time(self):
return self._is_valid_time
@property
def cer(self):
return self._cer
@property
def cer_pem(self):
return self._cer_pem.encode()
@property
def cer_txt(self):
return self._cer_txt
@property
def key_pem(self):
return self._get_key_pem()
@property
def key_enc(self):
return self._key_enc
@property
def p12(self):
return self._get_p12()
@property
def error(self):
return self._error
@classmethod
def save_cert(cls, obj):
import hashlib
cer = x509.load_pem_x509_certificate(obj.cer_pem.encode(), default_backend())
cer_der = cer.public_bytes(serialization.Encoding.DER)
token = hashlib.md5(obj.rfc.encode()).hexdigest()
path = Path(f'/tmp/{obj.rfc}.key')
path.write_text(obj.key_enc)
args = f'openssl rsa -inform PEM -outform PEM -in "{str(path)}" -passin pass:{token}'
pem = subprocess.check_output(args, shell=True).decode()
key = serialization.load_pem_private_key(
pem.encode(), password=None, backend=default_backend())
digest = hashes.Hash(hashes.SHA512(), default_backend())
digest.update(obj.rfc.encode())
digest.update(obj.serie.encode())
digest.update(TOKEN.encode())
p = digest.finalize()
key_enc = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(p)
)
cert = SATCertificate(cer_der, key_enc)
obj.key_enc = cert.key_enc
obj.cer = cert.cer
obj.serie = cert.serial_number
obj.desde = cert.not_before
obj.hasta = cert.not_after
obj.save()
return
def main(args):
# ~ contra = getpass.getpass('Introduce la contraseña del archivo KEY: ')
contra = '12345678a'
if not contra.strip():
msg = 'La contraseña es requerida'
print(msg)
return
path_cer = Path(args.cer)
path_key = Path(args.key)
if not path_cer.is_file():
msg = 'El archivo CER es necesario'
print(msg)
return
if not path_key.is_file():
msg = 'El archivo KEY es necesario'
print(msg)
return
cer = path_cer.read_bytes()
key = path_key.read_bytes()
cert = SATCertificate(cer, key, contra)
if cert.error:
print(cert.error)
else:
print(cert)
return
def _process_command_line_arguments():
parser = argparse.ArgumentParser(description='CFDI Certificados')
help = 'Archivo CER'
parser.add_argument('-c', '--cer', help=help, default='')
help = 'Archivo KEY'
parser.add_argument('-k', '--key', help=help, default='')
args = parser.parse_args()
return args
if __name__ == '__main__':
args = _process_command_line_arguments()
main(args)

View File

@ -0,0 +1,3 @@
#!/usr/bin/env python3
from .comercio import PACComercioDigital

View File

@ -0,0 +1,431 @@
#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ 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 of the License, 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
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~
# ~ You should have received a copy of the GNU General Public License
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import logging
import lxml.etree as ET
import requests
from requests.exceptions import ConnectionError
from .conf import DEBUG
# ~ , AUTH
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
log = logging.getLogger(__name__)
logging.getLogger('requests').setLevel(logging.ERROR)
TIMEOUT = 10
NAMESPACES = {
'3.3': 'http://www.sat.gob.mx/cfd/3',
'4.0': 'http://www.sat.gob.mx/cfd/4',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
def pretty_print_POST(req):
"""
At this point it is completely built and ready
to be fired; it is "prepared".
However pay attention at the formatting used in
this function because it is programmed to be pretty
printed and may differ from the actual request.
"""
print('{}\n{}\r\n{}\r\n\r\n{}'.format(
'-----------START-----------',
req.method + ' ' + req.url,
'\r\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
req.body,
))
class PACComercioDigital(object):
ws = 'https://{}.comercio-digital.mx/{}'
api = 'https://app2.comercio-digital.mx/{}'
URL = {
'timbra': ws.format('ws', 'timbre4/timbrarV5'),
'cancel': ws.format('cancela', 'cancela4/cancelarUuid'),
'cancelxml': ws.format('cancela', 'cancela4/cancelarXml'),
'status': ws.format('cancela', 'arws/consultaEstatus'),
'client': api.format('x3/altaEmpresa'),
'saldo': api.format('x3/saldo'),
'timbres': api.format('x3/altaTimbres'),
}
CODES = {
'000': '000 Exitoso',
'004': '004 RFC {} ya esta dado de alta con Estatus=A',
'704': '704 Usuario Invalido',
'702': '702 Error rfc/empresa invalido',
}
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/4',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
if DEBUG:
ws = 'https://pruebas.comercio-digital.mx/{}'
ws6 = 'https://pruebas6.comercio-digital.mx/arws/{}'
URL = {
'timbra': ws.format('timbre4/timbrarV5'),
'cancel': ws.format('cancela4/cancelarUuid'),
'cancelxml': ws.format('cancela4/cancelarXml'),
'status': ws6.format('consultaEstatus'),
'client': api.format('x3/altaEmpresa'),
'saldo': api.format('x3/saldo'),
'timbres': api.format('x3/altaTimbres'),
}
def __init__(self):
self.error = ''
def _error(self, msg):
self.error = str(msg)
log.error(msg)
return
def _post(self, url, data, headers={}):
result = None
headers['host'] = url.split('/')[2]
headers['Content-type'] = 'text/plain'
headers['Connection'] = 'Keep-Alive'
headers['Expect'] = '100-continue'
if DEBUG:
req = requests.Request('POST', url, headers=headers, data=data)
prepared = req.prepare()
pretty_print_POST(prepared)
try:
result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT)
except ConnectionError as e:
self._error(e)
return result
def _validate_cfdi(self, xml):
"""
Comercio Digital solo soporta la declaración con doble comilla
"""
# ~ tree = ET.fromstring(xml.encode())
# ~ xml = ET.tostring(tree,
# ~ pretty_print=True, doctype='<?xml version="1.0" encoding="utf-8"?>')
return xml.encode('utf-8')
def stamp(self, cfdi, auth):
# ~ if DEBUG or not auth:
# ~ auth = AUTH
url = self.URL['timbra']
headers = {
'usrws': auth['user'],
'pwdws': auth['pass'],
'tipo': 'XML',
}
cfdi = self._validate_cfdi(cfdi)
result = self._post(url, cfdi, headers)
if result is None:
return ''
if result.status_code != 200:
return ''
if 'errmsg' in result.headers:
self._error(result.headers['errmsg'])
return ''
xml = result.content
tree = ET.fromstring(xml)
cfdi_uuid = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
namespaces=self.NS_CFDI)
date_stamped = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@FechaTimbrado)',
namespaces=self.NS_CFDI)
data = {
'xml': xml.decode(),
'uuid': cfdi_uuid,
'date': date_stamped,
}
return data
def _get_data_cancel(self, cfdi, info, auth):
info['tipo'] = 'cfdi'
info['key'] = base64.b64encode(info['key_enc']).decode()
info['cer'] = base64.b64encode(info['cer_ori']).decode()
tree = ET.fromstring(cfdi.encode())
version = tree.attrib['Version']
namespaces = {
'cfdi': NAMESPACES[version],
'tdf': NAMESPACES['tdf'],
}
tipo = tree.xpath(
'string(//cfdi:Comprobante/@TipoDeComprobante)',
namespaces=namespaces)
total = tree.xpath(
'string(//cfdi:Comprobante/@Total)',
namespaces=namespaces)
rfc_emisor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
namespaces=namespaces)
rfc_receptor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
namespaces=namespaces)
uid = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
namespaces=namespaces)
data = (
f"USER={auth['user']}",
f"PWDW={auth['pass']}",
f"RFCE={rfc_emisor}",
f"UUID={uid}",
f"PWDK={info['pass']}",
f"KEYF={info['key']}",
f"CERT={info['cer']}",
f"TIPO1={info['tipo']}",
f"ACUS=SI",
f"RFCR={rfc_receptor}",
f"TIPOC={tipo}",
f"TOTAL={total}",
f"UUIDREL={info['args']['uuid']}",
f"MOTIVO={info['args']['reason']}",
)
return '\n'.join(data)
def cancel(self, cfdi, info, auth):
# ~ if DEBUG or not auth:
# ~ auth = AUTH
url = self.URL['cancel']
data = self._get_data_cancel(cfdi, info, auth)
result = self._post(url, data)
if result is None:
return ''
if result.status_code != 200:
return ''
if result.headers['codigo'] != '000':
self._error(result.headers['errmsg'])
return ''
tree = ET.fromstring(result.text)
date_cancel = tree.xpath('string(//Acuse/@Fecha)')[:19]
data = {
'acuse': result.text,
'date': date_cancel,
}
return data
def _get_headers_cancel_xml(self, cfdi, info, auth):
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
tree = ET.fromstring(cfdi.encode())
tipocfdi = tree.xpath(
'string(//cfdi:Comprobante/@TipoDeComprobante)',
namespaces=NS_CFDI)
total = tree.xpath(
'string(//cfdi:Comprobante/@Total)',
namespaces=NS_CFDI)
rfc_receptor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
namespaces=NS_CFDI)
headers = {
'usrws': auth['user'],
'pwdws': auth['pass'],
'rfcr': rfc_receptor,
'total': total,
'tipocfdi': tipocfdi,
}
headers.update(info)
return headers
def cancel_xml(self, xml, auth, cfdi='', info={'tipo': 'cfdi'}):
# ~ if DEBUG or not auth:
# ~ auth = AUTH
url = self.URL['cancelxml']
headers = self._get_headers_cancel_xml(cfdi, info, auth)
result = self._post(url, xml, headers)
if result is None:
return ''
if result.status_code != 200:
return ''
if result.headers['codigo'] != '000':
self._error(result.headers['errmsg'])
return ''
tree = ET.fromstring(result.text)
date_cancel = tree.xpath('string(//Acuse/@Fecha)')[:19]
data = {
'acuse': result.text,
'date': date_cancel,
}
return data
def status(self, data, auth):
# ~ if not auth:
# ~ auth = AUTH
url = self.URL['status']
data = (
f"USER={auth['user']}",
f"PWDW={auth['pass']}",
f"RFCR={data['rfc_receptor']}",
f"RFCE={data['rfc_emisor']}",
f"TOTAL={data['total']}",
f"UUID={data['uuid']}",
)
data = '\n'.join(data)
result = self._post(url, data)
if result is None:
return ''
if result.status_code != 200:
self._error(result.status_code)
return self.error
return result.text
def _get_data_client(self, auth, values):
data = [f"usr_ws={auth['user']}", f"pwd_ws={auth['pass']}"]
fields = (
'rfc_contribuyente',
'nombre_contribuyente',
'calle',
'noExterior',
'noInterior',
'colonia',
'localidad',
'municipio',
'estado',
'pais',
'cp',
'contacto',
'telefono',
'email',
'rep_nom',
'rep_rfc',
'email_fact',
'pwd_asignado',
)
data += [f"{k}={values[k]}" for k in fields]
return '\n'.join(data)
def client_add(self, data, auth):
# ~ auth = AUTH
url = self.URL['client']
data = self._get_data_client(auth, data)
result = self._post(url, data)
if result is None:
return False
if result.status_code != 200:
self._error(f'Code: {result.status_code}')
return False
if result.text != self.CODES['000']:
self._error(result.text)
return False
return True
def client_balance(self, data, rfc=''):
url = self.URL['saldo']
host = url.split('/')[2]
headers = {
'Content-type': 'text/plain',
'Host': host,
'Connection' : 'Keep-Alive',
}
data = {'usr': data['user'], 'pwd': data['pass']}
try:
result = requests.get(url, params=data, headers=headers, timeout=TIMEOUT)
except ConnectionError as e:
self._error(e)
return ''
if result.status_code != 200:
return ''
if result.text == self.CODES['704']:
self._error(result.text)
return ''
if result.text == self.CODES['702']:
self._error(result.text)
return ''
return result.text
def client_add_timbres(self, data, auth):
# ~ if not auth:
# ~ auth = AUTH
url = self.URL['timbres']
data = '\n'.join((
f"usr_ws={auth['user']}",
f"pwd_ws={auth['pass']}",
f"rfc_recibir={data['rfc']}",
f"num_timbres={data['timbres']}"
))
result = self._post(url, data)
if result is None:
return False
if result.status_code != 200:
self._error(f'Code: {result.status_code}')
return False
if result.text != self.CODES['000']:
self._error(result.text)
return False
return True

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ 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 of the License, 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
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~
# ~ You should have received a copy of the GNU General Public License
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
# ~ Siempre consulta la documentación de PAC
# ~ AUTH = Las credenciales de timbrado proporcionadas por el PAC
# ~ NO cambies las credenciales de prueba
DEBUG = False
AUTH = {
'user': '',
'pass': '',
}
if DEBUG:
AUTH = {
'user': 'AAA010101AAA',
'pass': 'PWD',
}

View File

@ -0,0 +1,3 @@
#!/usr/bin/env python3
from .finkok import PACFinkok

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ 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 of the License, 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
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~
# ~ You should have received a copy of the GNU General Public License
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
# ~ Siempre consulta la documentación de PAC
# ~ AUTH = Las credenciales de timbrado proporcionadas por el PAC
# ~ NO cambies las credenciales de prueba
DEBUG = False
AUTH = {
'user': '',
'pass': '',
'RESELLER': {
'user': '',
'pass': ''
}
}
if DEBUG:
AUTH = {
'user': 'pruebas-finkok@correolibre.net',
'pass': '5c9a88da105bff9a8c430cb713f6d35269f51674bdc5963c1501b7316366',
'RESELLER': {
'user': '',
'pass': ''
}
}

View File

@ -0,0 +1,580 @@
#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ 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 of the License, 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
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~
# ~ You should have received a copy of the GNU General Public License
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
# ~ import base64
import datetime
import logging
import os
import re
from io import BytesIO
from xml.sax.saxutils import unescape
import lxml.etree as ET
from zeep import Client
from zeep.plugins import Plugin
from zeep.cache import SqliteCache
from zeep.transports import Transport
from zeep.exceptions import Fault, TransportError
from requests.exceptions import ConnectionError
from .conf import DEBUG, AUTH
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
log = logging.getLogger(__name__)
logging.getLogger('requests').setLevel(logging.ERROR)
logging.getLogger('zeep').setLevel(logging.ERROR)
TIMEOUT = 10
DEBUG_SOAP = False
class DebugPlugin(Plugin):
def _to_string(self, envelope, name):
if DEBUG_SOAP:
data = ET.tostring(envelope, pretty_print=True, encoding='utf-8').decode()
path = f'/tmp/soap_{name}.xml'
with open(path, 'w') as f:
f.write(data)
# ~ print(data)
return
def egress(self, envelope, http_headers, operation, binding_options):
self._to_string(envelope, 'request')
return envelope, http_headers
def ingress(self, envelope, http_headers, operation):
self._to_string(envelope, 'response')
return envelope, http_headers
class PACFinkok(object):
WS = 'https://facturacion.finkok.com/servicios/soap/{}.wsdl'
NS_TYPE = 'ns1'
if DEBUG:
WS = 'https://demo-facturacion.finkok.com/servicios/soap/{}.wsdl'
NS_TYPE = 'ns0'
URL = {
'quick_stamp': False,
'timbra': WS.format('stamp'),
'cancel': WS.format('cancel'),
'client': WS.format('registration'),
'util': WS.format('utilities'),
}
CODE = {
'200': 'Comprobante timbrado satisfactoriamente',
'205': 'No Encontrado',
'307': 'Comprobante timbrado previamente',
'702': 'No se encontro el RFC del emisor',
'IP': 'Invalid Passphrase',
'IPMSG': 'Frase de paso inválida',
'NE': 'No Encontrado',
}
def __init__(self):
self._error = ''
self._transport = Transport(cache=SqliteCache(), timeout=TIMEOUT)
self._plugins = [DebugPlugin()]
@property
def error(self):
return self._error
def _validate_result(self, result):
if hasattr(result, 'Incidencias') and not result.Incidencias is None:
fault = result.Incidencias.Incidencia[0]
cod_error = fault.CodigoError.encode('utf-8').decode()
msg_error = fault.MensajeIncidencia.encode('utf-8').decode()
error = 'Error: {}\n{}'.format(cod_error, msg_error)
if cod_error == '307':
return result
self._error = self.CODE.get(cod_error, error)
return {}
if hasattr(result, 'CodEstatus'):
ce = result.CodEstatus
if ce is None:
return result
if ce == self.CODE['IP']:
self._error = self.CODE['IPMSG']
return {}
if self.CODE['NE'] in ce:
self._error = 'UUID ' + self.CODE['NE']
return {}
if ce == 'UUID Not Found':
self._error = 'UUID ' + self.CODE['NE']
return {}
if self.CODE['200'] != ce:
self._error = ce
return {}
return result
return result
def _get_result(self, client, method, args):
self._error = ''
try:
result = getattr(client.service, method)(**args)
except Fault as e:
self._error = str(e)
return {}
except TransportError as e:
if '413' in str(e):
self._error = '413<BR><BR><b>Documento muy grande para timbrar</b>'
else:
self._error = str(e)
return {}
except ConnectionError as e:
msg = '502 - Error de conexión'
self._error = msg
return {}
return self._validate_result(result)
def _to_string(self, data):
root = ET.parse(BytesIO(data.encode('utf-8'))).getroot()
xml = ET.tostring(root,
pretty_print=True, xml_declaration=True, encoding='utf-8')
return xml.decode('utf-8')
def stamp(self, cfdi, auth={}):
if DEBUG or not auth:
auth = AUTH
method = 'timbra'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
args = {
'username': auth['user'],
'password': auth['pass'],
'xml': cfdi.encode('utf-8'),
}
result = self._get_result(client, 'stamp', args)
if self.error:
log.error(self.error)
return ''
data = {
'xml': self._to_string(result.xml),
'uuid': result.UUID,
'date': result.Fecha,
}
return data
def _get_data_cancel(self, cfdi):
VERSIONS = {
'3.3': 'http://www.sat.gob.mx/cfd/3',
'4.0': 'http://www.sat.gob.mx/cfd/4',
}
NS_CFDI = {
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
tree = ET.fromstring(cfdi.encode())
version = tree.attrib['Version']
NS_CFDI['cfdi'] = VERSIONS[version]
rfc_emisor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
namespaces=NS_CFDI)
cfdi_uuid = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
namespaces=NS_CFDI)
return rfc_emisor, cfdi_uuid
def cancel(self, cfdi, info, auth={}):
if DEBUG or not auth:
auth = AUTH
rfc_emisor, cfdi_uuid = self._get_data_cancel(cfdi)
method = 'cancel'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
uuid_type = client.get_type(f'{self.NS_TYPE}:UUIDS')
ns1_uuid = client.get_type(f'{self.NS_TYPE}:UUID')
# ~ sa = client.get_type('ns0:stringArray')
data_uuid = {
'UUID': cfdi_uuid,
'FolioSustitucion': info['args']['uuid'],
'Motivo': info['args']['reason'],
}
# ~ 'UUIDS': uuid_type(uuids=sa(string=cfdi_uuid)),
args = {
'UUIDS': uuid_type(ns1_uuid(**data_uuid)),
'username': auth['user'],
'password': auth['pass'],
'taxpayer_id': rfc_emisor,
'cer': info['cer'],
'key': info['key'],
'store_pending': False,
}
result = self._get_result(client, 'cancel', args)
if self.error:
log.error(self.error)
return ''
folio = result['Folios']['Folio'][0]
status = folio['EstatusUUID']
if status != '201':
log.debug(f'Cancel status: {status} - {cfdi_uuid}')
data = {
'acuse': result['Acuse'],
'date': result['Fecha'],
}
return data
def cancel_xml(self, xml, auth={}, cfdi=''):
if DEBUG or not auth:
auth = AUTH
method = 'cancel'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
client.set_ns_prefix('can', 'http://facturacion.finkok.com/cancel')
args = {
'xml': xml.encode(),
'username': auth['user'],
'password': auth['pass'],
'store_pending': False,
}
result = self._get_result(client, 'cancel_signature', args)
if self.error:
log.error(self.error)
return ''
folio = result['Folios']['Folio'][0]
status = folio['EstatusUUID']
if status == '708':
self._error = 'Error 708 del SAT, intenta más tarde.'
log.error(self.error)
return ''
if status != '201':
log.debug(f'Cancel status: {status} -')
data = {
'acuse': result['Acuse'],
'date': result['Fecha'],
}
return data
def client_add(self, rfc, type_user=False):
"""Agrega un nuevo cliente para timbrado.
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del nuevo cliente
Kwargs:
type_user (bool):
False == 'P' == Prepago
True == 'O' == On demand
Returns:
True or False
origin PAC
'message':
'Account Created successfully'
'Account Already exists'
'success': True or False
"""
auth = AUTH['RESELLER']
tu = {True: 'O', False: 'P'}
method = 'client'
client = Client(
self.URL[method], transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['user'],
'reseller_password': auth['pass'],
'taxpayer_id': rfc,
'type_user': tu[type_user],
'added': datetime.datetime.now().isoformat()[:19],
}
result = self._get_result(client, 'add', args)
if self.error:
return False
if not result.success:
self.error = result.message
return False
# ~ PAC success debería ser False
msg = 'Account Already exists'
if result.message == msg:
self.error = msg
return True
return result.success
def client_get_token(self, rfc, email):
"""Genera un nuevo token al cliente para timbrado.
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del cliente, ya debe existir
email (str): El correo del cliente, funciona como USER al timbrar
Returns:
token (str): Es la contraseña para timbrar
origin PAC
dict
'username': 'username',
'status': True or False
'name': 'name',
'success': True or False
'token': 'Token de timbrado',
'message': None
"""
auth = AUTH['RESELLER']
method = 'util'
client = Client(
self.URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': auth['user'],
'password': auth['pass'],
'name': rfc,
'token_username': email,
'taxpayer_id': rfc,
'status': True,
}
result = self._get_result(client, 'add_token', args)
if self.error:
log.error(self.error)
return ''
if not result.success:
self.error = result.message
log.error(self.error)
return ''
return result.token
def client_add_timbres(self, rfc, credit):
"""Agregar credito a un emisor
Se requiere cuenta de reseller
Args:
rfc (str): El RFC del emisor, debe existir
credit (int): Cantidad de folios a agregar
Returns:
dict
'success': True or False,
'credit': nuevo credito despues de agregar or None
'message':
'Success, added {credit} of credit to {RFC}.'
'RFC no encontrado'
"""
auth = AUTH['RESELLER']
method = 'client'
client = Client(
self.URL[method], transport=self._transport, plugins=self._plugins)
args = {
'username': auth['user'],
'password': auth['pass'],
'taxpayer_id': rfc,
'credit': credit,
}
result = self._get_result(client, 'assign', args)
if self.error:
log.error(error)
return ''
if not result.success:
self.error = result.message
return 0
return result.credit
def client_balance(self, auth={}, rfc=''):
"""Regresa los timbres restantes del cliente
Se pueden usar las credenciales de relleser o las credenciales del emisor
Args:
auth (dict): Credenciales del emisor
rfc (str): El RFC del emisor
Returns:
int Cantidad de timbres restantes
"""
if not auth:
auth = AUTH['RESELLER']
method = 'client'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['user'],
'reseller_password': auth['pass'],
'taxpayer_id': rfc,
}
result = self._get_result(client, 'get', args)
if self.error:
log.error(self.error)
return ''
success = bool(result.users)
if not success:
self._error = result.message or 'RFC no existe'
log.error(self.error)
return 0
return result.users.ResellerUser[0].credit
def client_set_status(self, rfc, status):
"""Edita el estatus (Activo o Suspendido) de un cliente
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del cliente
Kwargs:
status (bool):
True == 'A' == Activo
False == 'S' == Suspendido
Returns:
dict
'message':
'Account Created successfully'
'Account Already exists'
'success': True or False
"""
auth = AUTH['RESELLER']
ts = {True: 'A', False: 'S'}
method = 'client'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
args = {
'reseller_username': auth['user'],
'reseller_password': auth['pass'],
'taxpayer_id': rfc,
'status': ts[status],
}
result = self._get_result(client, 'edit', args)
if self.error:
return False
if not result.success:
self.error = result.message
return False
return True
def client_switch(self, rfc, type_user):
"""Edita el tipo de timbrado (OnDemand o Prepago) de un cliente
Se requiere cuenta de reseller para usar este método
Args:
rfc (str): El RFC del cliente
Kwargs:
status (bool):
True == 'O' == OnDemand
False == 'P' == Prepago
Returns:
dict
'message':
'Account Created successfully'
'Account Already exists'
'success': True or False
"""
auth = AUTH['RESELLER']
tu = {True: 'O', False: 'P'}
method = 'client'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
args = {
'username': auth['user'],
'password': auth['pass'],
'taxpayer_id': rfc,
'type_user': tu[type_user],
}
result = self._get_result(client, 'switch', args)
if self.error:
return False
if not result.success:
self.error = result.message
return False
return True
def client_report_folios(self, rfc, date_from, date_to, invoice_type='I'):
"""Obtiene un reporte del total de facturas timbradas
"""
auth = AUTH['RESELLER']
args = {
'username': auth['user'],
'password': auth['pass'],
'taxpayer_id': rfc,
'date_from': date_from,
'date_to': date_to,
'invoice_type': invoice_type,
}
method = 'util'
client = Client(self.URL[method],
transport=self._transport, plugins=self._plugins)
result = self._get_result(client, 'report_total', args)
if result.result is None:
# ~ PAC - Debería regresar RFC inexistente o sin registros
self.error = 'RFC no existe o no tiene registros'
return 0
total = result.result.ReportTotal[0].total
return total

View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
from decimal import Decimal, getcontext
# ~ getcontext().prec = 6
import lxml.etree as ET
from requests.structures import CaseInsensitiveDict as CIDict
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tfd': 'http://www.sat.gob.mx/TimbreFiscalDigital',
'nomina12': 'http://www.sat.gob.mx/nomina12',
}
PRE = '/cfdi:Comprobante'
class CfdiRead(object):
def __init__(self, source):
self._source = source
self._data = {}
self._error = ''
self._rfc_emisor = ''
self._rfc_receptor = ''
self._parse()
@property
def source(self):
return self._source
@property
def data(self):
return self._data
@property
def rfc_emisor(self):
return self._rfc_emisor
@property
def rfc_receptor(self):
return self._rfc_receptor
@property
def error(self):
return self._error
def _parse(self):
self._tree = ET.fromstring(self.source)
self._data['cfdi'] = dict(self._tree.attrib)
node_name = f'{PRE}/cfdi:Emisor'
self._data['emisor'] = self._get_attr(node_name)
self._rfc_emisor = self._data['emisor']['Rfc']
node_name = f'{PRE}/cfdi:Receptor'
self._data['receptor'] = self._get_attr(node_name)
self._rfc_receptor = self._data['receptor']['Rfc']
node_name = f'{PRE}/cfdi:Complemento/tfd:TimbreFiscalDigital'
self._data['timbre'] = self._get_attr(node_name)
self._parse_details()
return
def _get_attr(self, node_name):
node = self._tree.xpath(node_name, namespaces=NS_CFDI)[0]
attr = dict(node.attrib)
return attr
def _parse_details(self):
node_name = f'{PRE}/cfdi:Conceptos/cfdi:Concepto'
details = self._tree.xpath(node_name, namespaces=NS_CFDI)
rows = []
for detail in details:
row = dict(detail.attrib)
for k, v in row.items():
if k in ('Cantidad', 'ValorUnitario', 'Descuento', 'Importe'):
row[k] = Decimal(v)
# ~ row['taxes'] = self._get_taxes(detail)
rows.append(row)
self._data['conceptos'] = rows
return
class CfdiWrite(object):
pass

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ from middleware import (
AuthMiddleware,
JSONTranslator,
ConnectionMiddleware,
static,
handle_404
)
from models.db import StorageEngine
@ -18,7 +17,16 @@ from controllers.main import (AppEmpresas,
AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco,
AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina,
AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco,
AppSATFormaPago
AppSATFormaPago, AppSATLeyendaFiscales, AppCert, AppSucursales,
AppPartnerProducts,
AppInventoryEntries,
AppTicketsDetails,
AppUsers,
AppWareHouse,
AppWareHouseProduct,
AppSATUnidadesPeso,
AppSATRegimenes,
AppSociosRegimenes,
)
@ -62,11 +70,20 @@ api.add_route('/cfdipay', AppCfdiPay(db))
api.add_route('/satbancos', AppSATBancos(db))
api.add_route('/satformapago', AppSATFormaPago(db))
api.add_route('/socioscb', AppSociosCuentasBanco(db))
api.add_route('/leyendasfiscales', AppSATLeyendaFiscales(db))
api.add_route('/cert', AppCert(db))
api.add_route('/sucursales', AppSucursales(db))
api.add_route('/partnerproducts', AppPartnerProducts(db))
api.add_route('/inventoryentries', AppInventoryEntries(db))
api.add_route('/warehouse', AppWareHouse(db))
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))
api.add_route('/sociosregimenes', AppSociosRegimenes(db))
# ~ Activa si usas waitress y NO estas usando servidor web
# ~ api.add_sink(static, '/static')
session_options = {
'session.type': 'file',
'session.cookie_expires': True,

View File

@ -8,4 +8,4 @@ threads = 4
py-autoreload = 1
thunder-lock = true
static-map = /static=../static
http-timeout = 300
http-timeout = 300

View File

@ -3,7 +3,7 @@
import falcon
from controllers import util
from models import main
from settings import MV, PATH_STATIC
from settings import MV
def handle_404(req, resp):
@ -20,18 +20,21 @@ def get_template(req, resp, resource):
resp.body = util.get_template(resource.template, data)
def static(req, res):
path = PATH_STATIC + req.path
if util.is_file(path):
res.content_type = util.get_mimetype(path)
res.stream, res.stream_len = util.get_stream(path)
res.status = falcon.HTTP_200
else:
res.status = falcon.HTTP_404
# ~ def static(req, res):
# ~ path = PATH_STATIC + req.path
# ~ if util.is_file(path):
# ~ res.content_type = util.get_mimetype(path)
# ~ res.stream, res.stream_len = util.get_stream(path)
# ~ res.status = falcon.HTTP_200
# ~ else:
# ~ res.status = falcon.HTTP_404
class AuthMiddleware(object):
def process_response(self, req, resp, resource):
pass
def process_resource(self, req, resp, resource, params):
session = req.env['beaker.session']
user = session.get('userobj', None)
@ -65,9 +68,15 @@ class JSONTranslator(object):
def process_response(self, req, resp, resource):
if 'result' not in req.context:
return
if '/doc/' in req.path:
resp.body = req.context['result']
return
if 'blob' in req.context:
resp.body = req.context['blob']
return
resp.body = util.dumps(req.context['result'])

View File

@ -33,25 +33,19 @@ class StorageEngine(object):
def get_nomina(self, values):
return main.CfdiNomina.get_by(values)
def nomina(self, values):
opt = values.pop('opt')
if opt == 'cancel':
return main.CfdiNomina.cancel(int(values['id']))
def empresa_agregar(self, values):
return main.empresa_agregar(values['alta_rfc'], False)
# ~ return main.empresa_agregar(values['alta_rfc'], False)
return main._new_client(values['alta_rfc'], False)
def empresa_borrar(self, values):
return main.empresa_borrar(values['rfc'])
# ~ return main.empresa_borrar(values['rfc'])
return main._delete_client(values['rfc'], False, False)
def respaldar_dbs(self):
return main.respaldar_dbs()
def _get_empresas(self, values):
return main.get_empresas()
def get_values(self, table, values=None, session=None):
if table in ('allusuarios', 'usuarioupdate'):
if table in ('allusuarios', 'usuarioupdate', 'main'):
return getattr(self, '_get_{}'.format(table))(values, session)
return getattr(self, '_get_{}'.format(table))(values)
@ -76,8 +70,11 @@ class StorageEngine(object):
def _get_importinvoice(self, values):
return main.import_invoice()
def _get_main(self, values):
return main.config_main()
def _get_importceods(self, values):
return main.import_ceods()
def _get_main(self, values, session):
return main.config_main(session['userobj'])
def _get_configtimbrar(self, values):
return main.config_timbrar()
@ -126,12 +123,6 @@ class StorageEngine(object):
def enviar_prefac(self, values):
return main.PreFacturas.enviar(values['id'])
# ~ def _get_cancelinvoice(self, values):
# ~ return main.Facturas.cancel(values['id'])
def _get_statussat(self, values):
return main.Facturas.get_status_sat(values['id'])
def _get_verifysat(self, values):
return main.Facturas.get_verify_sat(values['id'])
@ -173,6 +164,9 @@ class StorageEngine(object):
def _get_unidades(self, values):
return main.SATUnidades.get_activos()
def _get_unitbykey(self, values):
return main.SATUnidades.get_activos_by_key()
def add_moneda(self, values):
return main.SATMonedas.add(values)
@ -209,6 +203,10 @@ class StorageEngine(object):
def _get_allusoscfdi(self, values):
return main.SATUsoCfdi.get_all()
def _get_allregimenes(self, values):
filters = {'opt': 'all'}
return main.SATRegimenes.get_data(filters, None)
def _get_allusuarios(self, values, session):
return main.Usuarios.get_(session['userobj'])
@ -236,6 +234,9 @@ class StorageEngine(object):
def _get_usocfdiupdate(self, values):
return main.SATUsoCfdi.actualizar(values)
def _get_regimenesupdate(self, values):
return main.SATRegimenes.actualizar(values)
def _get_emisorcuentasbanco(self, values):
return main.CuentasBanco.emisor()
@ -248,6 +249,9 @@ class StorageEngine(object):
def _get_satunidades(self, values):
return main.get_sat_unidades(values['key'])
def _get_satunidadespeso(self, values):
return main.get_sat_unidadespeso(values['key'])
def _get_satproductos(self, values):
return main.get_sat_productos(values['key'])
@ -354,6 +358,9 @@ class StorageEngine(object):
return main.Facturas.add(values, user)
def invoice_put(self, values, user):
return main.Facturas.put(values, user)
def preinvoice(self, values):
id = int(values.pop('id', '0'))
#~ if id:
@ -375,17 +382,19 @@ class StorageEngine(object):
if opt == 'add':
return main.Tickets.add(values, user)
if opt == 'cancel':
return main.Tickets.cancel(values)
return main.Tickets.cancel(values, user)
if opt == 'invoice':
return main.Tickets.invoice(values, user)
if opt == 'print':
return main.Tickets.printer(values)
def get_tickets(self, values):
return main.Tickets.get_by(values)
def get_tickets(self, values, user):
return main.Tickets.get_by(values, user)
def get_invoices(self, values):
return main.Facturas.get_(values)
def get_invoices(self, filters, user):
if filters.get('by', ''):
return main.Facturas.get_by(filters, user)
return main.Facturas.get_(filters)
def get_preinvoices(self, values):
return main.PreFacturas.get_(values)
@ -459,3 +468,81 @@ class StorageEngine(object):
def partners_accounts_bank(self, values):
return main.SociosCuentasBanco.post(values)
def nomina(self, values, user):
return main.CfdiNomina.post(values, user)
def sat_leyendas_fiscales_get(self, values):
return main.SATLeyendasFiscales.get_values(values)
def sat_leyendas_fiscales_post(self, values):
return main.SATLeyendasFiscales.post(values)
def sat_leyendas_fiscales_delete(self, values):
return main.SATLeyendasFiscales.remove(values)
# ~ v2
def cert_get(self, filters):
return main.Certificado.get_data(filters)
def cert_post(self, filters):
return main.Certificado.post(filters)
def sucursales_get(self, filters):
return main.Sucursales.get_data(filters)
def sucursales_post(self, filters):
return main.Sucursales.post(filters)
def partner_products_get(self, filters):
return main.PartnerProducts.get_data(filters)
def inventory_entries_get(self, filters):
return main.InventoryEntries.get_data(filters)
def inventory_entries_post(self, filters, user):
return main.InventoryEntries.post(filters, user)
def warehouse_get(self, filters):
return main.Almacenes.get_data(filters)
def warehouse_post(self, values, user):
return main.Almacenes.post(values, user)
def warehouseproduct_get(self, filters, user):
return main.WareHouseProduct.get_data(filters, user)
def warehouseproduct_post(self, filters, user):
return main.WareHouseProduct.post(filters, user)
def users_get(self, filters, user):
return main.Usuarios.get_data(filters, user)
def users_post(self, args, user):
return main.Usuarios.post(args, user)
def ticketsdetails_get(self, filters, user):
return main.TicketsDetalle.get_data(filters, user)
def products_get(self, filters, user):
return main.Productos.get_data(filters, user)
def nomina_get(self, filters, user):
return main.CfdiNomina.get_data(filters, user)
def sat_unidades_peso_get(self, filters, user):
return main.SATUnidadesPeso.get_data(filters, user)
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)
def socios_regimenes_get(self, filters, user):
return main.SociosRegimenes.get_data(filters, user)
# Companies only in MV
def _get_empresas(self, values):
return main.companies_get()

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# ~ Empresa Libre
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@correolibre.net)
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (publico@cuates.net)
# ~
# ~ 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
@ -28,17 +28,9 @@ from conf import DEBUG, MV, LOG_PATH
try:
from conf import DEFAULT_PASSWORD
except ImportError:
DEFAULT_PASSWORD = 'salgueiro3.3'
DEFAULT_PASSWORD = 'salgueiro4.0'
try:
from conf import SEAFILE_SERVER
except ImportError:
SEAFILE_SERVER = {}
try:
from conf import TITLE_APP
except ImportError:
TITLE_APP = 'Empresa Libre'
TITLE_APP = 'Empresa Libre'
try:
from conf import NO_HTTPS
@ -47,13 +39,24 @@ except ImportError:
DEBUG = DEBUG
VERSION = '1.21.1'
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
VERSION = '2.3.2'
EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
path_static = os.path.abspath(os.path.join(BASE_DIR, '..', 'static'))
path_docs = os.path.abspath(os.path.join(BASE_DIR, '..', 'docs'))
path_css = os.path.join(path_static, 'css')
path_img = os.path.join(path_static, 'img')
path_user_template = os.path.join(path_docs, 'templates')
path_user_logos = os.path.join(path_docs, 'logos')
# ~ PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..'))
PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..'))
PATH_TEMPLATES = os.path.abspath(os.path.join(BASE_DIR, '..', 'templates'))
PATH_MEDIA = os.path.abspath(os.path.join(BASE_DIR, '..', 'docs'))
@ -68,13 +71,16 @@ PATH_SESSIONS = {
IV = 'valores_iniciales.json'
INIT_VALUES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', IV))
CT = 'cancel_template.xml'
TEMPLATE_CANCEL = os.path.abspath(os.path.join(PATH_TEMPLATES, CT))
# ~ CT = 'cancel_template.xml'
# ~ 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')
@ -119,12 +125,29 @@ if 'win' in sys.platform:
PATH_XMLSEC = os.path.join(PATH_BIN, 'xmlsec.exe')
PRE_DEFAULT = {
'CFDI': {'VERSION': '4.0', 'PRE': '{http://www.sat.gob.mx/cfd/4}'},
'NOMINA': {'VERSION': '1.2', 'PRE': '{http://www.sat.gob.mx/nomina12}'},
'PAGOS': {'VERSION': '2.0', 'PRE': '{http://www.sat.gob.mx/Pagos20}'},
'TIBRE': {'VERSION': '1.1', 'PRE': '{http://www.sat.gob.mx/TimbreFiscalDigital}'},
}
pre2 ='{http://www.sat.gob.mx/cfd/2}'
pre3 ='{http://www.sat.gob.mx/cfd/3}'
PRE_HISTORY = {
'CFDI': {'2.0': pre2, '2.2': pre2,
'3.0': pre3, '3.2': pre3, '3.3': pre3},
'NOMINA': {'1.1': '{http://www.sat.gob.mx/nomina}'},
'PAGOS': {'1.0': '{http://www.sat.gob.mx/Pagos}'},
}
PRE = {
'2.0': '{http://www.sat.gob.mx/cfd/2}',
'2.2': '{http://www.sat.gob.mx/cfd/2}',
'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}',
@ -133,11 +156,15 @@ PRE = {
'1.1': '{http://www.sat.gob.mx/nomina}',
'1.2': '{http://www.sat.gob.mx/nomina12}',
},
'pagos': '{http://www.sat.gob.mx/Pagos}',
'PAGOS': {
'1.0': '{http://www.sat.gob.mx/Pagos}',
}
}
CURRENT_CFDI = '3.3'
CURRENT_CFDI_NOMINA = '1.2'
# To delete
# ~ CURRENT_CFDI = '4.0'
# ~ CURRENT_CFDI_NOMINA = '1.2'
DECIMALES = 2
DECIMALES_TAX = 4
DECIMALES_PRECIOS = 4
@ -160,7 +187,8 @@ DEFAULT_CFDIPAY = {
'TYPE': 'P',
'WAYPAY': 'PPD',
'CURRENCY': 'XXX',
'USED': 'P01',
'TC': '1',
'USED': 'CP01',
'KEYSAT': '84111506',
'UNITKEY': 'ACT',
'DESCRIPTION': 'Pago',
@ -173,7 +201,7 @@ PUBLIC = 'Público en general'
DEFAULT_SAT_NOMINA = {
'SERIE': 'N',
'FORMA_PAGO': '99',
'USO_CFDI': 'P01',
'USO_CFDI': 'CN01',
'CLAVE': '84111505',
'UNIDAD': 'ACT',
'DESCRIPCION': 'Pago de nómina',
@ -182,3 +210,110 @@ DEFAULT_SAT_NOMINA = {
API = 'https://api.empresalibre.net{}'
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'))
path_bk = os.path.join(path_docs, 'tmp')
path_local = 'facturas'
path_sat = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'valores_iniciales.json'))
EXT = {
'CSS': 'css',
'HTML': 'html',
'ODS': 'ods',
'PNG': 'png',
'JSON': 'json',
}
MXN = 'MXN'
CARTA_PORTE = {
'MONEDA': 'XXX',
}
PATHS = {
'STATIC': path_static,
'CSS': path_css,
'IMG': path_img,
'DOCS': path_docs,
'USER': path_user_template,
'LOGOS': path_user_logos,
'BK': path_bk,
'LOCAL': path_local,
'SAT': path_sat,
'xslt': PATH_XSLT,
}
VALUES_PDF = {
'CANCEL': {True: 'inline', False: 'none'},
'TYPE': {'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado'},
'TAX': {'001': 'ISR', '002': 'IVA', '003': 'IEPS'},
'METHOD': {
'PUE': 'Pago en una sola exhibición',
'PPD': 'Pago en parcialidades o diferido',
},
}
RESICO = '626'
RFCS = {
'PUBLIC': 'XAXX010101000',
'FOREIGN': 'XEXX010101000',
'CVD110412TF6': 'finkok',
'SCD110105654': 'comercio',
'AAA010101AAA': 'comercio',
'SPR190613I52': 'comercio',
}
URL = {
'SEAFILE': 'https://seafile.cuates.net',
}
DEFAULT_GLOBAL = {
'cantidad': 1.00,
'unidad': 'ACT',
'descripcion': 'Venta',
'clave_sat': '01010101',
}
# ~ TEMPLATE_CANCEL = """<CancelaCFD xmlns="http://cancelacfd.sat.gob.mx">
# ~ <Cancelacion xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RfcEmisor="{rfc}" Fecha="{fecha}">
# ~ <Folios>
# ~ <Folio UUID="{uuid}" Motivo="{motivo}"{folio}/>
# ~ </Folios>
# ~ <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
# ~ <SignedInfo>
# ~ <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
# ~ <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
# ~ <Reference URI="">
# ~ <Transforms>
# ~ <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
# ~ </Transforms>
# ~ <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
# ~ <DigestValue/>
# ~ </Reference>
# ~ </SignedInfo>
# ~ <SignatureValue/>
# ~ <KeyInfo>
# ~ <X509Data>
# ~ <X509IssuerSerial>
# ~ <X509IssuerName/>
# ~ <X509SerialNumber/>
# ~ </X509IssuerSerial>
# ~ <X509Certificate/>
# ~ </X509Data>
# ~ <KeyValue>
# ~ <RSAKeyValue>
# ~ <Modulus/>
# ~ <Exponent/>
# ~ </RSAKeyValue>
# ~ </KeyValue>
# ~ </KeyInfo>
# ~ </Signature>
# ~ </Cancelacion>
# ~ </CancelaCFD>"""
TEMPLATE_CANCEL = """<CancelaCFD xmlns="http://cancelacfd.sat.gob.mx"><Cancelacion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Fecha="{fecha}" RfcEmisor="{rfc}" xmlns="http://cancelacfd.sat.gob.mx"><Folios><Folio UUID="{uuid}" Motivo="{motivo}"{folio}/></Folios><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue/></Reference></SignedInfo><SignatureValue/><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName/><X509SerialNumber/></X509IssuerSerial><X509Certificate/></X509Data><KeyValue><RSAKeyValue><Modulus/><Exponent/></RSAKeyValue></KeyValue></KeyInfo></Signature></Cancelacion></CancelaCFD>"""

Binary file not shown.

Binary file not shown.

473
source/db/unidad_peso.csv Normal file
View File

@ -0,0 +1,473 @@
id key name
1 Tu Contenedor externo
2 X1A Tambor de acero
3 X1B Tambor de aluminio
4 X1D Tambor contrachapado
5 X1F Contenedor flexible
6 X1G Tambor de fibra
7 X1w Tambor de madera
8 X2C Barril de madera
9 X3A Bidón de acero
10 X3H Bidón de plástico
11 X43 Bolsa de gran tamaño
12 X44 Bolsa de plástico
13 X4A Caja de acero
14 X4B Caja de aluminio
15 X4C Caja de madera natural
16 X4D Caja de contrachapado
17 X4F Caja de madera reconstituida
18 X4G Caja de cartón
19 X4H Caja de plástico
20 X5H Bolsa de plástico tejido
21 X5L Bolsa textil
22 X5M Bolsa de papel
23 X6H Recipiente de plástico, Contenedor compuesto.
24 X6P Recipiente de vidrio, Contenedor compuesto.
25 X7A Estuche para carro
26 X7B Estuche de madera
27 X8A Pallet de madera
28 X8B Cajón de madera
29 X8C Madera flejada
30 XAA Contenedor intermedio para gráneles de plástico rígido
31 XAB Contenedor de fibra
32 XAC Contenedor de papel
33 XAD Contenedor de madera
34 XAE Aerosol
35 XAF Pallet modular con collares, 80cms * 60cms
36 XAG Pallet o empaquetado
37 XAH Pallet, 100cms X 110cms
38 XAI Contenedor tipo concha
39 XAJ Cono
40 XAL Esfera
41 XAM Ampolleta no protegida
42 XAP Ampolleta protegida
43 XAT Atomizador
44 XAV Cápsula
45 XB4 Cinturón
46 XBA Barril
47 XBB Bobina
48 XBC Cajón para botellas / Estante para botellas
49 XBD Tablero
50 XBE Flejado
51 XBF Globo no protegido
52 XBG Bolso
53 XBH Manojo
54 XBI Compartimiento
55 XBJ Cubeta
56 XBK Cesta
57 XBL Paca comprimida
58 XBM Cuenco
59 XBN Paca no comprimida
60 XBO Botella no-protegida y cilíndrica
61 XBP Globo protegido
62 XBQ Botella cilíndrica protegida
63 XBR Barra
64 XBS Botella, no-protegida en forma de bulbo
65 XBT Rollo de tela
66 XBU Butt
67 XBV Botella de bulbo protegido
68 XBW Caja para líquidos
69 XBX Caja
70 XBY Tablero, con fleje/ agrupados/ armados
71 XBZ Barras, con fleje/ agrupados/ armados
72 XCA Lata rectangular
73 XCB Cajón para cerveza
74 XCC Mantequera
75 XCD Lata con mango y boquilla
76 XCE Cesto tejido
77 XCF Cofre
78 XCG Contenedor tipo Jaula
79 XCH Cajonera
80 XCI Frasco
81 XCJ Ataúd
82 XCK Barrica
83 XCL Espiral
84 XCM Paquete de tarjetas
85 XCN Contenedor, no especificado como equipo de transporte
86 XCO Garrafón no protegido
87 XCP Garrafón protegido
88 XCQ Cartucho
89 XCR Cajón
90 XCS Estuche
91 XCT Cartón
92 XCU Vaso
93 XCV Cubierta
94 XCW Jaula estilo rodillo
95 XCX Lata cilíndrica
96 XCY Cilindro
97 XCZ Lona
98 XDA Cajón multicapa de plástico
99 XDB Cajón de varias capas de madera
100 XDC Cajón multicapa de cartón
101 XDG Jaula, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP))
102 XDH Caja, Según la clasificación de la compañía (CHEP), Eurobox
103 XDI Tambor de hierro
104 XDJ damajuana o garrafa, no protegido
105 XDK Cajón a granel, cartón
106 XDL Cajas de plástico
107 XDM Cajones a granel de madera
108 XDN Dispensador
109 XDP damajuana o garrafa, protegido
110 XDR Tambor
111 XDS Bandeja de una capa sin cubierta y de plástico
112 XDT Bandeja de una capa sin cubierta y de madera
113 XDU Bandeja de una capa sin cubierta y poliestireno
114 XDV Bandeja de una capa sin cubierta y de cartón
115 XDW Bandeja de dos capas sin tapa y con bandeja de plástico
116 XDX Bandeja de dos capas sin cubierta y de madera
117 XDY Bandeja de dos capas sin cubierta y de cartón
118 XEC Bolsa de plástico
119 XED Estuche, con pallet de base
120 XEE Estuche, con pallet base de madera
121 XEF Estuche, con pallet base de cartón
122 XEG Estuche, con pallet base de plástico
123 XEH Estuche, con pallet base de metal
124 XEI Estuche isotérmico
125 XEN Sobre
126 XFB Bolsa flexible
127 XFC Cajón para fruta
128 XFD Cajón enmarcado
129 XFE Tanque flexible
130 XFI Firkin
131 XFL Matraz
132 XFO Cajón para zapatos
133 XFP Caja auxiliar para película fotográfica
134 XFR Marco
135 XFT Contenedor para alimentos
136 XFW Carro de cama plana
137 XFX Bolsa flexible tipo contenedor
138 XGB Botella para gas
139 XGI Viga
140 XGL Contenedor tipo galón
141 XGR Recipiente de vidrio
142 XGU Bandeja contenedor para apilar horizontalmente objetos planos
143 XGY Costal de Yute
144 XGZ Vigas con correas o agrupadas
145 XHA Cesta con mango y de plástico
146 XHB Cesta con mango y de madera
147 XHC Cesta con asa y de cartón
148 XHG Hogshead
149 XHN Gancho
150 XHR Cesto
151 XIA Paquete con pantalla y de madera
152 XIB Paquete con pantalla y de cartón
153 XIC Paquete con pantalla y de plástico
154 XID Paquete con pantalla y de metal
155 XIE Paquete de mostrador.
156 XIF Envase para alimentos
157 XIG Paquete envuelto en papel
158 XIH Tambor de plástico
159 XIK Paquete de cartón con los agujeros para botellas
160 XIL Bandeja rígida con tapa y apilable (CEN TS 14482: 2002)
161 XIN Lingote
162 XIZ Lingotes con correas/ agrupados
163 XJB Bolsa jumbo
164 XJC Bidón rectangular
165 XJG Jarra
166 XJR Tarro
167 XJT Bolsa de yute
168 XJY Bidón, cilíndrico
169 XKG Barrilete
170 XKI Kit (Conjunto de piezas)
171 XLE Valijas
172 XLG Bitácora
173 XLT Lote
174 XLU Caja de arrastre
175 XLV Contenedor pequeño
176 XLZ Registros con fleje/ agrupados/ armados
177 XMA Cajón metálico
178 XMB Múltiplo de bolsas
179 XMC Cajón para leche
180 XME Contenedor de metal
181 XMR Recipiente de metal
182 XMS Saco milti-pared
183 XMT Tapete
184 XMW Contenedor envuelto en plástico
185 XMX Caja pequeña de cerillos
186 XNA No disponible
187 XNE Sin empaque o no empaquetado
188 XNF Sin empaque o no empaquetado, unidad simple
189 XNG Sin empaque o no empaquetado, unidades múltiples
190 XNS Caja nido
191 XNT Red
192 XNU Red de plástico con tubo
193 XNV Red textil con tubo
194 XOA Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 40 cm x 60 cm
195 XOB Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 80 cm x 120 cm
196 XOC Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 100 cm x 120 cm
197 XOD Pallet, AS 4068-1993
198 XOE Pallet, ISO T11
199 XOF Plataforma, peso o dimensión no especificada
200 XOK Bloque
201 XOT Octabin
202 XP2 Charola
203 XPA Cajetilla
204 XPB Pallet, Caja combinada y abierta con caja y pallet.
205 XPC Paquete postal
206 XPD Pallet modular con collares (80cms * 100cms)
207 XPE Pallet modular con collares (80cms * 120cms)
208 XPF Corral
209 XPG Placa
210 XPH Cantaro
211 XPI Pleca
212 XPJ Canastilla
213 XPK Paquete
214 XPL Balde
215 XPN Tablón
216 XPO Bolsa pequeña
217 XPR Contenedor de plástico
218 XPT Maceta
219 XPU Cacerola
220 XPV Tubos, con fleje/ agrupados/ armados
221 XPX Pallet
222 XPY Placas con fleje/ agrupados/ armados
223 XPZ Tablones con fleje/ agrupados/ armados
224 XQA Tambor de acero con cabeza no desmontable
225 XQB Tambor de acero con cabeza extraíble
226 XQC Tambor de aluminio con cabeza no extraíble
227 XQD Tambor de aluminio con cabeza extraíble
228 XQF Tambor, plástico con cabeza no desmontable
229 XQG Tambor, plástico, cabeza extraíble
230 XQH Barril de madera con tapón
231 XQJ Barril de madera con cabeza desprendible
232 XQK Bidón de acero con cabeza no desmontable
233 XQL Bidón de acero con cabeza desmontable
234 XQM Bidón de plástico con cabeza no desmontable
235 XQN Bidón de plástico con cabeza extraíble
236 XQP Caja de madera natural estándar
237 XQQ Caja de madera natural con muros a prueba de filtraciones
238 XQR Caja de plástico expandida
239 XQS Caja de plástico sólida
240 XRD Rod
241 XRG Anillo
242 XRJ Estante, Perchero para ropa
243 XRK Estante
244 XRL Carrete
245 XRO Rollo
246 XRT Red Roja
247 XRZ Varillas con fleje/ agrupados/ armados
248 XSA Saco
249 XSB Losa
250 XSC Cajón poco profundo
251 XSD Huso
252 XSE Baúl
253 XSH Bolsa pequeña hermética
254 XSI Patín
255 XSK Carcasa esqueleto
256 XSL Hoja de deslizamiento
257 XSM Hoja de metal
258 XSO Carrete pequeño
259 XSP Hoja de empaque de plástico
260 XSS Cajón de acero
261 XSU Maleta
262 XSV Sobre de acero
263 XSW Envoltorio
264 XSY Manga
265 XSZ Hojas con fleje/ agrupados/ armados
266 XT1 Tableta
267 XTB Tina
268 XTC Caja para té
269 XTD Tubo plegable
270 XTG Contenedor de tanque genérico
271 XTI Tierce
272 XTK Tanque rectangular
273 XTL Tina con tapa
274 XTN Hojalata
275 XTO Tonel
276 XTR Maletero
277 XTS Estructura
278 XTT Bolsa de mano
279 XTU Tubo
280 XTV Tubo con boquilla
281 XTW Pallet tricapa
282 XTY Tanque cilíndrico
283 XTZ Tubos con fleje/ agrupados/ armados
284 XUC Sin empaque
285 XUN Unidad
286 XVA Tanque
287 XVG Tanque de gas (a 1,031 mbar y 15° C)
288 XVI Frasco pequeño
289 XVK Paquete transportable
290 XVL Contenedor para líquidos a granel
291 XVN Vehículo
292 XVO "Contenedor para sólido de partículas grandes a granel (""nódulos"")"
293 XVP Envasado al vacío
294 XVQ Tanque para Gas licuado (a temperatura / presión anormal)
295 XVR Contenedor para sólidos de partículas granulares a granel (Granos)
296 XVS Contenedor de chatarra a granel
297 XVY "Contenedor para sólido de partículas finas a granel (""polvos"")"
298 XWA Contenedor de granel intermedio
299 XWB Botella de mimbre
300 XWC Contenedor intermedio para gráneles y de acero
301 XWD Contenedor intermedio para gráneles y de aluminio
302 XWF Contenedor intermedio para gráneles y de metal
303 XWG Contenedor intermedio para gráneles y de acero presurizado menor a 10 kpa
304 XWH Contenedor intermedio para gráneles y de aluminio, presurizado menor a 10 kpa
305 XWJ Contenedor intermedio para gráneles y de metal con una presión de 10 kpa
306 XWK Contenedor intermedio para gráneles y de acero para líquido
307 XWL Contenedor intermedio para gráneles y de aluminio para líquido
308 XWM Contenedor intermedio para gráneles y de metal para líquido
309 XWN Contenedor intermedio para gráneles con tejido plástico sin capa con revestimiento
310 XWP Contenedor intermedio para gráneles con tejido plástico y recubierto
311 XWQ Contenedor intermedio para gráneles con tejido plástico con revestimiento
312 XWR Contenedor intermedio para gráneles con tejido plástico, revestido y con forro
313 XWS Contenedor intermedio para gráneles con película de plástico
314 XWT Contenedor intermedio para gráneles textil sin capa / forro
315 XWU Contenedor intermedio para gráneles de madera natural con forro interior
316 XWV Contenedor intermedio para gráneles textil recubierto
317 XWW Contenedor intermedio para gráneles textil con revestimiento
318 XWX Contenedor intermedio para gráneles textil recubierto y con forro
319 XWY Contenedor intermedio para gráneles contrachapado con revestimiento interior
320 XWZ Contenedor intermedio para gráneles de madera reconstituida con revestimiento interior
321 XXA Bolsa de tejido plástico, sin abrigo interior ni forro
322 XXB Bolsa de tejido plástico a prueba de filtraciones
323 XXC Bolsa de tejido plástico resistente al agua
324 XXD Bolsa con película de plástico
325 XXF Bolsa textil sin capa ni forro interior
326 XXG Bolsa textil a prueba de filtraciones
327 XXH Bolsa textil resistente al agua
328 XXJ Bolsa de papel multi-pared
329 XXK Bolsa de papel multi-pared, resistente al agua
330 XYA Empaque compuesto, recipiente de plástico en tambor de acero
331 XYB Empaque compuesto, recipiente de plástico en cajas de acero
332 XYC Empaque compuesto, recipiente de plástico en tambor de aluminio
333 XYD Empaque compuesto, recipiente de plástico en cajón de aluminio
334 XYF Empaque compuesto, recipiente de plástico en caja de madera
335 XYG Empaque compuesto, recipiente de plástico en tambor de madera contrachapada
336 XYH Empaque compuesto, recipiente de plástico en caja de madera contrachapada
337 XYJ Empaque compuesto, recipiente de plástico en tambor de fibra
338 XYK Empaque compuesto, recipiente de plástico en caja de cartón
339 XYL Empaque compuesto, recipiente de plástico en el tambor de plástico
340 XYM Empaque compuesto, recipiente de plástico en caja de plástico sólido
341 XYN Empaque compuesto, receptáculo de vidrio en tambor de acero
342 XYP Empaque compuesto, receptáculo de vidrio en caja de cajas de acero
343 XYQ Empaque compuesto, recipiente de vidrio en tambor de aluminio
344 XYR Empaque compuesto, receptáculo de vidrio en caja de aluminio
345 XYS Empaque compuesto, recipiente de vidrio en caja de madera
346 XYT Empaque compuesto, recipiente de vidrio en tambor de madera contrachapada
347 Xyv Empaque compuesto, recipiente de vidrio en el cesto de mimbre
348 XYW Empaque compuesto, recipiente de vidrio en tambor de fibra
349 XYX Empaque compuesto, recipiente de vidrio en caja de cartón
350 XYY Empaque compuesto, recipiente de vidrio en paquete de plástico expandible
351 XYZ Empaque compuesto, recipiente de vidrio en paquete de plástico sólido
352 XZA Contenedor de granel intermedio, papel, multi-pared
353 XZB Bolsa grande
354 XZC Contenedor intermedio para gráneles de papel, multi-pared y resistente al agua
355 XZD Contenedor intermedio para gráneles de plástico rígido, con equipo estructural para sólidos
356 XZF Contenedor intermedio para gráneles de plástico rígido, autoportante para sólidos
357 XZG Contenedor intermedio para gráneles de plástico rígido, con equipo estructural, presurizado
358 XZH Contenedor intermedio para gráneles de plástico rígido, autoportante y presurizado
359 XZJ Contenedor intermedio para gráneles de plástico rígido, con equipo estructural para líquidos
360 XZK Contenedor intermedio para gráneles de plástico rígido, autoportante, líquidos
361 XZL Contenedor intermedio para gráneles, compuesto y de plástico rígido, sólidos
362 XZM Contenedor intermedio para gráneles, compuesto y de plástico flexible, sólidos
363 XZN Contenedor intermedio para gráneles, compuesto y de plástico rígido, presurizado
364 XZP Contenedor intermedio para gráneles, compuesto y de plástico flexible, presurizado
365 XZQ Contenedor intermedio para gráneles, compuesto y de plástico rígido, líquidos
366 XZR Contenedor intermedio para gráneles, compuesto y de plástico flexible para líquidos
367 XZS Contenedor intermedio para gráneles, compuesto
368 XZT Contenedor intermedio para gráneles con tablero de fibras
369 XZU Contenedor intermedio para gráneles flexible
370 XZV Contenedor intermedio para gráneles de metal, distinto del acero
371 XZW Contenedor intermedio para gráneles, de madera natural
372 XZX Contenedor intermedio para gráneles, de contrachapado
373 XZY Contenedor intermedio para gráneles, de madera reconstituida
374 KGM Kilogramo
375 MC Microgramo
376 DJ Decagramo
377 DG Decigramo
378 GRM Gramo
379 CGM Centigramo
380 TNE Tonelada (tonelada métrica)
381 DTN Decitonelada métrica
382 MGM Miligramo
383 HGM Hectogramo
384 KTN Kilotonelada Métrica
385 2U Megagramo
386 LBR Libra
387 GRN Grano
388 ONZ Onza (avoirdupois)
389 CWI Hundredweight
390 CWA Hundred pound
391 LTN Tonelada (UK) o tonelada larga (estados unidos)
392 STI Estone (UK)
393 STN Tonelada (estados unidos) o tonelada corta (UK y estados unidos)
394 APZ Onza troy u onza farmacéutica
395 F13 Slug
396 K64 Libra (avoirdupois) por grado fahrenheit
397 L69 Tonelada por kelvin
398 L87 Tonelada corta por grado fahrenheit
399 M85 Tonelada, ensayo
400 M86 Libra Alemana
401 J33 Microgramo por kilogramo
402 L32 Nanogramo por kilogramo
403 NA Miligramo por kilogramo
404 M29 Kilogramo por kilogramo
405 M91 Libra por libra
406 Q29 Microgramo por hectogramo
407 MTQ Metro cúbico
408 MAL Megalitro
409 LTR Litro
410 MMQ Milímetro cúbico
411 CMQ Centímetro cúbico
412 DMQ Decímetro cúbico
413 MLT Mililitro
414 HLT Hectolitro
415 CLT Centilitro
416 DMA Decámetro cúbico
417 H19 Hectómetro cúbico
418 H20 Kilómetro cúbico
419 M71 Metro cúbico por pascal (joules)
420 DLT Decilitro
421 4G Microlitro
422 K6 Kilolitro
423 A44 Decalitro
424 G94 Centímetro cúbico por bar
425 G95 Litro por bar
426 G96 Metro cúbico por bar
427 G97 Mililitro por bar
428 5I Pies cúbicos estándar
429 INQ Pulgada cúbica
430 FTQ Pie cúbico
431 YDQ Yarda cúbica
432 GLI Galón (UK)
433 GLL Galón (EUA)
434 PT Pinta (US)
435 PTI Pint (uk)
436 QTI Cuarto (UK)
437 PTL Pinta líquida (estados unidos)
438 QTL Cuarto de líquido (estadis unidos)
439 PTD Pinta seca (estados unidos)
440 OZI Onza líquida (UK)
441 QT Cuarto (EUA)
442 J57 Barril (uk petróleo)
443 K21 Pie cúbico por grado fahrenheit
444 K23 Pie cúbico por psi (libra por pulgada cuadrada)
445 L43 Peck (UK)
446 L61 Pinta (US seco)
447 L62 Cuarto de galón (seco de los EUA)
448 L84 Tonelada (flota UK)
449 L86 Tonelada (flota estados unidos)
450 M11 Yarda cúbica por grado fahrenheit
451 M14 Yarda cúbica por psi (libra por pulgada cuadrada)
452 OZA Onza líquida (estados unidos)
453 BUI Bushel (UK)
454 BUA Bushel (EUA)
455 BLL Barril (EUA)
456 BLD Barril seco (EUA)
457 GLD Galón seco (EUA)
458 QTD Cuarto seco (estados unidos)
459 G26 Estere
460 G21 Taza (unidad de volumen)
461 G24 Cucharada (estados unidos)
462 G25 Cucharilla (estados unidos)
463 G23 Peck
464 M67 Acre-pie
465 M68 Cordón
466 M69 Milla cúbica (reino unido)
467 M70 Unidad tradicional de capacidad de carga
468 Q32 Femtolitro
469 Q33 Picolitro
470 Q34 Nanolitro
471 NM3 Metro cúbico normalizado
472 SM3 Metro cúbico estándar
1 id key name
2 1 Tu Contenedor externo
3 2 X1A Tambor de acero
4 3 X1B Tambor de aluminio
5 4 X1D Tambor contrachapado
6 5 X1F Contenedor flexible
7 6 X1G Tambor de fibra
8 7 X1w Tambor de madera
9 8 X2C Barril de madera
10 9 X3A Bidón de acero
11 10 X3H Bidón de plástico
12 11 X43 Bolsa de gran tamaño
13 12 X44 Bolsa de plástico
14 13 X4A Caja de acero
15 14 X4B Caja de aluminio
16 15 X4C Caja de madera natural
17 16 X4D Caja de contrachapado
18 17 X4F Caja de madera reconstituida
19 18 X4G Caja de cartón
20 19 X4H Caja de plástico
21 20 X5H Bolsa de plástico tejido
22 21 X5L Bolsa textil
23 22 X5M Bolsa de papel
24 23 X6H Recipiente de plástico, Contenedor compuesto.
25 24 X6P Recipiente de vidrio, Contenedor compuesto.
26 25 X7A Estuche para carro
27 26 X7B Estuche de madera
28 27 X8A Pallet de madera
29 28 X8B Cajón de madera
30 29 X8C Madera flejada
31 30 XAA Contenedor intermedio para gráneles de plástico rígido
32 31 XAB Contenedor de fibra
33 32 XAC Contenedor de papel
34 33 XAD Contenedor de madera
35 34 XAE Aerosol
36 35 XAF Pallet modular con collares, 80cms * 60cms
37 36 XAG Pallet o empaquetado
38 37 XAH Pallet, 100cms X 110cms
39 38 XAI Contenedor tipo concha
40 39 XAJ Cono
41 40 XAL Esfera
42 41 XAM Ampolleta no protegida
43 42 XAP Ampolleta protegida
44 43 XAT Atomizador
45 44 XAV Cápsula
46 45 XB4 Cinturón
47 46 XBA Barril
48 47 XBB Bobina
49 48 XBC Cajón para botellas / Estante para botellas
50 49 XBD Tablero
51 50 XBE Flejado
52 51 XBF Globo no protegido
53 52 XBG Bolso
54 53 XBH Manojo
55 54 XBI Compartimiento
56 55 XBJ Cubeta
57 56 XBK Cesta
58 57 XBL Paca comprimida
59 58 XBM Cuenco
60 59 XBN Paca no comprimida
61 60 XBO Botella no-protegida y cilíndrica
62 61 XBP Globo protegido
63 62 XBQ Botella cilíndrica protegida
64 63 XBR Barra
65 64 XBS Botella, no-protegida en forma de bulbo
66 65 XBT Rollo de tela
67 66 XBU Butt
68 67 XBV Botella de bulbo protegido
69 68 XBW Caja para líquidos
70 69 XBX Caja
71 70 XBY Tablero, con fleje/ agrupados/ armados
72 71 XBZ Barras, con fleje/ agrupados/ armados
73 72 XCA Lata rectangular
74 73 XCB Cajón para cerveza
75 74 XCC Mantequera
76 75 XCD Lata con mango y boquilla
77 76 XCE Cesto tejido
78 77 XCF Cofre
79 78 XCG Contenedor tipo Jaula
80 79 XCH Cajonera
81 80 XCI Frasco
82 81 XCJ Ataúd
83 82 XCK Barrica
84 83 XCL Espiral
85 84 XCM Paquete de tarjetas
86 85 XCN Contenedor, no especificado como equipo de transporte
87 86 XCO Garrafón no protegido
88 87 XCP Garrafón protegido
89 88 XCQ Cartucho
90 89 XCR Cajón
91 90 XCS Estuche
92 91 XCT Cartón
93 92 XCU Vaso
94 93 XCV Cubierta
95 94 XCW Jaula estilo rodillo
96 95 XCX Lata cilíndrica
97 96 XCY Cilindro
98 97 XCZ Lona
99 98 XDA Cajón multicapa de plástico
100 99 XDB Cajón de varias capas de madera
101 100 XDC Cajón multicapa de cartón
102 101 XDG Jaula, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP))
103 102 XDH Caja, Según la clasificación de la compañía (CHEP), Eurobox
104 103 XDI Tambor de hierro
105 104 XDJ damajuana o garrafa, no protegido
106 105 XDK Cajón a granel, cartón
107 106 XDL Cajas de plástico
108 107 XDM Cajones a granel de madera
109 108 XDN Dispensador
110 109 XDP damajuana o garrafa, protegido
111 110 XDR Tambor
112 111 XDS Bandeja de una capa sin cubierta y de plástico
113 112 XDT Bandeja de una capa sin cubierta y de madera
114 113 XDU Bandeja de una capa sin cubierta y poliestireno
115 114 XDV Bandeja de una capa sin cubierta y de cartón
116 115 XDW Bandeja de dos capas sin tapa y con bandeja de plástico
117 116 XDX Bandeja de dos capas sin cubierta y de madera
118 117 XDY Bandeja de dos capas sin cubierta y de cartón
119 118 XEC Bolsa de plástico
120 119 XED Estuche, con pallet de base
121 120 XEE Estuche, con pallet base de madera
122 121 XEF Estuche, con pallet base de cartón
123 122 XEG Estuche, con pallet base de plástico
124 123 XEH Estuche, con pallet base de metal
125 124 XEI Estuche isotérmico
126 125 XEN Sobre
127 126 XFB Bolsa flexible
128 127 XFC Cajón para fruta
129 128 XFD Cajón enmarcado
130 129 XFE Tanque flexible
131 130 XFI Firkin
132 131 XFL Matraz
133 132 XFO Cajón para zapatos
134 133 XFP Caja auxiliar para película fotográfica
135 134 XFR Marco
136 135 XFT Contenedor para alimentos
137 136 XFW Carro de cama plana
138 137 XFX Bolsa flexible tipo contenedor
139 138 XGB Botella para gas
140 139 XGI Viga
141 140 XGL Contenedor tipo galón
142 141 XGR Recipiente de vidrio
143 142 XGU Bandeja contenedor para apilar horizontalmente objetos planos
144 143 XGY Costal de Yute
145 144 XGZ Vigas con correas o agrupadas
146 145 XHA Cesta con mango y de plástico
147 146 XHB Cesta con mango y de madera
148 147 XHC Cesta con asa y de cartón
149 148 XHG Hogshead
150 149 XHN Gancho
151 150 XHR Cesto
152 151 XIA Paquete con pantalla y de madera
153 152 XIB Paquete con pantalla y de cartón
154 153 XIC Paquete con pantalla y de plástico
155 154 XID Paquete con pantalla y de metal
156 155 XIE Paquete de mostrador.
157 156 XIF Envase para alimentos
158 157 XIG Paquete envuelto en papel
159 158 XIH Tambor de plástico
160 159 XIK Paquete de cartón con los agujeros para botellas
161 160 XIL Bandeja rígida con tapa y apilable (CEN TS 14482: 2002)
162 161 XIN Lingote
163 162 XIZ Lingotes con correas/ agrupados
164 163 XJB Bolsa jumbo
165 164 XJC Bidón rectangular
166 165 XJG Jarra
167 166 XJR Tarro
168 167 XJT Bolsa de yute
169 168 XJY Bidón, cilíndrico
170 169 XKG Barrilete
171 170 XKI Kit (Conjunto de piezas)
172 171 XLE Valijas
173 172 XLG Bitácora
174 173 XLT Lote
175 174 XLU Caja de arrastre
176 175 XLV Contenedor pequeño
177 176 XLZ Registros con fleje/ agrupados/ armados
178 177 XMA Cajón metálico
179 178 XMB Múltiplo de bolsas
180 179 XMC Cajón para leche
181 180 XME Contenedor de metal
182 181 XMR Recipiente de metal
183 182 XMS Saco milti-pared
184 183 XMT Tapete
185 184 XMW Contenedor envuelto en plástico
186 185 XMX Caja pequeña de cerillos
187 186 XNA No disponible
188 187 XNE Sin empaque o no empaquetado
189 188 XNF Sin empaque o no empaquetado, unidad simple
190 189 XNG Sin empaque o no empaquetado, unidades múltiples
191 190 XNS Caja nido
192 191 XNT Red
193 192 XNU Red de plástico con tubo
194 193 XNV Red textil con tubo
195 194 XOA Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 40 cm x 60 cm
196 195 XOB Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 80 cm x 120 cm
197 196 XOC Pallet, Según la clasificación de la compañía (Commonwealth Handling Equipment Pool (CHEP) 100 cm x 120 cm
198 197 XOD Pallet, AS 4068-1993
199 198 XOE Pallet, ISO T11
200 199 XOF Plataforma, peso o dimensión no especificada
201 200 XOK Bloque
202 201 XOT Octabin
203 202 XP2 Charola
204 203 XPA Cajetilla
205 204 XPB Pallet, Caja combinada y abierta con caja y pallet.
206 205 XPC Paquete postal
207 206 XPD Pallet modular con collares (80cms * 100cms)
208 207 XPE Pallet modular con collares (80cms * 120cms)
209 208 XPF Corral
210 209 XPG Placa
211 210 XPH Cantaro
212 211 XPI Pleca
213 212 XPJ Canastilla
214 213 XPK Paquete
215 214 XPL Balde
216 215 XPN Tablón
217 216 XPO Bolsa pequeña
218 217 XPR Contenedor de plástico
219 218 XPT Maceta
220 219 XPU Cacerola
221 220 XPV Tubos, con fleje/ agrupados/ armados
222 221 XPX Pallet
223 222 XPY Placas con fleje/ agrupados/ armados
224 223 XPZ Tablones con fleje/ agrupados/ armados
225 224 XQA Tambor de acero con cabeza no desmontable
226 225 XQB Tambor de acero con cabeza extraíble
227 226 XQC Tambor de aluminio con cabeza no extraíble
228 227 XQD Tambor de aluminio con cabeza extraíble
229 228 XQF Tambor, plástico con cabeza no desmontable
230 229 XQG Tambor, plástico, cabeza extraíble
231 230 XQH Barril de madera con tapón
232 231 XQJ Barril de madera con cabeza desprendible
233 232 XQK Bidón de acero con cabeza no desmontable
234 233 XQL Bidón de acero con cabeza desmontable
235 234 XQM Bidón de plástico con cabeza no desmontable
236 235 XQN Bidón de plástico con cabeza extraíble
237 236 XQP Caja de madera natural estándar
238 237 XQQ Caja de madera natural con muros a prueba de filtraciones
239 238 XQR Caja de plástico expandida
240 239 XQS Caja de plástico sólida
241 240 XRD Rod
242 241 XRG Anillo
243 242 XRJ Estante, Perchero para ropa
244 243 XRK Estante
245 244 XRL Carrete
246 245 XRO Rollo
247 246 XRT Red Roja
248 247 XRZ Varillas con fleje/ agrupados/ armados
249 248 XSA Saco
250 249 XSB Losa
251 250 XSC Cajón poco profundo
252 251 XSD Huso
253 252 XSE Baúl
254 253 XSH Bolsa pequeña hermética
255 254 XSI Patín
256 255 XSK Carcasa esqueleto
257 256 XSL Hoja de deslizamiento
258 257 XSM Hoja de metal
259 258 XSO Carrete pequeño
260 259 XSP Hoja de empaque de plástico
261 260 XSS Cajón de acero
262 261 XSU Maleta
263 262 XSV Sobre de acero
264 263 XSW Envoltorio
265 264 XSY Manga
266 265 XSZ Hojas con fleje/ agrupados/ armados
267 266 XT1 Tableta
268 267 XTB Tina
269 268 XTC Caja para té
270 269 XTD Tubo plegable
271 270 XTG Contenedor de tanque genérico
272 271 XTI Tierce
273 272 XTK Tanque rectangular
274 273 XTL Tina con tapa
275 274 XTN Hojalata
276 275 XTO Tonel
277 276 XTR Maletero
278 277 XTS Estructura
279 278 XTT Bolsa de mano
280 279 XTU Tubo
281 280 XTV Tubo con boquilla
282 281 XTW Pallet tricapa
283 282 XTY Tanque cilíndrico
284 283 XTZ Tubos con fleje/ agrupados/ armados
285 284 XUC Sin empaque
286 285 XUN Unidad
287 286 XVA Tanque
288 287 XVG Tanque de gas (a 1,031 mbar y 15° C)
289 288 XVI Frasco pequeño
290 289 XVK Paquete transportable
291 290 XVL Contenedor para líquidos a granel
292 291 XVN Vehículo
293 292 XVO Contenedor para sólido de partículas grandes a granel ("nódulos")
294 293 XVP Envasado al vacío
295 294 XVQ Tanque para Gas licuado (a temperatura / presión anormal)
296 295 XVR Contenedor para sólidos de partículas granulares a granel (Granos)
297 296 XVS Contenedor de chatarra a granel
298 297 XVY Contenedor para sólido de partículas finas a granel ("polvos")
299 298 XWA Contenedor de granel intermedio
300 299 XWB Botella de mimbre
301 300 XWC Contenedor intermedio para gráneles y de acero
302 301 XWD Contenedor intermedio para gráneles y de aluminio
303 302 XWF Contenedor intermedio para gráneles y de metal
304 303 XWG Contenedor intermedio para gráneles y de acero presurizado menor a 10 kpa
305 304 XWH Contenedor intermedio para gráneles y de aluminio, presurizado menor a 10 kpa
306 305 XWJ Contenedor intermedio para gráneles y de metal con una presión de 10 kpa
307 306 XWK Contenedor intermedio para gráneles y de acero para líquido
308 307 XWL Contenedor intermedio para gráneles y de aluminio para líquido
309 308 XWM Contenedor intermedio para gráneles y de metal para líquido
310 309 XWN Contenedor intermedio para gráneles con tejido plástico sin capa con revestimiento
311 310 XWP Contenedor intermedio para gráneles con tejido plástico y recubierto
312 311 XWQ Contenedor intermedio para gráneles con tejido plástico con revestimiento
313 312 XWR Contenedor intermedio para gráneles con tejido plástico, revestido y con forro
314 313 XWS Contenedor intermedio para gráneles con película de plástico
315 314 XWT Contenedor intermedio para gráneles textil sin capa / forro
316 315 XWU Contenedor intermedio para gráneles de madera natural con forro interior
317 316 XWV Contenedor intermedio para gráneles textil recubierto
318 317 XWW Contenedor intermedio para gráneles textil con revestimiento
319 318 XWX Contenedor intermedio para gráneles textil recubierto y con forro
320 319 XWY Contenedor intermedio para gráneles contrachapado con revestimiento interior
321 320 XWZ Contenedor intermedio para gráneles de madera reconstituida con revestimiento interior
322 321 XXA Bolsa de tejido plástico, sin abrigo interior ni forro
323 322 XXB Bolsa de tejido plástico a prueba de filtraciones
324 323 XXC Bolsa de tejido plástico resistente al agua
325 324 XXD Bolsa con película de plástico
326 325 XXF Bolsa textil sin capa ni forro interior
327 326 XXG Bolsa textil a prueba de filtraciones
328 327 XXH Bolsa textil resistente al agua
329 328 XXJ Bolsa de papel multi-pared
330 329 XXK Bolsa de papel multi-pared, resistente al agua
331 330 XYA Empaque compuesto, recipiente de plástico en tambor de acero
332 331 XYB Empaque compuesto, recipiente de plástico en cajas de acero
333 332 XYC Empaque compuesto, recipiente de plástico en tambor de aluminio
334 333 XYD Empaque compuesto, recipiente de plástico en cajón de aluminio
335 334 XYF Empaque compuesto, recipiente de plástico en caja de madera
336 335 XYG Empaque compuesto, recipiente de plástico en tambor de madera contrachapada
337 336 XYH Empaque compuesto, recipiente de plástico en caja de madera contrachapada
338 337 XYJ Empaque compuesto, recipiente de plástico en tambor de fibra
339 338 XYK Empaque compuesto, recipiente de plástico en caja de cartón
340 339 XYL Empaque compuesto, recipiente de plástico en el tambor de plástico
341 340 XYM Empaque compuesto, recipiente de plástico en caja de plástico sólido
342 341 XYN Empaque compuesto, receptáculo de vidrio en tambor de acero
343 342 XYP Empaque compuesto, receptáculo de vidrio en caja de cajas de acero
344 343 XYQ Empaque compuesto, recipiente de vidrio en tambor de aluminio
345 344 XYR Empaque compuesto, receptáculo de vidrio en caja de aluminio
346 345 XYS Empaque compuesto, recipiente de vidrio en caja de madera
347 346 XYT Empaque compuesto, recipiente de vidrio en tambor de madera contrachapada
348 347 Xyv Empaque compuesto, recipiente de vidrio en el cesto de mimbre
349 348 XYW Empaque compuesto, recipiente de vidrio en tambor de fibra
350 349 XYX Empaque compuesto, recipiente de vidrio en caja de cartón
351 350 XYY Empaque compuesto, recipiente de vidrio en paquete de plástico expandible
352 351 XYZ Empaque compuesto, recipiente de vidrio en paquete de plástico sólido
353 352 XZA Contenedor de granel intermedio, papel, multi-pared
354 353 XZB Bolsa grande
355 354 XZC Contenedor intermedio para gráneles de papel, multi-pared y resistente al agua
356 355 XZD Contenedor intermedio para gráneles de plástico rígido, con equipo estructural para sólidos
357 356 XZF Contenedor intermedio para gráneles de plástico rígido, autoportante para sólidos
358 357 XZG Contenedor intermedio para gráneles de plástico rígido, con equipo estructural, presurizado
359 358 XZH Contenedor intermedio para gráneles de plástico rígido, autoportante y presurizado
360 359 XZJ Contenedor intermedio para gráneles de plástico rígido, con equipo estructural para líquidos
361 360 XZK Contenedor intermedio para gráneles de plástico rígido, autoportante, líquidos
362 361 XZL Contenedor intermedio para gráneles, compuesto y de plástico rígido, sólidos
363 362 XZM Contenedor intermedio para gráneles, compuesto y de plástico flexible, sólidos
364 363 XZN Contenedor intermedio para gráneles, compuesto y de plástico rígido, presurizado
365 364 XZP Contenedor intermedio para gráneles, compuesto y de plástico flexible, presurizado
366 365 XZQ Contenedor intermedio para gráneles, compuesto y de plástico rígido, líquidos
367 366 XZR Contenedor intermedio para gráneles, compuesto y de plástico flexible para líquidos
368 367 XZS Contenedor intermedio para gráneles, compuesto
369 368 XZT Contenedor intermedio para gráneles con tablero de fibras
370 369 XZU Contenedor intermedio para gráneles flexible
371 370 XZV Contenedor intermedio para gráneles de metal, distinto del acero
372 371 XZW Contenedor intermedio para gráneles, de madera natural
373 372 XZX Contenedor intermedio para gráneles, de contrachapado
374 373 XZY Contenedor intermedio para gráneles, de madera reconstituida
375 374 KGM Kilogramo
376 375 MC Microgramo
377 376 DJ Decagramo
378 377 DG Decigramo
379 378 GRM Gramo
380 379 CGM Centigramo
381 380 TNE Tonelada (tonelada métrica)
382 381 DTN Decitonelada métrica
383 382 MGM Miligramo
384 383 HGM Hectogramo
385 384 KTN Kilotonelada Métrica
386 385 2U Megagramo
387 386 LBR Libra
388 387 GRN Grano
389 388 ONZ Onza (avoirdupois)
390 389 CWI Hundredweight
391 390 CWA Hundred pound
392 391 LTN Tonelada (UK) o tonelada larga (estados unidos)
393 392 STI Estone (UK)
394 393 STN Tonelada (estados unidos) o tonelada corta (UK y estados unidos)
395 394 APZ Onza troy u onza farmacéutica
396 395 F13 Slug
397 396 K64 Libra (avoirdupois) por grado fahrenheit
398 397 L69 Tonelada por kelvin
399 398 L87 Tonelada corta por grado fahrenheit
400 399 M85 Tonelada, ensayo
401 400 M86 Libra Alemana
402 401 J33 Microgramo por kilogramo
403 402 L32 Nanogramo por kilogramo
404 403 NA Miligramo por kilogramo
405 404 M29 Kilogramo por kilogramo
406 405 M91 Libra por libra
407 406 Q29 Microgramo por hectogramo
408 407 MTQ Metro cúbico
409 408 MAL Megalitro
410 409 LTR Litro
411 410 MMQ Milímetro cúbico
412 411 CMQ Centímetro cúbico
413 412 DMQ Decímetro cúbico
414 413 MLT Mililitro
415 414 HLT Hectolitro
416 415 CLT Centilitro
417 416 DMA Decámetro cúbico
418 417 H19 Hectómetro cúbico
419 418 H20 Kilómetro cúbico
420 419 M71 Metro cúbico por pascal (joules)
421 420 DLT Decilitro
422 421 4G Microlitro
423 422 K6 Kilolitro
424 423 A44 Decalitro
425 424 G94 Centímetro cúbico por bar
426 425 G95 Litro por bar
427 426 G96 Metro cúbico por bar
428 427 G97 Mililitro por bar
429 428 5I Pies cúbicos estándar
430 429 INQ Pulgada cúbica
431 430 FTQ Pie cúbico
432 431 YDQ Yarda cúbica
433 432 GLI Galón (UK)
434 433 GLL Galón (EUA)
435 434 PT Pinta (US)
436 435 PTI Pint (uk)
437 436 QTI Cuarto (UK)
438 437 PTL Pinta líquida (estados unidos)
439 438 QTL Cuarto de líquido (estadis unidos)
440 439 PTD Pinta seca (estados unidos)
441 440 OZI Onza líquida (UK)
442 441 QT Cuarto (EUA)
443 442 J57 Barril (uk petróleo)
444 443 K21 Pie cúbico por grado fahrenheit
445 444 K23 Pie cúbico por psi (libra por pulgada cuadrada)
446 445 L43 Peck (UK)
447 446 L61 Pinta (US seco)
448 447 L62 Cuarto de galón (seco de los EUA)
449 448 L84 Tonelada (flota UK)
450 449 L86 Tonelada (flota estados unidos)
451 450 M11 Yarda cúbica por grado fahrenheit
452 451 M14 Yarda cúbica por psi (libra por pulgada cuadrada)
453 452 OZA Onza líquida (estados unidos)
454 453 BUI Bushel (UK)
455 454 BUA Bushel (EUA)
456 455 BLL Barril (EUA)
457 456 BLD Barril seco (EUA)
458 457 GLD Galón seco (EUA)
459 458 QTD Cuarto seco (estados unidos)
460 459 G26 Estere
461 460 G21 Taza (unidad de volumen)
462 461 G24 Cucharada (estados unidos)
463 462 G25 Cucharilla (estados unidos)
464 463 G23 Peck
465 464 M67 Acre-pie
466 465 M68 Cordón
467 466 M69 Milla cúbica (reino unido)
468 467 M70 Unidad tradicional de capacidad de carga
469 468 Q32 Femtolitro
470 469 Q33 Picolitro
471 470 Q34 Nanolitro
472 471 NM3 Metro cúbico normalizado
473 472 SM3 Metro cúbico estándar

File diff suppressed because it is too large Load Diff

View File

@ -47,6 +47,13 @@
font-size: 125%;
}
.link_default {
font-weight: bold;
color: #610B0B;
text-decoration: none;
}
.link_default:hover {text-decoration:underline;}
.link_forum {
font-weight: bold;
color: #610B0B;

View File

@ -0,0 +1,519 @@
@page{
size: Letter;
margin: 0.5cm;
background-color: white !important;
}
@media print {
thead {display: table-header-group;}
body {
background-color: #fff;
}
}
.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;
}
body {
/*
font-family: 'Avenir Next';
*/
}
#plantilla {
border: 1px solid #fff;
border-color: rgb(204,204,204);
background: #fff;
display: block;
margin: 0 auto;
width: 19.5cm;
height: 25.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: 60%;
}
.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: 9px;
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: 5px 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: 14px;
}
.notas{
float: left;
font-size: 11px;
margin: 20px 0;
width: 70%;
}
.formapago-metodopago, .moneda-tipocambio, .tiporelacion, .relacionados{
float: left;
font-size: 11px;
line-height: 12px;
width: 70%;
}
.factura-info{
float: left;
font-size: 11px;
line-height: 13px;
width: 70%;
}
.tipocomite, .tipoproceso, .idcontabilidad{
float: right;
font-size: 10px;
line-height: 12px;
width: 20%;
}
.sello{
margin: 10px 0;
}
.sello .cbb{
border: 1px solid #000;
height: auto;
margin-top: 10px;
margin-right: 1px;
width: 18%;
}
.sello .cadenas-sello{
color: #000;
float: right;
font-size: 8px;
font-weight: bold;
line-height: 15px;
width: 79%;
margin-left: 5px;
margin-right: 5px;
word-break: break-all;
}
.sello .cadenas-sello .cadena{
margin: 5px 0;
}
.sello .cadenas-sello .cadena div{
font-weight: normal;
background-color: #dbc5c6;
color: #7d1916;
}
.cadena-original{
color: #000;
float: right;
font-size: 8px;
font-weight: bold;
line-height: 15px;
width: 99%;
margin: 5px;
word-break: break-all;
}
.cadena-original div{
font-weight: normal;
background-color: #dbc5c6;
color: #7d1916;
}
.rfc-pac{
background-color: #dbc5c6;
color: #7d1916;
font-size: 10px;
line-height: 15px;
text-align: center;
margin: 5px;
}

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.

Some files were not shown because too many files have changed in this diff Show More