Si querés una sola oración: las partes que hacen que esta app móvil se sienta viva — chat, push, OTP, medios — no pueden correr en Podio, y las partes que sí podrían correr ahí llegarían lisiadas por los rate limits per-user.
Mover las funciones pesadas de DSF a Podio no es viable. Podio es una plataforma de work-management cuya envoltura técnica — 1 000 llamadas por usuario por hora, 100 MB por archivo, sin chunked uploads, sin real-time desde el bearer móvil, tope de 100 fields por app, sin compute server-side — fue diseñada para flujos CRM staff-side con unos pocos miles de records.
Lo que sí puede moverse: perfil, metadatos low-volume de tareas y casos, status workflows simples, almacenamiento de documentos. Lo que tiene que quedarse: chat, push, OTP, procesamiento de medios, almacenamiento particionado, búsqueda FTS, y los queue workers detrás de los ciclos MEP y Amparo. El desenlace honesto es un híbrido: DSF3 sigue siendo el system of record; Podio se vuelve el workspace agent-side, espejado vía webhooks.
Routes auditadas
644api
sobre la superficie dsf3/backend Laravel, mayo 2026
Tope de Podio
1k/hr/user
250/hr en endpoints rate-limited; per user, per key
Features móviles
15blockers
Capacidades que Podio no puede entregar del todo
Costo realista de seats
$28k/mes
2 000 leads activos × Plus tier, si fueran usuarios de Podio
§ 01El perfil de carga hoy
Antes de medir Podio tenés que medir lo que tendría que absorber. Un Explore agent recorrió el repo hermano dsf3/backend y contó todo. La forma que emerge no es un CRM low-traffic. Es un sistema consumer-facing real-time de volumen medio, con tabla de chat particionada, observers de denormalización, una queue de push dedicada, ffmpeg, Whisper, OpenAI Vision, y quince parsers especializados de PDF que alimentan un motor de workflows.
Algunos de estos conteos merecen una oración. Los 153 models incluyen nueve hermanos ChatMessage*; chat_messages en sí está particionado BY RANGE (TO_DAYS(created_at)) con particiones mensuales pre-creadas doce meses por adelantado. Los 42 jobs incluyen Chat\BroadcastMessageJob en una queue realtime, Chat\GenerateThumbnailJob que corre ffmpeg y calcula un blurhash, Chat\ProcessVideoJob que transcodifica uploads web a H.264 720p, Chat\TranscribeAudioJob en una queue transcription aparte, y Push\SendPushTokenJob en una queue push dedicada con cuatro supervisor workers.
Los 52 console commands incluyen mantenimiento de particiones (chat:create-monthly-partitions, chat:drop-old-partitions, chat:backfill-search-shadow), snapshots diarios de KPI, auto-advance de MEP, poda de particiones de telemetry, y un sampler de un 1% que corre EXPLAIN PARTITIONS y emite un evento de telemetry para observabilidad en producción. Ninguno tiene equivalente en Podio.
Las 327 migrations contienen DDL no trivial — composites UNIQUE de Idempotency-Key, tablas shadow de FULLTEXT, reescrituras polimórficas de read-receipts, reorganizaciones de particiones. Los 150+ services incluyen 80+ calculadores de KPI, 15 parsers de PDF, ImageValidation con OpenAI Vision, transcripción Whisper, PDF con Gotenberg, FCM/WebPush, sync con ERP. Los 30 broadcast events incluyen el cuarteto de chat (message.sent / .updated / .deleted / .read), whispers client-typing, task.updated, case.updated, y nueve variantes de eventos de rewards.
La app móvil, por su parte, llama unos 40 paths HTTP únicos y se suscribe a siete eventos de Pusher. Cada uno tiene un contrato que Podio tendría que reemplazar, debilitar, o quitarse del medio.
§ 02La envoltura real de Podio
Podio en 2026 es producto de Progress Software, no de Citrix. Sobrevivió la adquisición pero sus docs de developers no han crecido de manera significativa desde 2015. El feature set que anuncian es generoso; los límites debajo no.
Propiedad y estado estratégico
Citrix le vendió Podio (junto con ShareFile) a Progress Software el 31 de octubre de 2024. El portal de developers sigue en developers.podio.com; el help user-facing ahora vive en docs.sharefile.com/.../podio/. GlobiFlow, el add-on premium de workflows de toda la vida — luego rebautizado "Citrix Podio Workflow Automation" — está reportado como discontinuado; Progress renombró la superficie como Workflow Automation. No hay EOL anunciado — pero el público developer es chico, el roadmap público es opaco, y Progress no es una empresa SaaS de colaboración. Tratá el rumbo de Podio bajo el nuevo dueño como un known unknown.
La política de rate limit
Esta es la política que decide en silencio todas las demás preguntas. De developers.podio.com/index/limits:
La respuesta lleva dos headers — X-Rate-Limit-Limit y X-Rate-Limit-Remaining — pero, en palabras del propio Podio: "you cannot currently see when your rate limit counter is reset." Los aumentos requieren un ticket de soporte por cada client ID, abierto con el equipo de soporte de ShareFile de Progress. No hay header retry-after en el 420.
Items, la primitiva de records
Cada app — el nombre que Podio le da a un schema de records — tiene techo de 500 000 items, 100 fields, 5 000 revisiones rastreadas, y 20 000 filas en import/export de Excel. Los fields de app-reference topan las relaciones en 250 items. Los comments topan en 2 500 por item con un techo de 1 000 caracteres por mensaje. Los archivos topan en 200 por item. El campo de chat interno del propio Podio tiene, según sus docs, "1 000 characters max" — pista silenciosa de que nadie en Podio jamás pensó este producto para hospedar mensajería long-form.
Files
Tope duro de 100 MB por archivo. Sin chunked uploads ni resumables documentados — hacés POST del archivo entero en un round-trip HTTP. Para adjuntar a un item primero subís, luego llamás un endpoint attach-file aparte: dos golpes al rate limit por attachment. Los archivos tipo .exe, .bat, .com, .hta, o application/octet-stream pelado se rechazan — relevante porque RN Android a veces despoja el Content-Type a octet-stream en bodies multipart (memoria: feedback_rn_android_strips_content_encoding.md).
Push — CometD sobre Bayeux
El "push service" en https://push.podio.com/faye es long-polling con CometD/Bayeux. Para suscribirte a un canal hacés fetch de un private_pub_signature + private_pub_timestamp per-canal vía una llamada API regular (que descuenta de tu rate budget), y después lo metés en el campo ext del handshake de CometD. No hay SDK móvil documentado; el único código publicado son ejemplos jQuery + Faye para browser. Reconectar en cada foreground de la app cuesta llamadas API. CometD es materialmente más pesado en un teléfono que el envelope WebSocket de Pusher que usás hoy.
Webhooks
Cinco endpoints: create, delete, list, request-verification, validate-verification. Eventos: item.create/.update/.delete, comment.create/.delete, file.change, app.update/.delete, más eventos space-level y variantes field-scoped. El contrato tiene tres filos cortantes:
- HTTPS solo en ports 80 / 443 — nada de custom ports.
- Deadline de respuesta de 15 segundos antes de que Podio considere fallida la llamada.
- 50 fallos consecutivos auto-desverifican el hook. Modo de falla silencioso a menos que vos te hagás el health-check.
- El payload signing no está documentado. Te protegés con un shared secret en la URL.
Conversations
Existen 21 endpoints — create, reply, search, mark-read, star — pero los docs no publican máximo de participantes, longitud máxima de mensaje, reglas de attachments, ni semántica de edit/delete. Las Conversations son entre usuarios de Podio. Tus leads no son usuarios de Podio; son filas Person. Convertirlos a todos en usuarios pagos de Podio es la única forma de usar la primitiva Conversations nativamente — y ese es el movimiento cuyo precio se examina abajo.
Pricing (2026)
Free tope en 100 items por organización en total — inservible más allá de un demo. Plus son $14/user/mes ($11.20 anual). Premium — el que incluye Workflow Automation — son $24/user/mes ($19.20 anual). "Enterprise" se cotiza a la medida. Per-user quiere decir per cuenta humana de Podio.
§ 03Viabilidad feature por feature
Las features móviles bajo discusión, mapeadas contra la superficie real de capacidades de Podio. Las bandas de color no son un juicio subjetivo — son consecuencia directa de los límites en §02.
| Feature móvil | Qué hace hoy (DSF3) | Primitiva más cercana en Podio | Viabilidad |
|---|---|---|---|
| Chat lead⇄agente | chat_messages particionado, envelope tight de Pusher, Idempotency-Key, chunked uploads, FTS5 + shadow FULLTEXT, tombstones de edit/delete, blurhash, whispers de typing |
API Conversations, comments sobre items, API Files | No Rate limit + sin chunks + sin push móvil |
| Búsqueda dentro del chat | FTS5 local + LIKE recent/year + shadow FULLTEXT scope=all |
API Search, clase rate-limited | No Techo de 250 calls/hr |
| Indicador de typing | Whisper de Pusher client-typing |
typing de CometD en canal de conversación |
No Auth per-canal quema budget |
| Read receipts | chat_read_receipts polimórfico, PATCH batched por viewport |
Mark-as-read de conversación | Parcial Sin granularidad per-mensaje |
| Tareas | Task linked a opp, status workflow, real-time task.updated |
Podio Tasks nativos o Item-per-task | Parcial Sin real-time móvil |
| Encuestas (wizard CCSS/MEP) | Wizard sin scroll schema-driven, show_if condicional |
Item-per-respuesta; schema en código | Parcial El wizard se queda client-side; la lógica de backend no se mueve |
| Casos | Opportunity → vertical → progresión de step, real-time case.updated |
Item + status workflow + Workflow Automation | Parcial Auto-advance OK; el pipeline MEP/AI no se mueve |
| TSE lookup | Fetch directo del cliente a dsf.cr/tse/{cedula} |
— | N/A Ya es client-side |
| Perfil | SecureStore + mirror MMKV de la fila Person | Item-per-Person o API Contacts | Sí A través de un proxy DSF3 |
| Terceros (árbol de reps) | representative_id FK, descendientes recursivos |
Field de app-reference, tope de 250 items | Parcial Tope de 250 / sin primitiva de recursión |
| OTP por WhatsApp | Evolution API + cache registration:{cedula} + cooldown |
— | No Podio no manda SMS / WhatsApp / email-OTP |
| Push al móvil | SendPushTokenJob per-token en queue push, FCM v1, web push VAPID, supresión inteligente cuando está online |
— | No El push de Podio es solo para sus propios clientes |
| Voice agent (LiveKit) | Mint de room token, driver ElevenAgents | — | No Las signing keys no pueden vivir en Podio |
| Ingesta de Telemetry | device_logs particionado, EventRedactor, sampler |
— | No Un solo budget de 1k/hr se quema en un batch |
| Ciclo de reclamo MEP | 15 parsers de PDF + OpenAI Vision + Gotenberg + cron de MEP auto-advance + 9 jobs Mep* | Workflow Automation → webhook externo | No El compute pesado no corre en Podio Flows |
| Procesamiento de medios | Manipulator del cliente + ffmpeg en GenerateThumbnailJob + ProcessVideoJob + blurhash + Whisper |
— | No Sin compute server-side |
| KPI dashboards | 80+ services que agregan data particionada en snapshots diarios | Items + Workflow Automation | No La API Items no puede GROUP BY day a 6 meses |
| Rewards / gamificación | 40 reward models, badge engine, leaderboards, streak service | Item-per-badge + triggers de Flows | Parcial La superficie calza; el motor de criterios se queda |
| Link previews | oEmbed + OG scraping, cache 24 h | — | No Fetch server-side que no podés delegar |
| Preguntas de seguridad | security_verified per-device, iconos por slug |
Items + Items | Parcial Posible pero high-friction por ningún valor |
§ 04Los quince deal-breakers
Estas son las cosas que la app móvil necesita y Podio no entrega. Cada una, por sí sola, alcanza para descalificar una migración total. Listadas más o menos en orden de prioridad.
-
Chat en tiempo real a escala consumer
El envelope tight de Pusher de hoy son 400 bytes por mensaje, respaldado con una ventana de dedup de una hora vía Idempotency-Key sobre un UNIQUE composite. El rate limit de 1 000-calls-per-user-per-hour de Podio, la falta de un SDK de push móvil, y el modelo de conversation-as-Podio-user rompen cada uno de estos requerimientos por separado.
-
Push notifications hacia la app móvil
El push service de Podio es para sus propios clientes. Para entregarle a tus usuarios iOS / Android / WebPush vas a necesitar siempre un servidor que aloje la Firebase service account y las VAPID keys. Ese servidor no es Podio.
-
Entrega de OTP por WhatsApp
La integración con Evolution API, el cooldown de reintento, la ventana de resend de 30 segundos, y el tope de 5 intentos son hechos a la medida. Podio no tiene SMS, WhatsApp, ni un canal confiable de email-OTP.
-
Chunked uploads resumables con SHA-256
El pipeline de medios del chat depende de
lib/chunked-upload.tsylib/upload-resume.tscon verificación SHA-256 per-chunk y de archivo completo. La API Files de Podio es one-shot, máximo 100 MB, sin resume. -
Compute de medios server-side
Transcodificación con ffmpeg, generación de blurhash, transcripción con Whisper, ProcessVideoJob, GenerateThumbnailJob. Podio no tiene ninguna primitiva de compute.
-
Almacenamiento particionado time-series
chat_messages(TO_DAYS RANGE) ydevice_logsestán particionados mensualmente. Podio Items no tiene equivalente y degrada bastante antes de 500 000 filas por app. -
Búsqueda full-text a escala de chat
FTS5 local + shadow FULLTEXT entrega hoy sub-segundo a través de toda la historia. La API search de Podio es clase rate-limited (250/hr) y no tiene mirror offline.
-
Dedup con Idempotency-Key y ledger de una hora
Cache::lock + UNIQUE composite + ventana de re-fetch de 1 h. Podio no tiene primitiva de dedup ni por header ni por columna.
-
Pipeline de OpenAI Vision + parsers PDF
15 parsers especializados, ImageValidation, OpenAI Vision para validar cédulas, MepFirmaVerificationService. Ninguno portable a una plataforma no-code.
-
Queue workers propios con semántica de retry
Queues separadas
realtime,push,transcription,default;tries=3,retryAfter(), exponential backoff. Workflow Automation no tiene queues — tiene triggers. -
Recursión del árbol de reps multi-tenant
Person::allDescendantLeadIds()recorre un árbol de representantes de profundidad arbitraria. Los relationship fields de Podio topan en 250 entradas por field y no tienen primitiva de query recursiva. -
Mantenimiento cron-driven
Creación de particiones, backfill de FT-shadow, snapshots de KPI, premios de leaderboard, auto-advance de MEP. Workflow Automation de Podio tiene triggers pero no tiene cron nativo.
-
Denormalización mantenida por observers
ChatMessageObserver mantiene
opportunities.last_message_*. Replicar esto en Podio costaría un webhook + una llamada API de update inversa por mensaje — duplicando el consumo de rate limit. -
Relaciones polimórficas
chat_read_receipts.reader_type ∈ user|person. Los Items no hacen polimorfismo limpio. -
Modelo de bearer Sanctum / api_session
El bearer de la app móvil lo emite tu capa Laravel y actúa por el lead. Podio usa OAuth 2 con autenticación per-user; convertirlo a "Podio es la auth source" requeriría flujos OAuth que la app móvil no tiene hoy.
§ 05Lo que sí entra
La lista honesta. Estas son las superficies donde las primitivas de Podio se alinean con el trabajo, los volúmenes son lo suficientemente bajos como para que 1 000/hr sea cómodo, y la falta de real-time móvil es aceptable.
- Vista CRM agent-facing de leads, opportunities, casos. Un Podio item por opportunity, segmentado en views por vertical (CCSS, MEP) y status. El staff usa la propia UI web de Podio; el móvil no la ve. Sync desde DSF3 vía un
SyncOpportunityToPodioJob(ya tenésSyncOpportunityToErpJob— copiá el scaffolding). - Repositorio de documentos. PDFs generados por Gotenberg, escaneos de cédula validados por AI, formularios de reclamo MEP — adjuntos al item de Podio correspondiente. ≤100 MB cada uno, ≤200 por item. Adecuado.
- Listas de tareas del staff (no las tareas lead-side). Podio Tasks nativos para workflow entre colaboradores. No es la misma superficie que
/mis-tareas. - Automatización de cambios de status entre staff y herramientas. "Cuando opportunity → firmado, mensaje a Slack + crear carpeta en Drive." Workflow Automation lo resuelve trivialmente.
- Audit log / activity stream para staff. El stream de Podio cubre auditoría low-volume perfectamente.
- Formularios externos de intake. Webforms para abogados, testigos, terceros — sin construir UI custom.
- Comments y @-mentions del staff sobre un caso. Los comments de item (1 000 char cada uno, 2 500 máx.) están muy por arriba del uso real del staff.
Modelo mental: Podio es el mirror CRM staff-side. DSF3 es el system of record + la API consumer-facing + el Backend pesado.
§ 06La matemática del rate limit
Suponé que ignorás cada advertencia anterior y querés meter el chat por Podio. Un modelo de carga al ojo: un lead moderadamente activo manda 8 mensajes, hace scroll por 4 tareas, abre 2 casos, toca una notificación — llamémoslo 30 operaciones API en una sesión enfocada de 15 minutos. En pico podría haber 150 de estos leads simultáneos. El agregado anda bien. El techo per-user no.
La molestia que se acumula: cada attachment de chat cuesta dos calls API de Podio (upload → attach). Cada subscribe de CometD en una conversación nueva cuesta una call extra para traer la signature del canal. Cada webhook que aterriza te obliga a traer el item modificado para ver qué cambió — los webhooks de Podio cargan IDs, no bodies. El factor de amplificación entre "cosas que hizo el usuario" y "calls API a Podio" se sitúa entre 2× y 4×.
Un lead ruidoso — heavy uploader, fast typer — pega contra su techo per-user de 1 000/hr y le mandan 420 mientras todo el mundo anda bien. No podés subir el techo per-user a posteriori. Tendrías que abrir un ticket de soporte de Progress por cada cuenta bloqueada.
§ 07La matemática del costo
El castigo real no es la latencia, es el pricing per-user. Podio vende por cuenta paga de usuario de Podio. Para usar Conversations nativamente, los leads tienen que ser usuarios de Podio. Incluso si usás tokens de app-authentication (un solo usuario compartido haciéndose pasar por todos los leads), el rate limit es "per user per API key" — un solo bucket de 1 000/hr para toda la base de clientes.
§ 08Cuatro arquitecturas
Con todo lo que vimos en §§01–07, estas son las formas realistas de integrar Podio sin romper la app móvil. La primera es lo que hay que hacer. La cuarta es lo que no.
Arquitectura A · Recomendada
DSF3 primario, Podio como mirror CRM
El móvil se queda con todo lo que tiene hoy. DSF3 espejea lead / opportunity / caso / status / documentos hacia items de Podio vía un nuevo SyncToPodioJob (la inversa de tu SyncOpportunityToErpJob existente). Los webhooks de Podio regresan a un endpoint DSF3 cuando el staff edita un item; DSF3 reconcilia y rebroadcast al móvil vía Pusher.
Arquitectura B · Viable pero comprometida
Podio para metadatos low-volume, DSF3 para el resto
Movés solo perfil y los metadatos ligeros de tareas / casos a Podio Items. Chat, push, OTP, medios se quedan en DSF3. Los read paths del móvil quedan: chat / medios / uploads → DSF3; perfil / tareas / casos → proxy DSF3 → Podio. El proxy ratelimita, cachea, y hace circuit-break para que los 420s de Podio nunca le lleguen al dispositivo.
Arquitectura C · Opción pegamento
Podio + orquestación con Make / Workato / Zapier
Algunos equipos usan Make.com o Workato como capa de orquestación que se dispara de los hooks de Podio y corre lo pesado (OpenAI Vision por API, ffmpeg vía Cloudconvert, push vía FCM HTTPS). Funciona para workflows low-volume, no-time-critical. Para cualquier cosa con forma de chat o con target de latencia sub-segundo es demasiado lento — la cadencia free de triggers de Make es 15 minutos; tendrías que pagar premium por webhooks a nivel de segundo.
Arquitectura D · No recomendada
Podio como primario
Para hacer de Podio el system of record necesitarías: un seat pago de Podio por lead (o un único usuario de impersonation con límites elevados por ticket); un sidecar que emita tokens FCM / APNs; un sidecar que corra ffmpeg, Whisper, blurhash; un sidecar que firme tokens de LiveKit; un sidecar que corra los cálculos de KPI; un sidecar que mande OTP por Evolution.
Terminarías con más infraestructura de la que tenés hoy, más el techo de rate de Podio encima, más una relación de vendor con Progress Software cuyo rumbo es incierto. No lo hagás.
§ 09La Arquitectura A en detalle
Si la Arquitectura A es la que se adopta — y los datos de §§01–08 apuntan ahí — vale la pena examinarla con la profundidad que el resto del informe le dedicó a descartar las otras tres. Esta sección la desarma: qué vive dónde, cómo se mueven los datos, qué pasa cuando algo falla, y en qué orden la implementás.
El principio que la organiza
Una sola regla con dos lecturas:
- DSF3 es el system of record. Cada lead, cada opportunity, cada mensaje, cada documento se origina y persiste en MySQL primero. Si Podio desaparece mañana, no perdiste nada.
- Podio es un mirror, no una fuente. El staff vive ahí porque la UI no-code de Podio es donde son productivos. La data está ahí también, no en cambio.
Esto te compra dos cosas que ninguna de las otras arquitecturas te da: vendor risk acotado (Progress Software puede cambiar de rumbo sin que tu producto se rompa) y mobile risk cero (la app móvil no se entera de que Podio existe).
Qué vive dónde
Una vista por entidad para que no quede ambigüedad:
| Entidad | DSF3 (system of record) | Podio (mirror) | Dirección de sync |
|---|---|---|---|
| Lead / Person | Tabla persons con TSE, security questions, push tokens, terceros |
Item en app "Leads" con cédula + nombre + vertical principal + status | DSF3 → Podio |
| Opportunity | Tabla opportunities con denorm (last_message_*, unread_lead_count) |
Item en app "Oportunidades" con campo Lead-reference, vertical, status, current_step | DSF3 → Podio |
| Caso / CaseFile | Modelo CaseFile con progreso de step, fecha de firma, tipo de cierre |
Item en app "Casos" con Opportunity-reference y step actual | DSF3 → Podio |
| Chat messages | Tabla chat_messages particionada |
Nada. Quizás un snippet del último mensaje en el item Opportunity | DSF3 → Podio (snippet) |
| Documents / PDFs | Storage local + Caddy, paths inmutables en chat_message_attachments u opportunity_documents |
File adjunto en el item correspondiente (≤100 MB) | DSF3 → Podio |
| Notas / comentarios del staff | — | Comments en el item de Opportunity / Caso | Podio → DSF3 (via webhook) |
| Status workflow (firmado / cerrado / etc.) | Field opportunities.status |
Field categoría en el item; Workflow Automation reacciona al cambio | Bidireccional |
| KPI / Telemetry | Tablas device_logs, kpi_daily_snapshots |
Nada — Podio no puede absorber este volumen | No sync |
| Push tokens / FCM | Tabla person_push_tokens |
Nada — Podio no manda push al móvil | No sync |
El mapa del sistema
Visto de afuera, el sistema en Arquitectura A se ve así. El staff y los leads viven en mundos físicamente separados; DSF3 es el único punto que toca a ambos.
Ilus. 1 Arquitectura A vista de afuera. La app móvil habla solo con DSF3 (HTTPS para writes, Pusher para reads en tiempo real). DSF3 espejea hacia Podio de forma asíncrona vía un job nuevo (SyncToPodioJob); los webhooks de Podio regresan a DSF3 cuando el staff edita algo. Los servicios externos (FCM, Evolution para WhatsApp, OpenAI Vision) los sigue invocando DSF3 — Podio nunca los toca.
El ciclo de un mensaje
El sequence diagram más útil de la arquitectura es lo que pasa cuando un lead manda un mensaje al agente. El lead recibe el ack instantáneo via Pusher; el mirror de Podio aterriza unos cientos de milisegundos después, de forma asíncrona, sin bloquear nada.
Ilus. 2 El ciclo de un mensaje en Arquitectura A. Los pasos 1–4 son el camino rápido (≤ 200 ms al lead vía Pusher). El paso 5 dispara SyncToPodioJob en la queue, que aterriza en Podio asíncronamente — falle o no, el lead ya vio su mensaje confirmado. La línea naranja es el límite entre lo crítico (síncrono, mobile-visible) y lo eventual (asíncrono, staff-visible).
Mapeo de datos: Opportunity → Podio Item
El sync no es una serialización ciega. Cada field tiene un mapping intencional, y algunos campos de DSF3 directamente no se mueven. Esta es la única traducción que vale la pena hacer detallada — los demás models siguen el mismo patrón.
Ilus. 3 Mapeo field-por-field entre la tabla MySQL opportunities y el item de Podio en la app "Oportunidades". El External ID es la pieza clave — siempre "opp-{id}" derivado del primary key de DSF3 — porque permite que el sync sea idempotente: hacer upsert by external_id en lugar de create, evitando duplicados aunque el job corra dos veces. Lo de abajo a la izquierda no se espejea (volumen, ruido, o sensibilidad). Lo de abajo a la derecha es contenido nacido en Podio que regresa a DSF3 vía webhook.
El job pattern
El sync job sigue al pie de la letra el patrón de SyncOpportunityToErpJob que ya existe — eso te lo evita reinventar. Lo único nuevo es el cliente PodioClient (un wrapper delgado sobre la API REST de Podio con retry, rate-limit handling, y refresh de OAuth token).
app/Jobs/Podio/SyncOpportunityToPodioJob.php
namespace App\Jobs\Podio; use App\Models\Opportunity; use App\Services\Podio\PodioClient; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; class SyncOpportunityToPodioJob implements ShouldQueue { use Dispatchable, Queueable; public int $tries = 5; public int $backoff = 60; // segundos; 60 · 120 · 240 · 480 · 600 public function __construct(public int $opportunityId) {} public function handle(PodioClient $podio): void { $opp = Opportunity::with('lead', 'caseFile') ->findOrFail($this->opportunityId); // Upsert idempotente — Podio busca por external_id antes de crear. $podio->upsertItem( appId: config('podio.opportunity_app_id'), externalId: "opp-{$opp->id}", fields: [ 'lead_cedula' => $opp->lead->cedula, 'lead_name' => $opp->lead->full_name, 'vertical' => $opp->vertical, 'status' => $opp->status, 'current_step' => $opp->current_step, 'last_message_at' => $opp->last_message_at?->toIso8601String(), 'last_msg_snippet' => $opp->last_message_snippet, 'assigned_to_id' => $opp->assigned_to_id, ], ); } public function retryUntil(): \DateTime { // si Podio está caído >1h, dejá de pelearle al rate limit. return now()->addHour(); } }
El cliente PodioClient::upsertItem hace dos cosas que importan: (a) detecta HTTP 420 y agenda un retry con backoff exponencial, sin contar contra $tries hasta que el rate window se libere; (b) detecta el error de "external_id existe" y se cambia a updateItem automáticamente, garantizando idempotencia incluso si el job se dispara dos veces.
Webhooks de regreso
Cuando el staff edita un item en Podio — cambia el status, agrega un comment, sube un PDF — el webhook le pega a un endpoint nuevo en DSF3:
01
Podio dispara
POST /podio/webhooks/item-update con type, item_id, external_id. Sin body — solo IDs.
02
DSF3 ack inmediato
Verifica HMAC en query param, devuelve 200 OK en < 50 ms, enqueue ProcessPodioWebhookJob.
03
Reconciliación
El job hace GET /item/{id} a Podio, compara contra DSF3, aplica diff a MySQL, y broadcast opportunity.updated en Pusher.
GET /hook/{id} y re-verifique si está caído. La memoria feedback_observer_post_link_pattern.md tiene el patrón equivalente que ya usás en DSF3 para detectar denorm drift.
Rollout por fases
Ocho semanas, cuatro fases. Cada una entrega valor staff-visible sin tocar el código de la app móvil ni una vez.
Ilus. 4 El rollout en un Gantt resumido. Las fases 2 y 3 corren en paralelo en las semanas 4–7 — son features ortogonales (uploads vs callbacks) que no comparten código. La fase 4 es la última porque depende de que el sync ya esté estable: no querés Workflow Automations disparando Slacks contra estado inconsistente.
Setup organizacional
Crear la org de Podio, comprar 15 seats Premium, definir los workspaces (uno por vertical: CCSS, MEP). Definir el schema de las apps: Leads, Oportunidades, Casos, Documentos. Configurar OAuth + client_id, generar refresh tokens, guardarlos en .env de DSF3.
- Schema "Oportunidades" con los 10 fields del mapping de Ilus. 3
- Workflow Automations apagadas (todavía no)
- Roles configurados: staff abogado · staff admin · staff KPI
Sync one-way de opportunities y casos
Escribir App\Services\Podio\PodioClient con upsertItem, updateItem, manejo de HTTP 420. Implementar SyncOpportunityToPodioJob y SyncCaseToPodioJob. Disparar desde el OpportunityObserver existente en eventos created y updated.
- Job en queue
podio-sync(dedicada, 2 workers, separada derealtimeypush) - Telemetry:
podio_sync_enqueued,podio_sync_completed,podio_sync_failed - Comando one-shot
podio:backfill-opportunitiespara hacer el sync inicial sin esperar updates
Sync de documentos
Cuando se adjunta un PDF a una opportunity en DSF3 — generado por Gotenberg, escaneado por OpenAI Vision, etc. — se replica al item correspondiente de Podio. Sub-100 MB queda directo; los pocos archivos >100 MB se quedan en DSF3 con un placeholder link en Podio.
- Job
SyncDocumentToPodioJobescucha el eventoOpportunityDocumentCreated - Skip silencioso para archivos >100 MB; staff ve un link "Ver en DSF3"
- Telemetry:
podio_document_synced,podio_document_too_large
Webhooks Podio → DSF3
Endpoint POST /podio/webhooks/item-update con verificación HMAC. ProcessPodioWebhookJob trae el item, hace diff, aplica a MySQL. Health-check diario que verifica que el hook sigue verified.
- Status del staff → status en DSF3 (con
opportunity.updatedPusher event) - Comments del staff → activity log de DSF3 (no se le muestran al lead — son notas internas)
- Tests de integration: 50 hooks consecutivos failing no desactivan el endpoint en silencio
Workflow Automation
Encendés las flows que el staff ya pidió: "cuando status → firmado, mandar Slack a #ops", "cuando se sube un PDF a Casos, crear carpeta en Drive". Mantenelas boring — la lógica compleja se queda en DSF3.
- Slack notifications para status transitions críticas
- Auto-asignación de nuevos casos por carga del agente (Podio puede hacerlo sin sidecar)
- Email summary semanal a directores con conteo de items por status
Modo de falla y recuperación
Si la regla es que DSF3 es el system of record, entonces todos los modos de falla del lado de Podio son recuperables. Ninguno es data-loss. Esa es la diferencia que justifica esta arquitectura.
| Falla | Efecto visible | Recuperación |
|---|---|---|
| Podio API responde 420 (rate limit) | Sync queue acumula items pendientes | Job retries con backoff exponencial. Si la cola crece >500, alerta de PagerDuty. podio:backfill-opportunities --since=YYYY-MM-DD rehace todo si querés. |
| Podio está caído 4 horas | Staff no puede ver updates nuevos | Mobile app no se entera. Cuando Podio vuelve, la queue se drena. Los webhooks de Podio se reintentan automáticamente cuando recupera. Nada se pierde. |
| OAuth token expira | Todos los SyncToPodioJob fallan con 401 |
PodioClient detecta 401, refresca el token via refresh_token almacenado, reintenta una vez. Si el refresh también falla, alerta + manual re-auth desde panel admin. |
| Webhook auto-unverified después de 50 fallos | Updates del staff no llegan a DSF3 | Health-check diario detecta el hook inactivo y lo re-verifica automáticamente. Mientras tanto, no perdés data — el staff puede esperar; o corrés podio:reconcile-from-podio --opp={id}. |
| External ID duplicado en Podio | Dos items para la misma opportunity | El upsert idempotente lo previene por construcción. Si igual pasa (corrupción manual), el comando podio:dedupe-items --app=oportunidades los fusiona conservando el primero. |
| Progress decide deprecar Podio | Plataforma desaparece en N meses | DSF3 sigue funcionando exactamente igual sin Podio. El staff vuelve a usar la CRM web propia de DSF3 (que es lo que usan hoy de todos modos). El proyecto Podio se apaga sin pánico. |
Lo que esta arquitectura no resuelve (y no debería)
Es importante reconocer dónde Arquitectura A se queda corta:
- El staff sigue teniendo dos UIs. Mientras DSF3 tenga su propia CRM web (la que viven en
dsf3/src), el staff puede preferir esa para algunos workflows y Podio para otros. Resolver esto requiere decidir cuál es la UI canónica y deprecar la otra. - El sync introduce latencia de ~1–5 segundos. Un agente que ve un cambio en Podio puede que vea el state un par de segundos atrasado vs lo que el lead acaba de hacer. Aceptable para CRM, no para chat — por eso el chat queda fuera del sync.
- Workflow Automation no es Laravel. Cualquier flow que se vuelva complejo va a tener que migrarse a un
App\Listeners\*en DSF3 igual. Mantenerla simple es un compromiso disciplinario. - El costo de Podio es ~$360/mes recurrente. Si el staff no termina usándolo (como pasó con la "Dedicated messaging app" thread del forum de Podio en 2019), ese dinero está tirado. Hacé un pilot de 60 días antes de comprometer 15 seats anuales.
La Arquitectura A es la respuesta correcta a una pregunta intermedia: "¿cómo le damos al staff un workspace no-code sin romper la app móvil?" No es la respuesta a "¿cómo movemos DSF a Podio?" — esa pregunta, vista desde acá, no tiene una respuesta sensata.
§ 10Esencial vs deseable
Si una migración tuviera que ocurrir sí o sí, ¿qué se puede dar el lujo de perder la app móvil? Los tiers de abajo son míos; defendelos o reemplazálos, pero el orden de prioridad es honesto.
| Tier | Feature | Por qué cae acá | ¿Podio? |
|---|---|---|---|
| P0 | Chat real-time < 1 s | El producto es "hablá con tu abogado" | No |
| P0 | Push notifications | Sin ellas la app muere en background | No |
| P0 | OTP por WhatsApp al registrarse | El público lead tico lo espera | No |
| P0 | Outbox de chat offline + gap-fill | Realidad de la red móvil en CR | No |
| P0 | TSE lookup al registrarse | Ya es client-side — no requiere trabajo | N/A |
| P0 | File upload ≤25 MB con progress | Intake de evidencia — imagen, audio, PDF | No |
| P1 | Tareas + Casos con real-time | Drive de engagement + triggers de push | Parcial |
| P1 | Encuestas (wizard CCSS / MEP) | Requerido para intake del caso | Parcial |
| P1 | Read cacheado del perfil | Velocidad de first-render (zero-skeleton) | Sí |
| P1 | Terceros (árbol de reps) | Caso de uso multi-account | Parcial |
| P2 | Búsqueda dentro del chat | Degrada limpiamente si no está | No |
| P2 | Voice agent | Feature beta | No |
| P2 | Transcripción de audio | Quality of life, no bloqueante | No |
| P2 | Link previews | Cosmético | No |
| P3 | Telemetry analytics | Preocupación del operador, no del usuario | No |
| P3 | Rewards / gamificación | Aspiracional | Parcial |
§ 11Registro de riesgos
Si seguís con la Arquitectura A (la recomendada), o cualquier otra variante que ponga algo en Podio, estos son los riesgos operativos que vale la pena rastrear desde el día uno.
| Riesgo | Severidad | Mitigación |
|---|---|---|
| Incertidumbre estratégica de Podio bajo Progress Adquisición cerrada en Oct 2024 |
Alta | Tratá a Podio como una capa reemplazable detrás de un adapter; nunca escribás tu única copia de data en Podio. Dual-write a DSF3 primero, después espejeás. |
| 420 rate-limit en un mal día | Alta | Caching agresivo client-side, dual-write (Podio + tu propio cache), receptores de webhook idempotentes, backoff exponencial en 420. |
| Pérdida de dedup con Idempotency-Key en replies a Podio | Alta | No movás el chat. Si tenés que hacerlo, corré el dedup en tu propio proxy delante de Podio. |
| CometD en móvil no está probado | Alta | No uses el push de Podio para la app móvil. Quedate con FCM/APNs/WebPush. |
| 1 000/hr per-user bloquea UX con bursts | Alta | Los clientes móviles nunca hablan directo con Podio. Pasá siempre por proxy DSF3 (o Lambda) para poder ratelimit, cachear, circuit-break. |
| Disable silencioso del webhook tras 50 fallos | Media | Health-check diario: GET /hook/{id}; alerta + re-registro automático si está disabled. |
| Timeout de 15 s en webhook | Media | Devolvé 200-OK rápido y enqueue al queue worker. Nunca hagás trabajo real dentro del handler del webhook. |
| Archivos > 100 MB | Media | Dejá el video pipeline de tu lado. Espejeá solo thumbnails / metadata a Podio. |
Rechazo de application/octet-stream en RN Android |
Media | Verificá MIME server-side (libmagic) antes de reenviar a Podio. Memoria: feedback_rn_android_strips_content_encoding.md. |
| Sin payload signing en los hooks de Podio | Media | Firmá la callback URL con HMAC en query param. Rotá el secret trimestralmente. Validá antes de procesar. |
| Profundidad del árbol de reps > 250 nodos por rama | Baja | Hoy improbable. Si llega a pasar, el árbol de reps se queda en DSF3 — solo aplaná los roots a Podio. |
| Cost creep de seats de Podio | Baja | Capá los seats al staff. La identidad lead-side se queda en DSF3 para siempre. |
§ 12Recomendación y cierre
- No movás chat, push, OTP, procesamiento de medios, orquestación de reclamo MEP, telemetry, ni cálculo de KPI a Podio. Cada uno tiene una razón estructural por la que Podio no puede hostearlo. La auditoría encontró que el Backend es estructuralmente un sistema consumer-facing de alto throughput con particionado, observers, queues, ffmpeg, y OpenAI Vision — ninguno con análogo en Podio.
- Adoptá la Arquitectura A (DSF3 primario, Podio como mirror CRM staff-side). Usá Podio para lo que es realmente bueno: un workspace no-code agent-facing donde ops puede ver leads, status, PDFs adjuntos, y agregar comments. Sync de DSF3 a Podio vía un
SyncOpportunityToPodioJobsiguiendo el patrón existente deSyncOpportunityToErpJob. - TSE lookup ya está hecho. Client-side
fetch("https://www.dsf.cr/tse/{cedula}"). No requiere trabajo independientemente de a dónde vaya el resto. - Si los stakeholders empujan por "Podio como primario", vendéles la Arquitectura A como cobertura de lo que realmente quieren: una UI que el staff puede configurar sin engineering. La app móvil no cambia.
- Planeá para el vendor risk. El compromiso de Progress con Podio no está probado en esta etapa. Hacé Podio reemplazable detrás de un adapter; no dejés que se vuelva la única copia de nada.
- Independientemente de Podio, considerá estas mejoras de DSF3:
- Mové ffmpeg / Whisper / blurhash a un worker pool aparte o a AWS MediaConvert + Lambda — las queues
realtimeypushse benefician si el compute de medios se sale de la misma caja. - Considerá Cloudflare R2 + signed URLs para los medios del chat en lugar de disco local del VPS + Caddy.
- El sampler del 1 % de
chat_query_partitions_scannedenAppServiceProvider::bootes el ejemplo canónico de por qué tu propio stack vale la pena — tenés observabilidad de tu propia DB. Eso no lo tenés en Podio.
- Mové ffmpeg / Whisper / blurhash a un worker pool aparte o a AWS MediaConvert + Lambda — las queues
La pregunta correcta no es "¿podemos mover features a Podio?". Es "¿qué rol queremos que juegue Podio?". La respuesta a la que apuntan los datos es: mirror staff-side, mantenido en sync vía webhooks, mantenido reemplazable detrás de un adapter, pagado a escala staff-only.