Backend Python Engineer · Lima, Perú · Open to Remote
Backend Python Engineer
Lima, Perú · subliandev@gmail.com
I'm a Backend Python Engineer with 2+ years of professional experience across healthcare and enterprise systems — currently at Fiberlux, where I work on Odoo development and lead a full ERP migration from Odoo v13 to a Django-based microservices architecture.
My focus is on building scalable, maintainable architectures. I enjoy tackling performance bottlenecks head-on — profiling with cProfile, tuning queries with EXPLAIN ANALYZE, and applying design patterns (Singleton, Strategy, OCP) to turn monolithic code into modular, extensible systems.
At Clínica Santa Isabel, I built core healthcare systems including a Payroll module and an Electronic Health Records system using Django and FastAPI. I care deeply about test coverage, observability, and CI/CD practices that keep systems reliable in production.
When production systems fail silently — corrupt data, missing files, broken sync — I debug from first principles: reading packet captures, auditing XML configurations, writing targeted SQL, and building the tooling needed to recover. That mindset led to recovering 50,000 call recordings and 45 days of telephony data after an infrastructure incident that went undetected for weeks.
Currently deepening expertise in microservices architecture and AWS fundamentals. Languages: Spanish (Native) · English B2 (EFSet Certified) · Open to: Remote backend roles, Python infrastructure collaboration, mentorship.
ir.rule security framework — eliminated 100% of cross-team AccessError blocks, reducing access resolution from 2–4 hours to 0ms.ISIS_CSI — Payroll & HR System Reengineering
Hospital Management Web Ecosystem
Algorithms, data structures, software engineering, databases, computer networks, and mathematical modeling. Strong foundation for system design and performance analysis.
Comfortable in English-speaking teams, reading technical docs, participating in code reviews, and written/verbal communication in international environments.
Production-ready, reusable template for secure backend APIs. JWT authentication with refresh token rotation, role-based permissions (admin/staff/client), rate limiting, and fully containerized with Docker. Built as a foundation for SaaS products, mobile backends, or microservices.
Achieved 100% test coverage with 60+ tests including integration suites for auth flows, endpoint coverage, and permission scenarios. Factory Boy–based test data with advanced mocking patterns.
3-script toolkit that recovered 50,000 call recordings and 45 days of telephony data after an infrastructure incident. Scraper reuses browser session cookie; pipeline corrects file mtime from metadata; audit script verifies presence without downloading.
Async file processing (CSV/Excel) with Celery and Redis. Decouples requests from heavy jobs, with status dashboard for upload history and task tracking. Production-style retry patterns.
Medical data management backend handling ~1,000 new records/day across Hospitalización, Emergencias, Quirófano, and Neonatología. Secure JWT access control, structured data workflows, full digital replacement of paper forms.
Automated daily email delivery with multi-role accounts (free/premium), email confirmation, admin dashboard, retries, and cron scheduling. Deployed to production.
Deep-dive into professional testing: reusable fixtures, parametrized tests, mocking of HTTP/email services. Modern uv-based environment.
Nexus is a personal SaaS project — an Order Management System built weekend by weekend with a deliberate architectural thesis: that a Django project can scale without becoming a monolith trap. It targets Latin American SMBs that need electronic invoicing (SUNAT), multi-warehouse inventory, and customer traceability in one platform.
The goal is eventually a multi-tenant SaaS where each business (tenant) operates in full data isolation — its own orders, products, clients, and financial reports — on a shared infrastructure. A CRM layer for customer follow-up and sales opportunity tracking is planned for future versions.
Current status: core OMS fully functional, invoicing pipeline wired, 220+ tests passing. Not production-deployed yet — this is active development, not a finished product.
Most Django projects accumulate logic in models and views until they can't be tested without a running database. Nexus is built against that tendency from the start: Clean Architecture with a hard src/domain/, src/application/, src/infrastructure/, src/interfaces/ boundary structure.
Domain services hold business logic. Models are persistence schemas. Views are thin adapters. The payoff: the 220+ test suite runs deterministically without HTTP calls, mocking only at the infrastructure boundary.
Some parts of the domain are still coupled to Django ORM — an honest tradeoff accepted during active development. The architecture exists as a constraint that makes the coupling visible and removable, not as a description of a finished state.
| System | What it does | Technical highlight |
|---|---|---|
| Multi-Tenancy | Full org-level data isolation: each tenant sees only its own orders, products, clients, and KPIs | OrganizationMiddleware resolves tenant from header (X-Org-ID) or URL slug, stores in thread-local. Custom ORM managers inject WHERE tenant_id = X on every query automatically. |
| OMS Order Lifecycle | Full state machine: DRAFT → PENDING → PAID → SHIPPED → DELIVERED → RETURNED |
FSM enforces valid transitions. select_for_update() prevents race conditions on stock. Stock double-decrement bug caught and fixed: single source of truth via Django signal. |
| Financial Engine | Net margin reports with real COGS, commissions, and refund accounting | FinanceService: COGS calculated from last received PurchaseOrderItem per product. Fallback heuristic (50% margin) when no purchase history. Decoupled from order flow, reusable. |
| Electronic Invoicing Pipeline | SUNAT-ready invoice state machine with async sync, retry queue, and dead letter handling | Provider abstraction via Adapter pattern — MockNubefactClient in dev, real client in prod. Swap provider without touching domain logic. Use case: CreateInvoiceUseCase / QueryInvoiceStatusUseCase. |
| Async Task Pipeline | Background PDF generation, notification dispatch, finance reporting | Celery + Redis. Mocker Pattern for test isolation: async tasks are synchronous in unit tests — no broker required to run the suite. |
| Notification System | Multi-channel: Email, Telegram, WhatsApp — switchable per context | Strategy Pattern: EmailNotification, TelegramNotification, WhatsAppNotification implement the same interface. Channel selection at call time, no conditionals in business logic. |
| Operational Dashboard | KPIs per tenant: invoicing rates, queue depth, retry count, provider latency, reconciliation status | DashboardKPIService aggregates all metrics. HTMX partial updates for reactive refresh without SPA overhead. DailyInvoiceSeriesService feeds Chart.js visualizations. |
| Decision | Alternatives rejected | Reasoning |
|---|---|---|
| Modular monolith over microservices | Separate services from day 1 | Clean layer boundaries make extraction tractable later. Premature splitting adds operational cost before product-market fit. |
| Shared DB + tenant discriminator over DB-per-tenant | Separate schema or DB per tenant | Operationally simpler at early scale. ORM managers enforce isolation at query level. Easier to migrate to DB-per-tenant if needed. |
| HTMX + Django templates over React/SPA | Full SPA frontend | Operator-facing dashboard doesn't need client-side routing. Server-side rendering with HTMX partials gives reactive UX at fraction of the complexity. Alpine.js covers remaining interactivity. |
| Django 6 + Python 3.12 | Stable LTS versions | Personal project: deliberate choice to stay on the leading edge, learn new async capabilities, and avoid accumulating upgrade debt. |
Clínica Santa Isabel's internal payroll system (IsisPlan) handled standard payroll processes but had no module for employee profit-sharing (utilidades). The calculation was done entirely by hand in Excel — manually cross-referencing payroll and liquidation data for ~300 active workers, applying Peruvian labor law rules (D.Leg. 892), and adjusting for fifth-category income tax withholdings.
The process was error-prone and non-reproducible. Workers across the clinic had heterogeneous compensation structures — fixed-salary administrative staff and hourly-rate clinical staff (physicians, nurses, technicians) — each with different calculation bases under law. The hardest edge case: workers who had ceased employment and re-entered within the same fiscal year, requiring pro-rated calculations across two separate employment periods.
The project ran for 3 months from local development to production deployment, replacing the Excel process entirely.
The pipeline extracted data directly from IsisPlan's PostgreSQL database — using stored procedures to pre-aggregate payroll and liquidation records at the database level before ingestion. This kept complex join and filtering logic in SQL where it performs better, and reserved Python/Pandas for business rule application and edge case handling.
Once loaded into DataFrames, the transformation layer applied profit-sharing rules per worker category, detected cese/reingreso cases via duplicate worker IDs with non-contiguous date ranges, and processed each employment segment independently before aggregating the final entitlement.
A superuser interface allowed the payroll administrator (planillero) to review intermediate values and adjust criteria before committing — specifically for fifth-category tax withholding adjustments that required human judgment. The final results were persisted back to PostgreSQL and surfaced through three outputs: an individual record viewer/modal per worker, a per-worker PDF payment slip, and a master Excel file with all records and calculation variables for audit.
| Challenge | Complexity | Solution |
|---|---|---|
| Cese y reingreso | Worker ceases mid-year and re-enters — two employment periods in the same fiscal year, different accrued days and base salary per segment | DataFrame logic detects duplicate worker IDs with non-contiguous date ranges. Each segment processed independently, then aggregated with pro-rating capped per D.Leg. 892 limits. |
| Heterogeneous compensation | Fixed-salary (admin) vs. hourly-rate (clinical) — different calculation base for profit-sharing under Peruvian law | Compensation type flag set at extraction layer via stored procedure. Separate Pandas calculation branches per category, merged in final DataFrame before output. |
| Multi-source merge | Nómina and liquidación tables had different schemas, time granularities, and worker ID formats | Normalization step before merge: ID canonicalization, period alignment, null handling. Stored procedures pre-filtered irrelevant records to reduce Python-side complexity. |
| Fifth-category tax withholding | Retention amounts require human judgment — no fully automatable rule for all cases | Pipeline flags affected records for superuser review. Planillero adjusts values before final commit. System enforces that flagged records must be reviewed before output generation. |
Fiberlux operates an ISP with hundreds of active contracts. Any change to a customer's service status — suspension for non-payment, reactivation after payment, cancellation — required the NOC team to manually interact with the OLT (ZENIT ONE) through a web interface.
The existing Odoo module triggered a web scraping robot that simulated browser clicks on the ZENIT ONE interface. Each service change took ~30 seconds, with no actual confirmation that the change was applied in the network. On billing peak days (days 9–10 of each month), processing 94–135 contracts meant ~47 minutes of HTTP worker blockage — causing Odoo timeouts company-wide.
Additional failure modes: the robot had no result verification, silently failed on network changes, and made duplicate calls to a legacy v1 endpoint while the new v2 was active — causing race conditions that corrupted action records.
CHG-ONU-STAT-PON, LST-ONU-DETAIL) executing in ~7s per service.set_done() returns in <1s, enqueuing action lines with is_pending_dispatch=True. A dedicated cron dispatches batches of 8 every 2 minutes.PollZenitStatus, reading real ADMINSTAT from the OLT port before marking any action as complete. The SSR only closes when all lines are confirmed in the network.onu_id (last OID segment) — preventing accidental modification of a different customer's port.| Bug | Symptom | Root cause & fix |
|---|---|---|
Duplicate execute_action_manual body |
Reactivated services landed in pending instead of done when Fibra Directa NOC confirmed |
Second method body shadowed the first in Python; the shadowed copy used stop_date to determine state, reverting an agreed business rule. Removed duplicate; single implementation with savepoints retained. |
tipo_map mapping cancel → suspend |
NOC Panel showed "Suspension" type for cancellation actions; state_new_request wrote suspend instead of cancel |
Field type in request.action.subscription.line is a fixed Selection (only suspend/activation valid). Separated into two maps: tipo_map for the DB field, state_new_map for the display value. |
state_api_ids blocking cancellations |
All cancellation SSRs silently found zero processable lines — "sin líneas procesables" with no error |
state_api_ids only covers ZENIT ONE states (suspend/done). Cancellations always route to NOC manual — skip that filter for cancel/cancel-adm types. |
| Race condition on DID/OID write | ERROR: no se pudo serializar el acceso — APIOdoo writing back via XML-RPC while Odoo held the row lock |
APIOdoo returns DID/OID in the HTTP response body. Odoo writes locally in its own transaction. No cross-process writes. |
| v1 legacy duplicate calls | Each action line triggered both the v2 API and the old v1 robot endpoint simultaneously | Override of resent_json_action_robot() with three exit conditions: manual NOC line, action_in_progress=True, or json already contains DID/OID from v2. |
| Dimension | Legacy (Robot Scraper) | Re-engineered (TL1 APIs) |
|---|---|---|
| Execution method | Browser click simulation via web scraping | Direct TL1 API commands (CHG-ONU-STAT-PON) |
| Speed per service | ~30 seconds | ~7 seconds (~4× faster per service) |
| Peak day (135 contracts) | ~67 min — HTTP worker timeout | <1s operator wait; async dispatch in background |
| Network confirmation | Assumed — no real verification | ADMINSTAT read from OLT port (LST-ONU-DETAIL) |
| Multi-equipment contracts | No discrimination — risk of touching wrong port | Disambiguated by onu_id (OID last segment) |
| SSR close condition | Closed on first successful action | Closes only when all action lines confirmed |
| Cancellation flow | Not functional — silently returned 0 lines | 4 flows validated: suspend, reactivate, cancel, cancel-adm |
| Operator notification | Toast on screen only (missed if browser closed) | Email at dispatch complete + email at SSR close |
In December 2025, the call center telephony sync system began generating incomplete data. By January 2026 it had stopped entirely. No alert had fired. No error appeared in the logs. The failure was silent by nature — a public IP change by the infrastructure team, unnotified, had severed the connection to the data source.
Investigation required coordinating with the telephony provider and the NOC team to trace a change that had happened weeks earlier. Once found, the scope became clear: ~50,000 audio recordings and 45 days of CSV reports needed to be recovered from scratch.
The provider's portal had the data — but no bulk download API. Each recording was accessible only via individual button click. The CSV reports were available as master files covering multiple months, requiring segmentation into daily files matching the exact naming format the Odoo sync module expected.
Additional complication: downloaded audio files carried the download date as mtime, not the original call date — and the Odoo sync module used st_mtime to organize files. Uploading them directly would place all 50,000 recordings on a single incorrect date.
| Script | Purpose | Key technique |
|---|---|---|
five9_downloader.py |
Automated ~50,000 downloads over 3 days using browser session cookie injection | Atomic checkpoint via os.replace() — crash-safe progress, no restart from zero |
pipeline_fabric_five9.py |
4-phase ETL: upload to temp → correct mtime → deploy to production → archive locally | sftp.utime() sets correct call date from downloader's progress.json before deployment |
fabric_wav_audit.py |
Verified presence of specific recordings across all SFTP paths without downloading | sftp.stat() — existence check with zero data transfer; covers filename variants |
segmentador_five9.py |
Segmented master CSV files into 306 daily files with date range filter and TAC row removal | 2,875 corrupt TAC rows auto-discarded; YYYY_MM-DD naming format enforced for Odoo compatibility |
| Root cause | Evidence | Fix |
|---|---|---|
| IP change — unnotified | Complete data silence from Jan 2026 | Reconnected with new IP; added monitoring |
| CSV naming format bug | Odoo regex expected YYYY_MM-DD; script generated YYYYMMDD |
Fixed in normalizar_fecha(); returns tuple (fecha_id, date) |
TAC rows with embedded \n |
2,875 corrupt rows broke csv.DictReader in Odoo |
_sanitize_csv_content() patch in Odoo module + pre-filter in segmentador |
| 15 alias records only in DB | Created manually in Oct 2024, never in XML — reinstall would silently break everything | Formalized in file_type_values_csv_data.xml with external IDs |
| Audio mtime mismatch | Downloaded files showed download date, not call date | Pipeline reads progress.json and sets correct mtime via sftp.utime() before deploy |
Fiberlux's custom Odoo module synced telephony data (call recordings, transcripts, quality reports) from Five9's SFTP server into the ERP. The original implementation was fully synchronous — during large data loads, it blocked Odoo's worker processes company-wide, freezing CRM and invoicing operations simultaneously.
Additional issues: data inconsistencies between Five9 and Odoo, growing storage costs from unmanaged file accumulation (~1M files since 2023), and fragile SFTP connectivity with no retry logic.
Many2one foreign keys + PostgreSQL views reduced query cost on 40k+ record tables for high-performance reporting.days_to_sanitize with batch processing — auto-purges stale files on schedule, achieving 30% storage reduction.| Dimension | Legacy (Sync) | Async Architecture |
|---|---|---|
| ERP Impact | Company-wide Odoo freeze during sync | Zero perceived latency — fully decoupled |
| Data Consistency | Inconsistencies between Five9 and Odoo | 100% record-level consistency guaranteed |
| Storage | Unbounded accumulation (~1M files) | 30% reduction via auto-batch sanitization |
| Query Performance | Slow scans on 40k+ record tables | B-Tree indexed, view-optimized queries |
| SFTP Resilience | Hard failures on network issues | Auto-retry with hierarchical error handling |
| RAM Stability | Risk of OOM on 100k+ files | Flat memory footprint via batch-limit governor |
| Race Conditions | Possible data corruption under load | Eliminated via indexed thread-state flags |
Odoo's standard security model restricts CRM and Sales document ownership to a single Salesperson field. At Fiberlux, multiple executives — Postventa, Fidelización, and others — needed to operate on the same customer account simultaneously.
The result: critical AccessError blocks on every cross-team operation, invoicing bottlenecks, and manual admin intervention required every 2–4 hours.
create_level_ids inspects the operating uid and routes approvals to the correct team hierarchy.unlink AccessError by bridging: order_id.partner_id.linked_executive_ids.user_id — forcing an implicit JOIN at query time.| Dimension | Legacy (Standard Odoo) | EV Model (Implemented) |
|---|---|---|
| Ownership | Single salesperson | Owner + N linked executives |
| Access Control | Hard block via ir.rule | Dynamic access via relational domain |
| Approval Routing | Always to account owner's team | Branched by creator's actual role |
| Access Resolution | 2–4 hours (manual by admin) | 0 ms (fully automated) |
| Audit Trail | Salesperson only | Owner + executor logged independently |
Fiberlux's invoicing workflow ran entirely inside Odoo — generating a single electronic invoice took ~2 minutes, with no async support. During billing peaks, the process saturated Odoo workers and blocked the entire ERP.
| Dimension | Legacy (Odoo) | Django Engine |
|---|---|---|
| Throughput | 1 invoice / ~2 min | 50 invoices / 35s (~170× faster) |
| Concurrency | Single synchronous thread | 10 parallel async workers |
| ERP Impact | Saturated Odoo workers during peaks | Fully decoupled — zero ERP contention |
| Test Coverage | None | 100% (unit + integration) |
Fiberlux operates a critical production ERP on Odoo v13 managing mass invoicing, CRM, telephony integration, and customer contracts — all heavily customized. A v17 upgrade proved infeasible; deprecated APIs, large data volume, and billing continuity requirements drove the decision toward a full Django rewrite.
Critical Odoo and Django workflows exhibited unexplained latency under production load. Without systematic profiling, bottleneck identification was guesswork — developers were optimizing the wrong paths while true hotspots remained undetected.
select_related() and prefetch_related(), collapsing hundreds of queries into single optimized calls.Two separate VPS environments running Odoo 13 were independently producing 502 Bad Gateway failures and process crashes. Both had been stood up without proper infrastructure configuration — no Swap, no process supervision, and worker settings that either silently disabled all resource limits or set them below the minimum threshold required for startup.
The testapi environment (port 18069, 8 GB RAM VPS) ran in single-threaded mode (workers=0), which silently disables all memory and time limit enforcement in Odoo. It had no Swap, 5.3 GB trapped in OS buffer cache, and was leaking PostgreSQL connections to every database on the server due to a missing dbfilter. The same VPS also hosts the production API (port 8069) — which at the time of intervention had none of these fixes applied, making it an active risk.
The testodoo environment (separate VPS, 16 GB RAM) had the opposite problem: 6 workers configured with limit_memory_hard=2.5 GB — below the ~1.7 GB floor required just to compile the registry of 489 custom modules. Every worker was dying before processing a single request, creating an infinite zombie loop that pinned the CPU without serving any traffic.
The testodoo failure was especially deceptive. Reducing memory limits to "protect the server" is intuitive — but here it caused the exact opposite: a process supervisor that couldn't stop restarting dead workers. The diagnostic key was in the logs: request_count: 0, registry count: 1 on every death. Workers were being killed during initialization, before serving anything.
The testapi failure was a different class of problem: structural invisibility. With workers=0, Odoo's entire resource control layer — soft/hard memory limits, CPU time limits, worker recycling — is inert. The configuration file had values set, but they were completely ignored at runtime. The server was one heavy API request away from OOM-Killer termination with no recovery path.
Additional root cause: no Swap on either server meant the Linux OOM-Killer had zero room to maneuver. Any RAM spike would terminate processes immediately rather than spilling to disk.
| Parameter | Before | After (testapi) | After (testodoo) |
|---|---|---|---|
workers |
0 — all limits inert | 2 — preforking active | 4 — reduced from 6 |
limit_memory_soft |
2.0 GB (ignored) | 2.0 GB (active) | 3.0 GB — above registry floor |
limit_memory_hard |
2.5 GB (ignored / too low) | 2.5 GB (active) | 4.0 GB — 1 GB headroom over soft |
limit_time_cpu |
36,000s (10 hours) | 600s (10 min) | 900s |
dbfilter |
Not set — leaking to all DBs | ^testapi$ |
Scoped to env DB |
| Swap | 0 B — OOM-Killer unprotected | 2 GB provisioned | 4 GB provisioned |
| Action | Reason | Result |
|---|---|---|
OS cache purge via drop_caches |
5.3 GB locked in buff/cache left only 491 MB free — not enough to start workers | 5.3 GB of clean RAM immediately available for Odoo and PostgreSQL |
Port cleanup with fuser -k |
Orphaned sockets from crashed processes blocked clean restart on port 18069 | Port fully released; restart succeeded without manual kill loops |
Systemd service (odoo13-test.service) |
Processes had been managed with nohup & — invisible to OS, no restart policy, lost on reboot |
Restart=on-failure + systemctl enable: auto-recovery and persistence across reboots |
| Longpolling worker (gevent, port 8072) | testodoo had longpolling misconfigured; Nginx was rejecting its connections | Dedicated gevent subprocess enabled; basic routing functional. WebSocket upgrade logged as future improvement |
From ~2 min/invoice in Odoo to 50 invoices in 35s in Django — 10 concurrent async workers. (Fiberlux)
~50,000 call recordings recovered via cookie-session scraper with atomic checkpoint — zero manual downloads. (Fiberlux)
306 daily CSV files regenerated from master reports; 2,875 corrupt TAC rows removed automatically. (Fiberlux)
Automated file sanitization with configurable batch processing on the Five9 SFTP sync module.
Open to remote backend roles, Python infrastructure collaboration, and mentorship opportunities.