9 changed files with 765 additions and 30 deletions
@ -0,0 +1,183 @@ |
|||||
|
--[[ |
||||
|
{ |
||||
|
"uid": "53935f27-5cf0-4281-9a29-a68f100ee981", |
||||
|
"execution_status": "Borrador", |
||||
|
"tipo_comprobante": "factura c", |
||||
|
"fecha_comprobante": "13/11/2025", |
||||
|
"tipo_concepto": "servicios", |
||||
|
"servicio_fecha_desde": "01/10/2025", |
||||
|
"servicio_fecha_hasta": "01/10/2025", |
||||
|
"servicio_fecha_vencimiento_pago": "13/11/2025", |
||||
|
"referencia": "", |
||||
|
"tipo_iva_receptor": "consumidor final", |
||||
|
"cuit_receptor": "", |
||||
|
"forma_pago": "contado", |
||||
|
"monto_unitario": "10000", |
||||
|
"cantidad": "1", |
||||
|
"descripcion": "Clase de apoyo" |
||||
|
} |
||||
|
--]] |
||||
|
|
||||
|
local json = require("json") |
||||
|
|
||||
|
---------------------------------------------------------------------- |
||||
|
-- Helpers |
||||
|
---------------------------------------------------------------------- |
||||
|
local function logErrorAndExit(message) |
||||
|
log(message) |
||||
|
error(message) |
||||
|
end |
||||
|
|
||||
|
local function is_servicios(tipo_concepto) |
||||
|
if tipo_concepto == "servicios" or tipo_concepto == "servicio" then |
||||
|
return true |
||||
|
end |
||||
|
return false |
||||
|
end |
||||
|
|
||||
|
local function is_productos_y_servicios(tipo_concepto) |
||||
|
if tipo_concepto == "productos y servicios" or tipo_concepto == "productos servicios" then |
||||
|
return true |
||||
|
end |
||||
|
return false |
||||
|
end |
||||
|
|
||||
|
local function is_productos(tipo_concepto) |
||||
|
if tipo_concepto == "productos" or tipo_concepto == "producto" then |
||||
|
return true |
||||
|
end |
||||
|
return false |
||||
|
end |
||||
|
|
||||
|
-- Map "tipo_concepto" (as text) to the numeric value expected by AFIP in |
||||
|
-- the "idconcepto" select. |
||||
|
-- Mirrors TipoConceptoFromString in Go (internal/robot/tipos.go). |
||||
|
local function mapTipoConcepto(tipo) |
||||
|
if is_productos(tipo) then |
||||
|
return "1" -- TipoConceptoProductos |
||||
|
end |
||||
|
if is_servicios(tipo) then |
||||
|
return "2" -- TipoConceptoServicios |
||||
|
end |
||||
|
if is_productos_y_servicios(tipo) then |
||||
|
return "3" -- TipoConceptoProductosYServicios |
||||
|
end |
||||
|
|
||||
|
return "" |
||||
|
end |
||||
|
|
||||
|
local function conceptoRequierePeriodoServicio(tipo) |
||||
|
if is_servicios(tipo) or is_productos_y_servicios(tipo) then |
||||
|
return true |
||||
|
end |
||||
|
|
||||
|
return false |
||||
|
end |
||||
|
|
||||
|
---------------------------------------------------------------------- |
||||
|
-- Main logic |
||||
|
---------------------------------------------------------------------- |
||||
|
|
||||
|
local function main() |
||||
|
log("afip_ingresar_datos_emision: starting") |
||||
|
|
||||
|
local payload, err = json.decode(json_payload) |
||||
|
log("afip_ingresar_datos_emision: decoded payload: " .. json.encode(payload)) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: error decoding payload: " .. err) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Extract fields from payload |
||||
|
-------------------------------------------------------------------- |
||||
|
local fechaComprobante = payload["fecha_comprobante"] or "" |
||||
|
local tipoConceptoTexto = payload["tipo_concepto"] or "" |
||||
|
local servicioFechaDesde = payload["servicio_fecha_desde"] or "" |
||||
|
local servicioFechaHasta = payload["servicio_fecha_hasta"] or "" |
||||
|
local servicioFechaVencimiento= payload["servicio_fecha_vencimiento_pago"] or "" |
||||
|
local referencia = payload["referencia"] or "" |
||||
|
|
||||
|
if fechaComprobante == "" then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: fecha_comprobante is empty") |
||||
|
end |
||||
|
if tipoConceptoTexto == "" then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: tipo_concepto is empty") |
||||
|
end |
||||
|
|
||||
|
local conceptoValue = mapTipoConcepto(tipoConceptoTexto) |
||||
|
if conceptoValue == "" then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: tipo_concepto value not recognized: " .. tostring(tipoConceptoTexto)) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- 1) Fecha de comprobante y concepto (ids: fc, idconcepto) |
||||
|
-------------------------------------------------------------------- |
||||
|
local fechaComprobanteId = "fc" |
||||
|
local conceptoId = "idconcepto" |
||||
|
|
||||
|
err = setTextFieldById(fechaComprobanteId, tostring(fechaComprobante)) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: setTextFieldById for fechaComprobante failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
err = setTextFieldById(conceptoId, tostring(conceptoValue)) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: setTextFieldById for concepto failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- 2) Periodo de servicio y vencimiento de pago (solo servicios o |
||||
|
-- productos y servicios) |
||||
|
-------------------------------------------------------------------- |
||||
|
if conceptoRequierePeriodoServicio(tipoConceptoTexto) then |
||||
|
log("afip_ingresar_datos_emision: setting service period and due date") |
||||
|
|
||||
|
if servicioFechaDesde == "" or servicioFechaHasta == "" or servicioFechaVencimiento == "" then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: service period dates are required when tipo_concepto is 'servicios' or 'productos y servicios'") |
||||
|
end |
||||
|
|
||||
|
local fechaDesdeId = "fsd" |
||||
|
local fechaHastaId = "fsh" |
||||
|
local fechaVencimientoPagoId = "vencimientopago" |
||||
|
|
||||
|
err = setTextFieldById(fechaDesdeId, tostring(servicioFechaDesde)) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: setTextFieldById for fechaDesde failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
err = setTextFieldById(fechaHastaId, tostring(servicioFechaHasta)) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: setTextFieldById for fechaHasta failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
err = setTextFieldById(fechaVencimientoPagoId, tostring(servicioFechaVencimiento)) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: setTextFieldById for fechaVencimientoPago failed: " .. err) |
||||
|
end |
||||
|
else |
||||
|
log("afip_ingresar_datos_emision: tipo_concepto does not require service period: " .. tostring(tipoConceptoTexto)) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- 3) Referencia (id: refComEmisor) |
||||
|
-------------------------------------------------------------------- |
||||
|
local referenciaId = "refComEmisor" |
||||
|
|
||||
|
-- Even if referencia is empty, setting the field should be harmless and |
||||
|
-- mirrors the Go behavior that always calls SetValue. |
||||
|
err = setTextFieldById(referenciaId, tostring(referencia)) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_emision: setTextFieldById for referencia failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Done |
||||
|
-------------------------------------------------------------------- |
||||
|
log("afip_ingresar_datos_emision: completed successfully") |
||||
|
end |
||||
|
|
||||
|
local ok, err = pcall(main) |
||||
|
if not ok then |
||||
|
-- Let Go see this as a Lua error so it can fall back to legacy datosDeEmision |
||||
|
logErrorAndExit("afip_ingresar_datos_emision failed: " .. tostring(err)) |
||||
|
end |
||||
@ -0,0 +1,277 @@ |
|||||
|
--[[ |
||||
|
{ |
||||
|
"uid": "53935f27-5cf0-4281-9a29-a68f100ee981", |
||||
|
"execution_status": "Borrador", |
||||
|
"tipo_comprobante": "factura c", |
||||
|
"fecha_comprobante": "13/11/2025", |
||||
|
"tipo_concepto": "servicios", |
||||
|
"servicio_fecha_desde": "01/10/2025", |
||||
|
"servicio_fecha_hasta": "01/10/2025", |
||||
|
"servicio_fecha_vencimiento_pago": "13/11/2025", |
||||
|
"referencia": "", |
||||
|
"tipo_iva_receptor": "consumidor final", |
||||
|
"cuit_receptor": "", |
||||
|
"forma_pago": "contado", |
||||
|
"monto_unitario": "10000", |
||||
|
"cantidad": "1", |
||||
|
"descripcion": "Clase de apoyo" |
||||
|
} |
||||
|
--]] |
||||
|
|
||||
|
local json = require("json") |
||||
|
|
||||
|
---------------------------------------------------------------------- |
||||
|
-- Helpers |
||||
|
---------------------------------------------------------------------- |
||||
|
local function logErrorAndExit(message) |
||||
|
log(message) |
||||
|
error(message) |
||||
|
end |
||||
|
|
||||
|
local function is_consumidor_final(tipo) |
||||
|
return tipo == "consumidor final" or tipo == "final" |
||||
|
end |
||||
|
|
||||
|
local function is_responsable_inscripto(tipo) |
||||
|
return tipo == "responsable inscripto" or tipo == "responsable" |
||||
|
end |
||||
|
|
||||
|
local function is_responsable_monotributo(tipo) |
||||
|
return tipo == "responsable monotributo" or tipo == "monotributo" or tipo == "monotributista" |
||||
|
end |
||||
|
|
||||
|
-- Based on the values from the json returns the expected option value to select on the the UI |
||||
|
--[[ |
||||
|
// <option value="" selected="selected" style="color:#888;">seleccionar...</option> |
||||
|
// <option value="1"> IVA Responsable Inscripto</option> |
||||
|
// <option value="4"> IVA Sujeto Exento</option> |
||||
|
// <option value="5"> Consumidor Final</option> |
||||
|
// <option value="6"> Responsable Monotributo</option> |
||||
|
// <option value="7"> Sujeto No Categorizado</option> |
||||
|
// <option value="8"> Proveedor del Exterior</option> |
||||
|
// <option value="9"> Cliente del Exterior</option> |
||||
|
// <option value="10"> IVA Liberado - Ley Nº 19.640</option> |
||||
|
// <option value="13"> Monotributista Social</option> |
||||
|
// <option value="15"> IVA No Alcanzado</option> |
||||
|
// <option value="16"> Monotributista Trabajador Independiente Promovido</option> |
||||
|
]]-- |
||||
|
local function mapTipoIvaReceptor(tipo) |
||||
|
if is_consumidor_final(tipo) then |
||||
|
return "5" |
||||
|
end |
||||
|
if is_responsable_inscripto(tipo) then |
||||
|
return "1" |
||||
|
end |
||||
|
if is_responsable_monotributo(tipo) then |
||||
|
return "6" |
||||
|
end |
||||
|
return "" |
||||
|
end |
||||
|
|
||||
|
local function is_forma_pago_contado(forma) |
||||
|
return forma == "contado" or forma == "efectivo" |
||||
|
end |
||||
|
|
||||
|
local function is_forma_pago_debito(forma) |
||||
|
return forma == "debito" or forma == "tarjeta de debito" |
||||
|
end |
||||
|
|
||||
|
local function is_forma_pago_credito(forma) |
||||
|
return forma == "credito" or forma == "tarjeta de credito" |
||||
|
end |
||||
|
|
||||
|
local function is_forma_pago_cuenta_corriente(forma) |
||||
|
return forma == "cuenta corriente" or forma == "cta corriente" |
||||
|
end |
||||
|
|
||||
|
local function is_forma_pago_cheque(forma) |
||||
|
return forma == "cheque" |
||||
|
end |
||||
|
|
||||
|
local function is_forma_pago_ticket(forma) |
||||
|
return forma == "ticket" |
||||
|
end |
||||
|
|
||||
|
local function is_forma_pago_otra(forma) |
||||
|
return forma == "otra" or forma == "otro" |
||||
|
end |
||||
|
|
||||
|
local function mapFormaPago(forma) |
||||
|
--[[ |
||||
|
// <option value="" selected="selected" style="color:#888;">seleccionar...</option> |
||||
|
// <option value="1"> Contado</option> |
||||
|
// <option value="2"> Tarjeta de Debito</option> |
||||
|
// <option value="3"> Tarjeta de Credito</option> |
||||
|
// <option value="4"> Cuenta Corriente</option> |
||||
|
// <option value="5"> Cheque</option> |
||||
|
// <option value="6"> Ticket</option> |
||||
|
// <option value="7"> Otra</option> |
||||
|
|
||||
|
FormaPagoInvalido = 0 |
||||
|
FormaPagoContado = 1 |
||||
|
FormaPagoTarjetaDebito = 2 |
||||
|
FormaPagoTarjetaCredito = 3 |
||||
|
FormaPagoCuentaCorriente = 4 |
||||
|
FormaPagoCheque = 5 |
||||
|
FormaPagoTicket = 6 |
||||
|
FormaPagoOtra = 7 |
||||
|
|
||||
|
case "contado", "efectivo": |
||||
|
return FormaPagoContado, nil |
||||
|
case "debito", "tarjeta de debito": |
||||
|
return FormaPagoTarjetaDebito, nil |
||||
|
case "credito", "tarjeta de credito": |
||||
|
return FormaPagoTarjetaCredito, nil |
||||
|
case "cuenta corriente", "cta corriente": |
||||
|
return FormaPagoCuentaCorriente, nil |
||||
|
case "cheque": |
||||
|
return FormaPagoCheque, nil |
||||
|
case "ticket": |
||||
|
return FormaPagoTicket, nil |
||||
|
case "otra", "otro": |
||||
|
return FormaPagoOtra, nil |
||||
|
]]-- |
||||
|
|
||||
|
if is_forma_pago_contado(forma) then |
||||
|
return "1" |
||||
|
end |
||||
|
if is_forma_pago_debito(forma) then |
||||
|
return "2" |
||||
|
end |
||||
|
if is_forma_pago_credito(forma) then |
||||
|
return "3" |
||||
|
end |
||||
|
if is_forma_pago_cuenta_corriente(forma) then |
||||
|
return "4" |
||||
|
end |
||||
|
if is_forma_pago_cheque(forma) then |
||||
|
return "5" |
||||
|
end |
||||
|
if is_forma_pago_ticket(forma) then |
||||
|
return "6" |
||||
|
end |
||||
|
if is_forma_pago_otra(forma) then |
||||
|
return "7" |
||||
|
end |
||||
|
return "" |
||||
|
end |
||||
|
|
||||
|
---------------------------------------------------------------------- |
||||
|
-- Main logic |
||||
|
---------------------------------------------------------------------- |
||||
|
|
||||
|
local function main() |
||||
|
log("afip_ingresar_datos_receptor: starting") |
||||
|
|
||||
|
local payload, err = json.decode(json_payload) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: error decoding payload: " .. err) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Extract fields from payload |
||||
|
-------------------------------------------------------------------- |
||||
|
local tipoIvaReceptor = payload["tipo_iva_receptor"] or "" |
||||
|
local cuitReceptor = payload["cuit_receptor"] or "" |
||||
|
local formaPago = payload["forma_pago"] or "" |
||||
|
|
||||
|
if tipoIvaReceptor == "" then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: tipo_iva_receptor is empty") |
||||
|
end |
||||
|
if formaPago == "" then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: forma_pago is empty") |
||||
|
end |
||||
|
|
||||
|
local ivaValue = mapTipoIvaReceptor(tipoIvaReceptor) |
||||
|
local formaValue = mapFormaPago(formaPago) |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- 1) Select IVA del receptor (select#idivareceptor) |
||||
|
-------------------------------------------------------------------- |
||||
|
local ivaId = "idivareceptor" |
||||
|
err = setTextFieldById(ivaId, tostring(ivaValue)) |
||||
|
|
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: set IVA failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
log("afip_ingresar_datos_receptor: IVA receptor set to " .. tostring(ivaValue)) |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- 2) Ingresar CUIT receptor si corresponde |
||||
|
-- (Go logic: only for some IVA types and when CUIT is non-empty) |
||||
|
-- Here we only check that CUIT is non-empty; if you want to restrict |
||||
|
-- by tipoIva, put that logic in requiresCuit(type). |
||||
|
-------------------------------------------------------------------- |
||||
|
|
||||
|
local function requiresCuit(tipo) |
||||
|
-- Should we check that CUIT is non-empty? |
||||
|
return is_responsable_inscripto(tipo) or is_responsable_monotributo(tipo) |
||||
|
end |
||||
|
|
||||
|
if requiresCuit(tipoIvaReceptor) then |
||||
|
local cuitSelectorId = "nrodocreceptor" |
||||
|
|
||||
|
err = waitVisibleById(cuitSelectorId) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: waitVisible CUIT failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
--ok, setErr = pcall(function() |
||||
|
-- -- Clear first, then set and send Tab (same idea as Go code: SetValue + Tab) |
||||
|
-- clearTextFieldByQuerySelector(cuitSelector) |
||||
|
-- setTextFieldByQuerySelector(cuitSelector, tostring(cuitReceptor)) |
||||
|
-- sendReturnKeyByQuerySelector(cuitSelector) -- or Tab if you add a binding; Enter usually also triggers validation |
||||
|
--end) |
||||
|
--if not ok then |
||||
|
-- error("afip_ingresar_datos_receptor: set CUIT failed: " .. tostring(setErr)) |
||||
|
--end |
||||
|
err = setTextFieldById(cuitSelectorId, tostring(cuitReceptor)) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: setTextFieldById failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
err = sendTabKeyById(cuitSelectorId) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: sendTabKeyById failed: " .. err) |
||||
|
end |
||||
|
|
||||
|
log("afip_ingresar_datos_receptor: CUIT receptor set to " .. tostring(cuitReceptor)) |
||||
|
else |
||||
|
log("afip_ingresar_datos_receptor: CUIT not required for tipo_iva_receptor=" .. tostring(tipoIvaReceptor)) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- 3) Seleccionar forma de pago (input[id=formadepagoX]) |
||||
|
-------------------------------------------------------------------- |
||||
|
if formaValue ~= nil and formaValue ~= "" then |
||||
|
log("afip_ingresar_datos_receptor: seleccionando forma de pago " .. tostring(formaValue)) |
||||
|
local formaSelector = "#formadepago" .. tostring(formaValue) |
||||
|
|
||||
|
err = waitVisibleByQuerySelector(formaSelector) |
||||
|
|
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: waitVisible formaPago failed: " .. tostring(waitErr)) |
||||
|
end |
||||
|
|
||||
|
err = clickElementByQuerySelector(formaSelector) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor: click formaPago failed: " .. tostring(setErr)) |
||||
|
end |
||||
|
|
||||
|
log("afip_ingresar_datos_receptor: forma_pago selected: " .. tostring(formaValue)) |
||||
|
else |
||||
|
log("afip_ingresar_datos_receptor: forma_pago is empty, skipping selection") |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Done |
||||
|
-------------------------------------------------------------------- |
||||
|
log("afip_ingresar_datos_receptor: completed successfully") |
||||
|
end |
||||
|
|
||||
|
local ok, err = pcall(main) |
||||
|
if not ok then |
||||
|
-- Let Go see this as a Lua error so it can fall back to legacy datosReceptor |
||||
|
logErrorAndExit("afip_ingresar_datos_receptor failed: " .. tostring(err)) |
||||
|
end |
||||
@ -0,0 +1,57 @@ |
|||||
|
local json = require("json") |
||||
|
|
||||
|
---------------------------------------------------------------------- |
||||
|
-- Helpers |
||||
|
---------------------------------------------------------------------- |
||||
|
local function logErrorAndExit(message) |
||||
|
log(message) |
||||
|
error(message) |
||||
|
end |
||||
|
|
||||
|
---------------------------------------------------------------------- |
||||
|
-- Main logic |
||||
|
---------------------------------------------------------------------- |
||||
|
local function main() |
||||
|
log("afip_ingresar_generacion_comprobantes: starting") |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Decode payload (currently unused but kept for future extensibility) |
||||
|
-------------------------------------------------------------------- |
||||
|
local payload, err = json.decode(json_payload or "{}") |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_generacion_comprobantes: error decoding payload: " .. err) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Click on the Generar Comprobantes button |
||||
|
-- This mirrors the Go logic that waits for and clicks the element |
||||
|
-- with id="btn_gen_cmp" using chromedp. |
||||
|
-------------------------------------------------------------------- |
||||
|
local btnId = "btn_gen_cmp" |
||||
|
|
||||
|
-- Ensure the element is visible first |
||||
|
err = waitVisibleById(btnId) |
||||
|
if err then |
||||
|
logErrorAndExit( |
||||
|
"afip_ingresar_generacion_comprobantes: waitVisibleById failed for btnId= " .. |
||||
|
tostring(btnId) .. ": " .. tostring(err) |
||||
|
) |
||||
|
end |
||||
|
|
||||
|
-- Click using id |
||||
|
err = clickElementById(btnId) |
||||
|
if err then |
||||
|
logErrorAndExit( |
||||
|
"afip_ingresar_generacion_comprobantes: clickElementById failed for btnId= " .. |
||||
|
tostring(btnId) .. ": " .. tostring(err) |
||||
|
) |
||||
|
end |
||||
|
|
||||
|
log("afip_ingresar_generacion_comprobantes: completed successfully") |
||||
|
end |
||||
|
|
||||
|
local ok, err = pcall(main) |
||||
|
if not ok then |
||||
|
-- Let Go see this as a Lua error so it can fall back to legacy |
||||
|
logErrorAndExit("afip_ingresar_generacion_comprobantes failed: " .. tostring(err)) |
||||
|
end |
||||
@ -0,0 +1,215 @@ |
|||||
|
-- TODO GENERAL: El json del comprobante no contiene el punto de venta, esto es algo que es atributo del cliente! |
||||
|
|
||||
|
--[[ |
||||
|
{ |
||||
|
"uid": "53935f27-5cf0-4281-9a29-a68f100ee981", |
||||
|
"execution_status": "Borrador", |
||||
|
"tipo_comprobante": "factura c", |
||||
|
"fecha_comprobante": "13/11/2025", |
||||
|
"tipo_concepto": "servicios", |
||||
|
"servicio_fecha_desde": "01/10/2025", |
||||
|
"servicio_fecha_hasta": "01/10/2025", |
||||
|
"servicio_fecha_vencimiento_pago": "13/11/2025", |
||||
|
"referencia": "", |
||||
|
"tipo_iva_receptor": "consumidor final", |
||||
|
"cuit_receptor": "", |
||||
|
"forma_pago": "contado", |
||||
|
"monto_unitario": "10000", |
||||
|
"cantidad": "1", |
||||
|
"descripcion": "Clase de apoyo", |
||||
|
"request": { |
||||
|
"request_uid": "f976aa76-8d49-4564-8e44-2100819bae77", |
||||
|
"execution_status": "Fallido", |
||||
|
"user_uid": "bc31ddeb-b8c7-48e1-b569-20bbd8e7ec12", |
||||
|
"options": { |
||||
|
"automatic_confirmation_flag": false |
||||
|
}, |
||||
|
"csv_data": "factura c,16/11/2025,servicios,01/10/2025,01/10/2025,16/11/2025,,monotributo,27287630412,contado,10000,1,Clase de apoyo\\n", |
||||
|
"credentials": { |
||||
|
"cuit": "20292929092", |
||||
|
"password": "password", |
||||
|
"orden_punto_venta": 1, |
||||
|
"nombre_empresa": "NOMBRE DE EMPRESA" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
--]] |
||||
|
|
||||
|
local json = require("json") |
||||
|
|
||||
|
---------------------------------------------------------------------- |
||||
|
-- Helpers |
||||
|
---------------------------------------------------------------------- |
||||
|
local function logErrorAndExit(message) |
||||
|
log(message) |
||||
|
error(message) |
||||
|
end |
||||
|
|
||||
|
-- Normalizar el input que viene del JSON por si acaso lowercase |
||||
|
local function normalize(s) |
||||
|
if not s then return "" end |
||||
|
s = string.lower(s) |
||||
|
-- s = s:gsub("^%s+", ""):gsub("%s+$", "") |
||||
|
return s |
||||
|
end |
||||
|
|
||||
|
--[[ |
||||
|
TODO: Analizar la posta, los tipos de comprobantes dependen del punto de venta, en el caso de un monotributo salen estos |
||||
|
asi que ver como hacer con el caso de un consumidor final y otros. Quizas buscar por el texto y en funcion de eso elegir el valor? (como se hace con el punto de venta?) |
||||
|
|
||||
|
<select name="universoComprobante" onchange="actualizarDescripcionTC(this.selectedIndex);" id="universocomprobante"> |
||||
|
<option value="2">Factura C</option> |
||||
|
<option value="3">Nota de Débito C</option> |
||||
|
<option value="4">Nota de Crédito C</option> |
||||
|
<option value="5">Recibo C</option> |
||||
|
<option value="120">Factura de Crédito Electrónica MiPyMEs (FCE) C</option> |
||||
|
<option value="121">Nota de Débito Electrónica MiPyMEs (FCE) C</option> |
||||
|
<option value="122">Nota de Crédito Electrónica MiPyMEs (FCE) C</option> |
||||
|
</select> |
||||
|
]]-- |
||||
|
-- TODO: Factura A o B |
||||
|
local function is_factura_a(tipo) |
||||
|
return tipo == "factura a" or tipo == "a" |
||||
|
end |
||||
|
|
||||
|
local function is_factura_b(tipo) |
||||
|
return tipo == "factura b" or tipo == "b" |
||||
|
end |
||||
|
|
||||
|
local function is_factura_c(tipo) |
||||
|
return tipo == "factura c" or tipo == "c" |
||||
|
end |
||||
|
|
||||
|
local function is_nota_debito(tipo) |
||||
|
return tipo == "nota de debito" or tipo == "debito" |
||||
|
end |
||||
|
|
||||
|
local function is_nota_credito(tipo) |
||||
|
return tipo == "nota de credito" or tipo == "credito" |
||||
|
end |
||||
|
|
||||
|
local function is_recibo(tipo) |
||||
|
return tipo == "recibo" |
||||
|
end |
||||
|
|
||||
|
--[[ |
||||
|
Valores que corresponden al value del select |
||||
|
TipoComprobanteInvalido = 0 |
||||
|
TipoComprobanteFacturaC = 2 |
||||
|
TipoComprobanteNotaDebito = 3 |
||||
|
TipoComprobanteNotaCredito = 4 |
||||
|
TipoComprobanteRecibo = 5 |
||||
|
]]-- |
||||
|
local function mapTipoComprobante(tipo) |
||||
|
local t = normalize(tipo) |
||||
|
|
||||
|
if is_factura_c(t) then |
||||
|
return "2" |
||||
|
elseif is_nota_debito(t) then |
||||
|
return "3" |
||||
|
elseif is_nota_credito(t) then |
||||
|
return "4" |
||||
|
elseif is_recibo(t) then |
||||
|
return "5" |
||||
|
elseif is_factura_a(t) then |
||||
|
return "6" -- TODO: Check real value |
||||
|
elseif is_factura_b(t) then |
||||
|
return "7" -- TODO: Check real value |
||||
|
end |
||||
|
|
||||
|
return tipo or "" |
||||
|
end |
||||
|
|
||||
|
---------------------------------------------------------------------- |
||||
|
-- Main logic |
||||
|
---------------------------------------------------------------------- |
||||
|
local function main() |
||||
|
log("afip_ingresar_punto_venta_y_comprobante: starting") |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Decode payload |
||||
|
-------------------------------------------------------------------- |
||||
|
local payload, err = json.decode(json_payload) |
||||
|
if err then |
||||
|
logErrorAndExit("afip_ingresar_punto_venta_y_comprobante: error decoding payload: " .. err) |
||||
|
end |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Extract fields from payload |
||||
|
-------------------------------------------------------------------- |
||||
|
-- Punto de venta: should correspond to comprobante.PuntoDeVenta |
||||
|
-- as used in Go (selectDropdownItemByPosition second argument). |
||||
|
local puntoDeVenta = nil |
||||
|
if payload["request"] and payload["request"]["credentials"] and payload["request"]["credentials"]["orden_punto_venta"] then |
||||
|
puntoDeVenta = payload["request"]["credentials"]["orden_punto_venta"] |
||||
|
else |
||||
|
logErrorAndExit("afip_ingresar_punto_venta_y_comprobante: punto_de_venta is missing in payload") |
||||
|
end |
||||
|
|
||||
|
-- Tipo de comprobante: we can receive textual or numeric. |
||||
|
local tipoComprobanteRaw = payload["tipo_comprobante"] |
||||
|
|
||||
|
if not tipoComprobanteRaw or tipoComprobanteRaw == "" then |
||||
|
logErrorAndExit("afip_ingresar_punto_venta_y_comprobante: tipo_comprobante is missing in payload") |
||||
|
end |
||||
|
|
||||
|
-- Ensure punto de venta is numeric |
||||
|
local puntoDeVentaNum = tonumber(puntoDeVenta) |
||||
|
if not puntoDeVentaNum then |
||||
|
logErrorAndExit( |
||||
|
"afip_ingresar_punto_venta_y_comprobante: punto_de_venta must be numeric, got: " .. tostring(puntoDeVenta) |
||||
|
) |
||||
|
end |
||||
|
|
||||
|
local tipoComprobanteValor = mapTipoComprobante(tipoComprobanteRaw) |
||||
|
if tipoComprobanteValor == "" then |
||||
|
logErrorAndExit( |
||||
|
"afip_ingresar_punto_venta_y_comprobante: tipo_comprobante could not be mapped: " .. |
||||
|
tostring(tipoComprobanteRaw) |
||||
|
) |
||||
|
end |
||||
|
|
||||
|
log("afip_ingresar_punto_venta_y_comprobante: punto_de_venta=", tostring(puntoDeVentaNum), |
||||
|
" tipo_comprobante_valor=", tostring(tipoComprobanteValor)) |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- 1) Seleccionar punto de venta (select#puntodeventa) |
||||
|
-------------------------------------------------------------------- |
||||
|
local puntoVentaId = "puntodeventa" |
||||
|
|
||||
|
-- Uses the binding selectDropdownItemByPosition(id, position) |
||||
|
err = selectDropdownItemByPosition(puntoVentaId, puntoDeVentaNum) |
||||
|
if err then |
||||
|
logErrorAndExit( |
||||
|
"afip_ingresar_punto_venta_y_comprobante: selectDropdownItemByPosition failed for punto_de_venta: " .. |
||||
|
tostring(err) |
||||
|
) |
||||
|
end |
||||
|
log("afip_ingresar_punto_venta_y_comprobante: punto_de_venta selected OK") |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- 2) Seleccionar tipo de comprobante (select#universocomprobante) |
||||
|
-------------------------------------------------------------------- |
||||
|
local tipoComprobanteId = "universocomprobante" |
||||
|
|
||||
|
err = setTextFieldById(tipoComprobanteId, tostring(tipoComprobanteValor)) |
||||
|
if err then |
||||
|
logErrorAndExit( |
||||
|
"afip_ingresar_punto_venta_y_comprobante: setTextFieldById for tipo_comprobante failed: " .. |
||||
|
tostring(err) |
||||
|
) |
||||
|
end |
||||
|
|
||||
|
log("afip_ingresar_punto_venta_y_comprobante: tipo_comprobante selected OK") |
||||
|
|
||||
|
-------------------------------------------------------------------- |
||||
|
-- Done |
||||
|
-------------------------------------------------------------------- |
||||
|
log("afip_ingresar_punto_venta_y_comprobante: completed successfully") |
||||
|
end |
||||
|
|
||||
|
local ok, err = pcall(main) |
||||
|
if not ok then |
||||
|
-- Let Go see this as a Lua error so it can fall back to legacy |
||||
|
logErrorAndExit("afip_ingresar_punto_venta_y_comprobante failed: " .. tostring(err)) |
||||
|
end |
||||
@ -0,0 +1,32 @@ |
|||||
|
log("inicia afip_inicio_loading") |
||||
|
local err |
||||
|
local json = require("json") |
||||
|
local obj, err = json.decode(json_payload) |
||||
|
|
||||
|
if err then |
||||
|
log("Decoding JSON error" .. err) |
||||
|
error("Decoding JSON error" .. err) |
||||
|
end |
||||
|
|
||||
|
local function post_to_n8n(endpoint, json) |
||||
|
local http = require("http") |
||||
|
local client = http.client() |
||||
|
local request = http.request("POST", endpoint, json) |
||||
|
request:header_set("Content-Type", "application/json") |
||||
|
local result, err = client:do_request(request) |
||||
|
if err then |
||||
|
log_print("post_to_n8n error: " .. err) |
||||
|
error(err) |
||||
|
end |
||||
|
end |
||||
|
|
||||
|
post_to_n8n("https://automation.rodley.ar/webhook/98b3a336-bb11-4b36-b206-c48690b54e0f", json_payload) |
||||
|
|
||||
|
|
||||
|
log("Por abrir pagina de loading") |
||||
|
err = navigate("https://www.bot-bunny.com/loading") |
||||
|
if err then |
||||
|
log("navigate error" .. err) |
||||
|
error("navigate error" .. err) |
||||
|
end |
||||
|
waitSecs(5) |
||||
@ -1,21 +0,0 @@ |
|||||
log("inicia botbunny_inicio") |
|
||||
local err |
|
||||
print("BEFORE JSON LOADING payload: " .. json_payload) |
|
||||
local json = require("json") |
|
||||
print("AFTER JSON LOADING") |
|
||||
-- Parse the JSON string |
|
||||
local obj, err = json.decode(json_payload) |
|
||||
|
|
||||
if err then |
|
||||
log("Decoding error" .. err) |
|
||||
error("Decoding error" .. err) |
|
||||
end |
|
||||
|
|
||||
log("Por abrir pagina de loading") |
|
||||
err = navigate("https://www.bot-bunny.com/loading") |
|
||||
if err then |
|
||||
log("navigate error" .. err) |
|
||||
error("navigate error" .. err) |
|
||||
end |
|
||||
log("durmiendo por 5 segundos") |
|
||||
waitSecs(5) |
|
||||
Loading…
Reference in new issue