Keep workers online during long-running downloads

This commit is contained in:
erik
2026-09-04 14:53:37 -05:00
parent d9e2012e22
commit 632599b201
57 changed files with 8847 additions and 21 deletions
@@ -0,0 +1,310 @@
import fs from "node:fs/promises";
import path from "node:path";
import { SpreadsheetFile, Workbook } from "@oai/artifact-tool";
const workspace = "C:/Users/erik1/Documents/IPSExtractor";
const sourceDir = path.join(workspace, ".codex_tmp_apartments");
const outputDir = path.join(workspace, "outputs", "apartamentos_rentpana_20260830");
const outputPath = path.join(outputDir, "apartamentos_visibles_rentpana.xlsx");
const pageSize = 50;
const pages = [];
for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) {
const text = await fs.readFile(path.join(sourceDir, `page_${pageIndex}.json`), "utf8");
const rows = JSON.parse(text);
pages.push({ page: pageIndex + 1, offset: pageIndex * pageSize, rows });
}
const apartments = pages.flatMap((page) => page.rows);
if (apartments.length !== 101) throw new Error(`Se esperaban 101 registros y llegaron ${apartments.length}`);
const ids = apartments.map((row) => row.id);
if (new Set(ids).size !== apartments.length) throw new Error("Se detectaron IDs duplicados en la extracción");
const columns = Object.keys(apartments[0]);
const expectedColumns = 34;
if (columns.length !== expectedColumns) throw new Error(`Se esperaban ${expectedColumns} columnas y llegaron ${columns.length}`);
const normalize = (key, value) => {
if (value === null || value === undefined) return null;
if (key === "created_at" || key === "updated_at") {
const date = new Date(`${value}T00:00:00Z`);
return Number.isNaN(date.getTime()) ? String(value) : date;
}
if (Array.isArray(value)) return value.join(" | ");
if (typeof value === "object") return JSON.stringify(value);
return value;
};
const colLetter = (oneBased) => {
let n = oneBased;
let result = "";
while (n > 0) {
const remainder = (n - 1) % 26;
result = String.fromCharCode(65 + remainder) + result;
n = Math.floor((n - 1) / 26);
}
return result;
};
const workbook = Workbook.create();
const summary = workbook.worksheets.add("Resumen");
const dataSheet = workbook.worksheets.add("Apartamentos");
const photosSheet = workbook.worksheets.add("Fotos");
const paginationSheet = workbook.worksheets.add("Paginacion");
const navy = "#123047";
const teal = "#0F766E";
const cyan = "#DFF4F1";
const pale = "#F4F7F9";
const gold = "#E8B44F";
const white = "#FFFFFF";
const ink = "#1F2937";
const muted = "#60707D";
const border = "#D8E1E7";
for (const sheet of [summary, dataSheet, photosSheet, paginationSheet]) {
sheet.showGridLines = false;
}
// Hoja principal con todos los campos devueltos por la API.
const dataMatrix = [columns, ...apartments.map((row) => columns.map((key) => normalize(key, row[key])))];
const dataEndCol = colLetter(columns.length);
const dataEndRow = dataMatrix.length;
dataSheet.getRange(`A1:${dataEndCol}${dataEndRow}`).values = dataMatrix;
dataSheet.freezePanes.freezeRows(1);
dataSheet.freezePanes.freezeColumns(2);
const apartmentTable = dataSheet.tables.add(`A1:${dataEndCol}${dataEndRow}`, true, "ApartmentsTable");
apartmentTable.style = "TableStyleMedium2";
apartmentTable.showFilterButton = true;
dataSheet.getRange(`A1:${dataEndCol}1`).format = {
fill: navy,
font: { bold: true, color: white },
rowHeight: 28,
verticalAlignment: "center",
};
dataSheet.getRange(`A2:${dataEndCol}${dataEndRow}`).format = {
font: { color: ink, size: 10 },
verticalAlignment: "top",
};
const widths = {
id: 36, user_id: 36, user_name: 22, user_phone: 16, address: 34, description: 60,
bedrooms: 12, bathrooms: 12, area_m2: 12, monthly_rent: 16, available_rooms: 16,
building_amenities: 38, apartment_features: 44, included_services: 36, photo_urls: 65,
created_at: 14, updated_at: 14, show_phone: 12, show_profile: 13, is_visible: 12,
priority: 11, listing_type: 14, city: 17, area: 24, short_id: 13,
smart_rental_enabled: 20, parent_apartment_id: 36, tenant_user_id: 36,
tenant_name: 20, tenant_phone: 16, tenant_gender: 15, tenant_age: 12,
is_paid: 11, seo_slug: 38,
};
columns.forEach((key, index) => {
const letter = colLetter(index + 1);
dataSheet.getRange(`${letter}:${letter}`).format.columnWidth = widths[key] ?? 16;
});
for (const key of ["description", "building_amenities", "apartment_features", "included_services", "photo_urls"]) {
const index = columns.indexOf(key) + 1;
const letter = colLetter(index);
dataSheet.getRange(`${letter}2:${letter}${dataEndRow}`).format.wrapText = true;
}
dataSheet.getRange(`A2:${dataEndCol}${dataEndRow}`).format.rowHeight = 42;
for (const key of ["created_at", "updated_at"]) {
const letter = colLetter(columns.indexOf(key) + 1);
dataSheet.getRange(`${letter}2:${letter}${dataEndRow}`).format.numberFormat = "yyyy-mm-dd";
}
for (const key of ["bedrooms", "bathrooms", "area_m2", "available_rooms", "priority", "tenant_age"]) {
const letter = colLetter(columns.indexOf(key) + 1);
dataSheet.getRange(`${letter}2:${letter}${dataEndRow}`).format.numberFormat = "#,##0";
}
const rentLetter = colLetter(columns.indexOf("monthly_rent") + 1);
dataSheet.getRange(`${rentLetter}2:${rentLetter}${dataEndRow}`).format.numberFormat = '"$"#,##0';
// Una fila por foto para facilitar filtros, descarga y revisión.
const photoRows = [];
for (const apartment of apartments) {
const urls = Array.isArray(apartment.photo_urls) ? apartment.photo_urls : [];
urls.forEach((url, index) => photoRows.push([
apartment.id,
apartment.short_id,
apartment.city,
Array.isArray(apartment.area) ? apartment.area.join(" | ") : apartment.area,
apartment.address,
index + 1,
url,
]));
}
const photoMatrix = [["apartment_id", "short_id", "city", "area", "address", "photo_index", "photo_url"], ...photoRows];
photosSheet.getRange(`A1:G${photoMatrix.length}`).values = photoMatrix;
photosSheet.freezePanes.freezeRows(1);
photosSheet.freezePanes.freezeColumns(2);
const photosTable = photosSheet.tables.add(`A1:G${photoMatrix.length}`, true, "PhotosTable");
photosTable.style = "TableStyleMedium2";
photosTable.showFilterButton = true;
photosSheet.getRange("A1:G1").format = { fill: navy, font: { bold: true, color: white }, rowHeight: 28 };
photosSheet.getRange(`A2:G${photoMatrix.length}`).format = { font: { color: ink, size: 10 }, rowHeight: 22 };
photosSheet.getRange("A:A").format.columnWidth = 36;
photosSheet.getRange("B:B").format.columnWidth = 13;
photosSheet.getRange("C:C").format.columnWidth = 16;
photosSheet.getRange("D:D").format.columnWidth = 24;
photosSheet.getRange("E:E").format.columnWidth = 36;
photosSheet.getRange("F:F").format.columnWidth = 12;
photosSheet.getRange("G:G").format.columnWidth = 90;
photosSheet.getRange(`F2:F${photoMatrix.length}`).format.numberFormat = "#,##0";
// Control detallado de las tres páginas consultadas.
const paginationRows = pages.map((page) => {
const from = page.offset;
const to = page.offset + page.rows.length - 1;
return [
page.page,
page.offset,
pageSize,
`${from}-${to}`,
page.rows.length,
"200 OK",
page.rows[0]?.id ?? null,
page.rows.at(-1)?.id ?? null,
];
});
const paginationMatrix = [["pagina", "offset", "limite", "rango_respuesta", "filas", "estado_http", "primer_id", "ultimo_id"], ...paginationRows];
paginationSheet.getRange("A1:H4").values = paginationMatrix;
paginationSheet.getRange("A5:D5").merge();
paginationSheet.getRange("A5:D5").values = [["Total descargado"]];
paginationSheet.getRange("E5").formulas = [["=SUM(E2:E4)"]];
paginationSheet.getRange("F5:H5").merge();
paginationSheet.getRange("F5:H5").values = [["Validado contra Content-Range: 101"]];
paginationSheet.getRange("A1:H1").format = { fill: navy, font: { bold: true, color: white }, rowHeight: 28 };
paginationSheet.getRange("A2:H4").format = { fill: white, font: { color: ink }, rowHeight: 24 };
paginationSheet.getRange("A5:H5").format = { fill: cyan, font: { bold: true, color: navy }, rowHeight: 26 };
paginationSheet.getRange("A1:H5").format.borders = { preset: "all", style: "thin", color: border };
paginationSheet.getRange("A2:F5").format.horizontalAlignment = "center";
paginationSheet.getRange("A:C").format.columnWidth = 12;
paginationSheet.getRange("D:D").format.columnWidth = 20;
paginationSheet.getRange("E:E").format.columnWidth = 12;
paginationSheet.getRange("F:F").format.columnWidth = 16;
paginationSheet.getRange("G:H").format.columnWidth = 38;
paginationSheet.getRange("A2:C5").format.numberFormat = "#,##0";
paginationSheet.freezePanes.freezeRows(1);
// Resumen ejecutivo y trazabilidad de la extracción.
summary.getRange("A1:F2").merge();
summary.getRange("A1:F2").values = [["Apartamentos visibles · RentPana"]];
summary.getRange("A1:F2").format = {
fill: navy,
font: { bold: true, color: white, size: 20 },
horizontalAlignment: "left",
verticalAlignment: "center",
};
summary.getRange("A3:F3").merge();
summary.getRange("A3:F3").values = [["Extracción completa, paginada y validada contra el total informado por la API"]];
summary.getRange("A3:F3").format = { fill: navy, font: { color: "#D9EDF2", size: 11 }, rowHeight: 24 };
summary.getRange("A5:B5").merge();
summary.getRange("A5:B5").values = [["Apartamentos"]];
summary.getRange("C5:D5").merge();
summary.getRange("C5:D5").values = [["Páginas API"]];
summary.getRange("E5:F5").merge();
summary.getRange("E5:F5").values = [["Fotos"]];
summary.getRange("A6:B7").merge();
summary.getRange("A6:B7").formulas = [["=COUNTA('Apartamentos'!$A$2:$A$102)"]];
summary.getRange("C6:D7").merge();
summary.getRange("C6:D7").formulas = [["=COUNTA('Paginacion'!$A$2:$A$4)"]];
summary.getRange("E6:F7").merge();
summary.getRange("E6:F7").formulas = [[`=COUNTA('Fotos'!$A$2:$A$${photoMatrix.length})`]];
summary.getRange("A5:F5").format = { fill: teal, font: { bold: true, color: white }, horizontalAlignment: "center", rowHeight: 24 };
summary.getRange("A6:F7").format = { fill: cyan, font: { bold: true, color: navy, size: 20 }, horizontalAlignment: "center", verticalAlignment: "center" };
summary.getRange("A5:F7").format.borders = { preset: "outside", style: "thin", color: border };
summary.getRange("A9:F9").merge();
summary.getRange("A9:F9").values = [["Control de extracción"]];
summary.getRange("A9:F9").format = { fill: teal, font: { bold: true, color: white }, rowHeight: 26 };
const extractedAt = new Date();
const controlRows = [
["Fecha y hora (UTC)", extractedAt, null, null, null, null],
["Origen", "https://srv99857.rentpana.com/rest/v1/apartments", null, null, null, null],
["Filtro", "is_visible = true", null, null, null, null],
["Orden estable", "id ascendente", null, null, null, null],
["Tamaño de página", pageSize, null, null, null, null],
["Total informado por API", 101, null, null, null, null],
["IDs únicos validados", new Set(ids).size, null, null, null, null],
["Duplicados detectados", apartments.length - new Set(ids).size, null, null, null, null],
["Columnas de origen", columns.length, null, null, null, null],
["Credenciales incluidas", "No", null, null, null, null],
];
summary.getRange("A10:F19").values = controlRows;
for (let row = 10; row <= 19; row += 1) summary.getRange(`B${row}:F${row}`).merge();
summary.getRange("A10:A19").format = { fill: pale, font: { bold: true, color: muted }, rowHeight: 24 };
summary.getRange("B10:F19").format = { fill: white, font: { color: ink }, rowHeight: 24, wrapText: true, horizontalAlignment: "left" };
summary.getRange("A10:F19").format.borders = { preset: "outside", style: "thin", color: border };
summary.getRange("B10").format.numberFormat = "yyyy-mm-dd hh:mm:ss";
summary.getRange("B14:B18").format.numberFormat = "#,##0";
summary.getRange("A21:F21").merge();
summary.getRange("A21:F21").values = [["Contenido del archivo"]];
summary.getRange("A21:F21").format = { fill: teal, font: { bold: true, color: white }, rowHeight: 26 };
const contentsRows = [
["Apartamentos", "Todos los 34 campos de origen; filtros activados y encabezado congelado."],
["Fotos", "Una fila por URL de foto, vinculada por apartment_id y short_id."],
["Paginacion", "Offset, límite, rango, filas, estado y IDs extremos por página."],
];
summary.getRange("A22:F24").values = contentsRows.map(([a, b]) => [a, b, null, null, null, null]);
for (let row = 22; row <= 24; row += 1) summary.getRange(`B${row}:F${row}`).merge();
summary.getRange("A22:A24").format = { fill: pale, font: { bold: true, color: navy }, rowHeight: 28 };
summary.getRange("B22:F24").format = { fill: white, font: { color: ink }, wrapText: true, rowHeight: 28 };
summary.getRange("A22:F24").format.borders = { preset: "outside", style: "thin", color: border };
summary.getRange("A:A").format.columnWidth = 24;
summary.getRange("B:F").format.columnWidth = 18;
summary.getRange("A1:F24").format.font.name = "Aptos";
summary.freezePanes.freezeRows(3);
await fs.mkdir(outputDir, { recursive: true });
const checks = [];
checks.push((await workbook.inspect({
kind: "table",
range: "Resumen!A1:F24",
include: "values,formulas",
tableMaxRows: 24,
tableMaxCols: 6,
maxChars: 6000,
})).ndjson);
checks.push((await workbook.inspect({
kind: "table",
range: "Paginacion!A1:H5",
include: "values,formulas",
tableMaxRows: 8,
tableMaxCols: 8,
maxChars: 3000,
})).ndjson);
checks.push((await workbook.inspect({
kind: "match",
searchTerm: "#REF!|#DIV/0!|#VALUE!|#NAME\\?|#N/A",
options: { useRegex: true, maxResults: 300 },
summary: "final formula error scan",
})).ndjson);
const previews = [
["Resumen", "A1:F24", "preview_resumen.png"],
["Apartamentos", "A1:J10", "preview_apartamentos_1.png"],
["Apartamentos", "K1:AH8", "preview_apartamentos_2.png"],
["Fotos", "A1:G12", "preview_fotos.png"],
["Paginacion", "A1:H5", "preview_paginacion.png"],
];
for (const [sheetName, range, fileName] of previews) {
const blob = await workbook.render({ sheetName, range, scale: 1.4, format: "png" });
await fs.writeFile(path.join(outputDir, fileName), new Uint8Array(await blob.arrayBuffer()));
}
const output = await SpreadsheetFile.exportXlsx(workbook);
await output.save(outputPath);
console.log(JSON.stringify({
outputPath,
apartments: apartments.length,
uniqueIds: new Set(ids).size,
photos: photoRows.length,
pages: pages.length,
columns: columns.length,
checks,
previews: previews.map((item) => path.join(outputDir, item[2])),
}, null, 2));
+22
View File
@@ -0,0 +1,22 @@
HTTP/1.1 206 Partial Content
Date: Sun, 30 Aug 2026 19:12:16 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 108239
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: 0-49/101
Content-Location: /apartments?is_visible=eq.true&limit=50&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 12
X-Kong-Proxy-Latency: 1
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 36d60653723f406a162c6535d728d27d
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=e%2FhgsmNUvfdXbxPGFrz%2FMjaXlkCiuYPlrQQ42rkkNFx4x%2BMdhLL3ImSLwz7rtCdYR1DHKjSIzEPLp4dHKjL0cypB8Z46vlG%2FHPjsRaBFF1ycsDO3VL4I73MtA5QRjQShWznGiKw38Qw%3D"}]}
CF-RAY: a3361b23bd70dfba-MIA
alt-svc: h3=":443"; ma=86400
+22
View File
@@ -0,0 +1,22 @@
HTTP/1.1 206 Partial Content
Date: Sun, 30 Aug 2026 19:12:16 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 110785
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: 50-99/101
Content-Location: /apartments?is_visible=eq.true&limit=50&offset=50&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 18
X-Kong-Proxy-Latency: 0
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 43005d3aa4dfd17006586b41c9903306
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=OXpuhJ8kF%2FzhCWI3QCdCE6R6ZZ33yEpgyPCVOIjJeIe4SC6LK%2BZp0Hoa%2FyYXkm49AUgc9W3NEB3RodHaby%2B3W8lVVfVUhYFlw392LbBZSINbEaP1olAo7Gd5CLHEl9fMltUYK%2F3FuZg%3D"}]}
CF-RAY: a3361b26a99d8dc6-MIA
alt-svc: h3=":443"; ma=86400
+22
View File
@@ -0,0 +1,22 @@
HTTP/1.1 206 Partial Content
Date: Sun, 30 Aug 2026 19:12:16 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2399
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: 100-100/101
Content-Location: /apartments?is_visible=eq.true&limit=50&offset=100&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 11
X-Kong-Proxy-Latency: 0
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: e5b9ec1e2e2ea72419eb096189826631
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=hs0K9bWUzpaTwFm4%2BZ2bo3c5Km2mVBEkNxHpE8aghdPj%2FqrUGgznk0IlCooLvaGKoFkvTUiyMIJq7CUi2tBqRFq%2B8XCjAsLBo3%2FbYXWL2lNJtjA8qv%2Fs0j5tFTYBGGd3RwD7dTbRWcw%3D"}]}
CF-RAY: a3361b29ada95c66-MIA
alt-svc: h3=":443"; ma=86400
@@ -0,0 +1,181 @@
import fs from "node:fs/promises";
import path from "node:path";
import { SpreadsheetFile, Workbook } from "@oai/artifact-tool";
const workspace = "C:/Users/erik1/Documents/IPSExtractor";
const visibleSample = JSON.parse(await fs.readFile(path.join(workspace, ".codex_tmp_apartments", "page_0.json"), "utf8"));
const columns = Object.keys(visibleSample[0]);
const outputDir = path.join(workspace, "outputs", "apartamentos_no_visibles_rentpana_20260830");
const outputPath = path.join(outputDir, "apartamentos_no_visibles_rentpana.xlsx");
const colLetter = (oneBased) => {
let n = oneBased;
let result = "";
while (n > 0) {
const remainder = (n - 1) % 26;
result = String.fromCharCode(65 + remainder) + result;
n = Math.floor((n - 1) / 26);
}
return result;
};
const workbook = Workbook.create();
const summary = workbook.worksheets.add("Resumen");
const dataSheet = workbook.worksheets.add("Apartamentos");
const photosSheet = workbook.worksheets.add("Fotos");
const paginationSheet = workbook.worksheets.add("Paginacion");
const navy = "#123047";
const teal = "#0F766E";
const cyan = "#DFF4F1";
const pale = "#F4F7F9";
const white = "#FFFFFF";
const ink = "#1F2937";
const muted = "#60707D";
const border = "#D8E1E7";
for (const sheet of [summary, dataSheet, photosSheet, paginationSheet]) sheet.showGridLines = false;
const endCol = colLetter(columns.length);
dataSheet.getRange(`A1:${endCol}2`).values = [columns, columns.map(() => null)];
dataSheet.freezePanes.freezeRows(1);
dataSheet.freezePanes.freezeColumns(2);
const apartmentTable = dataSheet.tables.add(`A1:${endCol}2`, true, "HiddenApartmentsTable");
apartmentTable.style = "TableStyleMedium2";
apartmentTable.showFilterButton = true;
dataSheet.getRange(`A1:${endCol}1`).format = { fill: navy, font: { bold: true, color: white }, rowHeight: 28 };
dataSheet.getRange(`A2:${endCol}2`).format = { fill: white, rowHeight: 24 };
const widths = {
id: 36, user_id: 36, user_name: 22, user_phone: 16, address: 34, description: 60,
bedrooms: 12, bathrooms: 12, area_m2: 12, monthly_rent: 16, available_rooms: 16,
building_amenities: 38, apartment_features: 44, included_services: 36, photo_urls: 65,
created_at: 14, updated_at: 14, show_phone: 12, show_profile: 13, is_visible: 12,
priority: 11, listing_type: 14, city: 17, area: 24, short_id: 13,
smart_rental_enabled: 20, parent_apartment_id: 36, tenant_user_id: 36,
tenant_name: 20, tenant_phone: 16, tenant_gender: 15, tenant_age: 12,
is_paid: 11, seo_slug: 38,
};
columns.forEach((key, index) => dataSheet.getRange(`${colLetter(index + 1)}:${colLetter(index + 1)}`).format.columnWidth = widths[key] ?? 16);
const photoHeaders = ["apartment_id", "short_id", "city", "area", "address", "photo_index", "photo_url"];
photosSheet.getRange("A1:G2").values = [photoHeaders, photoHeaders.map(() => null)];
photosSheet.freezePanes.freezeRows(1);
photosSheet.freezePanes.freezeColumns(2);
const photosTable = photosSheet.tables.add("A1:G2", true, "HiddenPhotosTable");
photosTable.style = "TableStyleMedium2";
photosTable.showFilterButton = true;
photosSheet.getRange("A1:G1").format = { fill: navy, font: { bold: true, color: white }, rowHeight: 28 };
photosSheet.getRange("A:A").format.columnWidth = 36;
photosSheet.getRange("B:B").format.columnWidth = 13;
photosSheet.getRange("C:C").format.columnWidth = 16;
photosSheet.getRange("D:D").format.columnWidth = 24;
photosSheet.getRange("E:E").format.columnWidth = 36;
photosSheet.getRange("F:F").format.columnWidth = 12;
photosSheet.getRange("G:G").format.columnWidth = 90;
paginationSheet.getRange("A1:H2").values = [
["pagina", "offset", "limite", "content_range", "filas", "estado_http", "primer_id", "ultimo_id"],
[1, 0, 50, "*/0", 0, "200 OK", null, null],
];
paginationSheet.getRange("A3:D3").merge();
paginationSheet.getRange("A3:D3").values = [["Total descargado"]];
paginationSheet.getRange("E3").formulas = [["=SUM(E2:E2)"]];
paginationSheet.getRange("F3:H3").merge();
paginationSheet.getRange("F3:H3").values = [["Validado contra Content-Range: */0"]];
paginationSheet.getRange("A1:H1").format = { fill: navy, font: { bold: true, color: white }, rowHeight: 28 };
paginationSheet.getRange("A2:H2").format = { fill: white, font: { color: ink }, rowHeight: 24 };
paginationSheet.getRange("A3:H3").format = { fill: cyan, font: { bold: true, color: navy }, rowHeight: 26 };
paginationSheet.getRange("A1:H3").format.borders = { preset: "all", style: "thin", color: border };
paginationSheet.getRange("A2:F3").format.horizontalAlignment = "center";
paginationSheet.getRange("A:C").format.columnWidth = 12;
paginationSheet.getRange("D:D").format.columnWidth = 20;
paginationSheet.getRange("E:E").format.columnWidth = 12;
paginationSheet.getRange("F:F").format.columnWidth = 16;
paginationSheet.getRange("G:H").format.columnWidth = 38;
paginationSheet.freezePanes.freezeRows(1);
summary.getRange("A1:F2").merge();
summary.getRange("A1:F2").values = [["Apartamentos no visibles · RentPana"]];
summary.getRange("A1:F2").format = { fill: navy, font: { bold: true, color: white, size: 20 }, verticalAlignment: "center" };
summary.getRange("A3:F3").merge();
summary.getRange("A3:F3").values = [["Consulta independiente con is_visible = false"]];
summary.getRange("A3:F3").format = { fill: navy, font: { color: "#D9EDF2", size: 11 }, rowHeight: 24 };
summary.getRange("A5:B5").merge();
summary.getRange("C5:D5").merge();
summary.getRange("E5:F5").merge();
summary.getRange("A5:F5").values = [["Apartamentos", null, "Páginas consultadas", null, "Fotos", null]];
summary.getRange("A6:B7").merge();
summary.getRange("A6:B7").formulas = [["=COUNTA('Apartamentos'!$A$2:$A$2)"]];
summary.getRange("C6:D7").merge();
summary.getRange("C6:D7").formulas = [["=COUNTA('Paginacion'!$A$2:$A$2)"]];
summary.getRange("E6:F7").merge();
summary.getRange("E6:F7").formulas = [["=COUNTA('Fotos'!$A$2:$A$2)"]];
summary.getRange("A5:F5").format = { fill: teal, font: { bold: true, color: white }, horizontalAlignment: "center", rowHeight: 24 };
summary.getRange("A6:F7").format = { fill: cyan, font: { bold: true, color: navy, size: 20 }, horizontalAlignment: "center", verticalAlignment: "center" };
summary.getRange("A5:F7").format.borders = { preset: "outside", style: "thin", color: border };
summary.getRange("A9:F9").merge();
summary.getRange("A9:F9").values = [["Control de extracción"]];
summary.getRange("A9:F9").format = { fill: teal, font: { bold: true, color: white }, rowHeight: 26 };
const controlRows = [
["Fecha y hora (UTC)", new Date()],
["Origen", "https://srv99857.rentpana.com/rest/v1/apartments"],
["Filtro", "is_visible = false"],
["Orden estable", "id ascendente"],
["Tamaño de página", 50],
["Total informado por API", 0],
["IDs únicos validados", 0],
["Duplicados detectados", 0],
["Columnas de origen", columns.length],
["Resultado", "La API devolvió 0 registros para este filtro con la sesión suministrada."],
["Credenciales incluidas", "No"],
];
summary.getRange("A10:F20").values = controlRows.map(([a, b]) => [a, b, null, null, null, null]);
for (let row = 10; row <= 20; row += 1) summary.getRange(`B${row}:F${row}`).merge();
summary.getRange("A10:A20").format = { fill: pale, font: { bold: true, color: muted }, rowHeight: 24 };
summary.getRange("B10:F20").format = { fill: white, font: { color: ink }, rowHeight: 24, wrapText: true, horizontalAlignment: "left" };
summary.getRange("A10:F20").format.borders = { preset: "outside", style: "thin", color: border };
summary.getRange("B10").format.numberFormat = "yyyy-mm-dd hh:mm:ss";
summary.getRange("A22:F22").merge();
summary.getRange("A22:F22").values = [["Contenido del archivo"]];
summary.getRange("A22:F22").format = { fill: teal, font: { bold: true, color: white }, rowHeight: 26 };
const contents = [
["Apartamentos", "Estructura de 34 campos, lista para los registros devueltos por la consulta."],
["Fotos", "Hoja normalizada para enlaces de fotos; actualmente sin filas."],
["Paginacion", "Consulta, límite, rango, cantidad y estado HTTP documentados."],
];
summary.getRange("A23:F25").values = contents.map(([a, b]) => [a, b, null, null, null, null]);
for (let row = 23; row <= 25; row += 1) summary.getRange(`B${row}:F${row}`).merge();
summary.getRange("A23:A25").format = { fill: pale, font: { bold: true, color: navy }, rowHeight: 28 };
summary.getRange("B23:F25").format = { fill: white, font: { color: ink }, wrapText: true, rowHeight: 28 };
summary.getRange("A23:F25").format.borders = { preset: "outside", style: "thin", color: border };
summary.getRange("A:A").format.columnWidth = 24;
summary.getRange("B:F").format.columnWidth = 18;
summary.getRange("A1:F25").format.font.name = "Aptos";
summary.freezePanes.freezeRows(3);
await fs.mkdir(outputDir, { recursive: true });
const checks = [
(await workbook.inspect({ kind: "table", range: "Resumen!A1:F25", include: "values,formulas", tableMaxRows: 25, tableMaxCols: 6, maxChars: 6000 })).ndjson,
(await workbook.inspect({ kind: "table", range: "Paginacion!A1:H3", include: "values,formulas", tableMaxRows: 5, tableMaxCols: 8, maxChars: 2500 })).ndjson,
(await workbook.inspect({ kind: "match", searchTerm: "#REF!|#DIV/0!|#VALUE!|#NAME\\?|#N/A", options: { useRegex: true, maxResults: 300 }, summary: "final formula error scan" })).ndjson,
];
const previews = [
["Resumen", "A1:F25", "preview_resumen.png"],
["Apartamentos", "A1:J2", "preview_apartamentos_1.png"],
["Apartamentos", "K1:AH2", "preview_apartamentos_2.png"],
["Fotos", "A1:G2", "preview_fotos.png"],
["Paginacion", "A1:H3", "preview_paginacion.png"],
];
for (const [sheetName, range, fileName] of previews) {
const blob = await workbook.render({ sheetName, range, scale: 1.4, format: "png" });
await fs.writeFile(path.join(outputDir, fileName), new Uint8Array(await blob.arrayBuffer()));
}
const output = await SpreadsheetFile.exportXlsx(workbook);
await output.save(outputPath);
console.log(JSON.stringify({ outputPath, rows: 0, pagesConsulted: 1, columns: columns.length, checks, previews: previews.map((item) => path.join(outputDir, item[2])) }, null, 2));
@@ -0,0 +1,22 @@
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2026 19:58:23 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: */0
Content-Location: /apartments?is_visible=not.is.true&limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 4
X-Kong-Proxy-Latency: 0
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: ec6314205fccca4904d6d03407154c4a
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=8AohAv0A3ziLB3FtLIOhjzFdv85QxTqb1tAHIbT9ZaS8Azt0wu0aaZ%2FglV2Q18Z5XL%2F4QWsmSasuKWaPwKMMu7fkj6Ms1inrSl5uPbnKqH6JcOG66Cw%2FTMgdpHxGmOxLFIoMxU5F%2BdY%3D"}]}
CF-RAY: a3365eb52dfced7d-MIA
alt-svc: h3=":443"; ma=86400
@@ -0,0 +1,22 @@
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2026 19:58:22 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: */0
Content-Location: /apartments?is_visible=eq.false&limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 7
X-Kong-Proxy-Latency: 1
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: fd33fa311539833f4a5ff50e90483a1a
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=NVKvV0awlCCGFLEGDS6qtWWoE1OawW5qkTusVN7UPmcUUz9ANiLsKREPkBGGlVgXtHV%2BhGfN2HE5KLVyYayaXmhi12828xkTJfNHfgC8loe5z3oA45mHEkThiTqq4qeMFFBk0z%2FF3jU%3D"}]}
CF-RAY: a3365eb14b4920ce-MIA
alt-svc: h3=":443"; ma=86400
@@ -0,0 +1,22 @@
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2026 19:58:23 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: */0
Content-Location: /apartments?is_visible=is.false&limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 7
X-Kong-Proxy-Latency: 3
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 788604537f4afd42a27dc7376de1cc0d
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=DBm2LKUWUatJi1DLvGFP6TkUGCAMtPwejSduHaNrdK9LQpD%2BUydLT5bIhe8CGGf6bMizlPvFkMbqIsb1pT4FZP2mGjvL570cJpUe%2BroGEy6Tw0RmL%2Bf4f7L4zZ1RGc8sH6wuBA29GMw%3D"}]}
CF-RAY: a3365eb33e410cd1-MIA
alt-svc: h3=":443"; ma=86400
@@ -0,0 +1,22 @@
HTTP/1.1 206 Partial Content
Date: Sun, 30 Aug 2026 19:58:22 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2648
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: 0-0/101
Content-Location: /apartments?limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 12
X-Kong-Proxy-Latency: 1
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 1a3b2b0fc9128a7fa1ec2840f5635327
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=vgZDSTap%2BSWUr7NuXZGQIE5Rn48TAu8OqXveTvdG3%2F9IkXH1NAXG%2Fb%2F9zcO6pXtFdmaBDsQyN%2F40uUjtH0SwwmDE1Pzrp2dR3CiiBqds5HJFAb9M8mngLuZqb330Fg3xhsv3VjGysLA%3D"}]}
CF-RAY: a3365ead3d723405-MIA
alt-svc: h3=":443"; ma=86400
@@ -0,0 +1,22 @@
HTTP/1.1 206 Partial Content
Date: Sun, 30 Aug 2026 19:58:22 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2648
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: 0-0/101
Content-Location: /apartments?is_visible=eq.true&limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 4
X-Kong-Proxy-Latency: 1
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 9b533b383646b2728617ef2a30e7d92f
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=%2BGmFgkGsDol59lrAXlrXdP4PeE7tfE4zwIcSf7NgeVi1KjsaibLlq%2B%2FPHaZ%2FQSeodYa5%2BQsYoh1nULeLCJU9%2FzkRS3wsXDrF1K5jDwyiS1fOKMcM%2Fl%2B%2F5k84tjozth%2B%2FMgZ6mhogkFo%3D"}]}
CF-RAY: a3365eaf4e4e7bf9-MIA
alt-svc: h3=":443"; ma=86400
+22
View File
@@ -0,0 +1,22 @@
HTTP/1.1 206 Partial Content
Date: Sun, 30 Aug 2026 19:39:31 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2648
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: 0-0/101
Content-Location: /apartments?limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 45
X-Kong-Proxy-Latency: 1
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 039e86122c46200cd717916b6e135842
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=BKv08O8bUUr4qDMLzEOxeINGc5VFoXdVQNNDhmT8Lc%2BM%2ByjJsGfjnkOfLuHtkIBeBsGh4kmOPlN%2FB3s2If%2BcmCNw6oZLjYg8bPKo1QNCO0AmTmYXSNoT5e7%2BzJochHeSxCsD1gW7m38%3D"}]}
CF-RAY: a3364312bcedb887-MIA
alt-svc: h3=":443"; ma=86400
@@ -0,0 +1,22 @@
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2026 19:39:32 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: */0
Content-Location: /apartments?is_visible=is.false&limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 30
X-Kong-Proxy-Latency: 2
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 9c2a57070a4b53249c28ac4c437d4b56
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=mAw%2BHylzVDcPCkK%2BIYK9f8zB9WEkfty7bMwyvjbyO%2B2IRzTN54jQAgofMSBrJOJOX6lxDDMScXE6iWe59XKESXlSDxYhmGLT%2FdwAHTAErXqgnYHCDtgCOh5GliHuDLBpf3o5zL8JJJE%3D"}]}
CF-RAY: a3364315582aeeeb-MIA
alt-svc: h3=":443"; ma=86400
@@ -0,0 +1,22 @@
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2026 19:39:31 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: */0
Content-Location: /apartments?is_visible=not.is.true&limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 38
X-Kong-Proxy-Latency: 3
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 252f2d0642d8464a133408069dfd8a5a
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=JhblutXOican2RX5cbcS8caKnc6WpdsodPvtjWOzwAml8Yomg3xKSs8m2aCpXaXkTzM7Jka8X1htOJh3Zj5uoGMPrpnsHEgm6tWSnKdytSugJz2OePeD8PoHh6m7%2B%2FPMXKhXEJwmcjQ%3D"}]}
CF-RAY: a33643102b0e4fad-MIA
alt-svc: h3=":443"; ma=86400
@@ -0,0 +1,22 @@
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2026 19:39:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Server: cloudflare
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Content-Range: */0
Content-Location: /apartments?is_visible=eq.false&limit=1&offset=0&order=id.asc&select=%2A
Content-Profile: public
Preference-Applied: count=exact
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Encoding, Content-Location, Content-Range, Content-Type, Date, Location, Server, Transfer-Encoding, Range-Unit
X-Kong-Upstream-Latency: 28
X-Kong-Proxy-Latency: 2
Via: 1.1 kong/3.9.1
X-Kong-Request-Id: 6d6ae32336046505b27faf4e2bca1a3f
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=kgZGlNNkVBjD7LKeaIEHFnIeOOcPvmxhdN%2FN4gUHg3RSwToezpIG%2B0CTGdk%2FFozCIHhg34IuG92GUhUHgI3iwQdgyEouvC1744S1aZVWHXicpXdcY6OJyfB%2BqGA2lza4%2B%2FVGt1jIW%2Fo%3D"}]}
CF-RAY: a336424d59939aa6-MIA
alt-svc: h3=":443"; ma=86400
Binary file not shown.
+2 -2
View File
@@ -2,8 +2,8 @@
BOT_API_TOKEN=dev-token-change-me
BOT_API_URL=https://pys1.builker.xyz/bot-descarga
# Imaging (hostname interno; requiere VPN/DNS o IP alcanzable)
BOT_IMAGING_URL=http://serviciosimaging:8180/
# Imaging (requiere acceso a la red interna de Salud Total)
BOT_IMAGING_URL=https://serviciosimaging.saludtotal.loc/
# MinIO — bucket pys-cuentas-medicas: sin credenciales (PutObject anónimo)
MINIO_ENDPOINT=https://s3.builker.xyz
@@ -7,7 +7,7 @@ from pathlib import Path
@dataclass(frozen=True)
class Settings:
url_sistema: str = "http://serviciosimaging:8180/"
url_sistema: str = "https://serviciosimaging.saludtotal.loc/"
peso_minimo_bytes: int = 10_000
max_intentos_click: int = 3
max_intentos_pdf: int = 2
+47 -2
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import socket
import tempfile
import threading
import time
from pathlib import Path
from typing import Any, Dict, List, Optional
@@ -15,6 +16,9 @@ JOB_COMPLETED = "completed"
JOB_FAILED = "failed"
JOB_CANCELLED = "cancelled"
CONTROL_REQUEST_TIMEOUT_SECONDS = 5.0
ACTIVE_JOB_HEARTBEAT_SECONDS = 10.0
class RemoteApiClient:
"""Cliente HTTP hacia la API en IPSExtractor (vía gateway). Sin puertos locales."""
@@ -49,6 +53,7 @@ class RemoteApiClient:
r = self._client.post(
self._url("/workers/heartbeat"),
json={"worker_id": worker_id},
timeout=CONTROL_REQUEST_TIMEOUT_SECONDS,
)
r.raise_for_status()
return r.json()
@@ -70,11 +75,18 @@ class RemoteApiClient:
return dest
def post_event(self, job_id: str, event: Dict[str, Any]) -> None:
r = self._client.post(self._url(f"/jobs/{job_id}/events"), json=event)
r = self._client.post(
self._url(f"/jobs/{job_id}/events"),
json=event,
timeout=CONTROL_REQUEST_TIMEOUT_SECONDS,
)
r.raise_for_status()
def cancel_flag(self, job_id: str) -> bool:
r = self._client.get(self._url(f"/jobs/{job_id}/cancel-flag"))
r = self._client.get(
self._url(f"/jobs/{job_id}/cancel-flag"),
timeout=CONTROL_REQUEST_TIMEOUT_SECONDS,
)
r.raise_for_status()
return bool(r.json().get("cancel_requested"))
@@ -93,12 +105,14 @@ class WorkerAgent:
worker_id: str,
token: str,
poll_seconds: float = 8.0,
heartbeat_seconds: float = ACTIVE_JOB_HEARTBEAT_SECONDS,
staging_root: Optional[Path] = None,
log_func=None,
):
self.api = RemoteApiClient(api_url, token)
self.worker_id = worker_id
self.poll_seconds = poll_seconds
self.heartbeat_seconds = max(1.0, float(heartbeat_seconds))
self.staging_root = staging_root or Path(tempfile.gettempdir()) / "bot_descarga"
self.minio = WorkerMinioUploader(MinioSettings.from_env())
self._running = True
@@ -129,6 +143,25 @@ class WorkerAgent:
self.api.close()
self._log(f"[worker {self.worker_id}] detenido")
def _heartbeat_while_job_runs(self, stop_event: threading.Event) -> None:
"""Mantiene al worker online mientras el procesamiento bloqueante continúa."""
connection_failed = False
while not stop_event.is_set():
try:
self.api.heartbeat(self.worker_id)
if connection_failed:
self._log(f"[worker {self.worker_id}] conexión con servidor restablecida")
connection_failed = False
except Exception as e:
# Una caída temporal del gateway no debe cancelar la descarga local.
if not connection_failed:
self._log(
f"[worker {self.worker_id}] heartbeat temporalmente no disponible: {e}; "
"el job continúa"
)
connection_failed = True
stop_event.wait(self.heartbeat_seconds)
def _run_job(self, job: dict) -> None:
job_id = job["id"]
downloads_prefix = (job.get("downloads_prefix") or "").strip() or f"downloads/{job_id}/"
@@ -140,6 +173,15 @@ class WorkerAgent:
self._log(f"[worker {self.worker_id}] job {job_id} iniciado (prefix={downloads_prefix})")
heartbeat_stop = threading.Event()
heartbeat_thread = threading.Thread(
target=self._heartbeat_while_job_runs,
args=(heartbeat_stop,),
name=f"heartbeat-{self.worker_id}",
daemon=True,
)
heartbeat_thread.start()
try:
self.api.download_excel(job_id, excel_path)
@@ -265,3 +307,6 @@ class WorkerAgent:
)
except Exception:
pass
finally:
heartbeat_stop.set()
heartbeat_thread.join(timeout=CONTROL_REQUEST_TIMEOUT_SECONDS + 1.0)
+72
View File
@@ -0,0 +1,72 @@
"""Construye un ZIP reproducible y limpio del Bot de Descarga."""
from __future__ import annotations
import argparse
import hashlib
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUTPUT = ROOT.parent / "bot_descarga.zip"
TOP_LEVEL_FILES = (
".env.example",
".gitignore",
"README.md",
"requirements.txt",
"run_gui.py",
"main.py",
)
EXCLUDED_PARTS = {"__pycache__", ".pytest_cache", ".venv", "data", "logs", "out"}
EXCLUDED_SUFFIXES = {".pyc", ".pyo", ".log"}
def package_files(root: Path) -> list[Path]:
files: list[Path] = []
for name in TOP_LEVEL_FILES:
path = root / name
if path.is_file():
files.append(path)
package_root = root / "bot_descarga"
for path in package_root.rglob("*"):
relative = path.relative_to(root)
if not path.is_file() or any(part in EXCLUDED_PARTS for part in relative.parts):
continue
if path.suffix.lower() in EXCLUDED_SUFFIXES:
continue
files.append(path)
return sorted(files, key=lambda path: path.relative_to(root).as_posix())
def build_package(root: Path = ROOT, output: Path = DEFAULT_OUTPUT) -> Path:
output.parent.mkdir(parents=True, exist_ok=True)
with ZipFile(output, "w", compression=ZIP_DEFLATED, compresslevel=9) as archive:
for source in package_files(root):
relative = source.relative_to(root).as_posix()
info = ZipInfo(f"bot_descarga/{relative}", date_time=(2020, 1, 1, 0, 0, 0))
info.compress_type = ZIP_DEFLATED
info.external_attr = 0o644 << 16
archive.writestr(info, source.read_bytes())
return output
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
args = parser.parse_args()
target = build_package(output=args.output.resolve())
print(f"Paquete: {target}")
print(f"SHA256: {sha256(target)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+66
View File
@@ -0,0 +1,66 @@
"""Construye y publica el Bot de Descarga en el prefijo latest de MinIO."""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
import boto3
from botocore.config import Config
from package_bot import DEFAULT_OUTPUT, build_package, sha256
DEFAULT_ENDPOINT = "https://s3.builker.xyz"
DEFAULT_BUCKET = "apps-versions"
DEFAULT_KEY = "BotDescargaPYSCuentasMedicas/latest/bot_descarga.zip"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
parser.add_argument("--build-only", action="store_true")
args = parser.parse_args()
artifact = build_package(output=args.output.resolve())
print(f"Paquete validado: {artifact} ({sha256(artifact)})")
if args.build_only:
return 0
access = os.getenv("MINIO_ACCESS_KEY") or os.getenv("AWS_ACCESS_KEY_ID")
secret = os.getenv("MINIO_SECRET_KEY") or os.getenv("AWS_SECRET_ACCESS_KEY")
if not access or not secret:
print("Faltan MINIO_ACCESS_KEY/MINIO_SECRET_KEY para publicar.", file=sys.stderr)
return 2
endpoint = os.getenv("MINIO_ENDPOINT", DEFAULT_ENDPOINT).rstrip("/")
bucket = os.getenv("MINIO_APPS_BUCKET", DEFAULT_BUCKET)
key = os.getenv("DOWNLOAD_PACKAGE_KEY", DEFAULT_KEY).strip("/")
client = boto3.client(
"s3",
endpoint_url=endpoint,
aws_access_key_id=access,
aws_secret_access_key=secret,
region_name=os.getenv("MINIO_REGION", "us-east-1"),
config=Config(s3={"addressing_style": "path"}),
)
client.upload_file(
str(artifact),
bucket,
key,
ExtraArgs={"ContentType": "application/zip"},
)
remote = client.head_object(Bucket=bucket, Key=key)
remote_size = int(remote.get("ContentLength") or 0)
if remote_size != artifact.stat().st_size:
print(
f"La verificación remota falló: local={artifact.stat().st_size}, remoto={remote_size}.",
file=sys.stderr,
)
return 3
print(f"Publicado: {endpoint}/{bucket}/{key}")
print(f"Verificado: {remote_size} bytes")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -50,7 +50,7 @@ def test_browser_start_failure_sets_fatal_error(monkeypatch):
svc = DownloadService(
ruta_excel="dummy.xlsx",
carpeta_descarga=".",
settings=Settings(url_sistema="http://serviciosimaging:8180/"),
settings=Settings(url_sistema="https://serviciosimaging.saludtotal.loc/"),
event_callback=lambda e: events.append(e),
)
summary = svc.run()
@@ -0,0 +1,123 @@
import threading
import time
from pathlib import Path
import bot_descarga.worker.agent as agent_module
from bot_descarga.models.resultado import RunSummary
from bot_descarga.worker.agent import WorkerAgent
class FakeApi:
def __init__(self, failures: int = 0):
self.calls = 0
self.failures = failures
def heartbeat(self, worker_id: str) -> dict:
self.calls += 1
if self.calls <= self.failures:
raise RuntimeError("gateway temporalmente no disponible")
return {"worker_id": worker_id}
def make_agent(api: FakeApi) -> WorkerAgent:
agent = WorkerAgent.__new__(WorkerAgent)
agent.api = api
agent.worker_id = "zero0"
agent.heartbeat_seconds = 0.01
agent._log = lambda _message: None
return agent
def run_briefly(agent: WorkerAgent) -> threading.Thread:
stop_event = threading.Event()
thread = threading.Thread(
target=agent._heartbeat_while_job_runs,
args=(stop_event,),
daemon=True,
)
thread.start()
deadline = time.monotonic() + 1.0
while agent.api.calls < 3 and time.monotonic() < deadline:
time.sleep(0.01)
stop_event.set()
thread.join(timeout=1.0)
return thread
def test_active_job_sends_repeated_heartbeats() -> None:
api = FakeApi()
thread = run_briefly(make_agent(api))
assert api.calls >= 3
assert not thread.is_alive()
def test_transient_heartbeat_failure_does_not_stop_loop() -> None:
api = FakeApi(failures=1)
thread = run_briefly(make_agent(api))
assert api.calls >= 3
assert not thread.is_alive()
def test_successful_job_stays_completed_and_stops_heartbeat(monkeypatch, tmp_path: Path) -> None:
class JobApi(FakeApi):
def __init__(self):
super().__init__()
self.patches = []
def download_excel(self, _job_id: str, dest: Path) -> Path:
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(b"xlsx")
return dest
def cancel_flag(self, _job_id: str) -> bool:
return False
def post_event(self, _job_id: str, _event: dict) -> None:
return None
def patch_job(self, job_id: str, **body) -> dict:
self.patches.append((job_id, body))
return body
class FakeMinio:
def write_manifest(self, *_args, **_kwargs) -> None:
return None
class FakeDownloadService:
def __init__(self, **_kwargs):
pass
def run(self) -> RunSummary:
return RunSummary(total=1, exitosos=1)
api = JobApi()
agent = make_agent(api)
agent.staging_root = tmp_path
agent.minio = FakeMinio()
agent._uploaded_keys = []
monkeypatch.setattr(agent_module, "DownloadService", FakeDownloadService)
agent._run_job({"id": "job-1", "excel_key": "input.xlsx"})
calls_after_job = api.calls
time.sleep(0.03)
assert api.patches == [
(
"job-1",
{
"status": "completed",
"result": {
"total": 1,
"exitosos": 1,
"fallidos": 0,
"cancelled": False,
"downloads_prefix": "downloads/job-1/",
"uploaded_count": 0,
},
"error": None,
},
)
]
assert api.calls == calls_after_job
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,18 @@
# ADR 0001: Mantener el heartbeat durante trabajos de descarga
- Estado: Aceptada
- Fecha: 2026-09-04
## Contexto
El bot de descarga ejecuta el procesamiento del Excel de forma bloqueante. Durante ese tiempo el ciclo principal no enviaba heartbeats y el servidor declaraba al worker desconectado después de 45 segundos, reclamando un trabajo que todavía seguía activo.
## Decisión
El worker enviará un heartbeat cada 10 segundos en un hilo auxiliar durante toda la ejecución del trabajo. Las llamadas de heartbeat, eventos y consulta de cancelación tendrán un timeout de 5 segundos y sus fallos temporales no detendrán la descarga local. El servidor esperará 180 segundos antes de reclamar un trabajo por desconexión.
## Consecuencias
- Los trabajos largos no serán reclamados solamente porque el ciclo principal esté ocupado.
- Una interrupción breve del gateway no congelará el navegador durante un minuto por cada llamada de control.
- Si un worker realmente se apaga, la recuperación automática puede tardar hasta tres minutos.
@@ -0,0 +1,147 @@
{"kind":"workbook","id":"wb/vpj3q6","sheets":4,"tables":4}
{"kind":"sheet","id":"ws/85f3jb","name":"Resumen","index":0,"range":"A1:F25","address":"A1:F25","tables":1,"formulas":3}
{"kind":"table","sheet":"Resumen","address":"A1:F25","rows":25,"cols":6,"values":[["Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana"],["Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana"],["Consulta independiente con is_visible = false","Consulta independiente con is_visible = false","Consulta independiente con is_visible = false","Consulta independiente con is_visible = false","Consulta independiente con is_visible = false","Consulta independiente con is_visible = false"],[null,null,null,null,null,null],["Apartamentos",null,"Páginas consultadas",null,"Fotos",null],[0,null,1,null,0,null],[null,null,null,null,null,null],[null,null,null,null,null,null],["Control de extracción","Control de extracción","Control de extracción","Control de extracción","Control de extracción","Control de extracción"],["Fecha y hora (UTC)","2026-08-30T19:41:16.075Z",null,null,null,null],["Origen","https://srv99857.rentpana.com/rest/v1/apartments",null,null,null,null],["Filtro","is_visible = false",null,null,null,null],["Orden estable","id ascendente",null,null,null,null],["Tamaño de página",50,null,null,null,null],["Total informado por API",0,null,null,null,null],["IDs únicos validados",0,null,null,null,null],["Duplicados detectados",0,null,null,null,null],["Columnas de origen",34,null,null,null,null],["Resultado","La API devolvió 0 registros para este filtro con la sesión suministrada.",null,null,null,null],["Credenciales incluidas","No",null,null,null,null],[null,null,null,null,null,null],["Contenido del archivo","Contenido del archivo","Contenido del archivo","Contenido del archivo","Contenido del archivo","Contenido del archivo"],["Apartamentos","Estructura de 34 campos, lista para los registros devueltos por la consulta.",null,null,null,null],["Fotos","Hoja normalizada para enlaces de fotos; actualmente sin filas.",null,null,null,null],["Paginacion","Consulta, límite, rango, cantidad y estado HTTP documentados.",null,null,null,null]]}
{"kind":"region","sheet":"Resumen","address":"A9:F20","rows":12,"cols":6,"nonEmpty":28,"blank":44,"text":28,"maxTextLength":72,"preview":[["Control de extracción","Control de extracción","Control de extracción","Control de extracción","Control de extracción","Control de extracción"],["Fecha y hora (UTC)","46264.82032494213",null,null,null,null],["Origen","https://srv99857.rentpana.com/rest/v1/apartments",null,null,null,null],["Filtro","is_visible = false",null,null,null,null],["Orden estable","id ascendente",null,null,null,null]],"previewAddress":"A9:F13","previewRows":5,"previewCols":6}
{"kind":"region","sheet":"Resumen","address":"A1:F3","rows":3,"cols":6,"nonEmpty":18,"text":18,"maxTextLength":45,"preview":[["Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana"],["Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana","Apartamentos no visibles · RentPana"],["Consulta independiente con is_visible = false","Consulta independiente con is_visible = false","Consulta independiente con is_visible = false","Consulta independiente con is_visible = false","Consulta independiente con is_visible = false","Consulta independiente con is_visible = false"]],"previewAddress":"A1:F3","previewRows":3,"previewCols":6}
{"kind":"region","sheet":"Resumen","address":"A22:F25","rows":4,"cols":6,"nonEmpty":12,"blank":12,"text":12,"maxTextLength":76,"preview":[["Contenido del archivo","Contenido del archivo","Contenido del archivo","Contenido del archivo","Contenido del archivo","Contenido del archivo"],["Apartamentos","Estructura de 34 campos, lista para los registros devueltos por la consulta.",null,null,null,null],["Fotos","Hoja normalizada para enlaces de fotos; actualmente sin filas.",null,null,null,null],["Paginacion","Consulta, límite, rango, cantidad y estado HTTP documentados.",null,null,null,null]],"previewAddress":"A22:F25","previewRows":4,"previewCols":6}
{"kind":"region","sheet":"Resumen","address":"A5:F6","rows":2,"cols":6,"nonEmpty":6,"blank":6,"text":6,"maxTextLength":19,"preview":[["Apartamentos",null,"Páginas consultadas",null,"Fotos",null],["0",null,"1",null,"0",null]],"previewAddress":"A5:F6","previewRows":2,"previewCols":6}
{"kind":"formula","sheet":"Resumen","address":"A6","formula":"=COUNTA('Apartamentos'!$A$2:$A$2)"}
{"kind":"formula","sheet":"Resumen","address":"C6","formula":"=COUNTA('Paginacion'!$A$2:$A$2)"}
{"kind":"formula","sheet":"Resumen","address":"E6","formula":"=COUNTA('Fotos'!$A$2:$A$2)"}
{"kind":"computedStyle","sheet":"Resumen","for":"A1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"B1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"C1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"D1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"E1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"F1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"A2","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"B2","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"C2","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"D2","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"E2","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"F2","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":51}}
{"kind":"computedStyle","sheet":"Resumen","for":"A3","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"D9EDF2"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":52}}
{"kind":"computedStyle","sheet":"Resumen","for":"B3","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"D9EDF2"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":52}}
{"kind":"computedStyle","sheet":"Resumen","for":"C3","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"D9EDF2"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":52}}
{"kind":"computedStyle","sheet":"Resumen","for":"D3","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"D9EDF2"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":52}}
{"kind":"computedStyle","sheet":"Resumen","for":"E3","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"D9EDF2"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":52}}
{"kind":"computedStyle","sheet":"Resumen","for":"F3","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"D9EDF2"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":52}}
{"kind":"computedStyle","sheet":"Resumen","for":"A5","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":54}}
{"kind":"computedStyle","sheet":"Resumen","for":"C5","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":55}}
{"kind":"computedStyle","sheet":"Resumen","for":"E5","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":55}}
{"kind":"computedStyle","sheet":"Resumen","for":"A6","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":57}}
{"kind":"computedStyle","sheet":"Resumen","for":"C6","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"center","styleId":58}}
{"kind":"computedStyle","sheet":"Resumen","for":"E6","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":20,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"center","styleId":58}}
{"kind":"computedStyle","sheet":"Resumen","for":"A9","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"B9","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"C9","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"D9","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"E9","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"F9","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"A10","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":64}}
{"kind":"computedStyle","sheet":"Resumen","for":"B10","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"numberFormat":"yyyy-mm-dd hh:mm:ss","wrapText":true,"horizontalAlignment":"left","styleId":65}}
{"kind":"computedStyle","sheet":"Resumen","for":"A11","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B11","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A12","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B12","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A13","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B13","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A14","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B14","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A15","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B15","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A16","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B16","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A17","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B17","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A18","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B18","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A19","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":68}}
{"kind":"computedStyle","sheet":"Resumen","for":"B19","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"left","styleId":69}}
{"kind":"computedStyle","sheet":"Resumen","for":"A20","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"60707D"},"gradientStops":[],"pictureEffects":[]}},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":71}}
{"kind":"computedStyle","sheet":"Resumen","for":"B20","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":true,"horizontalAlignment":"left","styleId":72}}
{"kind":"computedStyle","sheet":"Resumen","for":"A22","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"B22","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"C22","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"D22","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"E22","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"F22","style":{"fill":{"type":1,"color":{"type":1,"value":"0F766E"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":63}}
{"kind":"computedStyle","sheet":"Resumen","for":"A23","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":74}}
{"kind":"computedStyle","sheet":"Resumen","for":"B23","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":75}}
{"kind":"computedStyle","sheet":"Resumen","for":"A24","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":77}}
{"kind":"computedStyle","sheet":"Resumen","for":"B24","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":true,"horizontalAlignment":"general","styleId":78}}
{"kind":"computedStyle","sheet":"Resumen","for":"A25","style":{"fill":{"type":1,"color":{"type":1,"value":"F4F7F9"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":80}}
{"kind":"computedStyle","sheet":"Resumen","for":"B25","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Aptos","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":true,"horizontalAlignment":"general","styleId":81}}
{"kind":"sheet","id":"ws/ui1vde","name":"Apartamentos","index":1,"range":"A1:AH1","address":"A1:AH1","tables":1}
{"kind":"table","sheet":"Apartamentos","address":"A1:AH1","rows":1,"cols":34,"values":[["id","user_id","user_name","user_phone","address","description","bedrooms","bathrooms","area_m2","monthly_rent","available_rooms","building_amenities","apartment_features","included_services","photo_urls","created_at","updated_at","show_phone","show_profile","is_visible","priority","listing_type","city","area","short_id","smart_rental_enabled","parent_apartment_id","tenant_user_id","tenant_name","tenant_phone","tenant_gender","tenant_age","is_paid","seo_slug"]]}
{"kind":"region","sheet":"Apartamentos","address":"A1:AH1","rows":1,"cols":34,"nonEmpty":34,"text":34,"maxTextLength":20,"preview":[["id","user_id","user_name","user_phone","address","description"]],"previewAddress":"A1:F1","previewRows":1,"previewCols":6}
{"kind":"computedStyle","sheet":"Apartamentos","for":"A1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"B1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"C1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"D1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"E1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"F1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"G1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"H1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"I1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"J1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"K1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"L1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"M1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"N1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"O1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"P1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"Q1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"R1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"S1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"T1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"U1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"V1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"W1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"X1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"Y1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"Z1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"AA1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"AB1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"AC1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"AD1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"AE1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"AF1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"AG1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Apartamentos","for":"AH1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"sheet","id":"ws/x3rkxn","name":"Fotos","index":2,"range":"A1:G1","address":"A1:G1","tables":1}
{"kind":"table","sheet":"Fotos","address":"A1:G1","rows":1,"cols":7,"values":[["apartment_id","short_id","city","area","address","photo_index","photo_url"]]}
{"kind":"region","sheet":"Fotos","address":"A1:G1","rows":1,"cols":7,"nonEmpty":7,"text":7,"maxTextLength":12,"preview":[["apartment_id","short_id","city","area","address","photo_index"]],"previewAddress":"A1:F1","previewRows":1,"previewCols":6}
{"kind":"computedStyle","sheet":"Fotos","for":"A1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Fotos","for":"B1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Fotos","for":"C1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Fotos","for":"D1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Fotos","for":"E1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Fotos","for":"F1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"computedStyle","sheet":"Fotos","for":"G1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{},"wrapText":false,"horizontalAlignment":"general","styleId":2}}
{"kind":"sheet","id":"ws/ye6w3r","name":"Paginacion","index":3,"range":"A1:H3","address":"A1:H3","tables":1,"formulas":1}
{"kind":"table","sheet":"Paginacion","address":"A1:H3","rows":3,"cols":8,"values":[["pagina","offset","limite","content_range","filas","estado_http","primer_id","ultimo_id"],[1,0,50,"*/0",0,"200 OK",null,null],["Total descargado","Total descargado","Total descargado","Total descargado",0,"Validado contra Content-Range: */0","Validado contra Content-Range: */0","Validado contra Content-Range: */0"]]}
{"kind":"region","sheet":"Paginacion","address":"A1:H3","rows":3,"cols":8,"nonEmpty":22,"blank":2,"text":22,"maxTextLength":34,"preview":[["pagina","offset","limite","content_range","filas","estado_http"],["1","0","50","*/0","0","200 OK"],["Total descargado","Total descargado","Total descargado","Total descargado","0","Validado contra Content-Range: */0"]],"previewAddress":"A1:F3","previewRows":3,"previewCols":6}
{"kind":"formula","sheet":"Paginacion","address":"E3","formula":"=SUM(E2:E2)"}
{"kind":"computedStyle","sheet":"Paginacion","for":"A1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":7}}
{"kind":"computedStyle","sheet":"Paginacion","for":"B1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":7}}
{"kind":"computedStyle","sheet":"Paginacion","for":"C1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":7}}
{"kind":"computedStyle","sheet":"Paginacion","for":"D1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":7}}
{"kind":"computedStyle","sheet":"Paginacion","for":"E1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":7}}
{"kind":"computedStyle","sheet":"Paginacion","for":"F1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":7}}
{"kind":"computedStyle","sheet":"Paginacion","for":"G1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":7}}
{"kind":"computedStyle","sheet":"Paginacion","for":"H1","style":{"fill":{"type":1,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":7}}
{"kind":"computedStyle","sheet":"Paginacion","for":"A2","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":10}}
{"kind":"computedStyle","sheet":"Paginacion","for":"B2","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":10}}
{"kind":"computedStyle","sheet":"Paginacion","for":"C2","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":10}}
{"kind":"computedStyle","sheet":"Paginacion","for":"D2","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":10}}
{"kind":"computedStyle","sheet":"Paginacion","for":"E2","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":10}}
{"kind":"computedStyle","sheet":"Paginacion","for":"F2","style":{"fill":{"type":1,"color":{"type":1,"value":"FFFFFF"},"gradientStops":[],"pictureEffects":[]},"font":{"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"1F2937"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":10}}
{"kind":"computedStyle","sheet":"Paginacion","for":"A3","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":11}}
{"kind":"computedStyle","sheet":"Paginacion","for":"B3","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":11}}
{"kind":"computedStyle","sheet":"Paginacion","for":"C3","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":11}}
{"kind":"computedStyle","sheet":"Paginacion","for":"D3","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":11}}
{"kind":"computedStyle","sheet":"Paginacion","for":"E3","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":11}}
{"kind":"computedStyle","sheet":"Paginacion","for":"F3","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"center","styleId":11}}
{"kind":"computedStyle","sheet":"Paginacion","for":"G3","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":9}}
{"kind":"computedStyle","sheet":"Paginacion","for":"H3","style":{"fill":{"type":1,"color":{"type":1,"value":"DFF4F1"},"gradientStops":[],"pictureEffects":[]},"font":{"bold":true,"fontSize":11,"typeface":"Carlito","fill":{"type":0,"color":{"type":1,"value":"123047"},"gradientStops":[],"pictureEffects":[]}},"border":{"top":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"bottom":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"left":{"style":"thin","color":{"type":1,"value":"D8E1E7"}},"right":{"style":"thin","color":{"type":1,"value":"D8E1E7"}}},"wrapText":false,"horizontalAlignment":"general","styleId":9}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

+3 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.11-slim
FROM python:3.11-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
@@ -6,7 +6,8 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
WORKDIR /app
RUN apt-get update \
RUN sed -i 's|http://deb.debian.org|https://deb.debian.org|g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
+3 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.12-slim
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
@@ -6,7 +6,8 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
WORKDIR /app
RUN apt-get update \
RUN sed -i 's|http://deb.debian.org|https://deb.debian.org|g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
@@ -26,7 +26,9 @@ from infrastructure.database import (
)
from infrastructure.storage_minio import downloads_prefix
HEARTBEAT_OFFLINE_SECONDS = 45
# El worker procesa el navegador de forma bloqueante. Tres minutos toleran una
# caída breve del gateway sin reclamar el mismo job mientras sigue descargando.
HEARTBEAT_OFFLINE_SECONDS = 180
# Un agent recién conectado no recibe jobs reclamados de otros workers.
_FRESH_WORKER_GRACE_SECONDS = 45
+3 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.12-slim
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
@@ -6,7 +6,8 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
WORKDIR /app
RUN apt-get update \
RUN sed -i 's|http://deb.debian.org|https://deb.debian.org|g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
+3 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.11-slim
FROM python:3.11-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
@@ -6,7 +6,8 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
WORKDIR /app
RUN apt-get update \
RUN sed -i 's|http://deb.debian.org|https://deb.debian.org|g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends libgl1 libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.12-slim
FROM python:3.12-slim-bookworm
WORKDIR /app
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ipsextractor-desktop",
"version": "1.3.1",
"version": "1.3.2",
"private": true,
"description": "Aplicación de escritorio Flujo IPS para automatizar procesos de cuentas médicas.",
"main": "electron/main.cjs",
+3 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.11-slim
FROM python:3.11-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
@@ -6,7 +6,8 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
WORKDIR /app
RUN apt-get update \
RUN sed -i 's|http://deb.debian.org|https://deb.debian.org|g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends poppler-utils libgl1 libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.11-slim
FROM python:3.11-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
@@ -29,7 +29,22 @@ ENV GOMAXPROCS=2
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
# La red hacia proxy.golang.org puede ser intermitente en Docker Desktop.
# Reintentar conserva la verificación de go.sum; si el proxy oficial sigue
# caído, la última tentativa descarga directamente desde el repositorio origen.
RUN --mount=type=cache,target=/go/pkg/mod \
set -eu; \
attempt=1; \
while [ "$attempt" -le 3 ]; do \
if GOPROXY=https://proxy.golang.org go mod download; then \
exit 0; \
fi; \
echo "go mod download falló (intento ${attempt}/3); reintentando..."; \
sleep $((attempt * 5)); \
attempt=$((attempt + 1)); \
done; \
echo "proxy.golang.org no respondió; intentando descarga directa..."; \
GOPROXY=direct go mod download
COPY . .