Agregar Puerto Rico y actualizar dist validado para produccion
This commit is contained in:
@@ -32,6 +32,11 @@ VITE_N8N_HONDURAS_WEBHOOK_URL="https://agenteit.digitalcompass.agency/webhook/no
|
||||
VITE_HISTORICOS_HN_URL="https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-hn-historicos"
|
||||
VITE_MARCAR_RESUELTO_HN_URL="https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-hn-marcar-resuelto"
|
||||
|
||||
# Puerto Rico: cruce, históricos y resolución.
|
||||
VITE_N8N_PUERTO_RICO_WEBHOOK_URL="https://agenteit.digitalcompass.agency/webhook/nominapr-bamboo-test"
|
||||
VITE_HISTORICOS_PR_URL="https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-pr-historicos"
|
||||
VITE_MARCAR_RESUELTO_PR_URL="https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-pr-marcar-resuelto"
|
||||
|
||||
# Endpoint opcional de n8n + Gemini para analizar el nombre del archivo de nómina.
|
||||
# La app ya incluye detección local como respaldo.
|
||||
VITE_ANALIZAR_NOMINA_URL="https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-analizar-nomina"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Corrección final de conciliación — Honduras · 08-09-2026
|
||||
|
||||
Se corrigió el workflow principal de Honduras después de comparar la ejecución real de Junio Q30 con los archivos fuente y con el reporte de referencia.
|
||||
|
||||
Cambios aplicados:
|
||||
|
||||
- Tolerancia monetaria de ±0.05 para diferencias puramente de redondeo.
|
||||
- La primera pasada de conciliación busca montos dentro de la misma tolerancia, no solo el centavo exacto.
|
||||
- `Banco sin Nómina` se conserva por transacción bancaria individual y no se agrupa por persona/cuenta.
|
||||
- Los truncamientos bancarios de un carácter al final (ej. `ALVARAD` → `ALVARADO`) no se reportan como diferencia de nombre.
|
||||
- No se toleran sustituciones ortográficas internas (ej. `GONZALES` → `GONZALEZ`), que siguen requiriendo revisión.
|
||||
- Para BambooHR, el nombre legal del cuentahabiente y la pertenencia al período tienen prioridad sobre un nombre corto ambiguo. Esto corrige el falso positivo de Suyapa Palma.
|
||||
- La auditoría directa del Excel original confirmó que Harold Panchame tiene `756669711` en nómina y banco; por lo tanto no es una cuenta mal digitada. El caso real confirmado es Brenda Baquedano (`745899511` → `745899611`).
|
||||
|
||||
Resultados de referencia finales:
|
||||
|
||||
| Período | Coincidencias | Discrepancias | Banco sin nómina | Banco sin Bamboo | Diferencias nombre banco | Cuenta mal digitada |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| Junio Q15 | 143 | 122 | 0 | 5 | 0 | 1 |
|
||||
| Junio Q30 | 331 | 0 | 176 | 5 | 5 | 1 |
|
||||
| Julio Q15 | 263 | 0 | 0 | 4 | 1 | 1 |
|
||||
| Julio Q30 | 335 | 0 | 1 | 5 | 3 | 1 |
|
||||
@@ -0,0 +1,16 @@
|
||||
# Puerto Rico — integración READY
|
||||
|
||||
Esta versión parte del paquete corregido existente y conserva sin reemplazo la autenticación Supabase/Google y la lógica de históricos de los países existentes.
|
||||
|
||||
Puerto Rico:
|
||||
- Código: PR
|
||||
- Moneda: USD
|
||||
- Locale: es-PR
|
||||
- Nómina: un Excel por período
|
||||
- Banco: PDF de Banco Popular de Puerto Rico
|
||||
- Webhook principal: /webhook/nominapr-bamboo-test
|
||||
- Históricos: /webhook/cruce-cuentas-pr-historicos
|
||||
- Marcar resuelto: /webhook/cruce-cuentas-pr-marcar-resuelto
|
||||
- Correcciones BambooHR: Noralmy Laureano
|
||||
|
||||
La compilación `dist/` se genera usando el mismo `.env.local` del paquete base, con las tres variables PR agregadas. Esto conserva el login y la conexión a Supabase usada por Históricos.
|
||||
+280
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-510
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"name": "Portal de Verificación de Nómina Puerto Rico - Históricos",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"path": "cruce-cuentas-pr-historicos",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
304,
|
||||
272
|
||||
],
|
||||
"id": "dff3b1cf-ebbe-4525-b367-0479d76a102d",
|
||||
"name": "Webhook Históricos PR",
|
||||
"webhookId": "24baab72-bc55-439d-96db-94780a3dffdf"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\n\nfunction formatDate(value) {\n if (!value) return '';\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return String(value).slice(0, 10);\n return date.toISOString().slice(0, 10);\n}\n\nfunction toNumber(value) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : 0;\n}\n\nfunction normalizeStatus(value, discrepancias) {\n const status = String(value || '').toLowerCase();\n\n if (status === 'resuelto') return 'resuelto';\n if (status === 'procesado' && toNumber(discrepancias) === 0) return 'procesado';\n\n return 'pendiente_revision';\n}\n\nfunction statusLabel(status) {\n if (status === 'resuelto') return 'RESUELTO';\n if (status === 'procesado') return 'PROCESADO';\n return 'PENDIENTE REVISIÓN';\n}\n\nconst reports = items.map((item) => {\n const row = item.json || {};\n\n const discrepancias = toNumber(row.discrepancias);\n const bancoSinNomina = toNumber(row.banco_sin_nomina);\n const nominaSinCuenta = toNumber(row.nomina_sin_cuenta);\n const bancoSinBamboo = toNumber(row.banco_sin_bamboo);\n const nominaSinBamboo = toNumber(row.nomina_sin_bamboo);\n const bambooSinNomina = toNumber(row.bamboo_sin_nomina);\n\n const totalPendientes =\n discrepancias +\n bancoSinNomina +\n nominaSinCuenta +\n bancoSinBamboo +\n nominaSinBamboo +\n bambooSinNomina;\n\n const estado = normalizeStatus(row.estado, totalPendientes);\n\n return {\n id: row.id,\n\n fecha: formatDate(row.period_end || row.ejecutado_en || row.created_at),\n fechaEjecucion: row.ejecutado_en || row.created_at,\n\n periodo: row.period_label || '',\n year: row.year,\n month: row.month,\n periodType: row.period_type,\n periodStart: row.period_start,\n periodEnd: row.period_end,\n\n archivoNomina: row.payroll_file_name || '',\n archivosBanco: row.bank_file_names || [],\n\n coincidencias: toNumber(row.coincidencias),\n discrepancias,\n bancoSinNomina,\n nominaSinCuenta,\n bancoSinBamboo,\n nominaSinBamboo,\n bambooSinNomina,\n totalPendientes,\n\n totalNomina: toNumber(row.total_nomina),\n totalBanco: toNumber(row.total_banco),\n diferenciaTotal: toNumber(row.diferencia_total),\n\n estado,\n estadoLabel: statusLabel(estado),\n\n reportUrl: row.report_url || '',\n spreadsheetId: row.spreadsheet_id || '',\n\n ejecutadoPorNombre: row.ejecutado_por_nombre || '',\n ejecutadoPorEmail: row.ejecutado_por_email || '',\n\n resueltoPorNombre: row.resuelto_por_nombre || '',\n resueltoPorEmail: row.resuelto_por_email || '',\n resueltoEn: row.resuelto_en || null,\n comentarioResolucion: row.comentario_resolucion || '',\n };\n});\n\nreturn [\n {\n json: {\n ok: true,\n stage: 'historicos_pr_listos',\n count: reports.length,\n reports,\n },\n },\n];"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
720,
|
||||
272
|
||||
],
|
||||
"id": "9c4a3deb-631f-409f-b3ac-a86c75514f14",
|
||||
"name": "Normalizar históricos para app"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://dbit.digitalcompass.agency/rest/v1/cruces_cuentas_gt_reportes?select=*&country=eq.PR&order=ejecutado_en.desc&limit=100",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "apikey",
|
||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
||||
},
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.4,
|
||||
"position": [
|
||||
512,
|
||||
272
|
||||
],
|
||||
"id": "70248bd5-e8ff-4ca5-9770-3a68ca418a12",
|
||||
"name": "Consultar históricos Supabase"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {
|
||||
"responseHeaders": {
|
||||
"entries": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Access-Control-Allow-Origin",
|
||||
"value": "*"
|
||||
},
|
||||
{
|
||||
"name": "Access-Control-Allow-Methods",
|
||||
"value": "GET,POST,OPTIONS"
|
||||
},
|
||||
{
|
||||
"name": "Access-Control-Allow-Headers",
|
||||
"value": "Content-Type, Authorization"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.5,
|
||||
"position": [
|
||||
944,
|
||||
272
|
||||
],
|
||||
"id": "65b35ac9-9272-4abf-86e4-c9330322c8ab",
|
||||
"name": "Responder históricos app"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "# 📚 HISTÓRICOS DE VERIFICACIÓN — PUERTO RICO\n\nEntrega a la aplicación el historial de reportes procesados para Puerto Rico.\n\n- Consulta únicamente registros con `country = PR`.\n- Supabase continúa usando la tabla histórica compartida `cruces_cuentas_gt_reportes`; el nombre de la tabla es legado y funciona como tabla multi-país.\n- Devuelve la misma estructura de históricos utilizada por los demás países.\n- Este webhook sirve como respaldo; la app intenta primero la RPC paginada de Supabase.",
|
||||
"height": 496,
|
||||
"width": 1360,
|
||||
"color": 6
|
||||
},
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"id": "8c0b0a43-8bfe-443b-8795-06d79512ca64",
|
||||
"name": "Sticky Note"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Consultar históricos Supabase": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalizar históricos para app",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Normalizar históricos para app": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Responder históricos app",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Webhook Históricos PR": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Consultar históricos Supabase",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"binaryMode": "separate"
|
||||
},
|
||||
"versionId": "ffb6f922-6c37-498e-9dec-0011deddb49e",
|
||||
"meta": {
|
||||
"instanceId": "b4b77b17af092830e794eef639ce2f6d7daccf7eddc075060b03b3b6545aac70"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
-- ============================================================
|
||||
-- CRUCE DE CUENTAS GLM · HABILITAR PUERTO RICO (PR)
|
||||
-- GT + TT + PA + SV + HN + PR
|
||||
-- ============================================================
|
||||
-- Ejecutar una sola vez en el SQL Editor del Supabase empresarial.
|
||||
-- Es idempotente respecto a PR: conserva datos existentes y puede
|
||||
-- volver a ejecutarse si se interrumpe antes del COMMIT.
|
||||
|
||||
begin;
|
||||
|
||||
-- 1) Validar tablas requeridas y ampliar constraints de país.
|
||||
do $$
|
||||
declare r record;
|
||||
begin
|
||||
if to_regclass('public.cruce_cuentas_bamboo_aliases') is null then raise exception 'No existe public.cruce_cuentas_bamboo_aliases.'; end if;
|
||||
if to_regclass('public.cruce_cuentas_bamboo_correcciones') is null then raise exception 'No existe public.cruce_cuentas_bamboo_correcciones.'; end if;
|
||||
if to_regclass('public.cruces_cuentas_gt_reportes') is null then raise exception 'No existe public.cruces_cuentas_gt_reportes.'; end if;
|
||||
if to_regclass('public.cruce_cuentas_usuarios_autorizados') is null then raise exception 'No existe public.cruce_cuentas_usuarios_autorizados.'; end if;
|
||||
|
||||
-- Alias BambooHR.
|
||||
for r in
|
||||
select conname
|
||||
from pg_constraint
|
||||
where conrelid='public.cruce_cuentas_bamboo_aliases'::regclass
|
||||
and contype='c'
|
||||
and pg_get_constraintdef(oid) ilike '%pais%'
|
||||
and pg_get_constraintdef(oid) ilike '%GT%'
|
||||
and pg_get_constraintdef(oid) not ilike '%PR%'
|
||||
loop
|
||||
execute format('alter table public.cruce_cuentas_bamboo_aliases drop constraint %I',r.conname);
|
||||
end loop;
|
||||
alter table public.cruce_cuentas_bamboo_aliases drop constraint if exists ck_cruce_cuentas_bamboo_aliases_pais;
|
||||
alter table public.cruce_cuentas_bamboo_aliases
|
||||
add constraint ck_cruce_cuentas_bamboo_aliases_pais
|
||||
check (upper(trim(pais)) in ('GT','TT','PA','SV','HN','PR'));
|
||||
|
||||
-- Solicitudes/correcciones BambooHR.
|
||||
for r in
|
||||
select conname
|
||||
from pg_constraint
|
||||
where conrelid='public.cruce_cuentas_bamboo_correcciones'::regclass
|
||||
and contype='c'
|
||||
and pg_get_constraintdef(oid) ilike '%pais%'
|
||||
and pg_get_constraintdef(oid) ilike '%GT%'
|
||||
and pg_get_constraintdef(oid) not ilike '%PR%'
|
||||
loop
|
||||
execute format('alter table public.cruce_cuentas_bamboo_correcciones drop constraint %I',r.conname);
|
||||
end loop;
|
||||
alter table public.cruce_cuentas_bamboo_correcciones drop constraint if exists ck_cruce_cuentas_bamboo_correcciones_pais;
|
||||
alter table public.cruce_cuentas_bamboo_correcciones
|
||||
add constraint ck_cruce_cuentas_bamboo_correcciones_pais
|
||||
check (upper(trim(pais)) in ('GT','TT','PA','SV','HN','PR'));
|
||||
|
||||
-- Históricos multi-país (el nombre de la tabla es legado).
|
||||
for r in
|
||||
select conname
|
||||
from pg_constraint
|
||||
where conrelid='public.cruces_cuentas_gt_reportes'::regclass
|
||||
and contype='c'
|
||||
and pg_get_constraintdef(oid) ilike '%country%'
|
||||
and pg_get_constraintdef(oid) ilike '%GT%'
|
||||
and pg_get_constraintdef(oid) not ilike '%PR%'
|
||||
loop
|
||||
execute format('alter table public.cruces_cuentas_gt_reportes drop constraint %I',r.conname);
|
||||
end loop;
|
||||
alter table public.cruces_cuentas_gt_reportes drop constraint if exists ck_cruces_cuentas_reportes_country;
|
||||
alter table public.cruces_cuentas_gt_reportes
|
||||
add constraint ck_cruces_cuentas_reportes_country
|
||||
check (country is null or upper(trim(country)) in ('GT','TT','PA','SV','HN','PR'));
|
||||
|
||||
-- Accesos: text[] de países habilitados.
|
||||
alter table public.cruce_cuentas_usuarios_autorizados drop constraint if exists cruce_cuentas_paises_validos_check;
|
||||
alter table public.cruce_cuentas_usuarios_autorizados
|
||||
add constraint cruce_cuentas_paises_validos_check
|
||||
check (paises is null or paises <@ array['GT','TT','PA','SV','HN','PR']::text[]);
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- 2) Dar PR a todos los usuarios actualmente autorizados, preservando permisos.
|
||||
update public.cruce_cuentas_usuarios_autorizados
|
||||
set paises = case
|
||||
when paises is null then array['GT','TT','PA','SV','HN','PR']::text[]
|
||||
when not ('PR'=any(paises)) then array_append(paises,'PR')
|
||||
else paises
|
||||
end,
|
||||
updated_at = now();
|
||||
|
||||
-- 3) Alias BambooHR activos: habilitar PR en la función compartida.
|
||||
create or replace function public.cruce_cuentas_bamboo_aliases_activos(p_pais text)
|
||||
returns jsonb
|
||||
language sql
|
||||
stable
|
||||
security definer
|
||||
set search_path=public
|
||||
as $$
|
||||
select jsonb_build_object(
|
||||
'ok',true,
|
||||
'pais',upper(trim(coalesce(p_pais,''))),
|
||||
'aliases',coalesce(
|
||||
jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id',a.id,
|
||||
'pais',a.pais,
|
||||
'nombre_origen',a.nombre_origen,
|
||||
'nombre_origen_normalizado',a.nombre_origen_normalizado,
|
||||
'cuenta_bancaria',a.cuenta_bancaria,
|
||||
'bamboo_employee_number',a.bamboo_employee_number,
|
||||
'bamboo_employee_id',a.bamboo_employee_id,
|
||||
'bamboo_nombre',a.bamboo_nombre,
|
||||
'motivo',a.motivo,
|
||||
'activo',a.activo
|
||||
) order by a.updated_at desc
|
||||
) filter (where a.id is not null),
|
||||
'[]'::jsonb
|
||||
)
|
||||
)
|
||||
from public.cruce_cuentas_bamboo_aliases a
|
||||
where a.activo=true
|
||||
and a.pais=upper(trim(coalesce(p_pais,'')))
|
||||
and upper(trim(coalesce(p_pais,''))) in ('GT','TT','PA','SV','HN','PR');
|
||||
$$;
|
||||
revoke all on function public.cruce_cuentas_bamboo_aliases_activos(text) from public,anon,authenticated;
|
||||
grant execute on function public.cruce_cuentas_bamboo_aliases_activos(text) to service_role;
|
||||
|
||||
-- 4) Históricos paginados multi-país, incluyendo PR.
|
||||
create index if not exists idx_cruces_cuentas_reportes_country_created
|
||||
on public.cruces_cuentas_gt_reportes(country,created_at desc);
|
||||
create index if not exists idx_cruces_cuentas_reportes_country_status_created
|
||||
on public.cruces_cuentas_gt_reportes(country,estado,created_at desc);
|
||||
|
||||
create or replace function public.cruce_cuentas_get_historicos(
|
||||
p_country text,
|
||||
p_page integer default 1,
|
||||
p_page_size integer default 10,
|
||||
p_search text default null,
|
||||
p_status text default null
|
||||
)
|
||||
returns jsonb
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path=public
|
||||
as $$
|
||||
declare
|
||||
v_country text := upper(trim(coalesce(p_country,'')));
|
||||
v_page integer := greatest(coalesce(p_page,1),1);
|
||||
v_page_size integer := least(greatest(coalesce(p_page_size,10),1),100);
|
||||
v_offset integer;
|
||||
v_search text := nullif(trim(coalesce(p_search,'')),'');
|
||||
v_status text := nullif(trim(coalesce(p_status,'')),'');
|
||||
v_total bigint;
|
||||
v_filtered_total bigint;
|
||||
v_reports jsonb;
|
||||
begin
|
||||
if v_country not in ('GT','TT','PA','SV','HN','PR') then
|
||||
raise exception 'País no permitido: %',v_country;
|
||||
end if;
|
||||
|
||||
v_offset := (v_page-1)*v_page_size;
|
||||
|
||||
select count(*) into v_total
|
||||
from public.cruces_cuentas_gt_reportes r
|
||||
where upper(coalesce(r.country,'GT'))=v_country;
|
||||
|
||||
select count(*) into v_filtered_total
|
||||
from public.cruces_cuentas_gt_reportes r
|
||||
where upper(coalesce(r.country,'GT'))=v_country
|
||||
and (v_status is null or r.estado=v_status)
|
||||
and (
|
||||
v_search is null
|
||||
or coalesce(r.period_label,'') ilike '%'||v_search||'%'
|
||||
or coalesce(r.payroll_file_name,'') ilike '%'||v_search||'%'
|
||||
or coalesce(r.ejecutado_por_nombre,'') ilike '%'||v_search||'%'
|
||||
or coalesce(r.resuelto_por_nombre,'') ilike '%'||v_search||'%'
|
||||
or r.id::text ilike '%'||v_search||'%'
|
||||
);
|
||||
|
||||
select coalesce(
|
||||
jsonb_agg((to_jsonb(q)-'detalle_banco_sin_bamboo') order by q.created_at desc),
|
||||
'[]'::jsonb
|
||||
) into v_reports
|
||||
from (
|
||||
select r.*
|
||||
from public.cruces_cuentas_gt_reportes r
|
||||
where upper(coalesce(r.country,'GT'))=v_country
|
||||
and (v_status is null or r.estado=v_status)
|
||||
and (
|
||||
v_search is null
|
||||
or coalesce(r.period_label,'') ilike '%'||v_search||'%'
|
||||
or coalesce(r.payroll_file_name,'') ilike '%'||v_search||'%'
|
||||
or coalesce(r.ejecutado_por_nombre,'') ilike '%'||v_search||'%'
|
||||
or coalesce(r.resuelto_por_nombre,'') ilike '%'||v_search||'%'
|
||||
or r.id::text ilike '%'||v_search||'%'
|
||||
)
|
||||
order by r.created_at desc
|
||||
limit v_page_size offset v_offset
|
||||
) q;
|
||||
|
||||
return jsonb_build_object(
|
||||
'ok',true,
|
||||
'country',v_country,
|
||||
'page',v_page,
|
||||
'page_size',v_page_size,
|
||||
'total',v_total,
|
||||
'filtered_total',v_filtered_total,
|
||||
'total_pages',greatest(ceil(v_filtered_total::numeric/v_page_size)::integer,1),
|
||||
'reports',v_reports
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
revoke all on function public.cruce_cuentas_get_historicos(text,integer,integer,text,text) from public,anon;
|
||||
grant execute on function public.cruce_cuentas_get_historicos(text,integer,integer,text,text) to authenticated;
|
||||
|
||||
-- 5) Detalle Banco sin BambooHR de históricos, incluyendo PR.
|
||||
create or replace function public.cruce_cuentas_get_banco_sin_bamboo_reporte(
|
||||
p_report_id text,
|
||||
p_country text
|
||||
)
|
||||
returns jsonb
|
||||
language plpgsql
|
||||
stable
|
||||
security definer
|
||||
set search_path=public
|
||||
as $$
|
||||
declare
|
||||
v_country text := upper(trim(coalesce(p_country,'')));
|
||||
v_report public.cruces_cuentas_gt_reportes%rowtype;
|
||||
v_corrections jsonb := '[]'::jsonb;
|
||||
begin
|
||||
if auth.uid() is null then raise exception 'No autenticado.'; end if;
|
||||
if v_country not in ('GT','TT','PA','SV','HN','PR') then raise exception 'País no permitido: %',v_country; end if;
|
||||
|
||||
select r.* into v_report
|
||||
from public.cruces_cuentas_gt_reportes r
|
||||
where r.id::text=trim(coalesce(p_report_id,''))
|
||||
and upper(coalesce(r.country,'GT'))=v_country
|
||||
limit 1;
|
||||
|
||||
if not found then
|
||||
return jsonb_build_object('ok',false,'message','No se encontró el reporte solicitado.');
|
||||
end if;
|
||||
|
||||
select coalesce(
|
||||
jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id',c.id,
|
||||
'pais',c.pais,
|
||||
'nombre_origen_principal',c.nombre_origen_principal,
|
||||
'nombres_origen',c.nombres_origen,
|
||||
'cuenta_bancaria',c.cuenta_bancaria,
|
||||
'bamboo_employee_number',c.bamboo_employee_number,
|
||||
'bamboo_nombre',c.bamboo_nombre,
|
||||
'estado',c.estado,
|
||||
'correo_enviado',c.correo_enviado,
|
||||
'correo_enviado_at',c.correo_enviado_at,
|
||||
'created_at',c.created_at
|
||||
) order by c.created_at desc
|
||||
),
|
||||
'[]'::jsonb
|
||||
) into v_corrections
|
||||
from public.cruce_cuentas_bamboo_correcciones c
|
||||
where c.pais=v_country
|
||||
and (
|
||||
c.execution_id=v_report.id::text
|
||||
or (
|
||||
nullif(trim(coalesce(c.execution_id,'')),'') is null
|
||||
and nullif(trim(coalesce(c.report_url,'')),'') is not null
|
||||
and c.report_url=v_report.report_url
|
||||
)
|
||||
);
|
||||
|
||||
return jsonb_build_object(
|
||||
'ok',true,
|
||||
'report',jsonb_build_object(
|
||||
'id',v_report.id,
|
||||
'country',v_report.country,
|
||||
'period_label',v_report.period_label,
|
||||
'period_start',v_report.period_start,
|
||||
'period_end',v_report.period_end,
|
||||
'report_url',v_report.report_url,
|
||||
'banco_sin_bamboo',v_report.banco_sin_bamboo
|
||||
),
|
||||
'cases',coalesce(v_report.detalle_banco_sin_bamboo,'[]'::jsonb),
|
||||
'corrections',v_corrections
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
revoke all on function public.cruce_cuentas_get_banco_sin_bamboo_reporte(text,text) from public,anon;
|
||||
grant execute on function public.cruce_cuentas_get_banco_sin_bamboo_reporte(text,text) to authenticated;
|
||||
|
||||
commit;
|
||||
|
||||
-- ============================================================
|
||||
-- VALIDACIÓN (solo lectura)
|
||||
-- ============================================================
|
||||
select email,nombre,paises
|
||||
from public.cruce_cuentas_usuarios_autorizados
|
||||
order by email;
|
||||
|
||||
select conname, pg_get_constraintdef(oid) as definicion
|
||||
from pg_constraint
|
||||
where conrelid in (
|
||||
'public.cruce_cuentas_bamboo_aliases'::regclass,
|
||||
'public.cruce_cuentas_bamboo_correcciones'::regclass,
|
||||
'public.cruces_cuentas_gt_reportes'::regclass,
|
||||
'public.cruce_cuentas_usuarios_autorizados'::regclass
|
||||
)
|
||||
and contype='c'
|
||||
and (pg_get_constraintdef(oid) ilike '%pais%' or pg_get_constraintdef(oid) ilike '%country%')
|
||||
order by conname;
|
||||
|
||||
select public.cruce_cuentas_get_historicos('PR',1,10,null,null) as historicos_pr;
|
||||
@@ -0,0 +1,18 @@
|
||||
# Validación final · Puerto Rico
|
||||
|
||||
Fecha: 10-09-2026
|
||||
|
||||
Este paquete parte de la versión `GT-TT-PA-SV-HN-BONO14-FINAL-READY-CORREGIDO` y agrega Puerto Rico sin sustituir la autenticación ni la lógica histórica existente.
|
||||
|
||||
Validaciones realizadas:
|
||||
- `src/main.tsx` y el árbol completo `src/` están incluidos.
|
||||
- `App.tsx`, `Login.tsx`, `lib/auth.ts`, `lib/sessionHealth.ts`, `lib/supabase.ts` y `lib/tabSessionState.ts` se conservaron idénticos a la base corregida.
|
||||
- Puerto Rico está habilitado como `PR`, moneda `USD`, locale `es-PR` y bandera oficial configurada.
|
||||
- La carga bancaria para Puerto Rico acepta PDF; los demás países conservan CSV.
|
||||
- Históricos de PR usan `cruce_cuentas_get_historicos` y el fallback `cruce-cuentas-pr-historicos`.
|
||||
- Resolución de PR usa `cruce-cuentas-pr-marcar-resuelto`.
|
||||
- El cruce de PR usa `nominapr-bamboo-test`.
|
||||
- El frontend de correcciones muestra a Noralmy Laureano para Puerto Rico.
|
||||
- `.env.local` conserva Supabase Auth y agrega únicamente los tres endpoints PR.
|
||||
- El `dist/` incluido contiene login habilitado, Supabase, históricos y endpoints PR, con cache-buster nuevo.
|
||||
- Los cinco workflows PR/compartidos incluidos son JSON válidos; sus conexiones apuntan a nodos existentes y los Code Nodes pasan validación sintáctica.
|
||||
Vendored
-270
File diff suppressed because one or more lines are too long
Vendored
+270
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -9,7 +9,7 @@
|
||||
<title>Portal de Cruce de Cuentas GLM</title>
|
||||
<link rel="icon" type="image/png" href="/cruce-cuentas/favicon.png" />
|
||||
<link rel="apple-touch-icon" href="/cruce-cuentas/favicon.png" />
|
||||
<script type="module" crossorigin src="/cruce-cuentas/assets/index-BmyDbce7.js"></script>
|
||||
<script type="module" crossorigin src="/cruce-cuentas/assets/index-saVPcx_I.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/cruce-cuentas/assets/index-CLG5SAqX.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -68,6 +68,7 @@ export default function BambooCorrectionModal({
|
||||
if (country.code === 'PA') return 'Delarys Peñalba';
|
||||
if (country.code === 'SV') return 'Carlos Antonio Malta Méndez';
|
||||
if (country.code === 'HN') return 'Melody Chang Blandin';
|
||||
if (country.code === 'PR') return 'Noralmy Laureano';
|
||||
return 'haguirre@gomezleemarketing.com';
|
||||
}, [country.code]);
|
||||
|
||||
|
||||
@@ -98,6 +98,13 @@ function getReconciliationUrl(countryCode: string) {
|
||||
);
|
||||
}
|
||||
|
||||
if (countryCode === 'PR') {
|
||||
return (
|
||||
(import.meta.env.VITE_N8N_PUERTO_RICO_WEBHOOK_URL as string | undefined) ||
|
||||
'https://agenteit.digitalcompass.agency/webhook/nominapr-bamboo-test'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
(import.meta.env.VITE_N8N_GUATEMALA_WEBHOOK_URL as string | undefined) ||
|
||||
(import.meta.env.VITE_N8N_WEBHOOK_URL as string | undefined) ||
|
||||
@@ -152,6 +159,10 @@ function isCsvFile(file: File) {
|
||||
return file.name.toLowerCase().endsWith('.csv');
|
||||
}
|
||||
|
||||
function isPdfFile(file: File) {
|
||||
return file.name.toLowerCase().endsWith('.pdf');
|
||||
}
|
||||
|
||||
function toUploadedPayrollFiles(files: FileList | File[]) {
|
||||
return Array.from(files).map((file) => ({
|
||||
name: file.name,
|
||||
@@ -814,7 +825,10 @@ export default function Dashboard({
|
||||
const isPanama = country.code === 'PA';
|
||||
const isElSalvador = country.code === 'SV';
|
||||
const isHonduras = country.code === 'HN';
|
||||
const isPuertoRico = country.code === 'PR';
|
||||
const isMultiPayrollCountry = isPanama || isElSalvador;
|
||||
const bankFileLabel = isPuertoRico ? 'PDF' : 'CSV';
|
||||
const bankFileExtensionLabel = isPuertoRico ? '.pdf' : '.csv';
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [processingStep, setProcessingStep] = useState('');
|
||||
const [filterMode, setFilterMode] = useState<
|
||||
@@ -1084,16 +1098,19 @@ export default function Dashboard({
|
||||
const handleBankDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
const files = Array.from(e.dataTransfer.files) as File[];
|
||||
const csvFiles = files.filter(isCsvFile);
|
||||
const validBankFiles = files.filter(isPuertoRico ? isPdfFile : isCsvFile);
|
||||
|
||||
if (csvFiles.length === 0) {
|
||||
onShowToast('Formato inválido. Por favor carga uno o varios archivos CSV (.csv).', 'error');
|
||||
if (validBankFiles.length === 0) {
|
||||
onShowToast(
|
||||
`Formato inválido. Por favor carga uno o varios archivos ${bankFileLabel} (${bankFileExtensionLabel}).`,
|
||||
'error'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setUploadedBankFiles([...uploadedBankFiles, ...toUploadedBankFiles(csvFiles)]);
|
||||
setUploadedBankFiles([...uploadedBankFiles, ...toUploadedBankFiles(validBankFiles)]);
|
||||
resetResults();
|
||||
onShowToast(`${csvFiles.length} planilla(s) del banco cargada(s) con éxito.`, 'success');
|
||||
onShowToast(`${validBankFiles.length} archivo(s) del banco cargado(s) con éxito.`, 'success');
|
||||
};
|
||||
|
||||
const handlePayrollSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -1126,16 +1143,19 @@ export default function Dashboard({
|
||||
const files = e.target.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const csvFiles = (Array.from(files) as File[]).filter(isCsvFile);
|
||||
const validBankFiles = (Array.from(files) as File[]).filter(isPuertoRico ? isPdfFile : isCsvFile);
|
||||
|
||||
if (csvFiles.length === 0) {
|
||||
onShowToast('Formato inválido. Por favor carga uno o varios archivos CSV (.csv).', 'error');
|
||||
if (validBankFiles.length === 0) {
|
||||
onShowToast(
|
||||
`Formato inválido. Por favor carga uno o varios archivos ${bankFileLabel} (${bankFileExtensionLabel}).`,
|
||||
'error'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setUploadedBankFiles([...uploadedBankFiles, ...toUploadedBankFiles(csvFiles)]);
|
||||
setUploadedBankFiles([...uploadedBankFiles, ...toUploadedBankFiles(validBankFiles)]);
|
||||
resetResults();
|
||||
onShowToast(`${csvFiles.length} planilla(s) del banco cargada(s) con éxito.`, 'success');
|
||||
onShowToast(`${validBankFiles.length} archivo(s) del banco cargado(s) con éxito.`, 'success');
|
||||
};
|
||||
|
||||
const removeBankFile = (indexToRemove: number) => {
|
||||
@@ -1263,7 +1283,9 @@ export default function Dashboard({
|
||||
setCurrentPage(1);
|
||||
|
||||
try {
|
||||
const duplicatedShipments = await findDuplicateBankShipments(uploadedBankFiles);
|
||||
const duplicatedShipments = isPuertoRico
|
||||
? []
|
||||
: await findDuplicateBankShipments(uploadedBankFiles);
|
||||
if (duplicatedShipments.length > 0) {
|
||||
const details = duplicatedShipments
|
||||
.map((item) => `Número de envío ${item.shipmentNumber}: ${item.fileNames.join(', ')}`)
|
||||
@@ -1667,7 +1689,7 @@ export default function Dashboard({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{(isMultiPayrollCountry || isHonduras) &&
|
||||
{(isMultiPayrollCountry || isHonduras || isPuertoRico) &&
|
||||
(uploadedPayroll !== null || uploadedPayrollFiles.length > 0 || uploadedBankFiles.length > 0) && (
|
||||
<div className="flex justify-end" style={{ marginBottom: '-20px' }}>
|
||||
<button
|
||||
@@ -1823,7 +1845,7 @@ export default function Dashboard({
|
||||
type="file"
|
||||
ref={bankInputRef}
|
||||
onChange={handleBankSelect}
|
||||
accept=".csv"
|
||||
accept={isPuertoRico ? ".pdf" : ".csv"}
|
||||
multiple
|
||||
className="hidden"
|
||||
/>
|
||||
@@ -1835,8 +1857,8 @@ export default function Dashboard({
|
||||
className="border-2 border-dashed border-[#D0D0D0] bg-neutral-50 hover:bg-[#4F758B]/5 hover:border-[#4F758B]/40 transition-colors flex flex-col items-center justify-center py-8 px-4 text-center cursor-pointer min-h-[130px]"
|
||||
>
|
||||
<Upload className="w-7 h-7 text-neutral-300 mb-2" />
|
||||
<p className="text-xs font-bold text-neutral-700">Arrastre y suelte archivo CSV</p>
|
||||
<p className="text-[10px] text-neutral-400 mt-1 font-medium">Formato .csv</p>
|
||||
<p className="text-xs font-bold text-neutral-700">{`Arrastre y suelte archivo ${bankFileLabel}`}</p>
|
||||
<p className="text-[10px] text-neutral-400 mt-1 font-medium">{`Formato ${bankFileExtensionLabel}`}</p>
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 border border-[#D0D0D0] bg-white px-4 py-1.5 text-[10px] font-bold text-neutral-600 hover:border-[#4F758B] hover:text-[#4F758B] transition-colors shadow-sm uppercase tracking-wide"
|
||||
@@ -1878,9 +1900,9 @@ export default function Dashboard({
|
||||
value={bankFileSearch}
|
||||
onChange={(event) => setBankFileSearch(event.target.value)}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
placeholder="Buscar archivo CSV por nombre..."
|
||||
placeholder={`Buscar archivo ${bankFileLabel} por nombre...`}
|
||||
className="w-full h-9 pl-9 pr-3 border border-[#D0D0D0] bg-white text-[11px] text-neutral-700 outline-none focus:border-[#4F758B] focus:ring-2 focus:ring-[#4F758B]/10 transition-all"
|
||||
aria-label="Buscar archivos CSV cargados"
|
||||
aria-label={`Buscar archivos ${bankFileLabel} cargados`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1912,7 +1934,7 @@ export default function Dashboard({
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className="text-[9px] text-neutral-400 font-mono">{file.size}</span>
|
||||
<span className="text-[8px] font-bold uppercase tracking-wide text-[#4F758B] bg-[#4F758B]/5 border border-[#4F758B]/10 px-1.5 py-0.5">
|
||||
CSV
|
||||
{bankFileLabel}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1935,7 +1957,7 @@ export default function Dashboard({
|
||||
<div className="py-8 px-4 text-center">
|
||||
<Search className="w-5 h-5 text-neutral-300 mx-auto mb-2" />
|
||||
<p className="text-[11px] font-bold text-neutral-600">No encontramos archivos con ese nombre.</p>
|
||||
<p className="text-[9px] text-neutral-400 mt-1">Pruebe con otra parte del nombre del CSV.</p>
|
||||
<p className="text-[9px] text-neutral-400 mt-1">Pruebe con otra parte del nombre del archivo.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -54,6 +54,13 @@ function getHistoryUrl(countryCode: string) {
|
||||
);
|
||||
}
|
||||
|
||||
if (countryCode === 'PR') {
|
||||
return (
|
||||
(import.meta.env.VITE_HISTORICOS_PR_URL as string | undefined) ||
|
||||
'https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-pr-historicos'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
(import.meta.env.VITE_HISTORICOS_GT_URL as string | undefined) ||
|
||||
'https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-gt-historicos'
|
||||
@@ -86,6 +93,13 @@ function getMarkResolvedUrl(countryCode: string) {
|
||||
);
|
||||
}
|
||||
|
||||
if (countryCode === 'PR') {
|
||||
return (
|
||||
(import.meta.env.VITE_MARCAR_RESUELTO_PR_URL as string | undefined) ||
|
||||
'https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-pr-marcar-resuelto'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
(import.meta.env.VITE_MARCAR_RESUELTO_GT_URL as string | undefined) ||
|
||||
'https://agenteit.digitalcompass.agency/webhook/cruce-cuentas-gt-marcar-resuelto'
|
||||
@@ -171,7 +185,7 @@ function normalizeReports(input: unknown, country: CountryConfig): HistoricalRep
|
||||
|
||||
return {
|
||||
id: String(report.id ?? `reporte-${index + 1}`),
|
||||
country: (rawCountry === 'TT' ? 'TT' : rawCountry === 'PA' ? 'PA' : rawCountry === 'SV' ? 'SV' : rawCountry === 'HN' ? 'HN' : 'GT') as HistoricalReport['country'],
|
||||
country: (rawCountry === 'TT' ? 'TT' : rawCountry === 'PA' ? 'PA' : rawCountry === 'SV' ? 'SV' : rawCountry === 'HN' ? 'HN' : rawCountry === 'PR' ? 'PR' : 'GT') as HistoricalReport['country'],
|
||||
date: formatDate(report.fecha ?? report.period_end ?? rawExecutionDate, country.locale),
|
||||
executionDate: formatDateTime(rawExecutionDate, country.locale),
|
||||
period: String(report.periodo ?? report.period_label ?? report.period ?? 'Período no especificado'),
|
||||
@@ -485,7 +499,7 @@ export default function History({
|
||||
if (!isCurrentRequest()) return;
|
||||
|
||||
// Respaldo temporal: webhook histórico existente. También se envía el país
|
||||
// para impedir que se mezclen reportes de Guatemala, Trinidad y Tobago, Panamá, El Salvador y Honduras.
|
||||
// para impedir que se mezclen reportes de Guatemala, Trinidad y Tobago, Panamá, El Salvador, Honduras y Puerto Rico.
|
||||
const historyUrl = getHistoryUrl(country.code);
|
||||
if (!historyUrl) {
|
||||
throw new Error(`No está configurado el origen histórico de ${country.name}.`);
|
||||
|
||||
@@ -41,6 +41,14 @@ export const COUNTRIES: CountryConfig[] = [
|
||||
locale: 'es-HN',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
code: 'PR',
|
||||
name: 'Puerto Rico',
|
||||
flag: 'https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/bandera-nacional-tela-puerto-rico_113767-1759.avif',
|
||||
currency: 'USD',
|
||||
locale: 'es-PR',
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_COUNTRY_CODE: CountryCode = 'GT';
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ export type ViewType = 'cruce' | 'historial';
|
||||
|
||||
export type ToastType = 'success' | 'error' | 'info';
|
||||
|
||||
export type CountryCode = 'GT' | 'TT' | 'PA' | 'SV' | 'HN';
|
||||
export type CountryCode = 'GT' | 'TT' | 'PA' | 'SV' | 'HN' | 'PR';
|
||||
|
||||
export interface CountryConfig {
|
||||
code: CountryCode;
|
||||
|
||||
Vendored
+3
@@ -7,6 +7,7 @@ interface ImportMetaEnv {
|
||||
readonly VITE_N8N_PANAMA_WEBHOOK_URL?: string;
|
||||
readonly VITE_N8N_EL_SALVADOR_WEBHOOK_URL?: string;
|
||||
readonly VITE_N8N_HONDURAS_WEBHOOK_URL?: string;
|
||||
readonly VITE_N8N_PUERTO_RICO_WEBHOOK_URL?: string;
|
||||
readonly VITE_HISTORICOS_GT_URL?: string;
|
||||
readonly VITE_MARCAR_RESUELTO_GT_URL?: string;
|
||||
readonly VITE_HISTORICOS_TT_URL?: string;
|
||||
@@ -17,6 +18,8 @@ interface ImportMetaEnv {
|
||||
readonly VITE_MARCAR_RESUELTO_SV_URL?: string;
|
||||
readonly VITE_HISTORICOS_HN_URL?: string;
|
||||
readonly VITE_MARCAR_RESUELTO_HN_URL?: string;
|
||||
readonly VITE_HISTORICOS_PR_URL?: string;
|
||||
readonly VITE_MARCAR_RESUELTO_PR_URL?: string;
|
||||
readonly VITE_ANALIZAR_NOMINA_URL?: string;
|
||||
readonly VITE_REPORTAR_BAMBOO_URL?: string;
|
||||
readonly VITE_REPORTAR_APP_URL?: string;
|
||||
|
||||
-279
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-514
File diff suppressed because one or more lines are too long
+347
File diff suppressed because one or more lines are too long
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"name": "Portal de Verificación de Nómina Puerto Rico - Históricos",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"path": "cruce-cuentas-pr-historicos",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
304,
|
||||
272
|
||||
],
|
||||
"id": "dff3b1cf-ebbe-4525-b367-0479d76a102d",
|
||||
"name": "Webhook Históricos PR",
|
||||
"webhookId": "24baab72-bc55-439d-96db-94780a3dffdf"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\n\nfunction formatDate(value) {\n if (!value) return '';\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return String(value).slice(0, 10);\n return date.toISOString().slice(0, 10);\n}\n\nfunction toNumber(value) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : 0;\n}\n\nfunction normalizeStatus(value, discrepancias) {\n const status = String(value || '').toLowerCase();\n\n if (status === 'resuelto') return 'resuelto';\n if (status === 'procesado' && toNumber(discrepancias) === 0) return 'procesado';\n\n return 'pendiente_revision';\n}\n\nfunction statusLabel(status) {\n if (status === 'resuelto') return 'RESUELTO';\n if (status === 'procesado') return 'PROCESADO';\n return 'PENDIENTE REVISIÓN';\n}\n\nconst reports = items.map((item) => {\n const row = item.json || {};\n\n const discrepancias = toNumber(row.discrepancias);\n const bancoSinNomina = toNumber(row.banco_sin_nomina);\n const nominaSinCuenta = toNumber(row.nomina_sin_cuenta);\n const bancoSinBamboo = toNumber(row.banco_sin_bamboo);\n const nominaSinBamboo = toNumber(row.nomina_sin_bamboo);\n const bambooSinNomina = toNumber(row.bamboo_sin_nomina);\n\n const totalPendientes =\n discrepancias +\n bancoSinNomina +\n nominaSinCuenta +\n bancoSinBamboo +\n nominaSinBamboo +\n bambooSinNomina;\n\n const estado = normalizeStatus(row.estado, totalPendientes);\n\n return {\n id: row.id,\n\n fecha: formatDate(row.period_end || row.ejecutado_en || row.created_at),\n fechaEjecucion: row.ejecutado_en || row.created_at,\n\n periodo: row.period_label || '',\n year: row.year,\n month: row.month,\n periodType: row.period_type,\n periodStart: row.period_start,\n periodEnd: row.period_end,\n\n archivoNomina: row.payroll_file_name || '',\n archivosBanco: row.bank_file_names || [],\n\n coincidencias: toNumber(row.coincidencias),\n discrepancias,\n bancoSinNomina,\n nominaSinCuenta,\n bancoSinBamboo,\n nominaSinBamboo,\n bambooSinNomina,\n totalPendientes,\n\n totalNomina: toNumber(row.total_nomina),\n totalBanco: toNumber(row.total_banco),\n diferenciaTotal: toNumber(row.diferencia_total),\n\n estado,\n estadoLabel: statusLabel(estado),\n\n reportUrl: row.report_url || '',\n spreadsheetId: row.spreadsheet_id || '',\n\n ejecutadoPorNombre: row.ejecutado_por_nombre || '',\n ejecutadoPorEmail: row.ejecutado_por_email || '',\n\n resueltoPorNombre: row.resuelto_por_nombre || '',\n resueltoPorEmail: row.resuelto_por_email || '',\n resueltoEn: row.resuelto_en || null,\n comentarioResolucion: row.comentario_resolucion || '',\n };\n});\n\nreturn [\n {\n json: {\n ok: true,\n stage: 'historicos_pr_listos',\n count: reports.length,\n reports,\n },\n },\n];"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
720,
|
||||
272
|
||||
],
|
||||
"id": "9c4a3deb-631f-409f-b3ac-a86c75514f14",
|
||||
"name": "Normalizar históricos para app"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://dbit.digitalcompass.agency/rest/v1/cruces_cuentas_gt_reportes?select=*&country=eq.PR&order=ejecutado_en.desc&limit=100",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "apikey",
|
||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
||||
},
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.4,
|
||||
"position": [
|
||||
512,
|
||||
272
|
||||
],
|
||||
"id": "70248bd5-e8ff-4ca5-9770-3a68ca418a12",
|
||||
"name": "Consultar históricos Supabase"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={{ $json }}",
|
||||
"options": {
|
||||
"responseHeaders": {
|
||||
"entries": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Access-Control-Allow-Origin",
|
||||
"value": "*"
|
||||
},
|
||||
{
|
||||
"name": "Access-Control-Allow-Methods",
|
||||
"value": "GET,POST,OPTIONS"
|
||||
},
|
||||
{
|
||||
"name": "Access-Control-Allow-Headers",
|
||||
"value": "Content-Type, Authorization"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.5,
|
||||
"position": [
|
||||
944,
|
||||
272
|
||||
],
|
||||
"id": "65b35ac9-9272-4abf-86e4-c9330322c8ab",
|
||||
"name": "Responder históricos app"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "# 📚 HISTÓRICOS DE VERIFICACIÓN — PUERTO RICO\n\nEntrega a la aplicación el historial de reportes procesados para Puerto Rico.\n\n- Consulta únicamente registros con `country = PR`.\n- Supabase continúa usando la tabla histórica compartida `cruces_cuentas_gt_reportes`; el nombre de la tabla es legado y funciona como tabla multi-país.\n- Devuelve la misma estructura de históricos utilizada por los demás países.\n- Este webhook sirve como respaldo; la app intenta primero la RPC paginada de Supabase.",
|
||||
"height": 496,
|
||||
"width": 1360,
|
||||
"color": 6
|
||||
},
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"id": "8c0b0a43-8bfe-443b-8795-06d79512ca64",
|
||||
"name": "Sticky Note"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Consultar históricos Supabase": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Normalizar históricos para app",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Normalizar históricos para app": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Responder históricos app",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Webhook Históricos PR": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Consultar históricos Supabase",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"binaryMode": "separate"
|
||||
},
|
||||
"versionId": "ffb6f922-6c37-498e-9dec-0011deddb49e",
|
||||
"meta": {
|
||||
"instanceId": "b4b77b17af092830e794eef639ce2f6d7daccf7eddc075060b03b3b6545aac70"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
Reference in New Issue
Block a user