feat: mudança de foco. Novo site de manutenção.

This commit is contained in:
Ricardo Carneiro 2026-02-16 20:28:27 -03:00
parent 1141594d20
commit c6ea9f7fe1
34 changed files with 443 additions and 5511 deletions

View File

@ -11,4 +11,10 @@
<PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>
<ItemGroup>
<Content Include="Content\**">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -0,0 +1,61 @@
---
title: "O Resgate do Vostro 5470: De 'Lixo Eletrônico' a Máquina de Performance"
slug: "vostro-5470-resgate-performance"
summary: "Um ultrabook condenado por superaquecimento e lentidão extrema foi transformado em uma ferramenta de trabalho ágil, economizando a compra de um novo notebook."
client: "Cliente Particular (SBC)"
device_model: "Dell Vostro 5470"
estimated_savings: 3500.00
category: "Resgate"
thumbnail: "/assets/img/portfolio/vostro-antes.jpg"
image: "/assets/img/portfolio/vostro-depois.jpg"
tags: ["Dell", "SSD Upgrade", "Limpeza Técnica", "Reparo de Carcaça"]
featured: true
order: 1
date: 2024-02-15
seo_title: "Case de Sucesso: Reparo e Upgrade Dell Vostro 5470 em SBC"
seo_description: "Veja como economizamos R$ 3.500,00 para um cliente de São Bernardo do Campo recuperando um Dell Vostro 5470."
seo_keywords: "Dell Vostro 5470, reparo notebook SBC, upgrade SSD, Golden Square Shopping"
---
<div class="text-center mb-5">
<img src="/img/Completa.png" class="img-fluid rounded shadow" alt="Comparativo Antes e Depois do Resgate">
<p class="text-muted small mt-2">À esquerda: Equipamento com danos estruturais e térmicos. À direita: Equipamento restaurado e atualizado.</p>
</div>
## O Desafio: Um veredito de "Morte Súbita"
O cliente chegou à **CarneiroTech** com um Dell Vostro 5470 que apresentava o clássico problema deste modelo: superaquecimento severo, dobradiças quebradas e uma lentidão que tornava o Windows 10 inutilizável. Em outras assistências, o veredito foi direto: *"Não vale a pena consertar, compre um novo"*.
Com o preço dos notebooks atuais com performance equivalente girando em torno de **R$ 4.000,00**, o desafio era provar que a engenharia de precisão e os componentes certos poderiam dar uma segunda vida ao equipamento.
## Nossa Abordagem: Storytelling Técnico
### 1. Diagnóstico Térmico e Estrutural
Identificamos que a pasta térmica original estava cristalizada, impedindo a dissipação de calor. As dobradiças, presas em suportes plásticos frágeis, estavam forçando a carcaça.
### 2. Intervenção Cirúrgica
- **Reparo de Carcaça:** Reconstrução das torres de fixação com resina industrial, garantindo que o abre-e-fecha fosse suave novamente.
- **Cooling Mod:** Limpeza completa do sistema de exaustão e aplicação de pasta térmica de alta condutividade (Arctic Silver).
### 3. O "Pulo do Gato": Upgrade de Performance
Substituímos o HD mecânico de 5400 RPM por um **SSD SATA III de alta performance** e expandimos a memória RAM. O resultado? O boot que levava 3 minutos passou para **12 segundos**.
## Resultado: Economia Real no Bolso
O investimento total do cliente no resgate foi inferior a 15% do valor de um notebook novo.
- **Custo do Reparo/Upgrade:** ~R$ 500,00
- **Valor de um Notebook Novo Equivalente:** ~R$ 4.000,00
- **Economia Estimada:** **R$ 3.500,00**
Hoje, o Vostro 5470 roda as ferramentas de trabalho do cliente com fluidez total, sem aquecer e com a estrutura reforçada para durar mais alguns anos.
---
### Precisa de um diagnóstico real para o seu equipamento?
Não descarte seu investimento antes de uma análise técnica especializada.
**📍 Onde estamos:** Golden Square Shopping, São Bernardo do Campo.
**🚀 Foco:** Performance máxima com o melhor custo-benefício.
[**AGENDAR AVALIAÇÃO AGORA**](/#contact)

View File

@ -1,329 +0,0 @@
---
title: "ASP 3.0 to .NET Core Migration - Cargo Tracking System"
slug: "asp-to-dotnet-migration"
summary: "Tech Lead in gradual migration of mission-critical ASP 3.0 system to .NET Core, with dual-write data synchronization and cost reduction of $20k/year in mapping APIs."
client: "Logistics and Tracking Company"
industry: "Logistics & Security"
timeline: "12 months (complete migration)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- ASP Classic
- .NET Core
- SQL Server
- Migration
- Tech Lead
- OSRM
- APIs
- Architecture
featured: true
order: 2
date: 2015-06-01
seo_title: "ASP 3.0 to .NET Core Migration - Carneiro Tech Case Study"
seo_description: "Case study of gradual ASP 3.0 to .NET Core migration with data synchronization and $20k/year cost savings in API expenses."
seo_keywords: "ASP migration, .NET Core, legacy modernization, SQL Server, OSRM, tech lead, routing API"
---
## Overview
Mission-critical cargo monitoring system for high-value loads (LED TVs worth $600 each, shipments up to 1000 units) using GPS satellite tracking. The application covered the entire lifecycle: from driver registration and evaluation (police background checks) to real-time monitoring and final delivery.
**Main challenge:** Migrate legacy ASP 3.0 application to .NET Core with zero downtime, maintaining 24/7 critical operations.
---
## Challenge
### Critical Legacy System
The company operated a mission-critical system in **ASP 3.0** (Classic ASP) that couldn't stop:
**Legacy technology:**
- ASP 3.0 (1998 technology)
- SQL Server 2005
- On-premises failover cluster (perfectly capable of handling the load)
- Integration with GPS satellite trackers
- Google Maps API (cost: **$20,000/year** just for route calculation)
**Constraints:**
- 24/7 system operation with high-value cargo
- No downtime allowed during migration
- Multiple interdependent modules
- Team needed to continue developing features during migration
---
## Solution Architecture
### Phase 1: Infrastructure Preparation (Months 1-3)
#### Database Upgrade
```
SQL Server 2005 → SQL Server 2014
- Full backup and validation
- Stored procedure migration
- Index optimization
- Performance testing
```
#### Dual-Write Synchronization Strategy
I implemented a **bidirectional synchronization system** that allowed:
1. **New modules (.NET Core)** wrote to the new database
2. **Automatic trigger** synchronized data to the legacy database
3. **Old modules (ASP 3.0)** continued working normally
4. **Zero downtime** throughout the entire migration
```csharp
// Synchronization implementation example
public class DualWriteService
{
public async Task SaveDriver(Driver driver)
{
// Write to new database (.NET Core)
await _newDbContext.Drivers.AddAsync(driver);
await _newDbContext.SaveChangesAsync();
// SQL trigger automatically syncs to legacy database
// ASP 3.0 modules continue functioning
}
}
```
**Why this approach?**
- Enabled **module-by-module** migration
- Team could continue developing
- Simple rollback if needed
- Reduced operational risk
---
### Phase 2: Gradual Module Migration (Months 4-12)
I migrated modules in increasing complexity order:
**Migration sequence:**
1. ✅ Basic registrations (drivers, vehicles)
2. ✅ Risk assessment (police database integration)
3. ✅ Cargo and route management
4. ✅ Real-time GPS monitoring
5. ✅ Alerts and notifications
6. ✅ Reports and analytics
**Migrated application stack:**
- `.NET Core 1.0` (2015-2016 was the beginning of .NET Core)
- `Entity Framework Core`
- `SignalR` for real-time monitoring
- `SQL Server 2014`
- RESTful APIs
---
### Phase 3: Cost Reduction with OSRM ($20k/year Savings)
#### Problem: Prohibitive Google Maps Cost
The company spent **$20,000/year** just on Google Maps Directions API for truck route calculation.
#### Solution: OSRM (Open Source Routing Machine)
I implemented a solution based on **OSRM** (open-source routing engine):
**Solution architecture:**
```
┌─────────────────┐
│ Frontend │
│ (Leaflet.js) │
└────────┬────────┘
┌─────────────────┐ ┌──────────────┐
│ API Wrapper │─────▶│ OSRM Server │
│ (.NET Core) │ │ (self-hosted)│
└────────┬────────┘ └──────────────┘
┌─────────────────┐
│ Google Maps │
│ (display only) │
└─────────────────┘
```
**Implementation:**
1. **OSRM Server configured** on own server
2. **User-friendly API wrapper** in .NET Core that:
- Received origin/destination
- Queried OSRM (free)
- Returned all route points
- Formatted for frontend
3. **Frontend** drew the route on Google Maps (visualization only, no routing API)
```csharp
[HttpGet("route")]
public async Task<IActionResult> GetRoute(double originLat, double originLng,
double destLat, double destLng)
{
// Query OSRM (free)
var osrmResponse = await _osrmClient.GetRouteAsync(
originLat, originLng, destLat, destLng);
// Return formatted points for frontend
return Ok(new {
points = osrmResponse.Routes[0].Geometry.Coordinates,
distance = osrmResponse.Routes[0].Distance,
duration = osrmResponse.Routes[0].Duration
});
}
```
**Frontend with Leaflet:**
```javascript
// Draw route on map (Google Maps only for tiles)
L.polyline(routePoints, {color: 'red'}).addTo(map);
```
#### OpenStreetMap Attempt
I tried to also replace Google Maps (tiles) with **OpenStreetMap**, which worked technically, but:
**Users didn't like** the appearance
❌ Preferred the familiar Google Maps interface
**Decision:** Keep Google Maps for visualization only (no routing API cost)
**Result:** Savings of **~$20,000/year** while maintaining route quality.
---
## Results & Impact
### Complete Migration in 12 Months
**100% of modules** migrated from ASP 3.0 to .NET Core
**Zero downtime** throughout the entire migration
**Productive team** throughout the process
✅ Faster and more scalable system
### Cost Reduction
💰 **$20,000/year saved** by replacing Google Maps Directions API
📉 **Optimized infrastructure** with SQL Server 2014
### Technical Improvements
🚀 **Performance:** .NET Core application 3x faster than ASP 3.0
🔒 **Security:** Modern stack with active security patches
🛠️ **Maintainability:** Modern C# code vs legacy VBScript
📊 **Monitoring:** SignalR for more efficient real-time tracking
---
## Unexecuted Phase: Microservices & Cloud
### Initial Planning
I participated in the **design and conception** of the second phase (never executed):
**Planned architecture:**
- Migration to **Azure** (cloud was just starting in 2015)
- Break into **microservices**:
- Authentication service
- GPS/tracking service
- Routing service
- Notification service
- **Event-driven architecture** with message queues
**Why it wasn't executed:**
I left the company right after completing the .NET Core migration. The second phase was planned but not implemented by me.
---
## Tech Stack
`ASP 3.0` `VBScript` `.NET Core 1.0` `C#` `Entity Framework Core` `SQL Server 2005` `SQL Server 2014` `OSRM` `Leaflet.js` `Google Maps` `SignalR` `REST APIs` `GPS/Satellite` `Migration Strategy` `Dual-Write Pattern`
---
## Key Decisions & Trade-offs
### Why dual-write synchronization?
**Alternatives considered:**
1. ❌ Big Bang migration (too risky)
2. ❌ Keep everything in ASP 3.0 (unsustainable)
3. ✅ **Gradual migration with sync** (chosen)
**Rationale:**
- Critical system couldn't stop
- Enabled module-by-module rollback
- Team remained productive
### Why OSRM instead of others?
**Alternatives:**
- Google Maps: $20k/year ❌
- Mapbox: Paid license ❌
- GraphHopper: Complex setup ❌
- **OSRM: Open-source, fast, configurable**
### Why not OpenStreetMap for tiles?
**UX-based decision:**
- Technically worked perfectly
- Users preferred familiar Google interface
- **Compromise:** Google Maps for visualization (free) + OSRM for routing (free)
---
## Lessons Learned
### 1. Gradual Migration > Big Bang
Migrating module by module with synchronization enabled:
- Continuous learning
- Route adjustments during the process
- Team and stakeholder confidence
### 2. Open Source Can Save a Lot
OSRM saved **$20k/year** without quality loss. But requires:
- Expertise to configure
- Own infrastructure
- Continuous maintenance
### 3. UX > Technology Sometimes
OpenStreetMap was technically superior (free), but users preferred Google Maps. **Lesson:** Listen to end users.
### 4. Plan for Cloud, but Validate ROI
In 2015, cloud was just starting. On-premises infrastructure (SQL Server cluster) was perfectly capable. **Don't force cloud if there's no clear benefit.**
---
## Context: Why 2015 Was a Special Moment?
**State of technology in 2015:**
- ☁️ **Cloud in early stages:** AWS existed, Azure growing, but low corporate adoption
- 🆕 **.NET Core 1.0 launched** in June 2016 (we used RC during the project)
- 📱 **Microservices:** New concept, Docker in early adoption
- 🗺️ **Google Maps dominant:** Paid APIs, few mature open-source alternatives
**Challenges of the time:**
- Non-existent ASP→.NET migration tools
- Scarce .NET Core documentation (version 1.0!)
- Architecture patterns still consolidating
This project was **pioneering** in adopting .NET Core right at the beginning, when most were migrating to .NET Framework 4.x.
---
**Result:** Successful migration of 24/7 critical system, $20k/year savings, and solid foundation for future evolution.
[Want to discuss a similar migration? Get in touch](#contact)

View File

@ -1,382 +0,0 @@
---
title: "CNPJ Fast - Alphanumeric CNPJ Migration Process"
slug: "cnpj-fast-process"
summary: "Creation of structured methodology for migrating applications to the new Brazilian alphanumeric CNPJ format, sold to insurance company and collection agency."
client: "Consulting Firm (Internal)"
industry: "Consulting & Digital Transformation"
timeline: "3 months (process creation)"
role: "Solution Architect & Process Designer"
image: ""
tags:
- Process Design
- CNPJ
- Migration Strategy
- Regulatory Compliance
- Consulting
- Sales Enablement
featured: true
order: 3
date: 2024-09-01
seo_title: "CNPJ Fast - Alphanumeric CNPJ Migration Methodology"
seo_description: "Case study of creating a structured process for migrating to Brazilian alphanumeric CNPJ, sold to insurance company and collection agency."
seo_keywords: "CNPJ alphanumeric, migration process, regulatory compliance, consulting, methodology"
---
## Overview
With the introduction of **alphanumeric CNPJ** by the Brazilian Federal Revenue Service, companies faced the challenge of adapting their legacy applications that stored CNPJ as numeric fields (`bigint`, `numeric`, `int`).
I created **CNPJ Fast**, a structured methodology to assess, plan, and execute CNPJ migrations in corporate applications and databases.
**Result:** Process sold to **2 clients** (insurance company and collection agency) before implementation.
---
## Challenge
### Complex Regulatory Change
**Regulatory context:**
- Brazilian Federal Revenue Service introduced **alphanumeric CNPJ**
- CNPJ is no longer just numbers (14 digits)
- Now accepts **letters and numbers** (alphanumeric format)
**Impact on companies:**
```sql
-- BEFORE: Numeric CNPJ
CNPJ BIGINT -- 12345678000190
-- AFTER: Alphanumeric CNPJ
CNPJ VARCHAR(18) -- 12.ABC.678/0001-90
```
**Identified problems:**
1. **Database:** `BIGINT`, `NUMERIC`, `INT` columns don't support characters
2. **Primary keys:** CNPJ used as PK in several tables
3. **Foreign keys:** Relationships between tables
4. **Volume:** Millions of records to migrate
5. **Applications:** Validations, masks, business rules
6. **Testing:** Ensure integrity after migration
7. **Downtime:** Limited maintenance windows
**Without a structured process**, companies risked:
- Data loss
- Database inconsistencies
- Broken applications
- Extended downtime
---
## Solution: CNPJ Fast Process
### 5-Phase Methodology
I designed a structured process that could be replicated across different clients:
```
┌─────────────────────────────────────────────┐
│ PHASE 1: DISCOVERY & ASSESSMENT │
│ - Application inventory │
│ - Database schema analysis │
│ - Identification of impacted tables │
│ - Data volume estimation │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PHASE 2: IMPACT ANALYSIS │
│ - Dependency mapping │
│ - Analysis of primary/foreign keys │
│ - Identification of business rules │
│ - Risk assessment │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PHASE 3: MIGRATION PLANNING │
│ - Migration strategy (phased commits) │
│ - Automated SQL scripts │
│ - Rollback plan │
│ - Maintenance windows │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PHASE 4: EXECUTION │
│ - Batch data migration │
│ - Application updates │
│ - Integration testing │
│ - Integrity validation │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PHASE 5: VALIDATION & GO-LIVE │
│ - Regression testing │
│ - Performance validation │
│ - Coordinated go-live │
│ - Post-migration monitoring │
└─────────────────────────────────────────────┘
```
---
### Phase 1: Discovery & Assessment
**Objective:** Understand the complete migration scope
**Deliverables:**
1. **Application Inventory**
- List of applications using CNPJ
- Technologies (ASP 3.0, VB6, .NET, microservices)
- Criticality of each application
2. **Schema Analysis**
```sql
-- Automated discovery script
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
c.COLUMN_NAME,
c.DATA_TYPE,
c.CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.TABLES t
JOIN INFORMATION_SCHEMA.COLUMNS c
ON t.TABLE_NAME = c.TABLE_NAME
WHERE c.COLUMN_NAME LIKE '%CNPJ%'
AND c.DATA_TYPE IN ('bigint', 'numeric', 'int')
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME;
```
3. **Volume Estimation**
- Total records per table
- Size in GB
- Estimated migration time
**Example output:**
| Table | Column | Current Type | Records | Criticality |
|--------|--------|------------|-----------|-------------|
| Clients | CNPJ_Client | BIGINT | 8,000,000 | High |
| Suppliers | CNPJ_Supplier | NUMERIC(14) | 2,500,000 | Medium |
| Transactions | CNPJ_Payer | BIGINT | 90,000,000 | Critical |
---
### Phase 2: Impact Analysis
**Objective:** Map all dependencies and risks
**Key analysis:**
```sql
-- Identifies PKs and FKs involving CNPJ
SELECT
fk.name AS FK_Name,
tp.name AS Parent_Table,
cp.name AS Parent_Column,
tr.name AS Referenced_Table,
cr.name AS Referenced_Column
FROM sys.foreign_keys fk
INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id
INNER JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id
AND fkc.parent_object_id = cp.object_id
INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id
INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id
AND fkc.referenced_object_id = cr.object_id
WHERE cp.name LIKE '%CNPJ%' OR cr.name LIKE '%CNPJ%';
```
**Risk Assessment:**
- **High:** Tables with CNPJ as PK and >10M records
- **Medium:** Tables with FK to CNPJ
- **Low:** Tables without constraints
---
### Phase 3: Migration Planning
**Gradual migration strategy:**
To avoid database locks, I designed a **phased commits** strategy:
```sql
-- Strategy for large tables (>1M records)
-- 1. Add new VARCHAR column
ALTER TABLE Clients
ADD CNPJ_Client_New VARCHAR(18) NULL;
-- 2. Migration in batches (phased commits)
DECLARE @BatchSize INT = 100000;
DECLARE @RowsAffected INT = 1;
WHILE @RowsAffected > 0
BEGIN
UPDATE TOP (@BatchSize) Clients
SET CNPJ_Client_New = FORMAT(CNPJ_Client, '00000000000000')
WHERE CNPJ_Client_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
WAITFOR DELAY '00:00:01'; -- Pause between batches
END;
-- 3. Remove constraints (PKs, FKs)
ALTER TABLE Clients DROP CONSTRAINT PK_Clients;
-- 4. Rename columns
EXEC sp_rename 'Clients.CNPJ_Client', 'CNPJ_Client_Old', 'COLUMN';
EXEC sp_rename 'Clients.CNPJ_Client_New', 'CNPJ_Client', 'COLUMN';
-- 5. Recreate constraints
ALTER TABLE Clients
ADD CONSTRAINT PK_Clients PRIMARY KEY (CNPJ_Client);
-- 6. Remove old column (after validation)
ALTER TABLE Clients DROP COLUMN CNPJ_Client_Old;
```
**Why this approach?**
- Avoids locking entire table
- Allows pausing/resuming migration
- Minimizes production impact
- Facilitates rollback if needed
---
### Phases 4 & 5: Execution and Validation
**Execution checklist:**
- [ ] Complete database backup
- [ ] Execute migration scripts in batches
- [ ] Update applications (validations, masks)
- [ ] Integration testing
- [ ] Referential integrity validation
- [ ] Performance testing
- [ ] Coordinated go-live
- [ ] 24h post-migration monitoring
---
## Sales Enablement: UX Presentation
**Collaboration with UX Manager:**
The company's UX manager created an **impactful visual presentation** of the CNPJ Fast process:
**Presentation content:**
- Infographics of the 5-phase process
- Examples of time/cost estimates
- Use cases (insurance, banks, fintechs)
- Executive checklist
- Documentation templates
**Result:** Presentation used by sales team for prospecting.
---
## Results & Impact
### Sales Achieved
**Client 1: Insurance Company**
- Stack: ASP 3.0, VB6 components, .NET, microservices
- Scope: Complete legacy application migration
- Status: **Project sold** (execution by another team)
- Value: [Confidential]
**Client 2: Collection Agency**
- Scope: Database migration (~100M records)
- Status: **Project sold and in execution** (by me)
- Particularity: Process was **restructured** to meet specific needs
- See complete case study: [CNPJ Migration - 100M Records](/cases/cnpj-migration-database)
---
### Business Impact
**2 projects sold** before first execution
**Replicable process** for new clients
**Positioning** as specialist in regulatory migrations
**Knowledge base** for future similar projects
---
### Technical Impact
**Tested methodology** in real scenarios
**Reusable documentation** (scripts, checklists, templates)
**Acceleration** of similar projects (from weeks to days)
---
## Tech Stack
`SQL Server` `Migration Strategy` `Process Design` `Regulatory Compliance` `ASP 3.0` `VB6` `.NET` `Microservices` `Batch Processing` `Database Optimization`
---
## Key Decisions & Trade-offs
### Why structured process?
**Alternatives:**
1. Ad-hoc approach per project
2. Manual consulting without methodology
3. **Replicable and scalable process**
**Justification:**
- Reduces Discovery time
- Standardizes deliveries
- Facilitates sales (ready presentation)
- Allows execution by different teams
### Why separate into 5 phases?
**Benefits:**
- Client can approve phase by phase
- Allows adjustments during process
- Facilitates risk management
- Incremental deliveries
---
## Lessons Learned
### 1. UX/Presentation Matters for Sales
The visual presentation made by the UX manager was **crucial** to closing both contracts. Good technical process + poor presentation = no sales.
### 2. Process Sells, Not Just Execution
Creating a **documented methodology** has more commercial value than just offering "consulting hours."
### 3. Each Client is Unique
The client requested **process restructuring**. A good process should be:
- Structured enough to be replicable
- Flexible enough to customize
### 4. Multidisciplinary Collaboration
Working with UX manager (presentations) + sales team (sales) + technical (execution) = success.
---
## Next Steps
**Future opportunities:**
1. **Expansion:** Offer CNPJ Fast to more sectors (banks, fintechs, retail)
2. **Product:** Transform into automated tool (SaaS)
3. **Training:** Enable clients' internal teams
4. **Evolution:** Adapt process for other regulatory migrations (PIX, Open Banking)
---
**Result:** Structured methodology that became a sellable product, generating revenue before the first technical execution.
[Want to implement CNPJ Fast in your company? Get in touch](#contact)

View File

@ -1,469 +0,0 @@
---
title: "Alphanumeric CNPJ Migration - 100 Million Records"
slug: "cnpj-migration-database"
summary: "Execution of massive CNPJ migration from numeric to alphanumeric in database with ~100M records, using phased commit strategy to avoid database locks."
client: "Collection Agency"
industry: "Collections & Financial Services"
timeline: "In execution"
role: "Database Architect & Tech Lead"
image: ""
tags:
- SQL Server
- Database Migration
- CNPJ
- Performance Optimization
- Batch Processing
- Big Data
featured: true
order: 4
date: 2024-11-01
seo_title: "Alphanumeric CNPJ Migration - 100M Records | Carneiro Tech"
seo_description: "Case study of massive CNPJ migration in database with 100 million records using phased commits and performance optimizations."
seo_keywords: "database migration, SQL Server, CNPJ, batch processing, performance optimization, phased commits"
---
## Overview
A collection agency that works with transitory data databases (no proprietary software) needs to adapt its systems to the new Brazilian **alphanumeric CNPJ** format.
**Main challenge:** Migrate ~**100 million records** in tables with `BIGINT` and `NUMERIC` columns to `VARCHAR`, without locking the production database.
**Status:** Project in execution (migration script preparation).
---
## Challenge
### Massive Data Volume
**Company context:**
- Collection agency (does not develop proprietary software)
- Works with **transitory data** (high turnover)
- SQL Server database with critical volume
**Initial analysis revealed:**
| Table | Column | Current Type | Records | Size |
|--------|--------|------------|-----------|---------|
| Debtors | CNPJ_Debtor | BIGINT | 8,000,000 | 60 GB |
| Transactions | CNPJ_Payer | NUMERIC(14) | 90,000,000 | 1.2 TB |
| Companies | CNPJ_Company | BIGINT | 2,500,000 | 18 GB |
| **TOTAL** | - | - | **~100,000,000** | **~1.3 TB** |
**Identified problems:**
1. **Tables with 8M+ rows** using `BIGINT` for CNPJ
2. **90 million records** in transactions table
3. **CNPJ as primary key** in some tables
4. **Foreign keys** relating multiple tables
5. **Impossibility of extended downtime** (24/7 operation)
6. **Disk space restrictions** (requires efficient strategy)
---
## Strategic Decision: Phased Commits
### Why NOT do ALTER COLUMN directly?
**Naive approach (DOESN'T work):**
```sql
-- NEVER DO THIS ON LARGE TABLES
ALTER TABLE Transactions
ALTER COLUMN CNPJ_Payer VARCHAR(18);
```
**Problems:**
- Locks entire table during conversion
- Can take hours/days on large tables
- Blocks all operations (INSERT, UPDATE, SELECT)
- Risk of timeout or failure mid-operation
- Complex rollback if something goes wrong
---
### Chosen Strategy: Column Swap with Phased Commits
**Based on previous experience**, I decided to use a gradual approach:
```
┌─────────────────────────────────────────────┐
│ 1. Create new VARCHAR column at END │
│ (fast operation, doesn't lock table) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 2. UPDATE in batches (phased commits) │
│ - 100k records at a time │
│ - Pause between batches (avoid lock) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 3. Remove PKs and FKs │
│ (after 100% migrated) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 4. Rename columns (swap) │
│ - CNPJ → CNPJ_Old │
│ - CNPJ_New → CNPJ │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 5. Recreate PKs/FKs with new column │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 6. Validation and old column deletion │
└─────────────────────────────────────────────┘
```
**Why this approach?**
**No complete table lock** (incremental operation)
**Can pause/resume** at any time
**Real-time progress monitoring**
**Simple rollback** (just drop new column)
**Minimizes production impact** (small commits)
**Decision based on:**
- Previous experience with large volume migrations
- Knowledge of SQL Server locks
- Need for zero downtime
**Note:** This decision was made **without consulting AI** - based purely on practical experience from previous projects.
---
## Implementation Details
### Phase 1: Create New Column
```sql
-- Fast operation (metadata change only)
ALTER TABLE Transactions
ADD CNPJ_Payer_New VARCHAR(18) NULL;
-- Add temporary index to speed up lookups
CREATE NONCLUSTERED INDEX IX_Temp_CNPJ_New
ON Transactions(CNPJ_Payer_New)
WHERE CNPJ_Payer_New IS NULL;
```
**Estimated time:** ~1 second (independent of table size)
---
### Phase 2: Batch Migration (Core Strategy)
```sql
-- Migration script with phased commits
DECLARE @BatchSize INT = 100000; -- 100k records per batch
DECLARE @RowsAffected INT = 1;
DECLARE @TotalProcessed INT = 0;
DECLARE @StartTime DATETIME = GETDATE();
WHILE @RowsAffected > 0
BEGIN
BEGIN TRANSACTION;
-- Update batch of 100k records not yet migrated
UPDATE TOP (@BatchSize) Transactions
SET CNPJ_Payer_New = RIGHT('00000000000000' + CAST(CNPJ_Payer AS VARCHAR), 14)
WHERE CNPJ_Payer_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
SET @TotalProcessed = @TotalProcessed + @RowsAffected;
COMMIT TRANSACTION;
-- Progress log
PRINT 'Processed: ' + CAST(@TotalProcessed AS VARCHAR) + ' rows. Batch: ' + CAST(@RowsAffected AS VARCHAR);
PRINT 'Elapsed time: ' + CAST(DATEDIFF(SECOND, @StartTime, GETDATE()) AS VARCHAR) + ' seconds';
-- Pause between batches (reduces contention)
WAITFOR DELAY '00:00:01'; -- 1 second between batches
END;
PRINT 'Migration completed! Total rows: ' + CAST(@TotalProcessed AS VARCHAR);
```
**Configurable parameters:**
- `@BatchSize`: 100k (balanced between performance and lock time)
- Too small = many transactions, overhead
- Too large = prolonged lock, production impact
- `WAITFOR DELAY`: 1 second (gives time for other queries to run)
**Time estimates:**
| Records | Batch Size | Estimated Time |
|-----------|------------|----------------|
| 8,000,000 | 100,000 | ~2-3 hours |
| 90,000,000 | 100,000 | ~20-24 hours |
**Advantages:**
- Doesn't freeze application
- Other queries can run between batches
- Can pause (Ctrl+C) and resume later (WHERE NULL picks up where it left off)
- Real-time progress log
---
### Phase 3: Constraint Removal
```sql
-- Identifies all PKs and FKs involving the column
SELECT name
FROM sys.key_constraints
WHERE type = 'PK'
AND parent_object_id = OBJECT_ID('Transactions')
AND COL_NAME(parent_object_id, parent_column_id) = 'CNPJ_Payer';
-- Remove PKs
ALTER TABLE Transactions
DROP CONSTRAINT PK_Transactions_CNPJ;
-- Remove FKs (tables that reference)
ALTER TABLE Payments
DROP CONSTRAINT FK_Payments_Transactions;
```
**Estimated time:** ~10 minutes (depends on how many constraints exist)
---
### Phase 4: Column Swap (Renaming)
```sql
-- Rename old column to _Old
EXEC sp_rename 'Transactions.CNPJ_Payer', 'CNPJ_Payer_Old', 'COLUMN';
-- Rename new column to original name
EXEC sp_rename 'Transactions.CNPJ_Payer_New', 'CNPJ_Payer', 'COLUMN';
-- Change to NOT NULL (after validating 100% populated)
ALTER TABLE Transactions
ALTER COLUMN CNPJ_Payer VARCHAR(18) NOT NULL;
```
**Estimated time:** ~1 second (metadata change)
---
### Phase 5: Constraint Recreation
```sql
-- Recreate PK with new VARCHAR column
ALTER TABLE Transactions
ADD CONSTRAINT PK_Transactions_CNPJ
PRIMARY KEY CLUSTERED (CNPJ_Payer);
-- Recreate FKs
ALTER TABLE Payments
ADD CONSTRAINT FK_Payments_Transactions
FOREIGN KEY (CNPJ_Payer) REFERENCES Transactions(CNPJ_Payer);
```
**Estimated time:** ~30-60 minutes (depends on volume)
---
### Phase 6: Validation and Cleanup
```sql
-- Validate that 100% was migrated
SELECT COUNT(*)
FROM Transactions
WHERE CNPJ_Payer IS NULL OR CNPJ_Payer = '';
-- Validate referential integrity
DBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS;
-- If everything OK, remove old column
ALTER TABLE Transactions
DROP COLUMN CNPJ_Payer_Old;
-- Remove temporary index
DROP INDEX IX_Temp_CNPJ_New ON Transactions;
```
---
## CNPJ Fast Process Customization
### Differences vs. Original Process
The original **CNPJ Fast** process was **restructured** for this client:
**Main changes:**
| Aspect | Original CNPJ Fast | Client (Customized) |
|---------|-------------------|---------------------|
| **Focus** | Applications + DB | DB only (no proprietary software) |
| **Discovery** | App inventory | Schema analysis only |
| **Execution** | Multiple applications | Massive SQL scripts |
| **Batch Size** | 50k-100k | 100k (optimized for volume) |
| **Monitoring** | Manual + tools | Real-time SQL logs |
| **Rollback** | Complex process | Simple (DROP COLUMN) |
**Reason for restructuring:**
- Client has no proprietary applications (only consumes data)
- 100% focus on database optimization
- Much larger volume than typical cases (100M vs ~10M)
---
## Tech Stack
`SQL Server` `T-SQL` `Batch Processing` `Performance Tuning` `Database Optimization` `Migration Scripts` `Phased Commits` `Index Optimization` `Constraint Management`
---
## Key Decisions & Trade-offs
### Why 100k per batch?
**Performance tests:**
| Batch Size | Time/Batch | Lock Duration | Contention |
|------------|-------------|---------------|-----------|
| 10,000 | 2s | Low | Minimal |
| 50,000 | 8s | Medium | Acceptable |
| **100,000** | 15s | **Medium** | **Balanced** |
| 500,000 | 90s | High | Production impact |
| 1,000,000 | 180s | Very high | Unacceptable |
**Choice:** 100k offers best balance between performance and impact.
---
### Why create column at END?
**SQL Server internals:**
- Add column at end = metadata change (fast)
- Add in middle = page rewrite (slow)
- For large tables, position matters
---
### Why WAITFOR DELAY of 1 second?
**Without delay:**
- Batch processing consumes 100% of I/O
- Application queries slow down
- Lock escalation may occur
**With 1s delay:**
- Other queries have window to execute
- Distributed I/O
- User experience preserved
**Trade-off:** Migration takes +1s per batch (~25% slower), but system remains responsive.
---
## Current Status & Next Steps
### Current Status (December 2024)
**Preparation Phase:**
- Discovery complete (100M records identified)
- Migration scripts developed
- Tests in staging environment
- Performance validation in progress
- Awaiting production maintenance window
### Next Steps
1. **Complete production backup**
2. **Production execution** (24/7 environment)
3. **Real-time monitoring** during migration
4. **Post-migration validation** (integrity, performance)
5. **Lessons learned documentation**
---
## Lessons Learned (So Far)
### 1. Previous Experience is Gold
Decision to use phased commits came from **practical experience** in previous projects, not from documentation or AI.
**Similar previous situations:**
- E-commerce data migration (50M records)
- Encoding conversion (UTF-8 in 100M+ rows)
- Historical table partitioning
---
### 2. "Measure Twice, Cut Once"
Before executing in production:
- Exhaustive tests in staging
- Scripts validated and reviewed
- Rollback tested
- Time estimates confirmed
**Preparation time:** 3 weeks
**Execution time:** Estimated at 48 hours
**Ratio:** 10:1 (preparation vs execution)
---
### 3. Customization > One-Size-Fits-All
The original CNPJ Fast process needed to be **restructured** for this client.
**Lesson:** Processes should be:
- Structured enough to repeat
- Flexible enough to adapt
---
### 4. Monitoring is Crucial
Scripts with **detailed progress logs** allow:
- Estimate remaining time
- Identify bottlenecks
- Pause/resume with confidence
- Report status to stakeholders
```sql
-- Log example
Processed: 10,000,000 rows. Batch: 100,000
Elapsed time: 3600 seconds (10% complete, ~9h remaining)
```
---
## Performance Optimizations
### Optimizations Implemented
1. **Temporary index WHERE NULL**
- Speeds up lookup of unmigrated records
- Removed after completion
2. **Optimized batch size**
- Balanced between performance and lock time
3. **Transaction log management**
```sql
-- Check log growth
DBCC SQLPERF(LOGSPACE);
-- Adjust recovery model (if allowed)
ALTER DATABASE MyDatabase SET RECOVERY SIMPLE;
```
4. **Execution during low-load hours**
- Overnight maintenance window
- Weekend (if possible)
---
**Expected result:** Migration of 100 million records in ~48 hours, without significant downtime and with possibility of fast rollback.
[Need to migrate massive data volumes? Get in touch](#contact)

View File

@ -1,588 +0,0 @@
---
title: "Industrial Training Platform - From Wireframes to Complete System"
slug: "industrial-learning-platform"
summary: "Solution Design for microlearning platform in industrial gas company. Identification of critical unmapped requirements (admin, registrations, exports) before client presentation, avoiding rework and ensuring real usability."
client: "Industrial Gas Company"
industry: "Industrial & Manufacturing"
timeline: "4 months"
role: "Solution Architect & Tech Lead"
image: ""
tags:
- Solution Design
- EdTech
- Learning Platform
- Requirements Analysis
- Tech Lead
- User Stories
- .NET
- Product Design
featured: true
order: 5
date: 2024-06-01
seo_title: "Industrial Training Platform - Solution Design"
seo_description: "Case study of Solution Design for microlearning platform, identifying critical requirements before client presentation and leading development to production."
seo_keywords: "solution design, learning platform, microlearning, requirements analysis, tech lead, industrial training"
---
## Overview
Industrial gas company requests platform to train employees using **microlearning** methodology (short and objective content).
**Initial requirement:** "We just want the structure - track, microlearning, test question and score."
**Problem:** Incomplete specification that would result in a system **impossible to use** (no way to register content, no administrators, no export of results).
**Solution:** Critical requirements analysis **before client presentation**, identifying functional gaps and proposing complete solution.
---
## Challenge
### Beautiful Wireframes, Incomplete Functionality
**Initial situation:**
UX created beautiful wireframes showing:
- Learning tracks
- Microlearnings (video/text + image)
- Test questions (multiple choice)
- Score per employee
**Identified problem:**
Nobody (client, UX, commercial) thought about:
**How does content enter the system?**
- Who registers tracks?
- Who creates microlearnings?
- Who writes questions?
- Manual interface or import?
**Who manages the system?**
- Is there admin concept?
- Can HR create admins?
- Can area manager see only their team?
**How does data leave the system?**
- HR needs reports
- Compliance needs evidence
- How to export data?
- Format: Excel? PDF? API?
**Real risk:**
If we developed exactly what was requested:
- System would work technically
- **But would be completely unusable**
- Client would have to pay for rework to include basic CRUD
- Rework + additional cost + frustration
---
## Solution Design Process
### Step 1: Critical Analysis (Before Presentation)
**Action taken:** Called meeting with UX **before** presenting to client.
**Points raised:**
**"How does the first content enter the system?"**
- UX: "Ah... we didn't think about that. Will you populate the database?"
- Me: "And when client wants to add new track? Will we modify production database?"
**"Who is the system owner?"**
- UX: "HR, I imagine."
- Me: "Just one person? What if they leave the company? How do they delegate?"
**"Did HR ask for reports?"**
- UX: "It wasn't mentioned in the briefing."
- Me: "HR always needs reports. It's for compliance (NR, ISO)."
---
### Step 2: Identified Functional Requirements
I proposed 4 additional **essential** modules:
#### 1. Administration System
**Features:**
- Standard user: Only takes training
- Admin user: Manages content + sees reports
- Admin can promote other users to admin
- Access control (general admin vs area admin)
**Why it's critical:**
System is static without this (content never updates).
---
#### 2. Content CRUD
**a) Track Registration:**
- Track name
- Description
- Microlearning order
- Active/inactive track (allows unpublishing)
**b) Microlearning Registration:**
- Title
- Type: Simple text (2 paragraphs) OR Video
- Image upload (if text)
- Video URL (if video)
- Order within track
**c) Question Registration:**
- Question (text)
- 3 answer options:
- "Great" (green)
- "So-so" (yellow)
- "Poor" (red)
- Points per answer (e.g., 10, 5, 0 points)
- Custom feedback per answer
**Why it's critical:**
Client needs to update content without calling dev/DBA.
---
#### 3. Data Export
**Features:**
- Export to Excel (.xlsx)
- Filters:
- By period (start/end date)
- By track
- By employee
- By area/department
- Exported columns:
- Employee name
- ID number
- Completed track
- Total score
- Completion date
- Individual answers (for audit)
**Why it's critical:**
HR needs to evidence training for:
- Regulatory Norms (NR-13, NR-20 - flammable gases)
- ISO audits
- Labor lawsuits
---
#### 4. User Management
**Features:**
- Import employees (CSV/Excel upload)
- Manual registration
- Activate/deactivate users
- Assign mandatory tracks by area
- Pending notifications
**Why it's critical:**
Company has 500+ employees, manual registration is unfeasible.
---
### Step 3: Client Presentation
**Approach:**
1. Showed UX wireframes (beautiful interface)
2. Asked: "How will you register the first track?"
3. Client: "Ah... good question. We hadn't thought about that."
4. Presented the 4 additional modules
5. Client: "Makes total sense! Without this we can't use it."
**Result:**
- Proposal approved **with additional modules**
- Adjusted scope (timeline + budget)
- Zero future rework
- Client recognized added value
---
## Implementation
### My Role in the Project
**1. Solution Architect**
- Identification of non-functional requirements
- Architecture design (modules, integrations)
- Technology definition
**2. Tech Lead**
- Technical team leadership (3 devs)
- Code review
- Code standards definition
- Technical risk management
**3. Technical Product Owner**
- Creation of complete **user stories**
- Backlog prioritization
- Continuous refinement with client
---
### Chosen Tech Stack
**Backend:**
- `.NET 7` - REST APIs
- `Entity Framework Core` - ORM
- `SQL Server` - Database
- `ClosedXML` - Excel generation
**Frontend:**
- `React` - Web interface
- `Material-UI` - Components
- `React Player` - Video player
- `Chart.js` - Progress charts
**Infrastructure:**
- `Azure App Service` - Hosting
- `Azure Blob Storage` - Video/image storage
- `Azure SQL Database` - Managed database
---
### Created User Stories
I wrote **32 user stories** covering all flows. Examples:
**US-01: Register Track (Admin)**
```
As system administrator
I want to register a new training track
So that employees can take the courses
Acceptance criteria:
- Admin accesses "Tracks" menu → "New Track"
- Fills in: Name, Description, Status (Active/Inactive)
- Can add existing microlearnings to track
- Defines microlearning order (drag & drop)
- System validates mandatory fields
- Saves and displays success message
```
**US-15: Complete Microlearning (Employee)**
```
As employee
I want to complete a microlearning from my track
To learn about the topic and earn points
Acceptance criteria:
- Employee accesses assigned track
- Sees list of microlearnings (uncompleted first)
- Clicks microlearning → opens screen with:
- Text (2 paragraphs) + Image OR
- Embedded video player
- "Continue" button appears after:
- 30s (if text)
- End of video (if video)
- Marks microlearning as seen
- Test question appears automatically
```
**US-22: Export Results (Admin)**
```
As administrator
I want to export training results to Excel
To generate compliance and audit reports
Acceptance criteria:
- Admin accesses "Reports" → "Export"
- Selects filters (period, track, area)
- Clicks "Generate Excel"
- System processes and downloads .xlsx file
- Excel contains columns: Name, ID, Track, Points, Date, Answers
- Readable format (bold headers, auto-adjusted columns)
```
---
## Key Features Implemented
### 1. Gamified Scoring System
**Mechanics:**
- Each question worth points (configurable)
- "Great" answer: 10 points
- "So-so" answer: 5 points
- "Poor" answer: 0 points
**Employee dashboard:**
- Total score
- Ranking (optional, configurable)
- Badges for completed tracks
- Visual progress (% bar)
**Why it works:**
Factory floor employees engage more with gamification elements.
---
### 2. Adaptive Microlearning
**Content types:**
**Text + Image:**
- 2 paragraphs (max 300 words)
- 1 illustrative image
- Ideal for: Procedures, norms, concepts
**Video:**
- Short videos (2-5 min)
- Embedded player (YouTube/Vimeo or upload)
- Ideal for: Demonstrations, equipment operations
**Why microlearning?**
- Employees complete during breaks (10-15min)
- Short content = higher retention
- Facilitates updates (vs long courses)
---
### 3. Delegated Administration System
**Hierarchy:**
```
General Admin (HR)
↓ can promote
Area Admin (Managers)
↓ can view only
Employees from their area
```
**Permissions:**
- General admin: Creates tracks, promotes admins, sees all data
- Area admin: Sees only their area reports
- Employee: Only takes training
**Audit:**
- Logs of who created/edited each content
- History of admin promotions
- SOX/ISO compliance
---
### 4. Export for Compliance
**Generated Excel format:**
| ID | Name | Area | Track | Completion Date | Points | Status |
|-----------|------|------|--------|----------------|--------|--------|
| 1001 | John Silva | Production | NR-20 Safety | 11/15/2024 | 95/100 | Approved |
| 1002 | Mary Santos | Logistics | Gas Handling | 11/14/2024 | 78/100 | Approved |
**Additional sheet: Answer Details**
- Allows audit: "Did employee X answer question Y correctly?"
- Evidence for labor lawsuits
- NR-13/NR-20 compliance
---
## Results & Impact
### System in Production
**Current status:** In use for 4+ months
**Adoption metrics:**
- 500+ registered employees
- 12 active tracks
- 150+ created microlearnings
- 8,000+ completed training sessions
- 100+ exported reports (compliance)
**Completion rate:** 87% (industry average: 45%)
---
### Client Impact
**Before:**
- In-person training (high cost, difficult scheduling)
- Paper evidence (losses, difficult audit)
- Difficulty updating content
**After:**
- Asynchronous training (employee completes when possible)
- Digital evidence (facilitated compliance)
- HR updates content without calling IT
- 70% reduction in training cost
**Client feedback:**
> "If we had implemented only what we initially requested, the system would be useless. The pre-analysis saved the project."
---
### Solution Design Value
**ROI of pre-sale analysis:**
**Scenario A (without analysis):**
1. Develop interface only (2 months)
2. Client tests and realizes CRUD is missing (1 month later)
3. Rework to add modules (2+ months)
4. **Total: 5+ months + client frustration**
**Scenario B (with analysis - what we did):**
1. Identify requirements beforehand (1 week)
2. Approve complete scope (1 week)
3. Develop correct solution (4 months)
4. **Total: 4 months + satisfied client**
**Savings:** 1+ month of rework + opportunity cost
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React` `Material-UI` `Azure App Service` `Azure Blob Storage` `ClosedXML` `Chart.js` `User Stories` `Solution Design` `Tech Lead`
---
## Key Decisions & Trade-offs
### Why not use ready-made LMS? (Moodle, Canvas)
**Alternatives considered:**
1. Moodle (open-source, free)
2. Totara/Canvas (corporate LMS)
3. **Custom development**
**Justification:**
- Generic LMS: Unnecessary complexity (forums, wikis, etc)
- Client wants **only microlearning** (simplicity)
- LMS license cost > custom dev cost
- Client AD/SSO integration (easier custom)
- UX optimized for factory floor (mobile-first, touch)
---
### Why 3 answer options (vs 4-5)?
**Choice:** Green (Great), Yellow (So-so), Red (Poor)
**Justification:**
- Factory floor employees prefer simplicity
- Universal colors (traffic light)
- Avoids choice paradox (fewer options = more engagement)
- Clearer gamification
---
### Why Excel Export (vs Online Dashboard)?
**Both were implemented**, but Excel is critical for:
**Regulatory compliance:**
- Auditors ask for "digitally signed file"
- NR-13/NR-20 require physical evidence
- Labor lawsuits accept Excel
**Flexibility:**
- HR can do custom analyses in Excel
- Combine with other data sources
- Presentations for board
---
## Lessons Learned
### 1. Solution Design Prevents Rework
**Lesson:** 1 week of critical analysis saves months of rework.
**Application:**
- Always question incomplete specifications
- Think about "the day after" (who manages this in production?)
- Involve client in requirements discussions
---
### 2. UX ≠ Functional Requirements
**Lesson:** Beautiful wireframes don't replace requirements analysis.
**UX focuses on:** How user **uses** the system
**Solution Design focuses on:** How system **works** end-to-end
Both are necessary and complementary.
---
### 3. Asking "How?" is More Important than "What?"
**Client says:** "I want tracks and microlearnings"
**Solution Designer asks:** "How does the first track enter the system?"
This simple question revealed 4 missing modules.
---
### 4. Well-Written User Stories Accelerate Development
**Investment:** 2 weeks writing 32 detailed user stories
**Return:**
- Devs knew exactly what to build
- Zero ambiguity
- Very few bugs (clear requirements)
- Client validated stories before coding
**Lesson:** Time spent planning reduces development time.
---
### 5. Compliance is Hidden Requirement
**In regulated industries** (health, energy, chemical), there will always be:
- Audit needs
- Evidence exports
- Logs of who did what
**Lesson:** Ask about compliance **before**, not after.
---
## Challenges Overcome
| Challenge | Solution | Result |
|---------|---------|-----------|
| Incomplete specification | Pre-sale critical analysis | Correct scope from start |
| Client without technical knowledge | User stories in business language | Client validated requirements |
| Employees with low digital literacy | Simplified UX (3 buttons, colors) | 87% completion rate |
| NR-13/NR-20 compliance | Excel export with details | Approved in 2 audits |
| Managing 500+ users | CSV import + admin hierarchy | Onboarding in 1 week |
---
## Next Steps (Future Roadmap)
**Planned features:**
1. **Push Notifications**
- Remind employee of pending training
- Notify of new mandatory track
2. **Native Mobile App**
- Offline-first (downloaded videos)
- Employees without computer
3. **Digital Certificates**
- Digitally signed PDF
- QR code for validation
4. **Data Intelligence**
- Which microlearnings have most errors?
- Identify knowledge gaps by area
---
**Result:** Functional system in production, satisfied client, zero rework - all because 1 week was invested in **thinking before coding**.
[Need critical requirements analysis? Get in touch](#contact)

View File

@ -1,577 +0,0 @@
---
title: "Pharma Lab Digital MVP - From Zero to Production"
slug: "pharma-digital-transformation"
summary: "Squad leadership in greenfield project for pharmaceutical lab, building digital platform MVP with complex integrations (Salesforce, Twilio, official APIs) starting from absolute zero - no Git, no servers, no infrastructure."
client: "Pharmaceutical Laboratory"
industry: "Pharmaceutical & Healthcare"
timeline: "4 months (2-month planned delay)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- MVP
- Digital Transformation
- .NET
- React
- Next.js
- Salesforce
- Twilio
- SQL Server
- Tech Lead
- Greenfield
featured: true
order: 3
date: 2023-03-01
seo_title: "Pharma Digital MVP - Digital Transformation from Scratch"
seo_description: "Case study of building digital MVP for pharmaceutical lab from scratch: no Git, no infrastructure, with complex integrations and successful delivery."
seo_keywords: "MVP, digital transformation, pharma, .NET, React, Next.js, Salesforce, greenfield project, tech lead"
---
## Overview
Pharmaceutical laboratory at the **beginning of digital transformation** hires consulting firm to build discount platform for prescribing physicians, starting from WordPress prototype.
**Unique challenge:** Start greenfield project in company **without basic development infrastructure** - no Git, no provisioned servers, no defined processes.
**Context:** Project executed in multi-squad environment. **Successful production delivery** despite initial infrastructure challenges, with controlled 2-month delay.
---
## Challenge
### Digital Transformation... Starting from Absolute Zero
**Company initial state (2023):**
**No Git/versioning**
- Code only on local machines
- Non-existent history
- Impossible collaboration
**No provisioned servers**
- Non-existent development environment
- Staging not configured
- Production not prepared
**No development processes**
- No CI/CD
- No code review
- No structured task management
**No experienced internal technical team**
- Team unfamiliar with modern stacks
- First contact with React, REST APIs
- Inexperience with complex integrations
**Technical starting point:**
- Functional prototype in **WordPress**
- Content and texts already approved
- UX/UI defined
- Business rules documented (partially)
---
### Required Complex Integrations
The MVP needed to integrate with multiple external systems:
1. **Salesforce** - Discount order registration
2. **Twilio** - SMS for login validation (2FA)
3. **Official physician API** - CRM validation + professional data
4. **Interplayers** - Discount record sending by CPF
5. **WordPress** - Content reading (headless CMS)
6. **SQL Server** - Data persistence
**Additional complexity:**
- Different credentials/environments per integration
- Varying SLAs (Twilio critical, WordPress tolerant)
- Provider-specific error handling
- LGPD compliance (sensitive physician data)
---
## Solution Architecture
### Strategy: Start Small, Build Solid
**Initial decision:** Explain to the team the process we would follow, establishing foundations before coding.
### Phase 1: Basic Infrastructure Setup (Weeks 1-2)
Even without provisioned servers, I started essential setup:
**Git & Versioning:**
```bash
# Structured repository from day 1
git init
git flow init # Defined branch strategy
# Monorepo structure
/
├── frontend/ # Next.js + React
├── backend/ # .NET APIs
├── cms-adapter/ # WordPress integration
└── docs/ # Architecture and ADRs
```
**Process explained to team:**
1. Everything in Git (atomic commits, descriptive messages)
2. Feature branches (never commit directly to main)
3. Mandatory code review (2 approvals)
4. CI/CD prepared (for when servers are ready)
**Local environments first:**
- Docker Compose for local development
- External API mocks (until credentials arrive)
- Local SQL Server with data seeds
---
### Phase 2: Modern & Decoupled Architecture
```
┌─────────────────────────────────────────────────────┐
│ FRONTEND (Next.js + React) │
│ - SSR for SEO │
│ - Client-side for interactivity │
│ - API consumption │
└────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ BACKEND APIs (.NET 7) │
│ - REST APIs │
│ - Authentication/Authorization │
│ - Business logic │
│ - Orchestration layer │
└────┬────┬────┬────┬────┬─────────────────────────┬──┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ ┌──────┐ ┌────────┐ ┌────────┐ ┌──────────┐
│Salesf. │ │Twilio│ │CRM │ │Interpl.│ │WordPr. │ │SQL Server│
│ │ │ │ │API │ │ │ │(CMS) │ │ │
└────────┘ └──────┘ └──────┘ └────────┘ └────────┘ └──────────┘
```
**Chosen stack:**
**Frontend:**
- `Next.js 13` - SSR, routing, optimizations
- `React 18` - Components, hooks, context
- `TypeScript` - Type safety
- `Tailwind CSS` - Modern styling
**Backend:**
- `.NET 7` - REST APIs
- `Entity Framework Core` - ORM
- `SQL Server 2019` - Database
- `Polly` - Resilience patterns (retry, circuit breaker)
**Why Next.js instead of keeping WordPress?**
- Performance (SSR vs monolithic PHP)
- Optimized SEO (critical for pharma)
- Modern experience (SPA when needed)
- Scalability
- WordPress kept only as CMS (headless)
---
### Phase 3: Integrations (Project Core)
#### 1. Salesforce - Campaigns and Order Registration
**Implemented solution:**
Salesforce was configured to manage two main functionalities:
**a) Discount campaigns:**
- Marketing configures campaigns in Salesforce (medication X, discount Y%, period)
- Backend queries active campaigns via API
- Frontend (Next.js) displays available discount percentage based on: medication + active campaign
**b) Order registration:**
- User informs: physician CRM, state, patient CPF, medication
- System validates data (real CRM via official API, valid CPF)
- Percentage calculated automatically (Salesforce campaigns + CMS rules)
- Order registered in Salesforce with all data (LGPD compliance)
**Overcome technical challenges:**
- OAuth2 authentication with automatic refresh token
- Rate limiting (Salesforce has API/day limits)
- Retry logic for transient failures (Polly)
- CPF masking for logs (LGPD)
---
#### 2. Twilio - SMS Authentication (2FA)
**Implemented solution:**
Two-factor authentication system to ensure security:
**Login flow:**
1. User enters phone number
2. Backend generates 6-digit code (valid for 5 minutes)
3. SMS sent via Twilio ("Your code: 123456")
4. User enters code in frontend
5. Backend validates code + expiration timestamp
6. JWT token issued after successful validation
**Compliance and audit:**
- Phone numbers masked in logs (LGPD)
- Complete audit (who, when, which SMS)
- Delivery rate: 99.8%
---
#### 3. Official Physician API (Regional Medical Council)
**Implemented solution:**
Automatic physician validation via official medical council API:
**Performed validations:**
- CRM exists and is active in council
- Physician name matches informed CRM
- Specialty is allowed (lab business rule)
- State corresponds to registration state
**Optimizations:**
- 24-hour cache to reduce official API calls
- Fallback if API is down (notifies admin)
- Automatic retry for transient failures
**Why this matters:**
Ensures only real and active physicians can prescribe discounts, avoiding fraud.
---
#### 4. WordPress as Headless CMS
**Implemented solution:**
Marketing continues managing content in WordPress (familiar), but frontend is modern Next.js.
**Architecture:**
- WordPress: Manages texts, images, campaign rules
- WordPress REST API: Exposes content via JSON
- Next.js: Consumes API and renders with SSR (SEO optimized)
**Benefits:**
- Marketing doesn't need to learn new tool
- Modern frontend (performance, UX)
- Optimized SEO (Server-Side Rendering)
- Clear separation of responsibilities (content vs code)
---
### Phase 4: Resilience & Error Handling
With multiple external integrations, failures are inevitable. The solution was to implement **resilience patterns** using Polly library (.NET):
**Implemented patterns:**
**1. Retry**
- If Salesforce/Twilio/CRM API fail, system automatically retries 2-3x
- Wait grows exponentially (1s, 2s, 4s) to avoid overload
- Only transient errors (timeout, 503) are retried
**2. Circuit Breaker**
- If service fails 5x in a row, "opens circuit" for 30s
- During 30s, doesn't try anymore (avoids wasting resources)
- After 30s, tries again (may have recovered)
**3. Timeout**
- Each integration has maximum response time
- Avoids indefinitely stuck requests
**4. Fallback (Plan B)**
- Salesforce down: Order goes to queue, processes later
- Twilio down: Alert administrator via email
- CRM API down: Uses cache (24h old data)
- WordPress down: Displays pre-loaded static content
**Strategies per integration:**
| Integration | Retry | Circuit Breaker | Timeout | Plan B |
|----------|-------|-----------------|---------|----------|
| Salesforce | 3x (exponential) | 5 failures/30s | 10s | Retry queue |
| Twilio | 2x (linear) | 3 failures/60s | 5s | Admin alert |
| CRM API | 3x (exponential) | No | 15s | Cache |
| WordPress | No | No | 3s | Static content |
**Production result:**
- Salesforce had maintenance (1h) → System continued working (queue processed later)
- Twilio had instability → Automatic retry resolved 95% of cases
- Zero downtime perceived by users
---
## Overcoming Infrastructure Challenges
### Problem: Servers Not Provisioned
**Temporary solution:**
1. 100% local development (Docker Compose)
2. External service mocks (when credentials delayed)
3. CI/CD prepared but not active (awaiting infra)
**When servers arrived (week 6):**
- Deploy in 2 hours (already prepared)
- Zero surprises (everything tested locally)
- Smooth rollout
---
### Problem: Delayed Integration Credentials
**Impact:** Twilio and Salesforce took 3 weeks to be provisioned.
**Solution:** Create "mock" (simulated) versions of each integration:
- Twilio mock: Logs instead of sending real SMS
- Salesforce mock: Saves order to local JSON file
- CRM API mock: Returns fictional physician data
**How it works:**
- Development environment: Uses mocks (no credentials needed)
- Production environment: Uses real integrations (when credentials arrive)
- Automatic switch based on configuration
**Result:** Team stayed 100% productive for 3 weeks, testing complete flows without depending on credentials.
---
### Problem: Team Inexperienced with Modern Stack
**Context:** Team had no experience with React, TypeScript, modern .NET Core, REST APIs.
**Enablement approach:**
**1. Pair Programming (1h/day per developer)**
- Tech lead works alongside dev
- Screen sharing + real-time explanation
- Dev writes code, tech lead guides
**2. Educational Code Review**
- Not just "approve" or "reject"
- Comments explain the **why** of each suggestion
- Example: "Always handle request errors! If API crashes, user needs to know what happened."
**3. Living Documentation**
- ADRs (Architecture Decision Records): Why did we choose X and not Y?
- READMEs: How to run, test, deploy
- Onboarding guide: From zero to first feature
**4. Weekly Live Coding (2h)**
- Tech lead solves real problem live
- Team observes thinking process
- Q&A at end
**Result:**
- After 4 weeks, team was autonomous
- Code quality consistently increased
- Devs started doing code review among themselves (peer review)
---
## Results & Impact
### Successful Delivery Despite Challenges
**Context:** Program with multiple squads working in parallel.
**Achieved result:**
- **MVP delivered to production successfully**
- Controlled 2-month delay (significantly less than other program initiatives)
- All integrations working as planned
- Zero critical bugs in production (first week)
**Why was delivery successful?**
1. **Anticipated setup** - Git, processes, local Docker from day 1
2. **Strategic mocks** - Team wasn't blocked waiting for infra
3. **Solid architecture** - Resilience from the start
4. **Continuous upskilling** - Team learned by doing
5. **Proactive communication** - Risks reported early
---
### MVP Metrics
**Performance:**
- Loading time: <2s (95th percentile)
- Lighthouse score: 95+ (mobile)
- SSL A+ rating
**Integrations:**
- Salesforce: 100% orders synchronized
- Twilio: 99.8% delivery rate
- CRM API: 10k validations/day (average)
- SQL Server: 50k records/month
**Adoption:**
- 2,000+ registered physicians (first 3 months)
- 15,000+ processed discount orders
- 4.8/5 satisfaction (internal survey)
---
### Client Impact
**Digital transformation initiated:**
- Git implemented and adopted
- Established development processes
- Internal team enabled in modern stacks
- Cloud infrastructure configured (Azure)
- Evolution roadmap defined
**Foundation for future projects:**
- Architecture served as reference for other initiatives
- Documented code patterns (coding standards)
- Reused CI/CD pipelines
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React 18` `Next.js 13` `TypeScript` `Tailwind CSS` `Salesforce API` `Twilio` `WordPress REST API` `Docker` `Polly` `OAuth2` `JWT` `LGPD Compliance`
---
## Key Decisions & Trade-offs
### Why Next.js instead of pure React?
**Requirements:**
- Critical SEO (pharma needs to rank)
- Performance (physicians use mobile)
- Dynamic content (WordPress)
**Next.js offers:**
- SSR out-of-the-box
- API routes (BFF pattern)
- Automatic optimizations (image, fonts)
- Simplified deploy (Vercel, Azure)
---
### Why keep WordPress?
**Alternatives considered:**
1. Migrate content to database + custom CMS (time)
2. Strapi/Contentful (costs + learning curve)
3. **WordPress headless** (best trade-off)
**Advantages:**
- Marketing team already knows how to use
- Approved content was already there
- WordPress REST API is solid
- Zero cost (already running)
---
### Why .NET 7 instead of Node.js?
**Context:** Client had preference for Microsoft stack.
**Additional benefits:**
- Superior performance (vs Node in APIs)
- Native type safety (C#)
- Entity Framework (mature ORM)
- Easy Azure integration (future deploy)
- Client team had familiarity
---
## Lessons Learned
### 1. Infrastructure Delayed? Prepare Alternatives
Don't wait for servers/credentials to start:
- Local Docker is your friend
- Mocks allow progress
- CI/CD can be prepared before having where to deploy
**Lesson:** Control what you can control.
---
### 2. Processes > Tools
Even without corporate Git, I established:
- Branching strategy
- Code review
- Commit conventions
- Documentation standards
**Result:** When tools arrived, team already knew how to use them.
---
### 3. Upskilling is Investment, Not Cost
Pair programming and code reviews took time, but:
- Team became autonomous faster
- Code quality increased
- Natural knowledge sharing
- Simplified onboarding of new devs
---
### 4. Resilience from the Start
Implementing Polly (retry, circuit breaker) at the start saved in production:
- Twilio had instability (resolved automatically)
- Salesforce had maintenance (queue worked)
- CRM API had slowness (cache mitigated)
**Lesson:** Don't leave resilience for "later". Failures will happen.
---
### 5. Clear Risk Communication
I reported weekly:
- Blockers (infrastructure, credentials)
- Risks (deadlines, dependencies)
- Alternative solutions (mocks, workarounds)
**Result:** Stakeholders knew exact status and had no surprises.
---
## Challenges & How They Were Overcome
| Challenge | Impact | Solution | Result |
|---------|---------|---------|-----------|
| No Git | Total blocker | Local setup + GitLab Cloud | Team productive day 1 |
| No servers | No dev environment | Local Docker Compose | Complete local dev/test |
| Delayed credentials | Integration blocked | Mock services | Progress without blocker |
| Inexperienced team | Low quality code | Pair prog + Code review | Ramp-up in 4 weeks |
| Multiple integrations | High complexity | Polly + patterns | Zero prod downtime |
---
## Next Steps (Post-MVP)
**Roadmap suggested to client:**
1. **Phase 2: Feature expansion**
- Dashboard for physicians (order history)
- Push notifications (Firebase)
- E-commerce integration (direct purchase)
2. **Phase 3: Optimizations**
- Distributed cache (Redis)
- CDN for static assets
- Advanced analytics (Amplitude)
3. **Phase 4: Scale**
- Kubernetes (AKS)
- Microservices (break monolith)
- Event-driven architecture (Azure Service Bus)
---
**Result:** MVP delivered to production despite starting literally from zero, establishing solid foundations for client's digital transformation.
[Need to build an MVP in a challenging scenario? Get in touch](#contact)

View File

@ -1,211 +0,0 @@
---
title: "SAP Healthcare Integration System"
slug: "sap-integration-healthcare"
summary: "Bidirectional integration processing 100k+ transactions/day with 99.9% uptime"
client: "Confidential - Healthcare Multinational"
industry: "Healthcare"
timeline: "6 months"
role: "Integration Architect"
image: ""
tags:
- SAP
- C#
- .NET
- Integrations
- Enterprise
- Healthcare
featured: true
order: 1
date: 2023-06-15
seo_title: "Case Study: SAP Healthcare Integration - 100k Transactions/Day"
seo_description: "How we architected SAP integration system processing 100k+ daily transactions with 99.9% uptime for healthcare company."
seo_keywords: "SAP integration, C#, .NET, SAP Connector, enterprise integration, healthcare"
---
## Overview
**Client:** Healthcare Multinational (confidential)
**Size:** 100,000+ employees
**Project:** Benefits integration
**Timeline:** 6 months
**My Role:** Integration Architect
---
## Challenge
Client had internal benefits management system that needed to sync with SAP ECC to process payroll.
### Main pain points:
- Manual process prone to errors
- 3-5 day delay between systems
- 100k employees waiting for processing
- Load spikes (month-end)
### Constraints:
- Limited budget (no SAP BTP)
- Small internal SAP team (2 developers)
- Tight deadline (6-month go-live)
- Legacy .NET Framework 4.5 system
---
## Solution
Bidirectional integration architecture:
```
[Internal System] ←→ [Queue] ←→ [SAP Connector] ←→ [SAP ECC]
↓ ↓
[MongoDB Logs] [ABAP Z_BENEFITS]
```
### Components:
- .NET Service with SAP Connector (NCo 3.0)
- Custom ABAP transaction (Z_BENEFITS)
- Queue system (RabbitMQ) for retry logic
- MongoDB for audit and troubleshooting
- Scheduler (Hangfire) for batch processing
### Flow:
1. System generates changes (new hires, modifications)
2. Service processes batch (500 records/batch)
3. SAP Connector calls Z_BENEFITS via RFC
4. SAP returns status (success/error)
5. Automatic retry if failure (max 3x)
6. MongoDB logs for troubleshooting
---
## Results
### Metrics:
- **100k+** transactions/day processed
- **99.9%** uptime
- Reduced **5 days → 4 hours** (delay)
- **80%** reduction in processing time
- **Zero** manual errors (vs 2-3% before)
### Benefits:
- Employees receive benefits on-time
- HR team saves 40h/month (manual work)
- Complete audit (compliance)
- Scalable (30% year-over-year growth without refactor)
---
## Tech Stack
`C#` `.NET Framework 4.5` `SAP NCo 3.0` `RabbitMQ` `MongoDB` `Hangfire` `Docker` `SAP ECC` `ABAP` `RFC`
---
## Decisions & Motivation
### Decision 1: SAP Connector vs SAP BTP
**Options evaluated:**
- SAP BTP (events, modern APIs, cloud)
- SAP Connector (direct RFC, on-premise)
**We chose:** SAP Connector
**Motivation:**
- Client had on-premise SAP ECC (not S/4)
- Budget didn't allow BTP license
- SAP team comfortable with ABAP/RFC
- Needs met with RFC (didn't need real-time event-driven)
**Accepted trade-off:**
- Less "modern" than BTP, but 100% functional
- $0 additional cost vs $30k+/year BTP
- 2 months faster delivery (no BTP learning curve)
---
### Decision 2: Queue System vs Direct Calls
**Options evaluated:**
- Direct synchronous calls (simpler)
- Queue with retry (more complex)
**We chose:** Queue + Retry
**Motivation:**
- SAP occasionally unavailable (maintenance)
- Load spikes (month-end = 200k requests)
- Ensure zero data loss
- Resilience > simplicity (critical environment)
**Implementation:**
- RabbitMQ with dead-letter queue
- Exponential retry (1min, 5min, 15min)
- Alerts if 3 consecutive failures
**Result:**
- Zero data loss in 2 years production
- HR team doesn't need to "keep watch"
---
### Decision 3: Custom ABAP vs Standard
**Options evaluated:**
- Standard SAP BAPIs (zero ABAP code)
- Custom transaction (Z_BENEFITS)
**We chose:** Custom transaction
**Motivation:**
- Standard BAPIs didn't have business-specific validations
- Client wanted logic centralized in SAP (single source of truth)
- Allowed complex validations (eligibility, dependents, limits)
**Trade-off:**
- Requires ABAP maintenance (internal SAP team)
- But: Client preferred vs duplicate logic (risk of desync)
---
### Alternatives NOT Chosen
**Webhook/Callback (Event-Driven):**
- Client had no infrastructure to expose APIs
- Internal system behind firewall
- Batch polling works well for the case
**Kubernetes Microservices:**
- Overkill for single integration
- Team had no K8s expertise
- Simple Docker sufficient
**Real-time Sync (<1min):**
- Business doesn't need (daily batch ok)
- Infrastructure cost would increase 3x
- 4h delay acceptable for payroll
---
## Learnings
### What worked very well:
- Involve SAP team from day 1 (buy-in)
- MongoDB for logs (10x faster troubleshooting)
- Retry logic saved countless times
### What I would do differently:
- Add health check endpoint earlier
- Monitoring dashboard from start (added later)
### Lessons for next projects:
- Client "limited budget" ≠ "limited solution" - creativity solves
- Document ALL architectural decisions (team turnover)
- Simplicity beats complexity when both work (KISS)
---
## Need Something Similar?
Complex SAP integrations, legacy systems, or high-availability architecture?
[Let's talk about your challenge →](/#contact)

View File

@ -1,329 +0,0 @@
---
title: "Migración ASP 3.0 a .NET Core - Sistema de Rastreo de Cargas"
slug: "asp-to-dotnet-migration"
summary: "Tech Lead en la migración gradual de sistema crítico ASP 3.0 a .NET Core, con sincronización de datos entre versiones y reducción de costos de $20k/año en APIs de mapeo."
client: "Empresa de Logística y Rastreo"
industry: "Logística & Seguridad"
timeline: "12 meses (migración completa)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- ASP Classic
- .NET Core
- SQL Server
- Migration
- Tech Lead
- OSRM
- APIs
- Arquitectura
featured: true
order: 2
date: 2015-06-01
seo_title: "Migración ASP 3.0 a .NET Core - Case Carneiro Tech"
seo_description: "Caso de migración gradual de aplicación ASP 3.0 a .NET Core con sincronización de datos y reducción de $20k/año en costos de APIs."
seo_keywords: "ASP migration, .NET Core, legacy modernization, SQL Server, OSRM, tech lead, routing API"
---
## Descripción General
Sistema crítico de monitoreo de cargas de alto valor (TVs LED de $600 cada una, cargamentos de hasta 1000 unidades) utilizando rastreo GPS vía satélite. La aplicación cubría todo el ciclo: desde registro y evaluación de conductores (verificación de antecedentes policiales) hasta monitoreo en tiempo real y entrega final.
**Desafío principal:** Migrar aplicación legacy ASP 3.0 a .NET Core sin downtime, manteniendo operación crítica 24/7.
---
## Desafío
### Sistema Legacy Crítico
La empresa operaba un sistema mission-critical en **ASP 3.0** (Classic ASP) que no podía detenerse:
**Tecnología legacy:**
- ASP 3.0 (tecnología de 1998)
- SQL Server 2005
- Cluster failover on-premises (perfectamente capaz de soportar la carga)
- Integración con rastreadores GPS vía satélite
- Google Maps API (costo: **$20,000/año** solo para cálculo de rutas)
**Restricciones:**
- Sistema operando 24/7 con cargas de alto valor
- Imposibilidad de downtime durante migración
- Múltiples módulos interdependientes
- Equipo necesitaba continuar desarrollando features durante la migración
---
## Arquitectura de Solución
### Fase 1: Preparación de Infraestructura (Meses 1-3)
#### Upgrade de Base de Datos
```
SQL Server 2005 → SQL Server 2014
- Backup completo y validación
- Migración de stored procedures
- Optimización de índices
- Pruebas de performance
```
#### Estrategia de Sincronización Dual-Write
Implementé un **sistema de sincronización bidireccional** que permitía:
1. **Módulos nuevos (.NET Core)** escribían en la base de datos nueva
2. **Trigger automático** sincronizaba datos hacia la base de datos legacy
3. **Módulos antiguos (ASP 3.0)** continuaban funcionando normalmente
4. **Zero downtime** durante toda la migración
```csharp
// Ejemplo de sincronización implementada
public class DualWriteService
{
public async Task SaveDriver(Driver driver)
{
// Escribe en base de datos nueva (.NET Core)
await _newDbContext.Drivers.AddAsync(driver);
await _newDbContext.SaveChangesAsync();
// Trigger SQL sincroniza automáticamente hacia base de datos legacy
// Módulos ASP 3.0 continúan funcionando
}
}
```
**¿Por qué este enfoque?**
- Permitió migración **módulo por módulo**
- Equipo podía continuar desarrollando
- Rollback sencillo si fuera necesario
- Reducción de riesgo operacional
---
### Fase 2: Migración Gradual de Módulos (Meses 4-12)
Migré los módulos en orden de complejidad creciente:
**Orden de migración:**
1. ✅ Registros básicos (conductores, vehículos)
2. ✅ Evaluación de riesgo (integración con base policial)
3. ✅ Gestión de cargas y rutas
4. ✅ Monitoreo GPS en tiempo real
5. ✅ Alertas y notificaciones
6. ✅ Reportes y analytics
**Stack de la aplicación migrada:**
- `.NET Core 1.0` (2015-2016 era el inicio de .NET Core)
- `Entity Framework Core`
- `SignalR` para monitoreo real-time
- `SQL Server 2014`
- APIs RESTful
---
### Fase 3: Reducción de Costos con OSRM (Ahorro de $20k/año)
#### Problema: Costo Prohibitivo de Google Maps
La empresa gastaba **$20,000/año** solo en Google Maps Directions API para cálculo de rutas de camiones.
#### Solución: OSRM (Open Source Routing Machine)
Implementé una solución basada en **OSRM** (motor de ruteo open-source):
**Arquitectura de la solución:**
```
┌─────────────────┐
│ Frontend │
│ (Leaflet.js) │
└────────┬────────┘
┌─────────────────┐ ┌──────────────┐
│ API Wrapper │─────▶│ OSRM Server │
│ (.NET Core) │ │ (self-hosted)│
└────────┬────────┘ └──────────────┘
┌─────────────────┐
│ Google Maps │
│ (display only) │
└─────────────────┘
```
**Implementación:**
1. **Servidor OSRM configurado** en servidor propio
2. **API wrapper amigable** en .NET Core que:
- Recibía origen/destino
- Consultaba OSRM (gratuito)
- Devolvía todos los puntos de la ruta
- Formateaba para el frontend
3. **Frontend** dibujaba la ruta en Google Maps (solo visualización, sin API de rutas)
```csharp
[HttpGet("route")]
public async Task<IActionResult> GetRoute(double originLat, double originLng,
double destLat, double destLng)
{
// Consulta OSRM (gratuito)
var osrmResponse = await _osrmClient.GetRouteAsync(
originLat, originLng, destLat, destLng);
// Retorna puntos formateados para el frontend
return Ok(new {
points = osrmResponse.Routes[0].Geometry.Coordinates,
distance = osrmResponse.Routes[0].Distance,
duration = osrmResponse.Routes[0].Duration
});
}
```
**Frontend con Leaflet:**
```javascript
// Dibuja ruta en el mapa (Google Maps solo para tiles)
L.polyline(routePoints, {color: 'red'}).addTo(map);
```
#### Intento con OpenStreetMap
Intenté sustituir también Google Maps (tiles) por **OpenStreetMap**, que funcionó técnicamente, pero:
**A los usuarios no les gustó** la apariencia
❌ Preferían la interfaz familiar de Google Maps
**Decisión:** Mantener Google Maps solo para visualización (sin costo de API de rutas)
**Resultado:** Ahorro de **~$20,000/año** manteniendo calidad de las rutas.
---
## Resultados e Impacto
### Migración Completa en 12 Meses
**100% de los módulos** migrados de ASP 3.0 a .NET Core
**Zero downtime** durante toda la migración
**Equipo productivo** durante todo el proceso
✅ Sistema más rápido y escalable
### Reducción de Costos
💰 **$20,000/año ahorrados** con sustitución de Google Maps Directions API
📉 **Infraestructura optimizada** con SQL Server 2014
### Mejoras Técnicas
🚀 **Performance:** Aplicación .NET Core 3x más rápida que ASP 3.0
🔒 **Seguridad:** Stack moderno con parches de seguridad activos
🛠️ **Mantenibilidad:** Código C# moderno vs VBScript legacy
📊 **Monitoreo:** SignalR para tracking real-time más eficiente
---
## Fase No Ejecutada: Microservicios & Cloud
### Planificación Inicial
Participé en el **diseño y concepción** de la segunda fase (nunca ejecutada):
**Arquitectura planificada:**
- Migración a **Azure** (cloud estaba apenas comenzando en 2015)
- División en **microservicios**:
- Servicio de autenticación
- Servicio de GPS/tracking
- Servicio de rutas
- Servicio de notificaciones
- **Event-driven architecture** con message queues
**Por qué no fue ejecutada:**
Salí de la empresa inmediatamente después de concluir la migración a .NET Core. La segunda fase quedó planificada pero no fue implementada por mí.
---
## Tech Stack
`ASP 3.0` `VBScript` `.NET Core 1.0` `C#` `Entity Framework Core` `SQL Server 2005` `SQL Server 2014` `OSRM` `Leaflet.js` `Google Maps` `SignalR` `REST APIs` `GPS/Satellite` `Migration Strategy` `Dual-Write Pattern`
---
## Decisiones Clave & Trade-offs
### ¿Por qué sincronización dual-write?
**Alternativas consideradas:**
1. ❌ Big Bang migration (demasiado arriesgado)
2. ❌ Mantener todo en ASP 3.0 (insostenible)
3. ✅ **Migración gradual con sync** (elegido)
**Justificación:**
- Sistema crítico no podía detenerse
- Permitió rollback módulo por módulo
- Equipo continuó productivo
### ¿Por qué OSRM en vez de otros?
**Alternativas:**
- Google Maps: $20k/año ❌
- Mapbox: Licencia paga ❌
- GraphHopper: Configuración compleja ❌
- **OSRM: Open-source, rápido, configurable**
### ¿Por qué no OpenStreetMap para tiles?
**Decisión basada en UX:**
- Técnicamente funcionó perfectamente
- Usuarios preferían interfaz familiar de Google
- **Compromiso:** Google Maps para visualización (gratis) + OSRM para rutas (gratis)
---
## Lecciones Aprendidas
### 1. Migración Gradual > Big Bang
Migrar módulo por módulo con sincronización permitió:
- Aprendizaje continuo
- Ajustes de ruta durante el proceso
- Confianza del equipo y stakeholders
### 2. Open Source Puede Ahorrar Mucho
OSRM ahorró **$20k/año** sin pérdida de calidad. Pero requiere:
- Expertise para configurar
- Infraestructura propia
- Mantenimiento continuo
### 3. UX > Tecnología A Veces
OpenStreetMap era técnicamente superior (gratuito), pero usuarios prefirieron Google Maps. **Lección:** Escuchar a los usuarios finales.
### 4. Planifique Cloud, pero Valide el ROI
En 2015, cloud estaba comenzando. La infraestructura on-premises (cluster SQL Server) era perfectamente capaz. **No fuerce cloud si no hay beneficio claro.**
---
## Contexto: Por qué 2015 fue un Momento Especial
**Estado de la tecnología en 2015:**
- ☁️ **Cloud en pañales:** AWS existía, Azure creciendo, pero adopción corporativa aún baja
- 🆕 **.NET Core 1.0 lanzado** en junio/2016 (usamos RC durante proyecto)
- 📱 **Microservicios:** Concepto nuevo, Docker en adopción inicial
- 🗺️ **Google Maps dominante:** APIs pagas, pocas alternativas open-source maduras
**Desafíos de la época:**
- Herramientas de migración ASP→.NET inexistentes
- Documentación .NET Core escasa (versión 1.0!)
- Patrones de arquitectura aún consolidándose
Este proyecto fue **pionero** al adoptar .NET Core al inicio, cuando la mayoría migraba a .NET Framework 4.x.
---
**Resultado:** Migración exitosa de sistema crítico 24/7, ahorro de $20k/año, y base sólida para evolución futura.
[¿Quiere discutir una migración similar? Póngase en contacto](#contact)

View File

@ -1,382 +0,0 @@
---
title: "CNPJ Fast - Proceso de Migración a CNPJ Alfanumérico"
slug: "cnpj-fast-process"
summary: "Creación de metodología estructurada para migración de aplicaciones al nuevo formato de CNPJ alfanumérico brasileño, vendida a aseguradora y empresa de cobranza."
client: "Empresa de Consultoría (Interno)"
industry: "Consultoría & Transformación Digital"
timeline: "3 meses (creación del proceso)"
role: "Solution Architect & Process Designer"
image: ""
tags:
- Process Design
- CNPJ
- Migration Strategy
- Regulatory Compliance
- Consulting
- Sales Enablement
featured: true
order: 3
date: 2024-09-01
seo_title: "CNPJ Fast - Metodología de Migración CNPJ Alfanumérico"
seo_description: "Caso de creación de proceso estructurado para migración a CNPJ alfanumérico brasileño, vendido a aseguradora y empresa de cobranza."
seo_keywords: "CNPJ alfanumérico, migration process, regulatory compliance, consulting, methodology"
---
## Descripción General
Con la introducción del **CNPJ alfanumérico** por la Receita Federal brasileña, las empresas enfrentaban el desafío de adaptar sus aplicaciones legacy que almacenaban CNPJ como campos numéricos (`bigint`, `numeric`, `int`).
Creé **CNPJ Fast**, una metodología estructurada para evaluar, planificar y ejecutar migraciones de CNPJ en aplicaciones y bases de datos corporativas.
**Resultado:** Proceso vendido a **2 clientes** (aseguradora y empresa de cobranza) antes incluso de la implementación.
---
## Desafío
### Cambio Regulatorio Complejo
**Contexto regulatorio:**
- Receita Federal brasileña introdujo **CNPJ alfanumérico**
- CNPJ deja de ser solo números (14 dígitos)
- Pasa a aceptar **letras y números** (formato alfanumérico)
**Impacto en las empresas:**
```sql
-- ANTES: CNPJ numérico
CNPJ BIGINT -- 12345678000190
-- DESPUÉS: CNPJ alfanumérico
CNPJ VARCHAR(18) -- 12.ABC.678/0001-90
```
**Problemas identificados:**
1. 🗄️ **Base de datos:** Columnas `BIGINT`, `NUMERIC`, `INT` no soportan caracteres
2. 🔑 **Claves primarias:** CNPJ usado como PK en varias tablas
3. 🔗 **Foreign keys:** Relaciones entre tablas
4. 📊 **Volumen:** Millones de registros para migrar
5. 💻 **Aplicaciones:** Validaciones, máscaras, reglas de negocio
6. 🧪 **Pruebas:** Garantizar integridad después de migración
7. ⏱️ **Downtime:** Ventanas de mantenimiento limitadas
**Sin un proceso estructurado**, empresas arriesgaban:
- Pérdida de datos
- Inconsistencias en la base de datos
- Aplicaciones rotas
- Downtime prolongado
---
## Solución: CNPJ Fast Process
### Metodología en 5 Fases
Diseñé un proceso estructurado que podría ser replicado en diferentes clientes:
```
┌─────────────────────────────────────────────┐
│ FASE 1: DISCOVERY & ASSESSMENT │
│ - Inventario de aplicaciones │
│ - Análisis de schemas de base de datos │
│ - Identificación de tablas impactadas │
│ - Estimación de volumen de datos │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 2: IMPACT ANALYSIS │
│ - Mapeo de dependencias │
│ - Análisis de claves primarias/foráneas │
│ - Identificación de reglas de negocio │
│ - Evaluación de riesgo │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 3: MIGRATION PLANNING │
│ - Estrategia de migración (phased commits) │
│ - Scripts SQL automatizados │
│ - Plan de rollback │
│ - Ventanas de mantenimiento │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 4: EXECUTION │
│ - Migración de datos en lotes │
│ - Actualización de aplicaciones │
│ - Pruebas de integración │
│ - Validación de integridad │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 5: VALIDATION & GO-LIVE │
│ - Pruebas de regresión │
│ - Validación de performance │
│ - Go-live coordinado │
│ - Monitoreo post-migración │
└─────────────────────────────────────────────┘
```
---
### Fase 1: Discovery & Assessment
**Objetivo:** Entender el alcance completo de la migración
**Entregables:**
1. **Inventario de Aplicaciones**
- Lista de aplicaciones que usan CNPJ
- Tecnologías (ASP 3.0, VB6, .NET, microservicios)
- Criticidad de cada aplicación
2. **Análisis de Schema**
```sql
-- Script de descubrimiento automático
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
c.COLUMN_NAME,
c.DATA_TYPE,
c.CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.TABLES t
JOIN INFORMATION_SCHEMA.COLUMNS c
ON t.TABLE_NAME = c.TABLE_NAME
WHERE c.COLUMN_NAME LIKE '%CNPJ%'
AND c.DATA_TYPE IN ('bigint', 'numeric', 'int')
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME;
```
3. **Estimación de Volumen**
- Total de registros por tabla
- Tamaño en GB
- Tiempo estimado de migración
**Ejemplo de output:**
| Tabla | Columna | Tipo Actual | Registros | Criticidad |
|--------|--------|------------|-----------|-------------|
| Clientes | CNPJ_Cliente | BIGINT | 8.000.000 | Alta |
| Proveedores | CNPJ_Proveedor | NUMERIC(14) | 2.500.000 | Media |
| Transacciones | CNPJ_Pagador | BIGINT | 90.000.000 | Crítica |
---
### Fase 2: Impact Analysis
**Objetivo:** Mapear todas las dependencias y riesgos
**Análisis de claves:**
```sql
-- Identifica PKs y FKs que involucran CNPJ
SELECT
fk.name AS FK_Name,
tp.name AS Parent_Table,
cp.name AS Parent_Column,
tr.name AS Referenced_Table,
cr.name AS Referenced_Column
FROM sys.foreign_keys fk
INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id
INNER JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id
AND fkc.parent_object_id = cp.object_id
INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id
INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id
AND fkc.referenced_object_id = cr.object_id
WHERE cp.name LIKE '%CNPJ%' OR cr.name LIKE '%CNPJ%';
```
**Evaluación de Riesgo:**
- 🔴 **Alto:** Tablas con CNPJ como PK y >10M registros
- 🟡 **Medio:** Tablas con FK hacia CNPJ
- 🟢 **Bajo:** Tablas sin constraints
---
### Fase 3: Migration Planning
**Estrategia de migración gradual:**
Para evitar bloqueo de base de datos, diseñé estrategia de **phased commits**:
```sql
-- Estrategia para tablas grandes (>1M registros)
-- 1. Agregar nueva columna VARCHAR
ALTER TABLE Clientes
ADD CNPJ_Cliente_New VARCHAR(18) NULL;
-- 2. Migración en lotes (commits faseados)
DECLARE @BatchSize INT = 100000;
DECLARE @RowsAffected INT = 1;
WHILE @RowsAffected > 0
BEGIN
UPDATE TOP (@BatchSize) Clientes
SET CNPJ_Cliente_New = FORMAT(CNPJ_Cliente, '00000000000000')
WHERE CNPJ_Cliente_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
WAITFOR DELAY '00:00:01'; -- Pausa entre lotes
END;
-- 3. Remover constraints (PKs, FKs)
ALTER TABLE Clientes DROP CONSTRAINT PK_Clientes;
-- 4. Renombrar columnas
EXEC sp_rename 'Clientes.CNPJ_Cliente', 'CNPJ_Cliente_Old', 'COLUMN';
EXEC sp_rename 'Clientes.CNPJ_Cliente_New', 'CNPJ_Cliente', 'COLUMN';
-- 5. Recrear constraints
ALTER TABLE Clientes
ADD CONSTRAINT PK_Clientes PRIMARY KEY (CNPJ_Cliente);
-- 6. Remover columna antigua (tras validación)
ALTER TABLE Clientes DROP COLUMN CNPJ_Cliente_Old;
```
**¿Por qué este enfoque?**
- ✅ Evita lock de tabla entera
- ✅ Permite pausar/reanudar migración
- ✅ Minimiza impacto en producción
- ✅ Facilita rollback si es necesario
---
### Fase 4 & 5: Execution y Validation
**Checklist de ejecución:**
- [ ] Backup completo de la base de datos
- [ ] Ejecutar scripts de migración en lotes
- [ ] Actualizar aplicaciones (validaciones, máscaras)
- [ ] Pruebas de integración
- [ ] Validación de integridad referencial
- [ ] Pruebas de performance
- [ ] Go-live coordinado
- [ ] Monitoreo 24h post-migración
---
## Sales Enablement: Presentación UX
**Colaboración con Gestor de UX:**
El gestor de UX de la empresa creó una **presentación visual impactante** del proceso CNPJ Fast:
**Contenido de la presentación:**
- 📊 Infografías del proceso de 5 fases
- 📈 Ejemplos de estimaciones de tiempo/costo
- 🎯 Casos de uso (aseguradoras, bancos, fintechs)
- ✅ Checklist ejecutivo
- 📋 Templates de documentación
**Resultado:** Presentación utilizada por el equipo comercial para prospección.
---
## Resultados e Impacto
### Ventas Realizadas
**Cliente 1: Aseguradora**
- Stack: ASP 3.0, VB6 components, .NET, microservicios
- Alcance: Migración completa de aplicaciones legacy
- Estado: **Proyecto vendido** (ejecución por otro equipo)
- Valor: [Confidencial]
**Cliente 2: Empresa de Cobranza**
- Alcance: Migración de base de datos (~100M registros)
- Estado: **Proyecto vendido y en ejecución** (por mí)
- Particularidad: Proceso fue **reestructurado** para atender necesidades específicas
- Ver caso completo: [Migración CNPJ - 100M Registros](/cases/cnpj-migration-database)
---
### Impacto en el Negocio
💰 **2 proyectos vendidos** antes incluso de la primera ejecución
📈 **Proceso replicable** para nuevos clientes
🎯 **Posicionamiento** como especialista en migraciones regulatorias
📚 **Base de conocimiento** para futuros proyectos similares
---
### Impacto Técnico
🔧 **Metodología probada** en escenarios reales
📖 **Documentación reutilizable** (scripts, checklists, templates)
🚀 **Aceleración** de proyectos similares (de semanas a días)
---
## Tech Stack
`SQL Server` `Migration Strategy` `Process Design` `Regulatory Compliance` `ASP 3.0` `VB6` `.NET` `Microservices` `Batch Processing` `Database Optimization`
---
## Decisiones Clave & Trade-offs
### ¿Por qué proceso estructurado?
**Alternativas:**
1. ❌ Enfoque ad-hoc por proyecto
2. ❌ Consultoría manual sin metodología
3. ✅ **Proceso replicable y escalable**
**Justificación:**
- Reduce tiempo de Discovery
- Estandariza entregas
- Facilita ventas (presentación lista)
- Permite ejecución por diferentes equipos
### ¿Por qué separar en 5 fases?
**Beneficios:**
- Cliente puede aprobar fase a fase
- Permite ajustes durante el proceso
- Facilita gestión de riesgos
- Entregas incrementales
---
## Lecciones Aprendidas
### 1. UX/Presentación Importa para Ventas
La presentación visual hecha por el gestor de UX fue **crucial** para cerrar los 2 contratos. Proceso técnico bueno + presentación mala = sin ventas.
### 2. Proceso Vende, No Solo Ejecución
Crear una **metodología documentada** tiene más valor comercial que solo ofrecer "horas de consultoría".
### 3. Cada Cliente es Único
El cliente solicitó **reestructuración del proceso**. Un buen proceso debe ser:
- Estructurado lo suficiente para ser replicable
- Flexible lo suficiente para personalizar
### 4. Colaboración Multidisciplinaria
Trabajar con gestor de UX (presentaciones) + equipo comercial (ventas) + técnico (ejecución) = éxito.
---
## Próximos Pasos
**Oportunidades futuras:**
1. 🌎 **Expansión:** Ofrecer CNPJ Fast para más sectores (bancos, fintechs, retail)
2. 📦 **Producto:** Transformar en herramienta automatizada (SaaS)
3. 📚 **Capacitación:** Capacitar equipos internos de clientes
4. 🔄 **Evolución:** Adaptar proceso para otras migraciones regulatorias (PIX, Open Banking)
---
**Resultado:** Metodología estructurada que se convirtió en producto vendible, generando ingresos antes incluso de la primera ejecución técnica.
[¿Quiere implementar CNPJ Fast en su empresa? Póngase en contacto](#contact)

View File

@ -1,469 +0,0 @@
---
title: "Migración CNPJ Alfanumérico - 100 Millones de Registros"
slug: "cnpj-migration-database"
summary: "Ejecución de migración masiva de CNPJ numérico a alfanumérico en base de datos con ~100M registros, usando estrategia de commits faseados para evitar bloqueo."
client: "Empresa de Cobranza"
industry: "Cobranza & Servicios Financieros"
timeline: "En ejecución"
role: "Database Architect & Tech Lead"
image: ""
tags:
- SQL Server
- Database Migration
- CNPJ
- Performance Optimization
- Batch Processing
- Big Data
featured: true
order: 4
date: 2024-11-01
seo_title: "Migración CNPJ Alfanumérico - 100M Registros | Carneiro Tech"
seo_description: "Caso de migración masiva de CNPJ en base de datos con 100 millones de registros usando commits faseados y optimizaciones de performance."
seo_keywords: "database migration, SQL Server, CNPJ, batch processing, performance optimization, phased commits"
---
## Descripción General
Una empresa de cobranza que trabaja con bases de datos de información transitoria (sin software propietario) necesita adaptar sus sistemas al nuevo formato de **CNPJ alfanumérico** brasileño.
**Desafío principal:** Migrar ~**100 millones de registros** en tablas con columnas `BIGINT` y `NUMERIC` a `VARCHAR`, sin bloquear la base de datos en producción.
**Estado:** Proyecto en ejecución (preparación de scripts de migración).
---
## Desafío
### Volumen Masivo de Datos
**Contexto de la empresa:**
- Empresa de cobranza (no desarrolla software propio)
- Trabaja con **datos transitorios** (alta rotación)
- Base de datos SQL Server con volumen crítico
**Análisis inicial reveló:**
| Tabla | Columna | Tipo Actual | Registros | Tamaño |
|--------|--------|------------|-----------|---------|
| Deudores | CNPJ_Deudor | BIGINT | 8.000.000 | 60 GB |
| Transacciones | CNPJ_Pagador | NUMERIC(14) | 90.000.000 | 1.2 TB |
| Empresas | CNPJ_Empresa | BIGINT | 2.500.000 | 18 GB |
| **TOTAL** | - | - | **~100.000.000** | **~1.3 TB** |
**Problemas identificados:**
1. 🔴 **Tablas con 8M+ líneas** usando `BIGINT` para CNPJ
2. 🔴 **90 millones de registros** en tabla de transacciones
3. 🔑 **CNPJ como clave primaria** en algunas tablas
4. 🔗 **Foreign keys** relacionando múltiples tablas
5. ⚠️ **Imposibilidad de downtime prolongado** (operación 24/7)
6. 💾 **Restricciones de espacio** en disco (necesita estrategia eficiente)
---
## Decisión Estratégica: Phased Commits
### ¿Por qué NO hacer ALTER COLUMN directo?
**Enfoque ingenuo (NO funciona):**
```sql
-- ❌ NUNCA HAGA ESTO EN TABLAS GRANDES
ALTER TABLE Transacciones
ALTER COLUMN CNPJ_Pagador VARCHAR(18);
```
**Problemas:**
- 🔒 Bloquea la tabla entera durante la conversión
- ⏱️ Puede tomar horas/días en tablas grandes
- 💥 Bloquea todas las operaciones (INSERT, UPDATE, SELECT)
- 🚨 Riesgo de timeout o falla en medio de la operación
- 🔙 Rollback complejo si algo sale mal
---
### Estrategia Elegida: Column Swap con Commits Faseados
**Basado en experiencia anterior**, decidí usar enfoque gradual:
```
┌─────────────────────────────────────────────┐
│ 1. Crear nueva columna VARCHAR al FINAL │
│ (operación rápida, no bloquea tabla) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 2. UPDATE en lotes (commits faseados) │
│ - 100k registros a la vez │
│ - Pausa entre lotes (evita contención) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 3. Remover PKs y FKs │
│ (tras 100% migrado) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 4. Renombrar columnas (swap) │
│ - CNPJ → CNPJ_Old │
│ - CNPJ_New → CNPJ │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 5. Recrear PKs/FKs con nueva columna │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 6. Validación y eliminación columna vieja │
└─────────────────────────────────────────────┘
```
**¿Por qué este enfoque?**
**Sin lock de tabla completa** (operación incremental)
**Puede pausar/reanudar** en cualquier momento
**Monitoreo de progreso** en tiempo real
**Rollback simple** (basta eliminar nueva columna)
**Minimiza impacto en producción** (commits pequeños)
**Decisión tomada basada en:**
- 📚 Experiencia anterior con migraciones de gran volumen
- 🔍 Conocimiento de locks de SQL Server
- 🎯 Necesidad de zero downtime
**Nota:** Esta decisión fue tomada **sin consultar IA** - basada puramente en experiencia práctica de proyectos anteriores.
---
## Detalles de Implementación
### Fase 1: Crear Nueva Columna
```sql
-- Operación rápida (metadata change solamente)
ALTER TABLE Transacciones
ADD CNPJ_Pagador_New VARCHAR(18) NULL;
-- Agrega índice temporal para acelerar lookups
CREATE NONCLUSTERED INDEX IX_Temp_CNPJ_New
ON Transacciones(CNPJ_Pagador_New)
WHERE CNPJ_Pagador_New IS NULL;
```
**Tiempo estimado:** ~1 segundo (independiente del tamaño de la tabla)
---
### Fase 2: Migración en Lotes (Core Strategy)
```sql
-- Script de migración con commits faseados
DECLARE @BatchSize INT = 100000; -- 100k registros por lote
DECLARE @RowsAffected INT = 1;
DECLARE @TotalProcessed INT = 0;
DECLARE @StartTime DATETIME = GETDATE();
WHILE @RowsAffected > 0
BEGIN
BEGIN TRANSACTION;
-- Actualiza lote de 100k registros aún no migrados
UPDATE TOP (@BatchSize) Transacciones
SET CNPJ_Pagador_New = RIGHT('00000000000000' + CAST(CNPJ_Pagador AS VARCHAR), 14)
WHERE CNPJ_Pagador_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
SET @TotalProcessed = @TotalProcessed + @RowsAffected;
COMMIT TRANSACTION;
-- Log de progreso
PRINT 'Processed: ' + CAST(@TotalProcessed AS VARCHAR) + ' rows. Batch: ' + CAST(@RowsAffected AS VARCHAR);
PRINT 'Elapsed time: ' + CAST(DATEDIFF(SECOND, @StartTime, GETDATE()) AS VARCHAR) + ' seconds';
-- Pausa entre lotes (reduce contención)
WAITFOR DELAY '00:00:01'; -- 1 segundo entre lotes
END;
PRINT 'Migration completed! Total rows: ' + CAST(@TotalProcessed AS VARCHAR);
```
**Parámetros configurables:**
- `@BatchSize`: 100k (balanceado entre performance y lock time)
- Muy pequeño = muchas transacciones, overhead
- Muy grande = lock prolongado, impacto en prod
- `WAITFOR DELAY`: 1 segundo (da tiempo a otras queries para ejecutar)
**Estimaciones de tiempo:**
| Registros | Batch Size | Tiempo Estimado |
|-----------|------------|----------------|
| 8.000.000 | 100.000 | ~2-3 horas |
| 90.000.000 | 100.000 | ~20-24 horas |
**Ventajas:**
- ✅ No bloquea aplicación
- ✅ Otras queries pueden ejecutar entre los lotes
- ✅ Puede pausar (Ctrl+C) y reanudar después (WHERE NULL toma desde donde paró)
- ✅ Log de progreso en tiempo real
---
### Fase 3: Remoción de Constraints
```sql
-- Identifica todas las PKs y FKs que involucran la columna
SELECT name
FROM sys.key_constraints
WHERE type = 'PK'
AND parent_object_id = OBJECT_ID('Transacciones')
AND COL_NAME(parent_object_id, parent_column_id) = 'CNPJ_Pagador';
-- Remueve PKs
ALTER TABLE Transacciones
DROP CONSTRAINT PK_Transacciones_CNPJ;
-- Remueve FKs (tablas que referencian)
ALTER TABLE Pagos
DROP CONSTRAINT FK_Pagos_Transacciones;
```
**Tiempo estimado:** ~10 minutos (depende de cuántas constraints existen)
---
### Fase 4: Column Swap (Renombramiento)
```sql
-- Renombra columna antigua a _Old
EXEC sp_rename 'Transacciones.CNPJ_Pagador', 'CNPJ_Pagador_Old', 'COLUMN';
-- Renombra nueva columna al nombre original
EXEC sp_rename 'Transacciones.CNPJ_Pagador_New', 'CNPJ_Pagador', 'COLUMN';
-- Altera a NOT NULL (tras validación de 100% completado)
ALTER TABLE Transacciones
ALTER COLUMN CNPJ_Pagador VARCHAR(18) NOT NULL;
```
**Tiempo estimado:** ~1 segundo (metadata change)
---
### Fase 5: Recreación de Constraints
```sql
-- Recrea PK con nueva columna VARCHAR
ALTER TABLE Transacciones
ADD CONSTRAINT PK_Transacciones_CNPJ
PRIMARY KEY CLUSTERED (CNPJ_Pagador);
-- Recrea FKs
ALTER TABLE Pagos
ADD CONSTRAINT FK_Pagos_Transacciones
FOREIGN KEY (CNPJ_Pagador) REFERENCES Transacciones(CNPJ_Pagador);
```
**Tiempo estimado:** ~30-60 minutos (depende del volumen)
---
### Fase 6: Validación y Limpieza
```sql
-- Valida que 100% fue migrado
SELECT COUNT(*)
FROM Transacciones
WHERE CNPJ_Pagador IS NULL OR CNPJ_Pagador = '';
-- Valida integridad referencial
DBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS;
-- Si todo OK, remueve columna antigua
ALTER TABLE Transacciones
DROP COLUMN CNPJ_Pagador_Old;
-- Remueve índice temporal
DROP INDEX IX_Temp_CNPJ_New ON Transacciones;
```
---
## Personalización del Proceso CNPJ Fast
### Diferencias vs. Proceso Original
El proceso **CNPJ Fast** original fue **reestructurado** para este cliente:
**Cambios principales:**
| Aspecto | CNPJ Fast Original | Cliente (Personalizado) |
|---------|-------------------|---------------------|
| **Foco** | Aplicaciones + DB | Solo DB (sin software propio) |
| **Discovery** | Inventario de apps | Solo análisis de schema |
| **Ejecución** | Múltiples aplicaciones | Scripts SQL masivos |
| **Batch Size** | 50k-100k | 100k (optimizado para volumen) |
| **Monitoreo** | Manual + herramientas | Logs SQL en tiempo real |
| **Rollback** | Proceso complejo | Simple (DROP COLUMN) |
**Motivo de la reestructuración:**
- Cliente no tiene aplicaciones propias (solo consume datos)
- Foco 100% en optimización de base de datos
- Volumen mucho mayor que casos típicos (100M vs ~10M)
---
## Tech Stack
`SQL Server` `T-SQL` `Batch Processing` `Performance Tuning` `Database Optimization` `Migration Scripts` `Phased Commits` `Index Optimization` `Constraint Management`
---
## Decisiones Clave & Trade-offs
### ¿Por qué 100k por batch?
**Pruebas de performance:**
| Batch Size | Tiempo/Batch | Lock Duration | Contención |
|------------|-------------|---------------|-----------|
| 10.000 | 2s | Bajo | ✅ Mínimo |
| 50.000 | 8s | Medio | ✅ Aceptable |
| **100.000** | 15s | **Medio** | **✅ Balanceado** |
| 500.000 | 90s | Alto | ❌ Impacto en prod |
| 1.000.000 | 180s | Muy alto | ❌ Inaceptable |
**Elección:** 100k ofrece mejor balance entre performance e impacto.
---
### ¿Por qué crear columna al FINAL?
**Internals de SQL Server:**
- Agregar columna al final = metadata change (rápido)
- Agregar en medio = reescritura de páginas (lento)
- Para tablas grandes, posición importa
---
### ¿Por qué WAITFOR DELAY de 1 segundo?
**Sin delay:**
- ❌ Batch processing consume 100% del I/O
- ❌ Queries de aplicación se vuelven lentas
- ❌ Lock escalation puede ocurrir
**Con delay de 1s:**
- ✅ Otras queries tienen ventana para ejecutar
- ✅ I/O distribuido
- ✅ Experiencia del usuario preservada
**Trade-off:** Migración toma +1s por batch (~25% más lenta), pero sistema permanece responsivo.
---
## Estado Actual & Próximos Pasos
### Estado Actual (Diciembre 2024)
📝 **Fase de Preparación:**
- ✅ Discovery completo (100M registros identificados)
- ✅ Scripts de migración desarrollados
- ✅ Pruebas en ambiente de homologación
- 🔄 Validación de performance
- ⏳ Esperando ventana de mantenimiento para producción
### Próximos Pasos
1. **Backup completo** de producción
2. **Ejecución en producción** (ambiente 24/7)
3. **Monitoreo en tiempo real** durante migración
4. **Validación post-migración** (integridad, performance)
5. **Documentación de lessons learned**
---
## Lecciones Aprendidas (Hasta Ahora)
### 1. Experiencia Anterior Vale Oro
Decisión de usar phased commits vino de **experiencia práctica** en proyectos anteriores, no de documentación o IA.
**Situaciones similares anteriores:**
- Migración de datos en e-commerce (50M registros)
- Conversión de encoding (UTF-8 en 100M+ rows)
- Particionamiento de tablas históricas
---
### 2. "Measure Twice, Cut Once"
Antes de ejecutar en producción:
- ✅ Pruebas exhaustivas en homologación
- ✅ Scripts validados y revisados
- ✅ Rollback probado
- ✅ Estimaciones de tiempo confirmadas
**Tiempo de preparación:** 3 semanas
**Tiempo de ejecución:** Estimado en 48 horas
**Ratio:** 10:1 (preparación vs ejecución)
---
### 3. Personalización > One-Size-Fits-All
El proceso CNPJ Fast original necesitó ser **reestructurado** para este cliente.
**Lección:** Los procesos deben ser:
- Estructurados lo suficiente para repetir
- Flexibles lo suficiente para adaptar
---
### 4. Monitoreo es Crucial
Scripts con **log detallado** de progreso permiten:
- Estimar tiempo restante
- Identificar cuellos de botella
- Pausar/reanudar con confianza
- Reportar estado a stakeholders
```sql
-- Log example
Processed: 10.000.000 rows. Batch: 100.000
Elapsed time: 3600 seconds (10% complete, ~9h remaining)
```
---
## Optimizaciones de Performance
### Optimizaciones Implementadas
1. **Índice temporal WHERE NULL**
- Acelera lookup de registros no migrados
- Removido tras conclusión
2. **Batch size optimizado**
- Balanceado entre performance y lock time
3. **Transaction log management**
```sql
-- Verificar crecimiento del log
DBCC SQLPERF(LOGSPACE);
-- Ajustar recovery model (si permitido)
ALTER DATABASE MyDatabase SET RECOVERY SIMPLE;
```
4. **Ejecución en horario de menor carga**
- Ventana de mantenimiento nocturna
- Fin de semana (si es posible)
---
**Resultado esperado:** Migración de 100 millones de registros en ~48 horas, sin downtime significativo y con posibilidad de rollback rápido.
[¿Necesita migrar volúmenes masivos de datos? Póngase en contacto](#contact)

View File

@ -1,588 +0,0 @@
---
title: "Plataforma de Capacitación Industrial - De Wireframes a Sistema Completo"
slug: "industrial-learning-platform"
summary: "Solution Design para plataforma de microlearning en empresa de gases industriales. Identificación de requisitos críticos no mapeados (admin, registros, exportación) antes de la presentación al cliente, evitando retrabajo y garantizando usabilidad real."
client: "Empresa de Gases Industriales"
industry: "Industrial & Manufactura"
timeline: "4 meses"
role: "Solution Architect & Tech Lead"
image: ""
tags:
- Solution Design
- EdTech
- Learning Platform
- Requirements Analysis
- Tech Lead
- User Stories
- .NET
- Product Design
featured: true
order: 5
date: 2024-06-01
seo_title: "Plataforma de Capacitación Industrial - Solution Design"
seo_description: "Caso de Solution Design para plataforma de microlearning, identificando requisitos críticos antes de la presentación al cliente y liderando desarrollo hasta producción."
seo_keywords: "solution design, learning platform, microlearning, requirements analysis, tech lead, industrial training"
---
## Descripción General
Empresa de gases industriales solicita plataforma para capacitar empleados usando metodología de **microlearning** (contenidos cortos y objetivos).
**Requisito inicial:** "Queremos solo la estructura - ruta de aprendizaje, microlearning, pregunta de test y puntuación."
**Problema:** Especificación incompleta que resultaría en sistema **imposible de usar** (sin forma de registrar contenido, sin administradores, sin exportar resultados).
**Solución:** Análisis crítico de requisitos **antes de la presentación al cliente**, identificando gaps funcionales y proponiendo solución completa.
---
## Desafío
### Wireframes Bonitos, Funcionalidad Incompleta
**Situación inicial:**
UX creó wireframes hermosos mostrando:
- ✅ Rutas de aprendizaje
- ✅ Microlearnings (video/texto + imagen)
- ✅ Preguntas de test (opción múltiple)
- ✅ Puntuación por empleado
**Problema identificado:**
Nadie (cliente, UX, comercial) pensó en:
**¿Cómo entra contenido en el sistema?**
- ¿Quién registra rutas?
- ¿Quién crea microlearnings?
- ¿Quién escribe preguntas?
- ¿Interfaz manual o import?
**¿Quién gestiona el sistema?**
- ¿Existe concepto de admin?
- ¿RRHH puede crear admins?
- ¿Gestor de área puede ver solo su equipo?
**¿Cómo salen datos del sistema?**
- RRHH necesita reportes
- Compliance necesita evidencias
- ¿Cómo exportar datos?
- ¿Formato: Excel? PDF? API?
**Riesgo real:**
Si desarrolláramos exactamente lo que fue pedido:
- Sistema funcionaría técnicamente ✅
- **Pero sería completamente inutilizable**
- Cliente tendría que pagar refacción para incluir CRUD básico
- Retrabajo + costo adicional + frustración
---
## Proceso de Solution Design
### Etapa 1: Análisis Crítico (Antes de la Presentación)
**Acción tomada:** Convoqué reunión con UX **antes** de presentar al cliente.
**Puntos levantados:**
**"¿Cómo entra el primer contenido al sistema?"**
- UX: "Ah... no pensamos en eso. ¿Ustedes van a poblar la base de datos?"
- Yo: "¿Y cuando cliente quiera agregar nueva ruta? ¿Vamos a alterar BD en producción?"
**"¿Quién es el dueño del sistema?"**
- UX: "RRHH, imagino."
- Yo: "¿Solo una persona? ¿Y si sale de la empresa? ¿Cómo delega?"
**"¿RRHH pidió reportes?"**
- UX: "No fue mencionado en el briefing."
- Yo: "RRHH siempre necesita reportes. Es para compliance (NR, ISO)."
---
### Etapa 2: Requisitos Funcionales Identificados
Propuse 4 módulos adicionales **esenciales**:
#### 1. Sistema de Administración
**Funcionalidades:**
- Usuario estándar: Solo realiza capacitaciones
- Usuario admin: Gestiona contenido + ve reportes
- Admin puede promover otros usuarios a admin
- Control de acceso (admin general vs admin de área)
**Por qué es crítico:**
Sin esto, sistema es estático (contenido nunca se actualiza).
---
#### 2. CRUD de Contenido
**a) Registro de Rutas:**
- Nombre de la ruta
- Descripción
- Orden de los microlearnings
- Ruta activa/inactiva (permite despublicar)
**b) Registro de Microlearnings:**
- Título
- Tipo: Texto simple (2 párrafos) O Video
- Upload de imagen (si texto)
- URL de video (si video)
- Orden dentro de la ruta
**c) Registro de Preguntas:**
- Pregunta (texto)
- 3 opciones de respuesta:
- "Excelente" (verde)
- "Regular" (amarillo)
- "Malo" (rojo)
- Puntuación por respuesta (ej: 10, 5, 0 puntos)
- Feedback personalizado por respuesta
**Por qué es crítico:**
Cliente necesita actualizar contenido sin llamar a dev/DBA.
---
#### 3. Exportación de Datos
**Funcionalidades:**
- Exportar a Excel (.xlsx)
- Filtros:
- Por período (fecha inicio/fin)
- Por ruta
- Por empleado
- Por área/departamento
- Columnas exportadas:
- Nombre del empleado
- Matrícula
- Ruta completada
- Puntuación total
- Fecha de conclusión
- Respuestas individuales (para auditoría)
**Por qué es crítico:**
RRHH necesita evidenciar capacitación para:
- Normas Reglamentarias (NR-13, NR-20 - gases inflamables)
- Auditorías ISO
- Procesos laborales
---
#### 4. Gestión de Usuarios
**Funcionalidades:**
- Importar empleados (upload CSV/Excel)
- Registro manual
- Activar/desactivar usuarios
- Asignar rutas obligatorias por área
- Notificaciones de pendientes
**Por qué es crítico:**
Empresa tiene 500+ empleados, registro manual es inviable.
---
### Etapa 3: Presentación al Cliente
**Abordaje:**
1. Mostré wireframes del UX (interfaz bonita)
2. Pregunté: "¿Cómo van a registrar la primera ruta?"
3. Cliente: "Ah... buena pregunta. No habíamos pensado en eso."
4. Presenté los 4 módulos adicionales
5. Cliente: "Tiene total sentido! Sin esto no podemos usar."
**Resultado:**
- Propuesta aprobada **con módulos adicionales**
- Alcance ajustado (timeline + presupuesto)
- Zero retrabajo futuro
- Cliente reconoció valor agregado
---
## Implementación
### Mi Rol en el Proyecto
**1. Solution Architect**
- Identificación de requisitos no funcionales
- Diseño de arquitectura (módulos, integraciones)
- Definición de tecnologías
**2. Tech Lead**
- Liderazgo técnico del equipo (3 devs)
- Code review
- Definición de estándares de código
- Gestión de riesgos técnicos
**3. Product Owner Técnico**
- Creación de **user stories** completas
- Priorización de backlog
- Refinamiento continuo con cliente
---
### Stack Técnico Elegido
**Backend:**
- `.NET 7` - APIs REST
- `Entity Framework Core` - ORM
- `SQL Server` - Base de datos
- `ClosedXML` - Generación de Excel
**Frontend:**
- `React` - Interfaz web
- `Material-UI` - Componentes
- `React Player` - Player de video
- `Chart.js` - Gráficos de progreso
**Infraestructura:**
- `Azure App Service` - Hospedaje
- `Azure Blob Storage` - Almacenamiento de videos/imágenes
- `Azure SQL Database` - Base de datos gestionada
---
### User Stories Creadas
Escribí **32 user stories** cubriendo todos los flujos. Ejemplos:
**US-01: Registrar Ruta (Admin)**
```
Como administrador del sistema
Quiero registrar una nueva ruta de capacitación
Para que empleados puedan realizar los cursos
Criterios de aceptación:
- Admin accede menú "Rutas" → "Nueva Ruta"
- Completa: Nombre, Descripción, Estado (Activa/Inactiva)
- Puede agregar microlearnings existentes a la ruta
- Define orden de los microlearnings (drag & drop)
- Sistema valida campos obligatorios
- Guarda y muestra mensaje de éxito
```
**US-15: Realizar Microlearning (Empleado)**
```
Como empleado
Quiero realizar un microlearning de mi ruta
Para aprender sobre el tema y ganar puntos
Criterios de aceptación:
- Empleado accede ruta asignada
- Ve lista de microlearnings (no completados primero)
- Hace clic en microlearning → abre pantalla con:
- Texto (2 párrafos) + Imagen O
- Player de video embebido
- Botón "Continuar" aparece después de:
- 30s (si texto)
- Final del video (si video)
- Marca microlearning como visto
- Pregunta de test aparece automáticamente
```
**US-22: Exportar Resultados (Admin)**
```
Como administrador
Quiero exportar resultados de capacitación a Excel
Para generar reportes de compliance y auditorías
Criterios de aceptación:
- Admin accede "Reportes" → "Exportar"
- Selecciona filtros (período, ruta, área)
- Hace clic "Generar Excel"
- Sistema procesa y descarga archivo .xlsx
- Excel contiene columnas: Nombre, Matrícula, Ruta, Puntos, Fecha, Respuestas
- Formato legible (headers en negrita, columnas autoajustadas)
```
---
## Características Clave Implementadas
### 1. Sistema de Puntuación Gamificado
**Mecánica:**
- Cada pregunta vale puntos (configurable)
- Respuesta "Excelente": 10 puntos
- Respuesta "Regular": 5 puntos
- Respuesta "Malo": 0 puntos
**Dashboard del empleado:**
- Puntuación total
- Ranking (opcional, configurable)
- Badges por rutas completadas
- Progreso visual (barra de %)
**Por qué funciona:**
Empleados de planta se enganchan más con elementos de gamificación.
---
### 2. Microlearning Adaptativo
**Tipos de contenido:**
**Texto + Imagen:**
- 2 párrafos (máx 300 palabras)
- 1 imagen ilustrativa
- Ideal para: Procedimientos, normas, conceptos
**Video:**
- Videos cortos (2-5 min)
- Player embebido (YouTube/Vimeo o upload)
- Ideal para: Demostraciones, operaciones de equipo
**¿Por qué microlearning?**
- Empleados realizan en el intervalo (10-15min)
- Contenido corto = mayor retención
- Facilita actualización (vs cursos largos)
---
### 3. Sistema de Administración Delegada
**Jerarquía:**
```
Admin General (RRHH)
↓ puede promover
Admin de Área (Gerentes)
↓ puede visualizar solo
Empleados de su área
```
**Permisos:**
- Admin general: Crea rutas, promueve admins, ve todos los datos
- Admin de área: Ve solo reportes de su área
- Empleado: Solo realiza capacitaciones
**Auditoría:**
- Logs de quién creó/editó cada contenido
- Histórico de promociones a admin
- Compliance SOX/ISO
---
### 4. Exportación para Compliance
**Formato del Excel generado:**
| Matrícula | Nombre | Área | Ruta | Fecha Conclusión | Puntos | Estado |
|-----------|------|------|--------|----------------|--------|--------|
| 1001 | João Silva | Producción | Seguridad NR-20 | 15/11/2024 | 95/100 | ✅ Aprobado |
| 1002 | María Santos | Logística | Manejo Gases | 14/11/2024 | 78/100 | ✅ Aprobado |
**Pestaña adicional: Detalle de Respuestas**
- Permite auditoría: "¿Empleado X acertó pregunta Y?"
- Evidencia para procesos laborales
- Compliance NR-13/NR-20
---
## Resultados e Impacto
### Sistema en Producción
**Estado actual:** En uso hace 4+ meses
**Métricas de adopción:**
- 👥 500+ empleados registrados
- 📚 12 rutas activas
- 📖 150+ microlearnings creados
- ✅ 8.000+ capacitaciones completadas
- 📊 100+ reportes exportados (compliance)
**Tasa de conclusión:** 87% (media industria: 45%)
---
### Impacto en el Cliente
**Antes:**
- Capacitaciones presenciales (costo alto, agenda difícil)
- Evidencias en papel (pérdidas, difícil auditoría)
- Dificultad en actualizar contenido
**Después:**
- Capacitación asíncrona (empleado realiza cuando puede)
- Evidencias digitales (compliance facilitado)
- RRHH actualiza contenido sin llamar a TI
- Reducción del 70% en costo de capacitación
**Feedback del cliente:**
> "Si hubiéramos implementado solo lo que pedimos inicialmente, el sistema sería inútil. El análisis previo salvó el proyecto."
---
### Valor del Solution Design
**ROI del análisis preventa:**
**Escenario A (sin análisis):**
1. Desarrollar solo interfaz (2 meses)
2. Cliente prueba y percibe que falta CRUD (1 mes después)
3. Refacción para agregar módulos (2+ meses)
4. **Total: 5+ meses + frustración del cliente**
**Escenario B (con análisis - lo que hicimos):**
1. Identificar requisitos antes (1 semana)
2. Aprobar alcance completo (1 semana)
3. Desarrollar solución correcta (4 meses)
4. **Total: 4 meses + cliente satisfecho**
**Economía:** 1+ mes de retrabajo + costo de oportunidad
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React` `Material-UI` `Azure App Service` `Azure Blob Storage` `ClosedXML` `Chart.js` `User Stories` `Solution Design` `Tech Lead`
---
## Decisiones Clave & Trade-offs
### ¿Por qué no usar LMS listo? (Moodle, Canvas)
**Alternativas consideradas:**
1. ❌ Moodle (open-source, gratuito)
2. ❌ Totara/Canvas (LMS corporativo)
3. ✅ **Desarrollo custom**
**Justificación:**
- LMS genérico: Complejidad innecesaria (foros, wikis, etc)
- Cliente quiere **solo microlearning** (simplicidad)
- Costo de licencia LMS > costo de dev custom
- Integración con AD/SSO del cliente (más fácil custom)
- UX optimizada para planta (mobile-first, touch)
---
### ¿Por qué 3 opciones de respuesta (vs 4-5)?
**Elección:** Verde (Excelente), Amarillo (Regular), Rojo (Malo)
**Justificación:**
- Empleados de planta prefieren simplicidad
- Colores universales (semáforo)
- Evita paradoja de la elección (menos opciones = más engagement)
- Gamificación más clara
---
### ¿Por qué Export Excel (vs Dashboard online)?
**Ambos fueron implementados**, pero Excel es crítico para:
**Compliance regulatorio:**
- Auditores piden "archivo firmado digitalmente"
- NR-13/NR-20 exigen evidencia física
- Procesos laborales aceptan Excel
**Flexibilidad:**
- RRHH puede hacer análisis personalizados en Excel
- Combinar con otras fuentes de datos
- Presentaciones para dirección
---
## Lecciones Aprendidas
### 1. Solution Design Previene Retrabajo
**Lección:** 1 semana de análisis crítico economiza meses de refacción.
**Aplicación:**
- Siempre cuestionar especificaciones incompletas
- Pensar en el "día siguiente" (¿quién gestiona esto en producción?)
- Involucrar cliente en discusiones de requisitos
---
### 2. UX ≠ Requisitos Funcionales
**Lección:** Wireframes bonitos no sustituyen análisis de requisitos.
**UX se enfoca en:** Cómo usuario **usa** el sistema
**Solution Design se enfoca en:** Cómo sistema **funciona** end-to-end
Ambos son necesarios y complementarios.
---
### 3. Preguntar "¿Cómo?" es Más Importante que "¿Qué?"
**Cliente dice:** "Quiero rutas y microlearnings"
**Solution Designer pregunta:** "¿Cómo entra la primera ruta al sistema?"
Esta pregunta simple reveló 4 módulos faltantes.
---
### 4. User Stories Bien Escritas Aceleran Desarrollo
**Inversión:** 2 semanas escribiendo 32 user stories detalladas
**Retorno:**
- Devs sabían exactamente qué construir
- Zero ambigüedad
- Muy pocos bugs (requisitos claros)
- Cliente validó historias antes de codificar
**Lección:** Tiempo gastado en planificación reduce tiempo de desarrollo.
---
### 5. Compliance es Requisito Oculto
**En industrias reguladas** (salud, energía, químico), siempre habrá:
- Necesidad de auditoría
- Exportación de evidencias
- Logs de quién hizo qué
**Lección:** Preguntar sobre compliance **antes**, no después.
---
## Desafíos Superados
| Desafío | Solución | Resultado |
|---------|---------|-----------|
| Especificación incompleta | Análisis crítico preventa | Alcance correcto desde inicio |
| Cliente sin conocimiento técnico | User stories en lenguaje de negocio | Cliente validó requisitos |
| Empleados con baja familiaridad digital | UX simplificado (3 botones, colores) | 87% tasa de conclusión |
| Compliance NR-13/NR-20 | Export Excel con detalle | Aprobado en 2 auditorías |
| Gestión de 500+ usuarios | Import CSV + jerarquía de admins | Onboarding en 1 semana |
---
## Próximos Pasos (Roadmap Futuro)
**Funcionalidades planificadas:**
1. **Notificaciones Push**
- Recordar empleado de capacitación pendiente
- Avisar de nueva ruta obligatoria
2. **App Mobile Nativo**
- Offline-first (videos descargados)
- Empleados sin computadora
3. **Certificados Digitales**
- PDF firmado digitalmente
- QR code para validación
4. **Inteligencia de Datos**
- ¿Qué microlearnings tienen más error?
- Identificar gaps de conocimiento por área
---
**Resultado:** Sistema funcional en producción, cliente satisfecho, zero retrabajo - todo porque 1 semana fue invertida en **pensar antes de codificar**.
[¿Necesita análisis crítico de requisitos? Póngase en contacto](#contact)

View File

@ -1,577 +0,0 @@
---
title: "MVP Digital para Laboratorio Farmacéutico - De Cero a Producción"
slug: "pharma-digital-transformation"
summary: "Liderazgo de squad en proyecto greenfield para laboratorio farmacéutico, construyendo MVP de plataforma digital con integraciones complejas (Salesforce, Twilio, APIs oficiales) partiendo de cero absoluto - sin Git, sin servidores, sin infraestructura."
client: "Laboratorio Farmacéutico"
industry: "Farmacéutica & Salud"
timeline: "4 meses (2 meses de retraso planificado)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- MVP
- Digital Transformation
- .NET
- React
- Next.js
- Salesforce
- Twilio
- SQL Server
- Tech Lead
- Greenfield
featured: true
order: 3
date: 2023-03-01
seo_title: "MVP Digital Farmacéutico - Transformación Digital de Cero"
seo_description: "Caso de construcción de MVP digital para laboratorio farmacéutico partiendo de cero: sin Git, sin infraestructura, con integraciones complejas y entrega exitosa."
seo_keywords: "MVP, digital transformation, pharma, .NET, React, Next.js, Salesforce, greenfield project, tech lead"
---
## Descripción General
Laboratorio farmacéutico en el **inicio de transformación digital** contrata consultoría para construir plataforma de descuentos para médicos prescriptores, partiendo de prototipo en WordPress.
**Desafío único:** Comenzar proyecto greenfield en empresa **sin infraestructura básica** de desarrollo - sin Git, sin servidores aprovisionados, sin procesos definidos.
**Contexto:** Proyecto ejecutado en ambiente de múltiples squads. **Entrega exitosa en producción** a pesar de los desafíos iniciales de infraestructura, con retraso controlado de 2 meses.
---
## Desafío
### Transformación Digital... Partiendo de Cero Absoluto
**Estado inicial de la empresa (2023):**
❌ **Sin Git/versionamiento**
- Código solo en máquinas locales
- Histórico inexistente
- Colaboración imposible
❌ **Sin servidores aprovisionados**
- Ambiente de desarrollo inexistente
- Homologación no configurada
- Producción no preparada
❌ **Sin procesos de desarrollo**
- Sin CI/CD
- Sin code review
- Sin gestión de tareas estructurada
❌ **Sin equipo técnico interno experimentado**
- Equipo sin familiaridad con stacks modernos
- Primer contacto con React, APIs REST
- Inexperiencia con integraciones complejas
**Punto de partida técnico:**
- Prototipo funcional en **WordPress**
- Contenido y textos ya aprobados
- UX/UI definido
- Reglas de negocio documentadas (parcialmente)
---
### Integraciones Complejas Requeridas
El MVP necesitaba integrar con múltiples sistemas externos:
1. 🔐 **Salesforce** - Registro de pedidos de descuento
2. 📱 **Twilio** - SMS para validación de login (2FA)
3. 🏥 **API oficial de médicos** - Validación de CRM + datos profesionales
4. 🎯 **Interplayers** - Envío de registros de descuento por CPF
5. 📄 **WordPress** - Lectura de contenido (CMS headless)
6. 💾 **SQL Server** - Persistencia de datos
**Complejidad adicional:**
- Diferentes credenciales/ambientes por integración
- SLAs variados (Twilio crítico, WordPress tolerante)
- Tratamiento de errores específico por provider
- Compliance LGPD (datos sensibles de médicos)
---
## Arquitectura de Solución
### Estrategia: Start Small, Build Solid
**Decisión inicial:** Explicar al equipo el proceso que seguiríamos, estableciendo fundaciones antes de codificar.
### Fase 1: Setup de Infraestructura Básica (Semanas 1-2)
Incluso sin servidores aprovisionados, inicié setup esencial:
**Git & Versionamiento:**
```bash
# Repositorio estructurado desde día 1
git init
git flow init # Branch strategy definida
# Estructura de monorepo
/
├── frontend/ # Next.js + React
├── backend/ # .NET APIs
├── cms-adapter/ # WordPress integration
└── docs/ # Arquitectura y ADRs
```
**Proceso explicado al equipo:**
1. ✅ Todo en Git (commits atómicos, mensajes descriptivos)
2. ✅ Feature branches (nunca commit directo en main)
3. ✅ Code review obligatorio (2 aprobaciones)
4. ✅ CI/CD preparado (para cuando servidores estén listos)
**Ambientes locales primero:**
- Docker Compose para desarrollo local
- Mock de APIs externas (hasta que lleguen credenciales)
- SQL Server local con seeds de datos
---
### Fase 2: Arquitectura Moderna & Desacoplada
```
┌─────────────────────────────────────────────────────┐
│ FRONTEND (Next.js + React) │
│ - SSR para SEO │
│ - Client-side para interactividad │
│ - Consumo de APIs │
└────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ BACKEND APIs (.NET 7) │
│ - REST APIs │
│ - Authentication/Authorization │
│ - Business logic │
│ - Orchestration layer │
└────┬────┬────┬────┬────┬─────────────────────────┬──┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ ┌──────┐ ┌────────┐ ┌────────┐ ┌──────────┐
│Salesf. │ │Twilio│ │CRM │ │Interpl.│ │WordPr. │ │SQL Server│
│ │ │ │ │API │ │ │ │(CMS) │ │ │
└────────┘ └──────┘ └──────┘ └────────┘ └────────┘ └──────────┘
```
**Stack elegido:**
**Frontend:**
- `Next.js 13` - SSR, routing, optimizaciones
- `React 18` - Componentes, hooks, context
- `TypeScript` - Type safety
- `Tailwind CSS` - Styling moderno
**Backend:**
- `.NET 7` - APIs REST
- `Entity Framework Core` - ORM
- `SQL Server 2019` - Base de datos
- `Polly` - Resilience patterns (retry, circuit breaker)
**¿Por qué Next.js en vez de mantener WordPress?**
- ✅ Performance (SSR vs PHP monolítico)
- ✅ SEO optimizado (crítico para farmacéutica)
- ✅ Experiencia moderna (SPA cuando necesario)
- ✅ Escalabilidad
- ✅ WordPress mantenido solo como CMS (headless)
---
### Fase 3: Integraciones (Core del Proyecto)
#### 1. Salesforce - Campañas y Registro de Pedidos
**Solución implementada:**
Salesforce fue configurado para gestionar dos funcionalidades principales:
**a) Campañas de descuento:**
- Marketing configura campañas en Salesforce (medicamento X, descuento Y%, período)
- Backend consulta campañas activas vía API
- Frontend (Next.js) muestra porcentaje de descuento disponible basado en: medicamento + campaña activa
**b) Registro de pedidos:**
- Usuario informa: CRM del médico, UF, CPF del paciente, medicamento
- Sistema valida datos (CRM real vía API oficial, CPF válido)
- Porcentaje es calculado automáticamente (campañas de Salesforce + reglas del CMS)
- Pedido es registrado en Salesforce con todos los datos (compliance LGPD)
**Desafíos técnicos superados:**
- Autenticación OAuth2 con refresh token automático
- Rate limiting (Salesforce tiene límites de API/día)
- Retry logic para fallas transitorias (Polly)
- Enmascaramiento de CPF para logs (LGPD)
---
#### 2. Twilio - Autenticación por SMS (2FA)
**Solución implementada:**
Sistema de autenticación de dos factores para garantizar seguridad:
**Flujo de login:**
1. Usuario informa teléfono
2. Backend genera código de 6 dígitos (válido por 5 minutos)
3. SMS enviado vía Twilio ("Su código: 123456")
4. Usuario digita código en frontend
5. Backend valida código + timestamp de expiración
6. Token JWT emitido tras validación exitosa
**Compliance y auditoría:**
- Teléfonos enmascarados en logs (LGPD)
- Auditoría completa (quién, cuándo, qué SMS)
- Tasa de entrega: 99.8%
---
#### 3. API Oficial de Médicos (Consejo Regional de Medicina)
**Solución implementada:**
Validación automática de médicos vía API oficial de los consejos de medicina:
**Validaciones realizadas:**
- CRM existe y está activo en el consejo
- Nombre del médico corresponde al CRM informado
- Especialidad es permitida (regla de negocio del laboratorio)
- UF corresponde al estado de registro
**Optimizaciones:**
- Cache de 24 horas para reducir llamadas a API oficial
- Fallback en caso de API fuera del aire (notifica admin)
- Retry automático para fallas transitorias
**Por qué esto importa:**
Garantiza que solo médicos reales y activos puedan prescribir descuentos, evitando fraudes.
---
#### 4. WordPress como CMS Headless
**Solución implementada:**
Marketing continúa gestionando contenido en WordPress (familiar), pero frontend es Next.js moderno.
**Arquitectura:**
- WordPress: Gestiona textos, imágenes, reglas de campañas
- WordPress REST API: Expone contenido vía JSON
- Next.js: Consume API y renderiza con SSR (SEO optimizado)
**Beneficios:**
- ✅ Marketing no necesita aprender nueva herramienta
- ✅ Frontend moderno (performance, UX)
- ✅ SEO optimizado (Server-Side Rendering)
- ✅ Separación clara de responsabilidades (contenido vs código)
---
### Fase 4: Resiliencia & Error Handling
Con múltiples integraciones externas, fallas son inevitables. La solución fue implementar **patrones de resiliencia** usando biblioteca Polly (.NET):
**Patrones implementados:**
**1. Retry (Reintentar)**
- Si Salesforce/Twilio/CRM API fallan, sistema intenta automáticamente 2-3x
- Espera crece exponencialmente (1s, 2s, 4s) para evitar sobrecarga
- Solo errores transitorios (timeout, 503) son reintentados
**2. Circuit Breaker (Disyuntor)**
- Si servicio falla 5x seguidas, "abre el circuito" por 30s
- Durante 30s, no intenta más (evita desperdiciar recursos)
- Tras 30s, intenta nuevamente (puede haber vuelto)
**3. Timeout**
- Cada integración tiene tiempo máximo de respuesta
- Evita requisiciones trabadas indefinidamente
**4. Fallback (Plan B)**
- Salesforce fuera: Pedido va a cola, procesa después
- Twilio fuera: Alerta administrador vía email
- CRM API fuera: Usa cache (datos de 24h atrás)
- WordPress fuera: Muestra contenido estático precargado
**Estrategias por integración:**
| Integración | Retry | Circuit Breaker | Timeout | Plan B |
|----------|-------|-----------------|---------|----------|
| Salesforce | 3x (exponencial) | 5 fallas/30s | 10s | Cola de retry |
| Twilio | 2x (lineal) | 3 fallas/60s | 5s | Alerta admin |
| CRM API | 3x (exponencial) | No | 15s | Cache |
| WordPress | No | No | 3s | Contenido estático |
**Resultado en producción:**
- Salesforce tuvo mantenimiento (1h) → Sistema continuó funcionando (cola procesó después)
- Twilio tuvo inestabilidad → Retry automático resolvió 95% de los casos
- Zero downtime percibido por los usuarios
---
## Superando Desafíos de Infraestructura
### Problema: Servidores No Aprovisionados
**Solución temporal:**
1. Desarrollo 100% local (Docker Compose)
2. Mocks de servicios externos (cuando credenciales se retrasaron)
3. CI/CD preparado pero no activo (esperando infra)
**Cuando llegaron servidores (semana 6):**
- Deploy en 2 horas (ya estaba preparado)
- Zero sorpresas (todo probado localmente)
- Rollout suave
---
### Problema: Credenciales de Integración Retrasadas
**Impacto:** Twilio y Salesforce demoraron 3 semanas en ser aprovisionadas.
**Solución:** Crear versiones "mock" (simuladas) de cada integración:
- Mock de Twilio: Registra en log en vez de enviar SMS real
- Mock de Salesforce: Guarda pedido en archivo JSON local
- Mock de CRM API: Retorna datos ficticios de médicos
**Cómo funciona:**
- Ambiente de desarrollo: Usa mocks (no necesita credenciales)
- Ambiente de producción: Usa integraciones reales (cuando lleguen credenciales)
- Cambio automático basado en configuración
**Resultado:** Equipo continuó 100% productivo durante 3 semanas, probando flujos completos sin depender de credenciales.
---
### Problema: Equipo Inexperto con Stack Moderno
**Contexto:** Equipo no tenía experiencia con React, TypeScript, .NET Core moderno, APIs REST.
**Abordaje de capacitación:**
**1. Pair Programming (1h/día por desarrollador)**
- Tech lead trabaja al lado del dev
- Compartir pantalla + explicación en tiempo real
- Dev escribe código, tech lead guía
**2. Code Review Educativo**
- No solo "aprobar" o "reprobar"
- Comentarios explican el **por qué** de cada sugerencia
- Ejemplo: "Siempre trate errores de requisiciones! Si API cae, usuario necesita saber qué pasó."
**3. Documentación Viva**
- ADRs (Architecture Decision Records): ¿Por qué elegimos X y no Y?
- READMEs: Cómo ejecutar, cómo probar, cómo deployar
- Onboarding guide: De cero a primera feature
**4. Live Coding Semanal (2h)**
- Tech lead resuelve problema real en vivo
- Equipo observa proceso de pensamiento
- Q&A al final
**Resultado:**
- Tras 4 semanas, equipo estaba autónomo
- Calidad de código aumentó consistentemente
- Devs pasaron a hacer code review entre sí (peer review)
---
## Resultados e Impacto
### Entrega Exitosa A Pesar de los Desafíos
**Contexto:** Programa con múltiples squads trabajando en paralelo.
**Resultado alcanzado:**
- ✅ **MVP entregado en producción con éxito**
- ✅ Retraso controlado de 2 meses (significativamente menor que otras iniciativas del programa)
- ✅ Todas las integraciones funcionando según planificado
- ✅ Zero critical bugs en producción (primera semana)
**¿Por qué la entrega fue exitosa?**
1. **Setup anticipado** - Git, procesos, Docker local desde día 1
2. **Mocks estratégicos** - Equipo no quedó bloqueado esperando infra
3. **Arquitectura sólida** - Resiliencia desde el inicio
4. **Upskilling continuo** - Equipo aprendió haciendo
5. **Comunicación proactiva** - Riesgos reportados temprano
---
### Métricas del MVP
**Performance:**
- ⚡ Tiempo de carga: <2s (95th percentile)
- 📱 Lighthouse score: 95+ (mobile)
- 🔒 SSL A+ rating
**Integraciones:**
- 📊 Salesforce: 100% de pedidos sincronizados
- 📱 Twilio: 99.8% delivery rate
- 🏥 CRM API: 10k validaciones/día (media)
- 💾 SQL Server: 50k registros/mes
**Adopción:**
- 👨‍⚕️ 2.000+ médicos registrados (3 primeros meses)
- 🎯 15.000+ pedidos de descuento procesados
- ⭐ 4.8/5 satisfacción (encuesta interna)
---
### Impacto en el Cliente
**Transformación digital iniciada:**
- ✅ Git implementado y adoptado
- ✅ Procesos de desarrollo establecidos
- ✅ Equipo interno capacitado en stacks modernos
- ✅ Infraestructura cloud configurada (Azure)
- ✅ Roadmap de evolución definido
**Base para futuros proyectos:**
- Arquitectura sirvió de referencia para otras iniciativas
- Patrones de código documentados (coding standards)
- Pipelines CI/CD reutilizados
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React 18` `Next.js 13` `TypeScript` `Tailwind CSS` `Salesforce API` `Twilio` `WordPress REST API` `Docker` `Polly` `OAuth2` `JWT` `LGPD Compliance`
---
## Decisiones Clave & Trade-offs
### ¿Por qué Next.js en vez de React puro?
**Requisitos:**
- SEO crítico (farmacéutica necesita ranquear)
- Performance (médicos usan mobile)
- Contenido dinámico (WordPress)
**Next.js ofrece:**
- ✅ SSR out-of-the-box
- ✅ API routes (BFF pattern)
- ✅ Optimizaciones automáticas (image, fonts)
- ✅ Deploy simplificado (Vercel, Azure)
---
### ¿Por qué mantener WordPress?
**Alternativas consideradas:**
1. ❌ Migrar contenido a BD + CMS custom (tiempo)
2. ❌ Strapi/Contentful (costos + learning curve)
3. ✅ **WordPress headless** (mejor trade-off)
**Ventajas:**
- Equipo de marketing ya sabe usar
- Contenido aprobado ya estaba ahí
- WordPress REST API es sólida
- Costo cero (ya estaba ejecutándose)
---
### ¿Por qué .NET 7 en vez de Node.js?
**Contexto:** Cliente tenía preferencia por stack Microsoft.
**Beneficios adicionales:**
- Performance superior (vs Node en APIs)
- Type safety nativa (C#)
- Entity Framework (ORM maduro)
- Integración fácil con Azure (deploy futuro)
- Equipo del cliente tenía familiaridad
---
## Lecciones Aprendidas
### 1. Infraestructura Retrasada? Prepare Alternativas
No espere servidores/credenciales para comenzar:
- Docker local es su amigo
- Mocks permiten progreso
- CI/CD puede ser preparado antes de tener dónde deployar
**Lección:** Controle lo que puede controlar.
---
### 2. Procesos > Herramientas
Incluso sin Git corporativo, establecí:
- Branching strategy
- Code review
- Commit conventions
- Documentation standards
**Resultado:** Cuando llegaron herramientas, equipo ya sabía usarlas.
---
### 3. Upskilling es Inversión, No Costo
Pair programming y code reviews tomaron tiempo, pero:
- ✅ Equipo quedó autónomo más rápido
- ✅ Calidad de código aumentó
- ✅ Knowledge sharing natural
- ✅ Onboarding de nuevos devs simplificado
---
### 4. Resiliencia Desde el Inicio
Implementar Polly (retry, circuit breaker) al inicio salvó en producción:
- Twilio tuvo inestabilidad (resuelta automáticamente)
- Salesforce tuvo mantenimiento (queue funcionó)
- CRM API tuvo lentitud (cache mitigó)
**Lección:** No deje resiliencia para "después". Fallas van a ocurrir.
---
### 5. Comunicación Clara de Riesgos
Reporté semanalmente:
- Bloqueos (infraestructura, credenciales)
- Riesgos (plazos, dependencias)
- Soluciones alternativas (mocks, workarounds)
**Resultado:** Stakeholders sabían exactamente el estado y no tuvieron sorpresas.
---
## Desafíos & Cómo Fueron Superados
| Desafío | Impacto | Solución | Resultado |
|---------|---------|---------|-----------|
| Sin Git | Bloqueo total | Setup local + GitLab Cloud | Equipo productivo día 1 |
| Sin servidores | Sin ambiente de dev | Docker Compose local | Dev/test local completo |
| Credenciales retrasadas | Integración bloqueada | Mock services | Progreso sin bloqueo |
| Equipo inexperto | Código de baja calidad | Pair prog + Code review | Ramp-up en 4 semanas |
| Múltiples integraciones | Complejidad alta | Polly + patterns | Zero downtime prod |
---
## Próximos Pasos (Post-MVP)
**Roadmap sugerido al cliente:**
1. **Fase 2: Expansión de funcionalidades**
- Dashboard para médicos (histórico de pedidos)
- Notificaciones push (Firebase)
- Integración con e-commerce (compra directa)
2. **Fase 3: Optimizaciones**
- Cache distribuido (Redis)
- CDN para assets estáticos
- Analytics avanzado (Amplitude)
3. **Fase 4: Escala**
- Kubernetes (AKS)
- Microservicios (quebrar monolito)
- Event-driven architecture (Azure Service Bus)
---
**Resultado:** MVP entregado en producción a pesar de comenzar literalmente de cero, estableciendo fundaciones sólidas para transformación digital del cliente.
[¿Necesita construir un MVP en escenario desafiante? Póngase en contacto](#contact)

View File

@ -1,211 +0,0 @@
---
title: "Sistema de Integración SAP Healthcare"
slug: "sap-integration-healthcare"
summary: "Integración bidireccional procesando 100k+ transacciones/día con 99.9% uptime"
client: "Confidencial - Multinacional Healthcare"
industry: "Healthcare"
timeline: "6 meses"
role: "Arquitecto de Integración"
image: ""
tags:
- SAP
- C#
- .NET
- Integraciones
- Enterprise
- Healthcare
featured: true
order: 1
date: 2023-06-15
seo_title: "Caso: Integración SAP Healthcare - 100k Transacciones/Día"
seo_description: "Cómo arquitectamos sistema de integración SAP procesando 100k+ transacciones diarias con 99.9% uptime para empresa healthcare."
seo_keywords: "integración SAP, C#, .NET, SAP Connector, enterprise integration, healthcare"
---
## Descripción General
**Cliente:** Multinacional Healthcare (confidencial)
**Tamaño:** 100.000+ empleados
**Proyecto:** Integración de beneficios
**Timeline:** 6 meses
**Mi Rol:** Arquitecto de Integración
---
## Desafío
El cliente tenía sistema interno de gestión de beneficios que necesitaba sincronizar con SAP ECC para procesar nómina.
### Dolores principales:
- Proceso manual sujeto a errores
- 3-5 días de delay entre sistemas
- 100k empleados esperando procesamiento
- Picos de carga (fin de mes)
### Constraints:
- Presupuesto limitado (sin SAP BTP)
- Equipo SAP interno pequeño (2 desarrolladores)
- Plazo ajustado (6 meses go-live)
- Sistema legacy .NET Framework 4.5
---
## Solución
Arquitectura de integración bidireccional:
```
[Sistema Interno] ←→ [Queue] ←→ [SAP Connector] ←→ [SAP ECC]
↓ ↓
[MongoDB Logs] [ABAP Z_BENEFITS]
```
### Componentes:
- .NET Service con SAP Connector (NCo 3.0)
- ABAP transaction personalizada (Z_BENEFITS)
- Queue system (RabbitMQ) para retry logic
- MongoDB para auditoría y troubleshooting
- Scheduler (Hangfire) para batch processing
### Flujo:
1. Sistema genera cambios (new hire, alteraciones)
2. Service procesa batch (500 registros/vez)
3. SAP Connector llama Z_BENEFITS vía RFC
4. SAP retorna estado (éxito/error)
5. Retry automático si falla (máx 3x)
6. Logs MongoDB para troubleshooting
---
## Resultado
### Métricas:
- **100k+** transacciones/día procesadas
- **99.9%** uptime
- Reducción de **5 días → 4 horas** (delay)
- **80%** reducción tiempo procesamiento
- **Zero** errores manuales (vs 2-3% antes)
### Beneficios:
- Empleados reciben beneficios a tiempo
- Equipo RRHH economiza 40h/mes (trabajo manual)
- Auditoría completa (compliance)
- Escalable (crecimiento 30% año sin refactor)
---
## Tech Stack
`C#` `.NET Framework 4.5` `SAP NCo 3.0` `RabbitMQ` `MongoDB` `Hangfire` `Docker` `SAP ECC` `ABAP` `RFC`
---
## Decisiones & Motivación
### 💡 Decisión 1: SAP Connector vs SAP BTP
**Opciones evaluadas:**
- SAP BTP (eventos, APIs modernas, cloud)
- SAP Connector (RFC directo, on-premise)
**Elegimos:** SAP Connector
**Motivación:**
- Cliente tenía SAP ECC on-premise (no S/4)
- Presupuesto no permitía licencia BTP
- Equipo SAP cómodo con ABAP/RFC
- Necesidades atendidas con RFC (no necesitaba event-driven real-time)
**Trade-off aceptado:**
- Menos "moderno" que BTP, pero 100% funcional
- Costo $0 adicional vs $30k+/año BTP
- Delivery 2 meses más rápido (sin learning curve BTP)
---
### 💡 Decisión 2: Queue System vs Llamadas Directas
**Opciones evaluadas:**
- Llamadas síncronas directas (más simple)
- Queue con retry (más complejo)
**Elegimos:** Queue + Retry
**Motivación:**
- SAP ocasionalmente indisponible (mantenimiento)
- Picos de carga (fin de mes = 200k reqs)
- Garantizar zero pérdida de datos
- Resiliencia > simplicidad (ambiente crítico)
**Implementación:**
- RabbitMQ con dead-letter queue
- Retry exponencial (1min, 5min, 15min)
- Alertas si 3 fallas consecutivas
**Resultado:**
- Zero pérdida datos en 2 años producción
- Equipo RRHH no necesita "estar pendiente"
---
### 💡 Decisión 3: ABAP Personalizado vs Standard
**Opciones evaluadas:**
- BAPIs standard SAP (zero código ABAP)
- Transaction personalizada (Z_BENEFITS)
**Elegimos:** Transaction personalizada
**Motivación:**
- BAPIs standard no tenían validaciones específicas del negocio
- Cliente quería lógica centralizada en SAP (single source of truth)
- Permitió validaciones complejas (elegibilidad, dependientes, límites)
**Trade-off:**
- Requiere mantenimiento ABAP (equipo SAP interno)
- Pero: Cliente prefirió vs lógica dual (riesgo desincronización)
---
### ❌ Alternativas NO Elegidas
**Webhook/Callback (Event-Driven):**
- Cliente no tenía infraestructura exponer APIs
- Sistema interno detrás de firewall
- Polling batch funciona bien para el caso
**Microservicios Kubernetes:**
- Overkill para integración única
- Equipo no tenía expertise K8s
- Docker simple suficiente
**Real-time Sync (<1min):**
- Negocio no necesita (batch diario ok)
- Costo infra aumentaría 3x
- 4h delay es aceptable para nómina
---
## Aprendizajes
### ✅ Lo que funcionó muy bien:
- Involucrar equipo SAP desde día 1 (buy-in)
- MongoDB para logs (troubleshooting 10x más rápido)
- Retry logic salvó innumerables veces
### 🔄 Lo que haría diferente:
- Agregar health check endpoint más temprano
- Dashboard de monitoreo desde inicio (agregamos después)
### 📚 Lecciones para próximos proyectos:
- Cliente "presupuesto limitado" ≠ "solución limitada" - creatividad resuelve
- Documentar TODAS decisiones arquitecturales (team turnover)
- Simplicidad vence complejidad cuando ambas funcionan (KISS)
---
## ¿Necesita Algo Similar?
Integraciones SAP complejas, sistemas legacy, o arquitectura de alta disponibilidad?
[Conversemos sobre su desafío →](/#contact)

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using CarneiroTech.Resources;
using CarneiroTech.Services;
namespace CarneiroTech.Controllers;
@ -7,11 +8,13 @@ public class CasesController : Controller
{
private readonly ICaseService _caseService;
private readonly ILogger<CasesController> _logger;
private readonly ILanguageService _languageService;
public CasesController(ICaseService caseService, ILogger<CasesController> logger)
public CasesController(ICaseService caseService, ILogger<CasesController> logger, ILanguageService languageService)
{
_caseService = caseService;
_logger = logger;
_languageService = languageService;
}
// GET: /cases
@ -22,13 +25,14 @@ public class CasesController : Controller
: await _caseService.GetCasesByTagAsync(tag);
var allTags = await _caseService.GetAllTagsAsync();
var lang = _languageService.GetCurrentLanguage(HttpContext);
ViewData["Title"] = string.IsNullOrEmpty(tag)
? "Cases - Carneiro Tech"
: $"Cases: {tag} - Carneiro Tech";
? SiteStrings.Get("cases.seo.title", lang)
: string.Format(SiteStrings.Get("cases.seo.title.tag", lang), tag);
ViewData["Description"] = string.IsNullOrEmpty(tag)
? "Explore our portfolio of technical consulting projects and solution design cases."
: $"Technical consulting cases related to {tag}.";
? SiteStrings.Get("cases.seo.description", lang)
: string.Format(SiteStrings.Get("cases.seo.description.tag", lang), tag);
ViewData["SelectedTag"] = tag;
ViewBag.AllTags = allTags;

View File

@ -2,6 +2,7 @@ using System.Diagnostics;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using CarneiroTech.Models;
using CarneiroTech.Resources;
using CarneiroTech.Services;
namespace CarneiroTech.Controllers;
@ -10,27 +11,31 @@ public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ICaseService _caseService;
private readonly ILanguageService _languageService;
public HomeController(ILogger<HomeController> logger, ICaseService caseService)
public HomeController(ILogger<HomeController> logger, ICaseService caseService, ILanguageService languageService)
{
_logger = logger;
_caseService = caseService;
_languageService = languageService;
}
public async Task<IActionResult> Index()
{
var featuredCases = await _caseService.GetFeaturedCasesAsync();
var lang = _languageService.GetCurrentLanguage(HttpContext);
ViewData["Title"] = "Carneiro Tech - Solution Design & Technical Consulting";
ViewData["Description"] = "20+ years connecting business and technology. Specialized in technical proposals, MVP definition, and due diligence.";
ViewData["Keywords"] = "solution design, technical consulting, SAP integration, enterprise architecture, MVP definition, due diligence";
ViewData["Title"] = SiteStrings.Get("home.seo.title", lang);
ViewData["Description"] = SiteStrings.Get("home.seo.description", lang);
ViewData["Keywords"] = SiteStrings.Get("home.seo.keywords", lang);
return View(featuredCases);
}
public IActionResult Privacy()
{
ViewData["Title"] = "Privacy Policy - Carneiro Tech";
var lang = _languageService.GetCurrentLanguage(HttpContext);
ViewData["Title"] = SiteStrings.Get("privacy.title", lang);
return View();
}

View File

@ -6,9 +6,10 @@ public class CaseMetadata
public string Slug { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string Client { get; set; } = string.Empty;
public string Industry { get; set; } = string.Empty;
public string Timeline { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public string DeviceModel { get; set; } = string.Empty;
public decimal EstimatedSavings { get; set; }
public string Category { get; set; } = string.Empty; // Resgate, Upgrade, Manutenção
public string Thumbnail { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
public List<string> Tags { get; set; } = new();
public bool Featured { get; set; }

View File

@ -5,80 +5,161 @@ namespace CarneiroTech.Resources
public static Dictionary<string, Dictionary<string, string>> Translations = new()
{
// Navigation
["nav.services"] = new() { ["pt"] = "Serviços", ["en"] = "Services", ["es"] = "Servicios" },
["nav.services"] = new() { ["pt"] = "Especialidades", ["en"] = "Specialties", ["es"] = "Especialidades" },
["nav.cases"] = new() { ["pt"] = "Cases", ["en"] = "Cases", ["es"] = "Casos" },
["nav.about"] = new() { ["pt"] = "Sobre", ["en"] = "About", ["es"] = "Acerca de" },
["nav.contact"] = new() { ["pt"] = "Contato", ["en"] = "Contact", ["es"] = "Contacto" },
["nav.about"] = new() { ["pt"] = "Por que nós?", ["en"] = "Why us?", ["es"] = "Por qué nosotros?" },
["nav.contact"] = new() { ["pt"] = "Agendar", ["en"] = "Schedule", ["es"] = "Agendar" },
["nav.menu"] = new() { ["pt"] = "Menu", ["en"] = "Menu", ["es"] = "Menu" },
// Hero Section
["hero.greeting"] = new() { ["pt"] = "Bem-vindo ao Carneiro Tech", ["en"] = "Welcome to Carneiro Tech", ["es"] = "Bienvenido a Carneiro Tech" },
["hero.tagline"] = new() { ["pt"] = "É um prazer ter você aqui!", ["en"] = "It's A Great Pleasure To Have You Here!", ["es"] = "¡Es un placer tenerlo aquí!" },
["hero.title"] = new() { ["pt"] = "Solution Design & Consultoria Técnica", ["en"] = "Solution Design & Technical Consulting", ["es"] = "Solution Design y Consultoría Técnica" },
["hero.cta"] = new() { ["pt"] = "Saiba Mais", ["en"] = "Learn More", ["es"] = "Más Información" },
["hero.greeting"] = new() { ["pt"] = "CarneiroTech SBC", ["en"] = "CarneiroTech SBC", ["es"] = "CarneiroTech SBC" },
["hero.tagline"] = new() { ["pt"] = "Assistência Técnica Premium no Golden Square", ["en"] = "Premium Tech Support at Golden Square", ["es"] = "Soporte Técnico Premium" },
["hero.title"] = new() { ["pt"] = "Não Descarte seu PC. Faça um Upgrade de Elite!", ["en"] = "Don't Toss Your PC. Elite Upgrade.", ["es"] = "No Descarte su PC. Upgrade de Elite." },
["hero.cta"] = new() { ["pt"] = "Agendar Avaliação", ["en"] = "Schedule Evaluation", ["es"] = "Agendar Evaluación" },
// Services Section
["services.title"] = new() { ["pt"] = "Serviços", ["en"] = "Services", ["es"] = "Servicios" },
["services.subtitle"] = new() { ["pt"] = "Transformando desafios técnicos em soluções eficientes", ["en"] = "Transforming technical challenges into efficient solutions", ["es"] = "Transformando desafíos técnicos en soluciones eficientes" },
["services.title"] = new() { ["pt"] = "O que fazemos", ["en"] = "What we do", ["es"] = "Qué hacemos" },
["services.subtitle"] = new() { ["pt"] = "Tecnologia de ponta para quem não aceita lentidão.", ["en"] = "Top technology for those who don't accept slowness.", ["es"] = "Tecnología de punta para quienes no aceptan lentitud." },
["service.solution.title"] = new() { ["pt"] = "Solution Design", ["en"] = "Solution Design", ["es"] = "Solution Design" },
["service.solution.desc"] = new() { ["pt"] = "Desenho completo de soluções técnicas, identificando requisitos não explícitos e propondo arquiteturas adequadas ao contexto do projeto.", ["en"] = "Complete technical solution design, identifying non-explicit requirements and proposing architectures suitable for the project context.", ["es"] = "Diseño completo de soluciones técnicas, identificando requisitos no explícitos y proponiendo arquitecturas adecuadas al contexto del proyecto." },
["service.solution.title"] = new() { ["pt"] = "Upgrade de Performance", ["en"] = "Performance Upgrade", ["es"] = "Upgrade de Performance" },
["service.solution.desc"] = new() { ["pt"] = "Transformamos notebooks lentos em máquinas de alta performance com SSDs NVMe e RAM de baixa latência.", ["en"] = "We turn slow laptops into high-performance machines with NVMe SSDs and low-latency RAM.", ["es"] = "Transformamos laptops lentos en máquinas de alto rendimiento con SSD NVMe y RAM de baja latencia." },
["service.modernization.title"] = new() { ["pt"] = "Modernização de Sistemas", ["en"] = "System Modernization", ["es"] = "Modernización de Sistemas" },
["service.modernization.desc"] = new() { ["pt"] = "Migração de aplicações legadas para tecnologias modernas, com estratégias que garantem continuidade operacional e zero downtime.", ["en"] = "Migration of legacy applications to modern technologies, with strategies that ensure operational continuity and zero downtime.", ["es"] = "Migración de aplicaciones heredadas a tecnologías modernas, con estrategias que garantizan continuidad operativa y cero tiempo de inactividad." },
["service.modernization.title"] = new() { ["pt"] = "Limpeza Técnica", ["en"] = "Technical Cleaning", ["es"] = "Limpieza Técnica" },
["service.modernization.desc"] = new() { ["pt"] = "Troca de pasta térmica industrial e remoção de oxidação. Seu equipamento frio e silencioso como novo.", ["en"] = "Industrial thermal paste replacement and oxidation removal. Your gear cool and quiet as new.", ["es"] = "Cambio de pasta térmica industrial y remoción de oxidación. Su equipo frío y silencioso." },
["service.architecture.title"] = new() { ["pt"] = "Arquitetura de Software", ["en"] = "Software Architecture", ["es"] = "Arquitectura de Software" },
["service.architecture.desc"] = new() { ["pt"] = "Definição de arquiteturas escaláveis, resilientes e adequadas ao contexto, considerando custos, prazos e manutenibilidade.", ["en"] = "Definition of scalable, resilient and context-appropriate architectures, considering costs, deadlines and maintainability.", ["es"] = "Definición de arquitecturas escalables, resilientes y adecuadas al contexto, considerando costos, plazos y mantenibilidad." },
["service.architecture.title"] = new() { ["pt"] = "Reparo Estrutural", ["en"] = "Structural Repair", ["es"] = "Reparo Estructural" },
["service.architecture.desc"] = new() { ["pt"] = "Reconstrução de carcaças e dobradiças com resina industrial. Recuperamos o que outros mandam descartar.", ["en"] = "Case and hinge reconstruction with industrial resin. We recover what others tell you to discard.", ["es"] = "Reconstrucción de carcasas y bisagras con resina industrial." },
["service.consulting.title"] = new() { ["pt"] = "Consultoria Técnica", ["en"] = "Technical Consulting", ["es"] = "Consultoría Técnica" },
["service.consulting.desc"] = new() { ["pt"] = "Análise de viabilidade técnica, otimização de processos, redução de custos operacionais e escolha de tecnologias adequadas.", ["en"] = "Technical feasibility analysis, process optimization, operational cost reduction and selection of appropriate technologies.", ["es"] = "Análisis de viabilidad técnica, optimización de procesos, reducción de costos operativos y selección de tecnologías adecuadas." },
["service.consulting.title"] = new() { ["pt"] = "Avaliação Técnica", ["en"] = "Technical Evaluation", ["es"] = "Evaluación Técnica" },
["service.consulting.desc"] = new() { ["pt"] = "Diagnóstico real e honesto. Se outros disseram que não tem conserto, traga para quem conhece a engenharia.", ["en"] = "Real and honest diagnosis. If others said it's unfixable, bring it to those who know the engineering.", ["es"] = "Diagnóstico real y honesto. Si otros dijeron que no tiene arreglo, tráigalo." },
// Portfolio/Cases Section
["portfolio.title"] = new() { ["pt"] = "Cases de Sucesso", ["en"] = "Success Cases", ["es"] = "Casos de Éxito" },
["portfolio.subtitle"] = new() { ["pt"] = "Conheça alguns projetos onde transformei desafios complexos em soluções elegantes", ["en"] = "Discover some projects where I transformed complex challenges into elegant solutions", ["es"] = "Conozca algunos proyectos donde transformé desafíos complejos en soluciones elegantes" },
["portfolio.viewcase"] = new() { ["pt"] = "Ver Case Completo", ["en"] = "View Full Case", ["es"] = "Ver Caso Completo" },
["portfolio.title"] = new() { ["pt"] = "Prova Real", ["en"] = "Real Proof", ["es"] = "Prueba Real" },
["portfolio.subtitle"] = new() { ["pt"] = "Veja como economizamos milhares de reais para nossos clientes de SBC", ["en"] = "See how we saved thousands of reais for our SBC clients", ["es"] = "Vea cómo ahorramos miles de reales para nuestros clientes" },
["portfolio.viewcase"] = new() { ["pt"] = "Ver Detalhes do Resgate", ["en"] = "View Rescue Details", ["es"] = "Ver Detalles del Rescate" },
// About Section
["about.title"] = new() { ["pt"] = "Sobre", ["en"] = "About", ["es"] = "Acerca de" },
["about.subtitle"] = new() { ["pt"] = "Mais de uma década transformando desafios técnicos em soluções eficientes", ["en"] = "Over a decade transforming technical challenges into efficient solutions", ["es"] = "Más de una década transformando desafíos técnicos en soluciones eficientes" },
["about.title"] = new() { ["pt"] = "20 Anos de Engenharia", ["en"] = "20 Years of Engineering", ["es"] = "20 Años de Ingeniería" },
["about.subtitle"] = new() { ["pt"] = "Eu conheço a tecnologia por dentro. Por isso sei como consertar.", ["en"] = "I know tech from the inside. That's why I know how to fix it.", ["es"] = "Conozco la tecnología por dentro. Por eso sé cómo arreglarla." },
["about.text"] = new() {
["pt"] = "Com mais de 10 anos de experiência em engenharia de software, especializei-me em Solution Design, arquitetura de sistemas e modernização de aplicações legadas. Meu diferencial está em identificar requisitos não explícitos, antecipar problemas e propor soluções que realmente funcionam na prática.",
["en"] = "With over 10 years of experience in software engineering, I specialize in Solution Design, systems architecture and legacy application modernization. My differential is in identifying non-explicit requirements, anticipating problems and proposing solutions that really work in practice.",
["es"] = "Con más de 10 años de experiencia en ingeniería de software, me especializo en Solution Design, arquitectura de sistemas y modernización de aplicaciones heredadas. Mi diferencial está en identificar requisitos no explícitos, anticipar problemas y proponer soluciones que realmente funcionan en la práctica."
["pt"] = "Com mais de 20 anos abrindo e montando o que há de melhor em hardware, minha missão na CarneiroTech é trazer o nível de excelência enterprise para o seu notebook pessoal. Não somos apenas uma assistência; somos especialistas em performance e longevidade de hardware em São Bernardo do Campo.",
["en"] = "With over 20 years opening and building the best in hardware, my mission at CarneiroTech is to bring enterprise-level excellence to your personal laptop. We are not just support; we are specialists in performance and hardware longevity in SBC.",
["es"] = "Con más de 20 años abriendo y montando lo mejor en hardware, mi misión en CarneiroTech é traer la excelencia enterprise a su laptop personal."
},
// Contact Section
["contact.title"] = new() { ["pt"] = "Contato", ["en"] = "Contact", ["es"] = "Contacto" },
["contact.subtitle"] = new() { ["pt"] = "Vamos conversar sobre seu desafio técnico", ["en"] = "Let's talk about your technical challenge", ["es"] = "Hablemos sobre su desafío técnico" },
["contact.title"] = new() { ["pt"] = "Agendar Avaliação", ["en"] = "Schedule Evaluation", ["es"] = "Agendar Evaluación" },
["contact.subtitle"] = new() { ["pt"] = "Atendimento com hora marcada no Golden Square Shopping", ["en"] = "By appointment at Golden Square Mall", ["es"] = "Atención con hora marcada en Golden Square Shopping" },
["contact.name"] = new() { ["pt"] = "Seu Nome *", ["en"] = "Your Name *", ["es"] = "Su Nombre *" },
["contact.email"] = new() { ["pt"] = "Seu Email *", ["en"] = "Your Email *", ["es"] = "Su Email *" },
["contact.phone"] = new() { ["pt"] = "Seu Telefone", ["en"] = "Your Phone", ["es"] = "Su Teléfono" },
["contact.message"] = new() { ["pt"] = "Sua Mensagem *", ["en"] = "Your Message *", ["es"] = "Su Mensaje *" },
["contact.send"] = new() { ["pt"] = "Enviar Mensagem", ["en"] = "Send Message", ["es"] = "Enviar Mensaje" },
["contact.phone"] = new() { ["pt"] = "Seu WhatsApp", ["en"] = "Your WhatsApp", ["es"] = "Su WhatsApp" },
["contact.message"] = new() { ["pt"] = "Qual o modelo do seu equipamento e o problema? *", ["en"] = "What's your device model and problem? *", ["es"] = "¿Cuál es el modelo y el problema? *" },
["contact.send"] = new() { ["pt"] = "Solicitar Horário", ["en"] = "Request Time Slot", ["es"] = "Solicitar Horario" },
["contact.sending"] = new() { ["pt"] = "Enviando...", ["en"] = "Sending...", ["es"] = "Enviando..." },
["contact.success"] = new() { ["pt"] = "Mensagem enviada com sucesso! Entraremos em contato em breve.", ["en"] = "Message sent successfully! We'll get in touch soon.", ["es"] = "¡Mensaje enviado con éxito! Nos pondremos en contacto pronto." },
["contact.error"] = new() { ["pt"] = "Erro ao enviar mensagem. Por favor, tente novamente ou entre em contato via email.", ["en"] = "Error sending message. Please try again or contact via email.", ["es"] = "Error al enviar mensaje. Por favor, inténtelo de nuevo o contacte por email." },
["contact.success"] = new() { ["pt"] = "Solicitação enviada! Chamaremos você no WhatsApp em breve.", ["en"] = "Request sent! We'll WhatsApp you shortly.", ["es"] = "¡Solicitud enviada! Lo llamaremos pronto." },
["contact.or"] = new() { ["pt"] = "OU", ["en"] = "OR", ["es"] = "O" },
["contact.whatsapp"] = new() { ["pt"] = "Falar via WhatsApp", ["en"] = "Chat on WhatsApp", ["es"] = "Hablar por WhatsApp" },
["contact.whatsapp.subtitle"] = new() { ["pt"] = "Resposta rápida e direta", ["en"] = "Quick and direct response", ["es"] = "Respuesta rápida y directa" },
["contact.whatsapp"] = new() { ["pt"] = "Agendar via WhatsApp", ["en"] = "Schedule via WhatsApp", ["es"] = "Agendar por WhatsApp" },
["contact.whatsapp.subtitle"] = new() { ["pt"] = "Fale direto com o técnico", ["en"] = "Talk directly with the tech", ["es"] = "Hable directo con el técnico" },
["contact.whatsapp.message"] = new() {
["pt"] = "[Site] Olá! Gostaria de agendar uma avaliação para meu notebook no Golden Square.",
["en"] = "[Website] Hello! I'd like to schedule an evaluation for my laptop at Golden Square.",
["es"] = "[Sitio] ¡Hola! Me gustaría agendar una evaluación para mi laptop."
},
// Footer
["footer.rights"] = new() { ["pt"] = "Carneiro Tech - Todos os direitos reservados", ["en"] = "Carneiro Tech - All rights reserved", ["es"] = "Carneiro Tech - Todos los derechos reservados" },
["footer.rights"] = new() { ["pt"] = "CarneiroTech SBC - Todos os direitos reservados", ["en"] = "CarneiroTech SBC - All rights reserved", ["es"] = "CarneiroTech SBC" },
["footer.privacy"] = new() { ["pt"] = "Privacidade", ["en"] = "Privacy", ["es"] = "Privacidad" },
["footer.terms"] = new() { ["pt"] = "Termos", ["en"] = "Terms", ["es"] = "Términos" },
["footer.copyright"] = new() { ["pt"] = "Copyright © {year} CarneiroTech - Ricardo Carneiro", ["en"] = "Copyright © {year} CarneiroTech - Ricardo Carneiro", ["es"] = "Copyright © {year} CarneiroTech - Ricardo Carneiro" },
// Case Details
["case.client"] = new() { ["pt"] = "Cliente", ["en"] = "Client", ["es"] = "Cliente" },
["case.industry"] = new() { ["pt"] = "Indústria", ["en"] = "Industry", ["es"] = "Industria" },
["case.timeline"] = new() { ["pt"] = "Prazo", ["en"] = "Timeline", ["es"] = "Plazo" },
["case.role"] = new() { ["pt"] = "Papel", ["en"] = "Role", ["es"] = "Rol" },
["case.tags"] = new() { ["pt"] = "Tecnologias", ["en"] = "Technologies", ["es"] = "Tecnologías" },
["case.back"] = new() { ["pt"] = "← Voltar para Cases", ["en"] = "← Back to Cases", ["es"] = "← Volver a Casos" },
["case.category"] = new() { ["pt"] = "Categoria", ["en"] = "Category", ["es"] = "Categoría" },
["case.device"] = new() { ["pt"] = "Dispositivo", ["en"] = "Device", ["es"] = "Dispositivo" },
["case.savings"] = new() { ["pt"] = "Economia", ["en"] = "Savings", ["es"] = "Ahorro" },
["case.tags"] = new() { ["pt"] = "Intervenções", ["en"] = "Interventions", ["es"] = "Intervenciones" },
["case.back"] = new() { ["pt"] = "Ver outros cases", ["en"] = "View other cases", ["es"] = "Ver otros casos" },
["case.contact"] = new() { ["pt"] = "Quero esse resultado", ["en"] = "I want this result", ["es"] = "Quiero este resultado" },
// Home Page
["home.seo.title"] = new() { ["pt"] = "CarneiroTech SBC - Upgrade e Manutenção Premium de Notebooks", ["en"] = "CarneiroTech SBC - Premium Laptop Upgrade & Maintenance", ["es"] = "CarneiroTech SBC - Upgrade y Mantenimiento Premium" },
["home.seo.description"] = new() { ["pt"] = "Especialista em upgrades de performance e recuperação de notebooks em São Bernardo do Campo. Atendimento no Golden Square Shopping.", ["en"] = "Specialist in performance upgrades and laptop recovery in SBC. Service at Golden Square Mall.", ["es"] = "Especialista en upgrades de performance y recuperación de laptops en SBC." },
["home.seo.keywords"] = new() { ["pt"] = "manutenção notebook SBC, upgrade ssd notebook, conserto notebook golden square, assistência técnica SBC, ricardo carneiro", ["en"] = "laptop repair SBC, ssd upgrade, golden square mall tech support", ["es"] = "mantenimiento laptop SBC, upgrade ssd, asistencia tecnica SBC" },
["home.masthead.subheading"] = new() { ["pt"] = "Assistência Técnica Premium em SBC", ["en"] = "Premium Tech Support in SBC", ["es"] = "Soporte Técnico Premium en SBC" },
["home.masthead.heading"] = new() { ["pt"] = "Não Descarte seu PC. Faça um Upgrade de Elite!", ["en"] = "Don't Toss Your PC. Elite Upgrade.", ["es"] = "No Descarte su PC. Upgrade de Elite." },
["home.masthead.cta"] = new() { ["pt"] = "Agendar no Golden Square", ["en"] = "Schedule at Golden Square", ["es"] = "Agendar en Golden Square" },
["home.services.title"] = new() { ["pt"] = "Nossas Especialidades", ["en"] = "Our Specialties", ["es"] = "Nuestras Especialidades" },
["home.services.subtitle"] = new() { ["pt"] = "Tratamos seu equipamento como uma peça de engenharia.", ["en"] = "We treat your gear like a piece of engineering.", ["es"] = "Tratamos su equipo como una pieza de ingeniería." },
["home.services.solution.title"] = new() { ["pt"] = "Upgrade de Performance", ["en"] = "Performance Upgrade", ["es"] = "Upgrade de Performance" },
["home.services.solution.desc"] = new() { ["pt"] = "SSDs NVMe de alta velocidade e memórias de baixa latência para dar vida nova ao seu computador.", ["en"] = "High-speed NVMe SSDs and low-latency RAM to breathe new life into your computer.", ["es"] = "SSDs NVMe de alta velocidad y memorias de baja latencia." },
["home.services.consulting.title"] = new() { ["pt"] = "Manutenção Preventiva", ["en"] = "Preventive Maintenance", ["es"] = "Mantenimiento Preventivo" },
["home.services.consulting.desc"] = new() { ["pt"] = "Limpeza interna profunda e troca de pasta térmica industrial (Arctic Silver) para evitar queimas.", ["en"] = "Deep internal cleaning and industrial thermal paste replacement to prevent burnouts.", ["es"] = "Limpieza interna profunda y cambio de pasta térmica industrial." },
["home.services.proposals.title"] = new() { ["pt"] = "Resgate de Carcaça", ["en"] = "Case Rescue", ["es"] = "Rescate de Carcasa" },
["home.services.proposals.desc"] = new() { ["pt"] = "Reparo profissional de dobradiças e plásticos quebrados. Seu notebook abrindo e fechando como novo.", ["en"] = "Professional hinge and broken plastic repair. Your laptop opening and closing like new.", ["es"] = "Reparo profesional de bisagras y plásticos rotos." },
["home.cases.title"] = new() { ["pt"] = "Cases de Sucesso", ["en"] = "Success Cases", ["es"] = "Casos de Éxito" },
["home.cases.subtitle"] = new() { ["pt"] = "Equipamentos condenados que voltaram à vida", ["en"] = "Condemned gear that came back to life", ["es"] = "Equipos condenados que volvieron a la vida" },
["home.cases.empty"] = new() { ["pt"] = "Novos resgates sendo documentados.", ["en"] = "New rescues being documented.", ["es"] = "Nuevos rescates siendo documentados." },
["home.cases.viewall"] = new() { ["pt"] = "Ver todos os resgates", ["en"] = "View all rescues", ["es"] = "Ver todos los rescates" },
["home.about.title"] = new() { ["pt"] = "Por que a CarneiroTech?", ["en"] = "Why CarneiroTech?", ["es"] = "¿Por qué CarneiroTech?" },
["home.about.subtitle"] = new() { ["pt"] = "20 anos de experiência real em hardware.", ["en"] = "20 years of real hardware experience.", ["es"] = "20 años de experiencia real en hardware." },
["home.about.timeline.2000_2005.title"] = new() { ["pt"] = "Paixão pelo Hardware", ["en"] = "Passion for Hardware", ["es"] = "Pasión por el Hardware" },
["home.about.timeline.2000_2005.text"] = new() { ["pt"] = "Início na eletrônica e montagem de PCs de alta performance. Onde tudo começou.", ["en"] = "Start in electronics and high-performance PC building. Where it all began.", ["es"] = "Inicio en la electrónica y montaje de PCs de alta performance." },
["home.about.timeline.2005_2015.title"] = new() { ["pt"] = "Nível Enterprise", ["en"] = "Enterprise Level", ["es"] = "Nivel Enterprise" },
["home.about.timeline.2005_2015.text"] = new() { ["pt"] = "Trabalhando com sistemas críticos onde a falha de hardware significava prejuízos milionários.", ["en"] = "Working with critical systems where hardware failure meant million-dollar losses.", ["es"] = "Trabajando con sistemas críticos." },
["home.about.timeline.2015_2020.title"] = new() { ["pt"] = "Especialista em Diagnóstico", ["en"] = "Diagnostic Specialist", ["es"] = "Especialista en Diagnóstico" },
["home.about.timeline.2015_2020.text"] = new() { ["pt"] = "Foco em resolver problemas complexos que assistências comuns não conseguiam diagnosticar.", ["en"] = "Focus on solving complex problems that common shops couldn't diagnose.", ["es"] = "Enfoque en resolver problemas complejos." },
["home.about.timeline.2020_now.title"] = new() { ["pt"] = "Golden Square SBC", ["en"] = "Golden Square SBC", ["es"] = "Golden Square SBC" },
["home.about.timeline.2020_now.text"] = new() { ["pt"] = "Fundação da CarneiroTech no coração de SBC, trazendo consultoria técnica de elite para o público final.", ["en"] = "CarneiroTech founded in the heart of SBC, bringing elite technical consulting to the end user.", ["es"] = "Fundación de CarneiroTech en el corazón de SBC." },
["home.about.cta.line1"] = new() { ["pt"] = "Seu", ["en"] = "Your", ["es"] = "Su" },
["home.about.cta.line2"] = new() { ["pt"] = "PC Novo", ["en"] = "New PC", ["es"] = "PC Nuevo" },
["home.about.cta.line3"] = new() { ["pt"] = "De Novo!", ["en"] = "Again!", ["es"] = "¡De Nuevo!" },
["home.contact.title"] = new() { ["pt"] = "Agendar Avaliação", ["en"] = "Schedule Evaluation", ["es"] = "Agendar Evaluación" },
["home.contact.subtitle"] = new() { ["pt"] = "Atendimento no Golden Square Shopping", ["en"] = "Service at Golden Square Mall", ["es"] = "Atención en Golden Square Shopping" },
["home.contact.name"] = new() { ["pt"] = "Seu Nome", ["en"] = "Your Name", ["es"] = "Su Nombre" },
["home.contact.email"] = new() { ["pt"] = "Seu Email", ["en"] = "Your Email", ["es"] = "Su Email" },
["home.contact.phone"] = new() { ["pt"] = "Seu WhatsApp", ["en"] = "Your WhatsApp", ["es"] = "Su WhatsApp" },
["home.contact.message"] = new() { ["pt"] = "Modelo do equipamento e sintomas", ["en"] = "Device model and symptoms", ["es"] = "Modelo y síntomas" },
["home.contact.send"] = new() { ["pt"] = "Solicitar Horário", ["en"] = "Request Time Slot", ["es"] = "Solicitar Horario" },
["home.contact.sending"] = new() { ["pt"] = "Enviando...", ["en"] = "Sending...", ["es"] = "Enviando..." },
["home.contact.success"] = new() { ["pt"] = "Solicitação recebida! Faremos o agendamento via WhatsApp.", ["en"] = "Request received! We'll schedule via WhatsApp.", ["es"] = "¡Solicitud recibida!" },
["home.contact.or"] = new() { ["pt"] = "OU", ["en"] = "OR", ["es"] = "O" },
["home.contact.whatsapp"] = new() { ["pt"] = "Chamar no WhatsApp", ["en"] = "WhatsApp Us", ["es"] = "Llamar por WhatsApp" },
["home.contact.whatsapp.subtitle"] = new() { ["pt"] = "Fale agora com o Ricardo", ["en"] = "Talk to Ricardo now", ["es"] = "Hable ahora con Ricardo" },
// Cases UI
["cases.header.subtitle"] = new() { ["pt"] = "Resgates", ["en"] = "Rescues", ["es"] = "Rescates" },
["cases.header.title"] = new() { ["pt"] = "Cases de Sucesso", ["en"] = "Success Cases", ["es"] = "Casos de Éxito" },
["cases.filter.title"] = new() { ["pt"] = "Filtrar por tipo:", ["en"] = "Filter by type:", ["es"] = "Filtrar por tipo:" },
["cases.filter.all"] = new() { ["pt"] = "Todos", ["en"] = "All", ["es"] = "Todos" },
["cases.empty.tag"] = new() { ["pt"] = "Nenhum resgate com \"{0}\".", ["en"] = "No rescues with \"{0}\".", ["es"] = "Sin rescates con \"{0}\"." },
["cases.empty.all"] = new() { ["pt"] = "Novos resgates em breve.", ["en"] = "New rescues soon.", ["es"] = "Nuevos rescates pronto." },
["cases.viewall"] = new() { ["pt"] = "Ver Todos", ["en"] = "View All", ["es"] = "Ver Todos" },
["cases.seo.title"] = new() { ["pt"] = "Resgates de Hardware - CarneiroTech SBC", ["en"] = "Hardware Rescues - CarneiroTech SBC", ["es"] = "Rescates de Hardware" },
["cases.seo.title.tag"] = new() { ["pt"] = "Resgates: {0} - CarneiroTech", ["en"] = "Rescues: {0} - CarneiroTech", ["es"] = "Rescates: {0} - CarneiroTech" },
["cases.seo.description"] = new() { ["pt"] = "Explore como transformamos máquinas condenadas em ferramentas de elite.", ["en"] = "Explore how we turn condemned machines into elite tools.", ["es"] = "Vea cómo transformamos máquinas condenadas." },
["cases.seo.description.tag"] = new() { ["pt"] = "Resgates de hardware relacionados a {0}.", ["en"] = "Hardware rescues related to {0}.", ["es"] = "Rescates relacionados con {0}." },
// Privacy
["privacy.title"] = new() { ["pt"] = "Política de Privacidade", ["en"] = "Privacy Policy", ["es"] = "Política de Privacidad" },
["privacy.body"] = new() { ["pt"] = "Seus dados estão seguros conosco e serão usados apenas para agendamento técnico.", ["en"] = "Your data is safe and will only be used for technical scheduling.", ["es"] = "Sus datos están seguros." },
// WhatsApp message
["whatsapp.message"] = new() {
["pt"] = "[Site] Olá! Eu gostaria de conversar com você sobre uma possível proposta comercial.",
["en"] = "[Website] Hello! I would like to talk to you about a possible business proposal.",
["es"] = "[Sitio] ¡Hola! Me gustaría hablar con usted sobre una posible propuesta comercial."
["pt"] = "Olá! Gostaria de um orçamento para o meu aparelho.",
["en"] = "Hello! I would like a quote for my device.",
["es"] = "¡Hola! Me gustaría um presupuesto para mi equipo."
},
};

View File

@ -9,62 +9,33 @@ public class CaseService : ICaseService
private readonly IMarkdownService _markdownService;
private readonly IMemoryCache _cache;
private readonly IWebHostEnvironment _environment;
private readonly ILanguageService _languageService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly string _casesBasePath;
private const string CACHE_KEY_PREFIX = "cases_";
private readonly string _casesPath;
private const string CACHE_KEY = "cases_retail";
private const int CACHE_MINUTES = 60;
public CaseService(IMarkdownService markdownService, IMemoryCache cache, IWebHostEnvironment environment,
ILanguageService languageService, IHttpContextAccessor httpContextAccessor)
public CaseService(IMarkdownService markdownService, IMemoryCache cache, IWebHostEnvironment environment)
{
_markdownService = markdownService;
_cache = cache;
_environment = environment;
_languageService = languageService;
_httpContextAccessor = httpContextAccessor;
_casesBasePath = Path.Combine(_environment.ContentRootPath, "Content", "Cases");
}
private string GetCurrentLanguage()
{
if (_httpContextAccessor.HttpContext != null)
{
return _languageService.GetCurrentLanguage(_httpContextAccessor.HttpContext);
}
return "pt"; // Default fallback
}
private string GetCasesPath(string language)
{
return Path.Combine(_casesBasePath, language);
_casesPath = Path.Combine(_environment.ContentRootPath, "Content", "Cases", "Retail");
}
public async Task<List<CaseModel>> GetAllCasesAsync()
{
var language = GetCurrentLanguage();
var cacheKey = $"{CACHE_KEY_PREFIX}{language}";
if (_cache.TryGetValue(cacheKey, out List<CaseModel>? cachedCases) && cachedCases != null)
if (_cache.TryGetValue(CACHE_KEY, out List<CaseModel>? cachedCases) && cachedCases != null)
{
return cachedCases;
}
var cases = new List<CaseModel>();
var casesPath = GetCasesPath(language);
if (!Directory.Exists(casesPath))
if (!Directory.Exists(_casesPath))
{
// Try Portuguese as fallback
casesPath = GetCasesPath("pt");
if (!Directory.Exists(casesPath))
{
Directory.CreateDirectory(casesPath);
return cases;
}
}
var markdownFiles = Directory.GetFiles(casesPath, "*.md");
var markdownFiles = Directory.GetFiles(_casesPath, "*.md");
foreach (var file in markdownFiles)
{
@ -75,12 +46,11 @@ public class CaseService : ICaseService
}
}
// Sort by order (ascending) and date (descending)
cases = cases.OrderBy(c => c.Metadata.Order)
.ThenByDescending(c => c.Metadata.Date)
.ToList();
_cache.Set(cacheKey, cases, TimeSpan.FromMinutes(CACHE_MINUTES));
_cache.Set(CACHE_KEY, cases, TimeSpan.FromMinutes(CACHE_MINUTES));
return cases;
}
@ -100,18 +70,13 @@ public class CaseService : ICaseService
public async Task<List<string>> GetAllTagsAsync()
{
var allCases = await GetAllCasesAsync();
var tags = allCases.SelectMany(c => c.Metadata.Tags)
.Distinct()
.OrderBy(t => t)
.ToList();
return tags;
return allCases.SelectMany(c => c.Metadata.Tags).Distinct().OrderBy(t => t).ToList();
}
public async Task<List<CaseModel>> GetCasesByTagAsync(string tag)
{
var allCases = await GetAllCasesAsync();
return allCases.Where(c => c.Metadata.Tags.Contains(tag, StringComparer.OrdinalIgnoreCase))
.ToList();
return allCases.Where(c => c.Metadata.Tags.Contains(tag, StringComparer.OrdinalIgnoreCase)).ToList();
}
private async Task<CaseModel?> ParseCaseFileAsync(string filePath)
@ -127,9 +92,10 @@ public class CaseService : ICaseService
Slug = GetStringValue(frontMatter, "slug"),
Summary = GetStringValue(frontMatter, "summary"),
Client = GetStringValue(frontMatter, "client"),
Industry = GetStringValue(frontMatter, "industry"),
Timeline = GetStringValue(frontMatter, "timeline"),
Role = GetStringValue(frontMatter, "role"),
DeviceModel = GetStringValue(frontMatter, "device_model"),
EstimatedSavings = GetDecimalValue(frontMatter, "estimated_savings"),
Category = GetStringValue(frontMatter, "category"),
Thumbnail = GetStringValue(frontMatter, "thumbnail"),
Image = GetStringValue(frontMatter, "image"),
Tags = GetListValue(frontMatter, "tags"),
Featured = GetBoolValue(frontMatter, "featured"),
@ -140,69 +106,31 @@ public class CaseService : ICaseService
SeoKeywords = GetStringValue(frontMatter, "seo_keywords")
};
var htmlContent = _markdownService.ConvertToHtml(bodyContent);
return new CaseModel
{
Metadata = metadata,
ContentHtml = htmlContent,
ContentHtml = _markdownService.ConvertToHtml(bodyContent),
ContentMarkdown = bodyContent
};
}
catch (Exception)
{
// Log error here if needed
return null;
}
catch { return null; }
}
private string GetStringValue(Dictionary<string, object> dict, string key)
{
return dict.ContainsKey(key) ? dict[key]?.ToString() ?? string.Empty : string.Empty;
}
private List<string> GetListValue(Dictionary<string, object> dict, string key)
{
if (!dict.ContainsKey(key)) return new List<string>();
if (dict[key] is List<object> list)
{
return list.Select(item => item?.ToString() ?? string.Empty).ToList();
}
return new List<string>();
}
private bool GetBoolValue(Dictionary<string, object> dict, string key)
{
if (!dict.ContainsKey(key)) return false;
var value = dict[key]?.ToString()?.ToLower();
return value == "true" || value == "yes" || value == "1";
}
private int GetIntValue(Dictionary<string, object> dict, string key)
{
if (!dict.ContainsKey(key)) return 0;
if (int.TryParse(dict[key]?.ToString(), out int result))
{
return result;
}
return 0;
}
private DateTime GetDateValue(Dictionary<string, object> dict, string key)
{
if (!dict.ContainsKey(key)) return DateTime.MinValue;
var dateString = dict[key]?.ToString();
if (DateTime.TryParse(dateString, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result))
{
return result;
}
return DateTime.MinValue;
}
private string GetStringValue(Dictionary<string, object> dict, string key) =>
dict.ContainsKey(key) ? dict[key]?.ToString() ?? string.Empty : string.Empty;
private decimal GetDecimalValue(Dictionary<string, object> dict, string key) =>
dict.ContainsKey(key) && decimal.TryParse(dict[key]?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out decimal res) ? res : 0;
private List<string> GetListValue(Dictionary<string, object> dict, string key) =>
dict.ContainsKey(key) && dict[key] is List<object> list ? list.Select(i => i?.ToString() ?? "").ToList() : new();
private bool GetBoolValue(Dictionary<string, object> dict, string key) =>
dict.ContainsKey(key) && (dict[key]?.ToString()?.ToLower() == "true" || dict[key]?.ToString() == "1");
private int GetIntValue(Dictionary<string, object> dict, string key) =>
dict.ContainsKey(key) && int.TryParse(dict[key]?.ToString(), out int res) ? res : 0;
private DateTime GetDateValue(Dictionary<string, object> dict, string key) =>
dict.ContainsKey(key) && DateTime.TryParse(dict[key]?.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime res) ? res : DateTime.MinValue;
}

View File

@ -29,10 +29,8 @@ namespace CarneiroTech.Services
// 2. Detect from browser Accept-Language header
var browserLanguage = DetectBrowserLanguage(context);
if (!string.IsNullOrEmpty(browserLanguage))
if (!string.IsNullOrEmpty(browserLanguage) && _supportedLanguages.Contains(browserLanguage))
{
// Save detected language to cookie
SetLanguage(context, browserLanguage);
return browserLanguage;
}
@ -48,7 +46,7 @@ namespace CarneiroTech.Services
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
HttpOnly = false, // Allow JavaScript to read for client-side logic
Secure = true,
Secure = context.Request.IsHttps,
SameSite = SameSiteMode.Lax,
Path = "/"
};
@ -63,7 +61,7 @@ namespace CarneiroTech.Services
if (string.IsNullOrEmpty(acceptLanguageHeader))
{
return DefaultLanguage;
return string.Empty;
}
// Parse Accept-Language header
@ -84,7 +82,7 @@ namespace CarneiroTech.Services
}
}
return DefaultLanguage;
return string.Empty;
}
}
}

View File

@ -1,4 +1,7 @@
@model CarneiroTech.Models.CaseModel
@{
var lang = LanguageService.GetCurrentLanguage(Context);
}
<!-- Case Detail Header -->
<section class="page-section" style="padding-top: 150px;">
@ -14,25 +17,29 @@
@if (!string.IsNullOrEmpty(Model.Metadata.Client))
{
<div class="col-md-3 mb-2">
<strong>Cliente:</strong> @Model.Metadata.Client
<i class="fas fa-user-tie text-primary me-1"></i>
<strong>@SiteStrings.Get("case.client", lang):</strong> @Model.Metadata.Client
</div>
}
@if (!string.IsNullOrEmpty(Model.Metadata.Industry))
@if (!string.IsNullOrEmpty(Model.Metadata.DeviceModel))
{
<div class="col-md-3 mb-2">
<strong>Indústria:</strong> @Model.Metadata.Industry
<i class="fas fa-laptop text-primary me-1"></i>
<strong>@SiteStrings.Get("case.device", lang):</strong> @Model.Metadata.DeviceModel
</div>
}
@if (!string.IsNullOrEmpty(Model.Metadata.Timeline))
@if (!string.IsNullOrEmpty(Model.Metadata.Category))
{
<div class="col-md-3 mb-2">
<strong>Timeline:</strong> @Model.Metadata.Timeline
<i class="fas fa-tag text-primary me-1"></i>
<strong>@SiteStrings.Get("case.category", lang):</strong> @Model.Metadata.Category
</div>
}
@if (!string.IsNullOrEmpty(Model.Metadata.Role))
@if (Model.Metadata.EstimatedSavings > 0)
{
<div class="col-md-3 mb-2">
<strong>Meu Role:</strong> @Model.Metadata.Role
<i class="fas fa-piggy-bank text-success me-1"></i>
<strong>@SiteStrings.Get("case.savings", lang):</strong> R$ @Model.Metadata.EstimatedSavings.ToString("N2")
</div>
}
</div>
@ -63,10 +70,10 @@
<div class="row mt-5">
<div class="col-lg-12 text-center">
<a href="/cases" class="btn btn-outline-primary btn-lg">
<i class="fas fa-arrow-left me-2"></i>Ver Todos os Cases
<i class="fas fa-arrow-left me-2"></i>@SiteStrings.Get("case.back", lang)
</a>
<a href="/#contact" class="btn btn-primary btn-lg ms-2">
<i class="fas fa-envelope me-2"></i>Vamos Conversar
<i class="fas fa-calendar-check me-2"></i>Agendar no Golden Square Shopping
</a>
</div>
</div>

View File

@ -1,5 +1,6 @@
@model List<CarneiroTech.Models.CaseModel>
@{
var lang = LanguageService.GetCurrentLanguage(Context);
var selectedTag = ViewData["SelectedTag"] as string;
var allTags = ViewBag.AllTags as List<string> ?? new List<string>();
}
@ -7,8 +8,8 @@
<!-- Page Header-->
<header class="masthead" style="padding: 150px 0 100px;">
<div class="container">
<div class="masthead-subheading">Portfolio</div>
<div class="masthead-heading text-uppercase">Nossos Cases</div>
<div class="masthead-subheading">@SiteStrings.Get("cases.header.subtitle", lang)</div>
<div class="masthead-heading text-uppercase">@SiteStrings.Get("cases.header.title", lang)</div>
</div>
</header>
@ -20,9 +21,9 @@
{
<div class="row mb-5">
<div class="col-12 text-center">
<h5 class="mb-3">Filtrar por tecnologia:</h5>
<h5 class="mb-3">@SiteStrings.Get("cases.filter.title", lang)</h5>
<div class="btn-group-wrap">
<a href="/cases" class="btn @(string.IsNullOrEmpty(selectedTag) ? "btn-primary" : "btn-outline-primary") m-1">Todos</a>
<a href="/cases" class="btn @(string.IsNullOrEmpty(selectedTag) ? "btn-primary" : "btn-outline-primary") m-1">@SiteStrings.Get("cases.filter.all", lang)</a>
@foreach (var tag in allTags)
{
<a href="/cases?tag=@tag" class="btn @(selectedTag == tag ? "btn-primary" : "btn-outline-primary") m-1">@tag</a>
@ -40,29 +41,43 @@
{
<div class="col-lg-4 col-sm-6 mb-4">
<!-- Portfolio item -->
<div class="portfolio-item">
<div class="portfolio-item h-100 shadow-sm border-0">
<a class="portfolio-link" href="/cases/@caseItem.Metadata.Slug">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<p class="text-white fw-bold px-3">@caseItem.Metadata.Summary</p>
</div>
</div>
@if (!string.IsNullOrEmpty(caseItem.Metadata.Image))
@{
var imgPath = !string.IsNullOrEmpty(caseItem.Metadata.Thumbnail) ? caseItem.Metadata.Thumbnail : caseItem.Metadata.Image;
}
@if (!string.IsNullOrEmpty(imgPath))
{
<img class="img-fluid" src="@caseItem.Metadata.Image" alt="@caseItem.Metadata.Title" />
<img class="img-fluid w-100" src="@imgPath" alt="@caseItem.Metadata.Title" style="height: 250px; object-fit: cover;" />
}
else
{
<div style="height: 300px; background: linear-gradient(135deg, #C42127 0%, #8B1E23 100%);"></div>
<div class="d-flex align-items-center justify-content-center" style="height: 250px; background: linear-gradient(135deg, #C42127 0%, #8B1E23 100%);">
<i class="fas fa-laptop-code fa-3x text-white opacity-50"></i>
</div>
}
</a>
<div class="portfolio-caption">
<div class="portfolio-caption-heading">@caseItem.Metadata.Title</div>
<div class="portfolio-caption-subheading text-muted">@caseItem.Metadata.Industry</div>
<div class="portfolio-caption text-start p-3">
<div class="badge bg-primary mb-2">@caseItem.Metadata.Category</div>
<div class="portfolio-caption-heading h5 fw-bold mb-1">@caseItem.Metadata.Title</div>
<div class="portfolio-caption-subheading text-muted small mb-2">
<i class="fas fa-microchip me-1"></i> @caseItem.Metadata.DeviceModel
</div>
<div class="bg-light p-2 rounded border-start border-4 border-success mb-3">
<span class="text-muted small d-block">@SiteStrings.Get("case.savings", lang) estimada</span>
<span class="text-success fw-bold h5 mb-0">R$ @caseItem.Metadata.EstimatedSavings.ToString("N2")</span>
</div>
<div class="mt-2">
@foreach (var tag in caseItem.Metadata.Tags.Take(3))
{
<span class="badge bg-secondary me-1">@tag</span>
<span class="badge bg-secondary opacity-75 me-1">@tag</span>
}
</div>
</div>
@ -76,14 +91,14 @@
<p class="lead text-muted">
@if (!string.IsNullOrEmpty(selectedTag))
{
<span>Nenhum case encontrado para a tag "@selectedTag".</span>
<span>@string.Format(SiteStrings.Get("cases.empty.tag", lang), selectedTag)</span>
}
else
{
<span>Em breve, novos cases serão adicionados.</span>
<span>@SiteStrings.Get("cases.empty.all", lang)</span>
}
</p>
<a href="/cases" class="btn btn-primary">Ver Todos os Cases</a>
<a href="/cases" class="btn btn-primary">@SiteStrings.Get("cases.viewall", lang)</a>
</div>
}
</div>

View File

@ -1,14 +1,18 @@
@using System.Text.Json
@model List<CarneiroTech.Models.CaseModel>
@{
ViewData["Title"] = ViewData["Title"] ?? "Carneiro Tech - Solution Design & Technical Consulting";
ViewData["Title"] = "CarneiroTech SBC - Assistência Técnica Premium e Upgrades";
ViewData["Description"] = "Não descarte seu notebook. Upgrades de performance e resgate de carcaça em São Bernardo do Campo. Atendimento exclusivo no Golden Square Shopping.";
}
<!-- Masthead-->
<header class="masthead">
<div class="container">
<div class="masthead-subheading">Solution Design & Technical Consulting</div>
<div class="masthead-heading text-uppercase">Conectando Negócio e Tecnologia</div>
<a class="btn btn-primary btn-xl text-uppercase" href="#services">Saiba Mais</a>
<div class="masthead-subheading">Assistência Técnica Premium em SBC</div>
<br/>
<div class="masthead-heading text-uppercase">Não descarte seu notebook.</div>
<div class="masthead-heading text-uppercase">Faça um Upgrade de Elite!</div>
<a class="btn btn-primary btn-xl text-uppercase shadow" href="#contact">Agendar no Golden Square</a>
</div>
</header>
@ -16,33 +20,33 @@
<section class="page-section" id="services">
<div class="container">
<div class="text-center">
<h2 class="section-heading text-uppercase">Serviços</h2>
<h3 class="section-subheading text-muted">20+ anos conectando negócio e tecnologia</h3>
<h2 class="section-heading text-uppercase">Nossas Especialidades</h2>
<h3 class="section-subheading text-muted">Tratamos seu equipamento como uma peça de engenharia, não apenas um eletrodoméstico.</h3>
</div>
<div class="row text-center">
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-lightbulb fa-stack-1x fa-inverse"></i>
<i class="fas fa-bolt fa-stack-1x fa-inverse"></i>
</span>
<h4 class="my-3">Solution Design</h4>
<p class="text-muted">Desenho de soluções técnicas que conectam objetivos de negócio com arquitetura de sistemas. Experiência com integrações SAP, arquiteturas enterprise e modernização de legados.</p>
<h4 class="my-3">Upgrade de Performance</h4>
<p class="text-muted">Transformamos máquinas lentas em foguetes com SSDs NVMe e memórias de alta velocidade. Performance superior a um PC novo por uma fração do preço.</p>
</div>
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-clipboard-check fa-stack-1x fa-inverse"></i>
<i class="fas fa-shield-alt fa-stack-1x fa-inverse"></i>
</span>
<h4 class="my-3">Technical Consulting</h4>
<p class="text-muted">Assessoria técnica para tomada de decisão: definição de MVP, priorização de backlog, análise de viabilidade técnica e due diligence de produtos digitais.</p>
<h4 class="my-3">Limpeza Técnica & Térmica</h4>
<p class="text-muted">Troca de pasta térmica industrial e remoção de poeira. Evitamos que seu processador queime por superaquecimento e garantimos silêncio total.</p>
</div>
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-code fa-stack-1x fa-inverse"></i>
<i class="fas fa-tools fa-stack-1x fa-inverse"></i>
</span>
<h4 class="my-3">Technical Proposals</h4>
<p class="text-muted">Elaboração de propostas técnicas detalhadas: estimativas, arquitetura, tecnologias, riscos e roadmap de implementação para projetos complexos.</p>
<h4 class="my-3">Resgate de Carcaça</h4>
<p class="text-muted">Reparo profissional de dobradiças e plásticos quebrados com resina industrial. Recuperamos o que outras assistências mandam para o lixo.</p>
</div>
</div>
</div>
@ -52,109 +56,137 @@
<section class="page-section bg-light" id="portfolio">
<div class="container">
<div class="text-center">
<h2 class="section-heading text-uppercase">Cases</h2>
<h3 class="section-subheading text-muted">Projetos que transformaram negócios</h3>
<h2 class="section-heading text-uppercase">Provas de Ressurreição</h2>
<h3 class="section-subheading text-muted">Veja como economizamos milhares de reais para nossos clientes de São Bernardo do Campo.</h3>
</div>
@if (Model != null && Model.Any(m => m.Metadata.Slug.Contains("vostro")))
{
var featuredCase = Model.First(m => m.Metadata.Slug.Contains("vostro"));
<div class="row mb-5">
<div class="col-12">
<div class="card border-0 shadow-lg overflow-hidden" style="border-radius: 15px;">
<div class="row g-0">
<div class="col-md-6">
<img src="/img/Depois.png"
class="img-fluid h-100" alt="Resultado do Resgate" style="object-fit: cover; min-height: 350px;">
</div>
<div class="col-md-6 d-flex align-items-center">
<div class="card-body p-4 p-lg-5">
<div class="badge bg-danger mb-2">DESTAQUE: RESGATE EM SBC</div>
<h2 class="card-title fw-bold">@featuredCase.Metadata.Title</h2>
<p class="card-text lead text-muted">@featuredCase.Metadata.Summary</p>
<div class="bg-success bg-opacity-10 p-3 rounded border-start border-4 border-success mb-4">
<span class="text-success fw-bold h4 mb-0">Economia Real: R$ @featuredCase.Metadata.EstimatedSavings.ToString("N2")</span>
</div>
<a href="/cases/@featuredCase.Metadata.Slug" class="btn btn-primary btn-xl text-uppercase">Ver como foi feito</a>
</div>
</div>
</div>
</div>
</div>
</div>
}
<div class="row">
@if (Model != null && Model.Any())
{
foreach (var caseItem in Model.Take(6))
foreach (var caseItem in Model.Where(m => !m.Metadata.Slug.Contains("vostro")).Take(3))
{
<div class="col-lg-4 col-sm-6 mb-4">
<!-- Portfolio item -->
<div class="portfolio-item">
<div class="portfolio-item h-100 shadow-sm">
<a class="portfolio-link" href="/cases/@caseItem.Metadata.Slug">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<p class="text-white fw-bold">@caseItem.Metadata.Summary</p>
<div class="portfolio-hover-content"><i class="fas fa-plus fa-3x"></i></div>
</div>
</div>
@if (!string.IsNullOrEmpty(caseItem.Metadata.Image))
{
<img class="img-fluid" src="@caseItem.Metadata.Image" alt="@caseItem.Metadata.Title" />
}
else
{
<div style="height: 300px; background: linear-gradient(135deg, #C42127 0%, #8B1E23 100%);"></div>
@{
var imgPath = !string.IsNullOrEmpty(caseItem.Metadata.Thumbnail) ? caseItem.Metadata.Thumbnail : caseItem.Metadata.Image;
}
<img class="img-fluid w-100" src="@imgPath" alt="..." style="height: 200px; object-fit: cover;" />
</a>
<div class="portfolio-caption">
<div class="portfolio-caption-heading">@caseItem.Metadata.Title</div>
<div class="portfolio-caption-subheading text-muted">@caseItem.Metadata.Industry</div>
<div class="portfolio-caption text-start p-3">
<div class="portfolio-caption-heading h6 fw-bold">@caseItem.Metadata.Title</div>
<div class="portfolio-caption-subheading text-muted small">@caseItem.Metadata.Category</div>
<div class="mt-2 text-success fw-bold small">Economia: R$ @caseItem.Metadata.EstimatedSavings.ToString("N2")</div>
</div>
</div>
</div>
}
}
else
{
<div class="col-12 text-center">
<p class="text-muted">Em breve, novos cases serão adicionados.</p>
</div>
}
</div>
<div class="text-center mt-4">
<a class="btn btn-primary btn-xl text-uppercase" href="/cases">Ver Todos os Cases</a>
</div>
</div>
</section>
<!-- About-->
<!-- Como Funciona -->
<section class="page-section" id="about">
<div class="container">
<div class="text-center">
<h2 class="section-heading text-uppercase">Sobre</h2>
<h3 class="section-subheading text-muted">Ricardo Carneiro - Carneiro Tech</h3>
<h2 class="section-heading text-uppercase">Como Funciona nosso Atendimento</h2>
<h3 class="section-subheading text-muted">Transparência total e engenharia de precisão para seu equipamento.</h3>
<div class="alert alert-primary d-inline-block mb-5 shadow-sm" style="border-radius: 50px; padding: 10px 30px;">
<i class="fas fa-bolt me-2 text-warning"></i> <strong>Agilidade:</strong> Diagnóstico e orçamento possíveis em até <strong>24 horas</strong> (dependendo da complexidade do caso).
</div>
</div>
<ul class="timeline">
<li>
<div class="timeline-image"><i class="fas fa-graduation-cap fa-3x text-white"></i></div>
<div class="timeline-image"><i class="fas fa-calendar-check fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>2000-2005</h4>
<h4 class="subheading">Início da Jornada</h4>
<h4>Passo 1</h4>
<h4 class="subheading">Agendamento no Golden Square</h4>
</div>
<div class="timeline-body"><p class="text-muted">Primeiros passos na tecnologia: desenvolvimento web, banco de dados e sistemas corporativos. Formação sólida em Engenharia de Software.</p></div>
<div class="timeline-body"><p class="text-muted">Você escolhe o melhor horário para deixar seu equipamento em nosso ponto de atendimento exclusivo no Golden Square Shopping, São Bernardo do Campo.</p></div>
</div>
</li>
<li class="timeline-inverted">
<div class="timeline-image"><i class="fas fa-building fa-3x text-white"></i></div>
<div class="timeline-image"><i class="fas fa-camera fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>2005-2015</h4>
<h4 class="subheading">Enterprise & SAP</h4>
<h4>Passo 2</h4>
<h4 class="subheading">Checklist e Laudo Digital</h4>
</div>
<div class="timeline-body"><p class="text-muted">Especialização em integrações SAP e arquiteturas enterprise. Projetos em multinacionais nos setores healthcare, varejo e manufatura.</p></div>
<div class="timeline-body"><p class="text-muted">Sua segurança em primeiro lugar. Tiramos fotos e emitimos um laudo de recebimento detalhando o estado atual do seu equipamento na hora, enviado direto pro seu WhatsApp.</p></div>
</div>
</li>
<li>
<div class="timeline-image"><i class="fas fa-rocket fa-3x text-white"></i></div>
<div class="timeline-image"><i class="fas fa-microchip fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>2015-2020</h4>
<h4 class="subheading">Digital Transformation</h4>
<h4>Passo 3</h4>
<h4 class="subheading">Diagnóstico de Engenharia</h4>
</div>
<div class="timeline-body"><p class="text-muted">Liderança técnica em transformação digital: cloud migration, modernização de legados e implementação de metodologias ágeis.</p></div>
<div class="timeline-body"><p class="text-muted">Nada de "tentativa e erro". Analisamos a eletrônica e o software do seu PC com ferramentas avançadas para identificar o problema real e a melhor solução com a maior agilidade possível.</p></div>
</div>
</li>
<li class="timeline-inverted">
<div class="timeline-image"><i class="fas fa-handshake fa-3x text-white"></i></div>
<div class="timeline-image"><i class="fas fa-file-invoice-dollar fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>2020-Hoje</h4>
<h4 class="subheading">Consultoria Independente</h4>
<h4>Passo 4</h4>
<h4 class="subheading">Orçamento Transparente</h4>
</div>
<div class="timeline-body"><p class="text-muted">Fundação da Carneiro Tech: consultoria especializada em Solution Design, Technical Proposals e Due Diligence para empresas de diversos portes.</p></div>
<div class="timeline-body"><p class="text-muted">Enviamos o diagnóstico e o orçamento detalhado via WhatsApp. Você só aprova se o valor fizer sentido para a vida útil do seu computador.</p></div>
</div>
</li>
<li>
<div class="timeline-image"><i class="fas fa-shipping-fast fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>Passo 5</h4>
<h4 class="subheading">Entrega e Performance</h4>
</div>
<div class="timeline-body"><p class="text-muted">Você retira sua máquina ressurgida, limpa e com orientações técnicas de uso para garantir que ela dure por mais muitos anos com performance máxima.</p></div>
</div>
</li>
<li class="timeline-inverted">
<div class="timeline-image">
<h4 style="vertical-align:central">
Vamos
<h4>
PC Novo
<br />
Trabalhar
De
<br />
Juntos!
Novo!
</h4>
</div>
</li>
@ -166,13 +198,10 @@
<section class="page-section" id="contact">
<div class="container">
<div class="text-center">
<h2 class="section-heading text-uppercase">Contato</h2>
<h3 class="section-subheading text-white">Vamos conversar sobre seu desafio técnico</h3>
<h2 class="section-heading text-uppercase">Agendar Avaliação</h2>
<h3 class="section-subheading text-white">Atendimento exclusivo com hora marcada no Golden Square Shopping.</h3>
</div>
<!-- Alert messages -->
<div id="contactAlert" class="alert text-center" style="display: none;"></div>
<form id="contactForm">
<div class="row align-items-stretch mb-5">
<div class="col-md-6">
@ -183,120 +212,61 @@
<input class="form-control" id="contactEmail" name="email" type="email" placeholder="Seu Email *" required />
</div>
<div class="form-group mb-md-0">
<input class="form-control" id="contactPhone" name="phone" type="tel" placeholder="Seu Telefone" />
<input class="form-control" id="contactPhone" name="phone" type="tel" placeholder="Seu WhatsApp (com DDD) *" required />
</div>
</div>
<div class="col-md-6">
<div class="form-group form-group-textarea mb-md-0">
<textarea class="form-control" id="contactMessage" name="message" placeholder="Sua Mensagem *" style="height: 100%;" required></textarea>
<textarea class="form-control" id="contactMessage" name="message" placeholder="Qual o modelo do seu equipamento e qual o problema principal? *" style="height: 100%;" required></textarea>
</div>
</div>
</div>
<div class="text-center">
<button class="btn btn-primary btn-xl text-uppercase" id="submitButton" type="submit">
<span id="buttonText">Enviar Mensagem</span>
<span id="buttonText">Solicitar Horário</span>
<span id="buttonSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;" role="status" aria-hidden="true"></span>
</button>
</div>
</form>
<!-- WhatsApp Contact Option -->
<div class="text-center mt-5">
<div class="mb-3">
<span class="text-white-50" style="font-size: 1.1rem;">OU</span>
</div>
<a href="https://wa.me/5511970792602?text=%5BSite%5D%20Ol%C3%A1%21%20Eu%20gostaria%20de%20conversar%20com%20voc%C3%AA%20sobre%20uma%20poss%C3%ADvel%20proposta%20comercial."
class="btn btn-success btn-xl text-uppercase"
<a href="https://wa.me/5511976822169?text=Olá! Gostaria de um orçamento para o meu aparelho."
class="btn btn-success btn-xl text-uppercase shadow-lg"
target="_blank"
style="background-color: #25D366; border-color: #25D366;">
<i class="fab fa-whatsapp me-2"></i>
Falar via WhatsApp
<i class="fab fa-whatsapp me-2"></i> (11) 97682-2169
</a>
<div class="mt-2">
<small class="text-white-50">Resposta rápida e direta</small>
<small class="text-white-50">Atendimento imediato via WhatsApp</small>
</div>
</div>
</div>
</section>
@section Scripts {
<script>
document.getElementById('contactForm').addEventListener('submit', function(e) {
e.preventDefault();
const form = this;
const submitButton = document.getElementById('submitButton');
const buttonText = document.getElementById('buttonText');
const buttonSpinner = document.getElementById('buttonSpinner');
const alertDiv = document.getElementById('contactAlert');
// Get form data
const formData = new FormData(form);
const data = {
name: formData.get('name'),
email: formData.get('email'),
phone: formData.get('phone') || '',
message: formData.get('message')
};
console.log('Sending form data:', data);
// Show loading state
submitButton.disabled = true;
buttonText.textContent = 'Enviando...';
buttonSpinner.style.display = 'inline-block';
alertDiv.style.display = 'none';
// Create form and submit via POST (Google Apps Script accepts form data)
const scriptUrl = 'https://script.google.com/macros/s/AKfycbwYIzI1TGYEKYmkUhhdvNDQcrpInwNJ9Olk24KLZYEb4AycMaGao2qbfk2gmPsp9yUZ4A/exec';
// Create a hidden iframe to submit the form
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.name = 'contact-frame';
document.body.appendChild(iframe);
// Create a temporary form
const tempForm = document.createElement('form');
tempForm.method = 'POST';
tempForm.action = scriptUrl;
tempForm.target = 'contact-frame';
// Add form fields
Object.keys(data).forEach(key => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = data[key];
tempForm.appendChild(input);
});
// Add to document and submit
document.body.appendChild(tempForm);
// Set timeout to show success message
const timeout = setTimeout(() => {
console.log('Form submitted successfully (timeout)');
// Success - assume it worked after 2 seconds
alertDiv.className = 'alert alert-success text-center';
alertDiv.textContent = 'Mensagem enviada com sucesso! Entraremos em contato em breve.';
alertDiv.style.display = 'block';
// Simulação de envio (Google Script seria aqui)
setTimeout(() => {
alert('Solicitação enviada! Entraremos em contato via WhatsApp em instantes.');
form.reset();
// Scroll to alert
alertDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Reset button state
submitButton.disabled = false;
buttonText.textContent = 'Enviar Mensagem';
buttonText.textContent = 'Solicitar Horário';
buttonSpinner.style.display = 'none';
// Cleanup
document.body.removeChild(tempForm);
document.body.removeChild(iframe);
}, 2000);
// Submit the form
tempForm.submit();
});
</script>
}

View File

@ -1,6 +1,7 @@
@{
ViewData["Title"] = "Privacy Policy";
var lang = LanguageService.GetCurrentLanguage(Context);
ViewData["Title"] = SiteStrings.Get("privacy.title", lang);
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
<p>@SiteStrings.Get("privacy.body", lang)</p>

View File

@ -1,4 +1,7 @@
<!DOCTYPE html>
@{
var lang = "pt"; // Site agora estritamente em PT-BR
}
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
@ -7,7 +10,7 @@
<!-- SEO Meta Tags -->
<title>@ViewData["Title"]</title>
<meta name="description" content="@ViewData["Description"]" />
<meta name="keywords" content="@ViewData["Keywords"]" />
<meta name="keywords" content="manutenção de notebook SBC, conserto de pc São Bernardo, upgrade SSD, reparo de carcaça notebook, Golden Square Shopping" />
<meta name="author" content="Ricardo Carneiro - CarneiroTech" />
<link rel="canonical" href="@($"https://carneirotech.com{Context.Request.Path}")" />
@ -16,15 +19,9 @@
<meta property="og:url" content="@($"https://carneirotech.com{Context.Request.Path}")" />
<meta property="og:title" content="@ViewData["Title"]" />
<meta property="og:description" content="@ViewData["Description"]" />
<meta property="og:image" content="@(ViewData["OgImage"] ?? "https://carneirotech.com/img/logo.svg")" />
<meta property="og:image" content="https://carneirotech.com/img/logo.svg" />
<meta property="og:locale" content="pt_BR" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="@ViewData["Title"]" />
<meta name="twitter:description" content="@ViewData["Description"]" />
<meta name="twitter:image" content="@(ViewData["OgImage"] ?? "https://carneirotech.com/img/logo.svg")" />
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="~/favicon.ico" />
@ -38,37 +35,32 @@
<!-- Custom CSS -->
<link href="~/css/custom.css" rel="stylesheet" />
<!-- JSON-LD Structured Data -->
<!-- JSON-LD Structured Data (Local Business) -->
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "ProfessionalService",
"name": "Carneiro Tech",
"description": "Solution Design & Technical Consulting",
"@@type": "ComputerRepairService",
"name": "CarneiroTech SBC",
"description": "Assistência Técnica Premium e Upgrades de Performance no Golden Square Shopping.",
"url": "https://carneirotech.com",
"logo": "https://carneirotech.com/img/logo.svg",
"image": "https://carneirotech.com/img/logo.svg",
"priceRange": "$$",
"address": {
"@@type": "PostalAddress",
"streetAddress": "Av. Kennedy, 700 - Jardim do Mar",
"addressLocality": "São Bernardo do Campo",
"addressRegion": "SP",
"postalCode": "09726-253",
"addressCountry": "BR"
},
"sameAs": [
"https://linkedin.com/in/ricardo-carneiro"
]
"geo": {
"@@type": "GeoCoordinates",
"latitude": -23.6823,
"longitude": -46.5511
},
"openingHours": "Mo-Sa 10:00-22:00, Su 14:00-20:00",
"telephone": "+5511976822169"
}
</script>
<!-- Microsoft Clarity -->
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "up7yoisy52");
</script>
</head>
<body id="page-top">
<!-- Navigation-->
@ -84,19 +76,14 @@
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
<li class="nav-item"><a class="nav-link" href="/#services">Serviços</a></li>
<li class="nav-item"><a class="nav-link" href="/cases">Cases</a></li>
<li class="nav-item"><a class="nav-link" href="/#about">Sobre</a></li>
<li class="nav-item"><a class="nav-link" href="/#contact">Contato</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-globe"></i>
<li class="nav-item"><a class="nav-link" href="/#services">Especialidades</a></li>
<li class="nav-item"><a class="nav-link" href="/cases">Resgates</a></li>
<li class="nav-item"><a class="nav-link" href="/#about">Como Funciona</a></li>
<li class="nav-item"><a class="nav-link" href="/#contact">Agendar</a></li>
<li class="nav-item">
<a class="nav-link text-success fw-bold" href="https://wa.me/5511976822169?text=Olá! Gostaria de um orçamento para o meu aparelho." target="_blank">
<i class="fab fa-whatsapp me-1"></i> WhatsApp
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="languageDropdown">
<li><a class="dropdown-item" href="#" onclick="changeLanguage('pt'); return false;">🇧🇷 Português</a></li>
<li><a class="dropdown-item" href="#" onclick="changeLanguage('en'); return false;">🇺🇸 English</a></li>
<li><a class="dropdown-item" href="#" onclick="changeLanguage('es'); return false;">🇪🇸 Español</a></li>
</ul>
</li>
</ul>
</div>
@ -110,12 +97,14 @@
<footer class="footer py-4">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-4 text-lg-start">Copyright &copy; @DateTime.Now.Year Carneiro Tech - Ricardo Carneiro</div>
<div class="col-lg-4 text-lg-start">Copyright © CarneiroTech @DateTime.Now.Year</div>
<div class="col-lg-4 my-3 my-lg-0">
<a class="btn btn-dark btn-social mx-2" href="https://wa.me/5511976822169?text=Olá! Gostaria de um orçamento para o meu aparelho." aria-label="WhatsApp" target="_blank"><i class="fab fa-whatsapp"></i></a>
<a class="btn btn-dark btn-social mx-2" href="https://linkedin.com/in/ricardo-carneiro" aria-label="LinkedIn" target="_blank"><i class="fab fa-linkedin-in"></i></a>
<span class="ms-3 fw-bold text-dark">(11) 97682-2169</span>
</div>
<div class="col-lg-4 text-lg-end">
<a class="link-dark text-decoration-none me-3" asp-controller="Home" asp-action="Privacy">Privacy Policy</a>
<a class="link-dark text-decoration-none me-3" asp-controller="Home" asp-action="Privacy">Privacidade</a>
</div>
</div>
</div>
@ -126,31 +115,6 @@
<!-- Core theme JS-->
<script src="~/js/scripts.js"></script>
<!-- Language Switcher -->
<script>
function changeLanguage(lang) {
// Create form to submit language change
const form = document.createElement('form');
form.method = 'POST';
form.action = '/Language/SetLanguage';
const langInput = document.createElement('input');
langInput.type = 'hidden';
langInput.name = 'language';
langInput.value = lang;
form.appendChild(langInput);
const returnUrlInput = document.createElement('input');
returnUrlInput.type = 'hidden';
returnUrlInput.name = 'returnUrl';
returnUrlInput.value = window.location.pathname + window.location.search + window.location.hash;
form.appendChild(returnUrlInput);
document.body.appendChild(form);
form.submit();
}
</script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@ -1,3 +1,6 @@
@using CarneiroTech
@using CarneiroTech.Models
@using CarneiroTech.Resources
@using CarneiroTech.Services
@inject ILanguageService LanguageService
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

BIN
wwwroot/img/Completa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
wwwroot/img/Depois.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB