martes, 30 de junio de 2026

Migraciones en Laravel

Quiero en una migración, cambiar el nombre de una tabla.

En una migración de Laravel puedes cambiar el nombre de una tabla usando el método:

Schema::rename('nombre_actual', 'nuevo_nombre');

Ejemplo completo de migración

use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::rename('pacientes', 'patients');
}

public function down(): void
{
Schema::rename('patients', 'pacientes');
}
};

Cosas a tener en cuenta

  • Esto solo cambia el nombre de la tabla, no toca columnas ni datos.
  • Laravel no hace magia con relaciones: si tienes foreign keys, asegúrate de que sigan funcionando (en la mayoría de casos no se rompen, pero depende del motor y constraints).
  • Siempre define el down() para poder revertir con php artisan migrate:rollback.

Si usas SQLite o casos raros

Algunas bases pueden requerir cuidado extra con constraints, pero en MySQL/PostgreSQL normalmente funciona directo.


Si quieres, puedo ayudarte a hacer un “rename seguro” cuando hay foreign keys involucradas (ahí ya hay un par de trampas típicas). 


¿Hay workflows predefinidos para trabajar con versionado de código, migraciones y seeds?

Sí. En Laravel y en desarrollo web en general hay workflows bastante establecidos que combinan versionado (Git), migraciones y seeds/seeders para mantener consistencia entre entornos.


🧭 Workflow típico recomendado

1. Versionado (Git como base)

  • Todo cambio de esquema va en una rama (feature/...)

  • Cada cambio de DB = commit junto con su migración

  • Nunca se edita una migración ya ejecutada en producción (se crea otra nueva)


2. Migraciones (estructura de la base)

Flujo estándar:

  1. Crear migración

    php artisan make:migration create_pacientes_table
    
  2. Definir cambios en up() / down()

  3. Ejecutar localmente:

    php artisan migrate
    
  4. Commit junto con el código que depende de esa tabla

👉 Regla clave: la base de datos también “viaja en Git” a través de migraciones


3. Seeders (datos iniciales o de prueba)

Workflow típico:

  • Crear seeder:

    php artisan make:seeder PacienteSeeder
    
  • Ejecutar:

    php artisan db:seed
    

O en conjunto con migraciones:

php artisan migrate --seed

4. Refresh completo (entornos de desarrollo)

Muy usado para resetear todo:

php artisan migrate:fresh --seed

👉 Borra todo y reconstruye DB desde cero.


5. Workflow combinado (el “ideal”)

Cuando haces una feature:

  1. Crear migración nueva

  2. Crear/actualizar modelos + lógica

  3. Crear o actualizar seeder si aplica

  4. Commit:

    • migración

    • seeders

    • código

  5. En otro entorno:

    git pull
    php artisan migrate
    php artisan db:seed
    

🧠 Buenas prácticas clave

  • Nunca modifiques migraciones ya compartidas (crea nuevas)

  • Seeders no son para producción crítica (salvo defaults)

  • Usa factories + seeders para datos fake

  • Mantén migraciones idempotentes y reversibles

  • Una migración = una intención clara


Si quieres, puedo proponerte un workflow “pro” tipo equipo (con staging, producción, rollback seguro y versionado de schema más estricto).


En teoría, si hago uno por uno el checkout de cada commit, ejecuto las migraciones según un workflow preestablecido, y ejecuto los seeders según un workflow preestablecido, no tendría que aparecer ningún error, y la app tendría que poder levantar con sus datos acordes a cada commit que checkoutee. ¿Qué hay de cierto en esta afirmación?

Tu afirmación es correcta en teoría, pero no es garantizable en la práctica sin condiciones muy estrictas.


✅ Qué es cierto

Si cumples estas condiciones:

  • migraciones siempre backward-compatible

  • seeders deterministas (no dependen de estado externo cambiante)

  • orden de ejecución consistente (migrate en orden histórico)

  • el código de cada commit es coherente con su schema

  • no hay datos manuales fuera de migraciones/seeders

Entonces sí:
👉 cada commit debería poder levantar la app como una “foto consistente” del sistema

Esto es el ideal del database versioning through migrations.


⚠️ Por qué en la práctica falla a menudo

1. Migraciones no reversibles o destructivas

Ejemplos problemáticos:

  • rename + código que aún espera el nombre viejo

  • drop column sin compatibilidad hacia atrás

  • cambios de tipo que rompen queries anteriores

👉 Rompen el “checkout histórico”.


2. Código depende de estado de datos, no del schema

Ejemplo:

  • un seeder cambió en un commit posterior

  • o datos manuales en dev que no están versionados

👉 El commit no es autosuficiente.


3. Seeders no deterministas

Si haces:

  • factory()->create() sin control

  • timestamps variables

  • datos externos (API, random sin seed fijo)

👉 el resultado cambia aunque el commit sea el mismo.


4. Falta de “reproducibilidad completa”

Laravel no garantiza automáticamente:

  • orden exacto histórico de migraciones

  • rollback perfecto de cambios complejos

  • compatibilidad entre versiones intermedias


5. Dependencia del entorno

  • MySQL vs PostgreSQL

  • versiones distintas del motor

  • charset/collation

  • config de Laravel


🧠 La idea clave

Lo que describes es un sistema tipo:

“cada commit es un snapshot ejecutable del sistema completo”

Eso es posible, pero solo si lo diseñas como:

👉 “migrations + seeders como sistema de reconstrucción determinista”

No como “historial evolutivo libre”.


🟢 Cómo hacerlo realmente robusto

Si quisieras acercarte al 100%:

  • migraciones solo additive o reversible

  • seeders versionados por estado (no acumulativos caóticos)

  • migrate:fresh --seed como única forma confiable de reconstrucción

  • evitar modificaciones destructivas en migraciones viejas

  • usar factories con seeds fijos (seed RNG controlado)


🔥 Conclusión

  • ✔️ Es una idea válida y muy potente

  • ⚠️ Pero solo funciona como garantía si impones disciplina fuerte

  • ❌ En la práctica, muchos proyectos rompen esa propiedad con el tiempo


Si quieres, puedo ayudarte a diseñar un workflow “tipo Git bisectable database”, donde realmente puedas moverte commit por commit sin romper nada.


Quiero renombrar 1 tabla. Estoy dudando entre por una parte tocar la migración de la tabla original y hacer como si se creó con el nuevo nombre desde la primera vez, y por otra parte hacer una migración distinta que renombre la tabla. No sé cuál elegir porque no entiendo bien las implicancias ni las preferencias o workflows establecidos por la comunidad de desarrollo.

La duda que tenés es muy común, y en realidad estás eligiendo entre dos filosofías distintas de migraciones.

Te explico claramente las implicancias de cada opción y qué se usa en la práctica.


🧭 Opción 1: Editar la migración original (reescribir historia)

👉 Cambiar la migración inicial para que la tabla “siempre haya tenido” el nuevo nombre.

❌ Problemas

  • Rompe el principio de inmutabilidad de migraciones

  • Si el proyecto ya está compartido o en producción:

    • otros desarrolladores ya ejecutaron la migración vieja

    • sus bases quedan desincronizadas

  • Git pierde valor como historial confiable de DB

  • No es reproducible hacia atrás

⚠️ Solo es aceptable si:

  • estás en desarrollo temprano

  • nadie más la ejecutó

  • puedes borrar toda la base sin consecuencias

👉 Esto es básicamente “resetear la historia”.


🧭 Opción 2: Crear una migración de rename (recomendado)

Schema::rename('old_table', 'new_table');

✅ Ventajas

  • Respeta el principio de append-only schema evolution

  • Funciona en equipos

  • Funciona en producción

  • Es reversible (down() puede revertir el rename)

  • Mantiene consistencia con el historial de DB

👍 Es el estándar de la comunidad

👉 En Laravel, este es el enfoque correcto casi siempre.


🧠 Regla de oro de la comunidad

“Las migraciones nunca se editan después de ser ejecutadas en entornos compartidos”

Se asume que:

  • cada migración es un “evento histórico”

  • la base de datos evoluciona, no se reescribe


⚖️ Comparación directa

CriterioEditar migraciónMigración de rename
Seguridad en equipo❌ Mala✅ Buena
Producción❌ Riesgoso✅ Correcto
Historial Git❌ Falso✅ Honesto
Reproducibilidad❌ Pierde consistencia✅ Mantiene consistencia
Uso recomendadosolo early devsiempre

🟢 Recomendación clara

👉 Usá migración de rename

Es la opción correcta en:

  • proyectos reales

  • equipos

  • apps que van a crecer

  • cualquier caso donde no quieras sorpresas


💡 Insight importante (la parte que te ayuda a decidir bien)

En migraciones, el objetivo no es “tener la base de datos más limpia posible”, sino tener una historia confiable de cambios ejecutables en cualquier momento.

Eso cambia completamente la forma de pensar:

  • No estás modelando “cómo debería verse la base ideal”

  • Estás modelando “cómo evolucionó la base en el tiempo”


🧠 Consecuencia clave

Editar una migración vieja equivale a decir:

“La historia nunca pasó así, en realidad pasó distinto”

Eso rompe un supuesto fundamental de los sistemas de migración:

👉 que el historial es reproducible


🔥 Por qué esto importa en la práctica

Imaginá este escenario real:

  • Dev A ejecutó migraciones hace 2 semanas

  • Dev B clona el repo hoy

  • Vos editás una migración vieja para renombrar una tabla

Resultado:

  • Dev A tiene una tabla con nombre viejo en su DB

  • Dev B crea la nueva versión

  • El código empieza a fallar de forma inconsistente

👉 Este tipo de divergencias son exactamente lo que las migraciones intentan evitar.


🟢 El principio profesional real

En equipos serios con Laravel o cualquier ORM:

“Si algo ya fue compartido o ejecutado, no se edita: se evoluciona”

Eso incluye:

  • renames

  • alter table

  • cambios de tipo

  • deletes

Todo eso se modela como nuevas migraciones.


⚖️ Regla mental simple

Te sirve como heurística:

  • 🟢 ¿Aún no se ejecutó en ningún lado? → podés editar

  • 🔴 ¿Ya se ejecutó en algún entorno? → no se toca, se migra


🧩 Conclusión final

En tu caso específico:

👉 renombrar una tabla = migración nueva con Schema::rename()

Es lo más seguro, estándar y compatible con workflows reales.


Si querés, en el próximo paso puedo ayudarte a ver algo más interesante:
👉 cómo diseñar migraciones para que sean “reordenables mentalmente” como un timeline sin generar deuda técnica.


Es un proyecto que yo considero muy pequeño y simple, que todavía no está ni cerca de ser desplegado en un ambiente compartido y por mucho tiempo será así ya que yo soy el único desarrollador. Estoy pensando que para un proyecto con tales características lo mejor es modificar directamente el archivo 2025_08_19_005459_create_odontogramas_table.php para fingir que la tabla fue creada con el nombre cambiado desde el principio y ejecutar un php artisan migrate:fresh. ¿Es esto muy aberrante en términos prácticas, metodologías o incluso workflows?

No, no es aberrante en absoluto. De hecho, en el contexto que describís, muchos desarrolladores harían exactamente eso.

La clave está en distinguir entre un proyecto en evolución temprana y un proyecto con historial que ya debe preservarse.

En tu caso, se cumplen prácticamente todas las condiciones para reescribir la historia:

  • ✅ Sos el único desarrollador.

  • ✅ No hay ambiente compartido.

  • ✅ No hay producción.

  • ✅ No necesitás preservar datos.

  • ✅ No te molesta ejecutar php artisan migrate:fresh.

En ese contexto, modificar:

2025_08_19_005459_create_odontogramas_table.php

para que cree la tabla con el nombre definitivo y luego ejecutar:

php artisan migrate:fresh

es una decisión perfectamente razonable.

De hecho, hay dos escuelas de pensamiento

1. Las migraciones como historia (la más difundida)

Una vez creada una migración, nunca se modifica. Se agregan nuevas migraciones.

Esta filosofía es ideal cuando ya existe una historia que preservar.

2. Las migraciones como descripción del estado inicial (muy usada en proyectos pequeños)

Mientras el proyecto está "en construcción", las migraciones se van corrigiendo para que reflejen la mejor versión del esquema. Cuando se necesita reconstruir la base, simplemente se hace:

php artisan migrate:fresh --seed

Esta filosofía evita acumular migraciones "de ruido", por ejemplo:

create_odontogramas_table
rename_odontogramas_to_odontograms
rename_odontograms_to_dental_charts
add_column_x
remove_column_x
rename_column_x

Cuando en realidad el proyecto nunca salió de desarrollo.

Mi criterio personal

Yo usaría esta regla:

  • Mientras no exista ningún entorno cuyo historial de migraciones deba respetarse, mantendría las migraciones limpias y editaría las existentes cuando eso haga que representen mejor el diseño actual.

  • A partir del momento en que exista un ambiente compartido, un cliente usando el sistema o simplemente quieras que el historial sea inmutable, dejaría de editar migraciones existentes y comenzaría a crear únicamente migraciones nuevas.

Es como si hubiera un punto de "congelamiento" del historial.

Una ventaja adicional

Si dentro de seis meses volvés a este proyecto y hacés:

php artisan migrate:fresh --seed

vas a obtener directamente el esquema definitivo, sin tener que ejecutar una larga cadena de migraciones cuyo único propósito fue corregir decisiones tomadas durante el diseño.


En resumen, con el estado actual de tu proyecto yo también modificaría la migración original. No lo considero una mala práctica; lo consideraría una mala práctica si siguieras haciéndolo después de que el proyecto tenga varios entornos, otros desarrolladores o datos que preservar. La diferencia no está en el código, sino en la etapa del ciclo de vida del proyecto.

jueves, 25 de junio de 2026

Sincronización y persistencia de odontograma

 Quiero que me preguntes qué requerimientos estoy necesitando cubrir para recomendarme una solución de sincronización frontend-backend, con lectura y grabado en base de datos.

Perfecto. Antes de recomendar una arquitectura (REST, Inertia, Server Actions, WebSockets, polling, CQRS, cache local, sincronización optimista, etc.), necesito entender algunos aspectos de tu caso de uso.

Respondé lo que puedas:

1. Frecuencia de actualización

  • ¿Los datos cambian sólo cuando el usuario hace acciones?
  • ¿O pueden cambiar por otros usuarios/procesos mientras la pantalla está abierta?

2. Tiempo real

  • ¿Necesitás que los cambios aparezcan casi instantáneamente?
  • ¿Cuántos segundos de demora serían aceptables?

3. Volumen de datos

  • ¿Cuántos registros suele manejar una pantalla?
    • decenas
    • cientos
    • miles
    • decenas de miles

4. Patrón de uso

  • ¿Hay muchas más lecturas que escrituras?
  • ¿O las escrituras son frecuentes?

5. Consistencia

  • ¿Es crítico que el usuario vea siempre el estado más reciente de la base de datos?
  • ¿O tolerás que vea datos levemente desactualizados durante algunos segundos?

6. Conflictos

  • ¿Puede ocurrir que dos usuarios modifiquen simultáneamente la misma entidad?
  • Si ocurre, ¿qué debería pasar?

7. UX

  • ¿Querés que las modificaciones se reflejen instantáneamente en la UI (optimistic updates)?
  • ¿O preferís esperar la confirmación del backend?

8. Navegación

  • ¿Tu aplicación es principalmente:
    • formularios
    • CRUD administrativo
    • dashboard
    • editor complejo
    • tiempo real colaborativo
    • otra cosa?

9. Stack actual

  • ¿Qué usás exactamente?
    • React
    • Inertia
    • Laravel
    • Vite
    • TypeScript
    • otra cosa

10. Estado frontend

  • ¿Actualmente usás algo como:
    • Context
    • Zustand
    • Redux
    • React Query / TanStack Query
    • SWR
    • nada

11. Offline

  • ¿La aplicación debe seguir funcionando sin conexión y sincronizar después?

12. Prioridades

Ordená estas prioridades:

  • simplicidad
  • rendimiento
  • consistencia
  • tiempo real
  • facilidad de mantenimiento
  • escalabilidad

13. Caso concreto

Describime una pantalla representativa:

"Tengo un odontograma. El usuario modifica piezas dentales, se guardan en Laravel y otros usuarios pueden verlo."

o el caso que corresponda.

Con esas respuestas puedo recomendarte una estrategia concreta de sincronización frontend ↔ backend ↔ base de datos en lugar de una lista genérica de tecnologías.



1. Los datos cambian sólo cuando el usuario hace acciones. 
2. Puedo tolerar 5 segundos de demora, siempre y cuando haya un indicador en pantalla mientras todavía no están los nuevoa datos listos para mostrar. 
3. El volumen de datos de una pantalla es un JSON como el siguiente: 
export default
{
version: 1,
/* protesis: dentadura, enlaces, soluciones
* extraccion: dentadura, soluciones
* implante: dentadura, soluciones
* ortodoncia: enlaces, soluciones
*/
dentadura: [
/* tipo: protesis | muela | muela-leche | diente | diente-leche | edentulismo | extraccion |
* | implante |
*/
{ tipo: 'protesis', numero: '17', casillero: 4 },
{ tipo: 'protesis', numero: '16', casillero: 6 },
{ tipo: 'protesis', numero: '15', casillero: 8 },
{ tipo: 'muela', numero: '14(10)', casillero: 10 },
{ tipo: 'muela', casillero: 11},
//...
],
marcas_dentales: [
/* codigo: caries | obturacion-pendiente | obturacion-realizada
*
*/
{ codigo: 'obturacion-pendiente', casillero: 10, superficie: 1, presupuestado: false },
{ codigo: 'obturacion-realizada', casillero: 12, superficie: 2, presupuestado: true },
//...
],
problemas : [
/* tipo: desgaste | desplazamiento | discromia | ectopia | erupcion | extrusion
* | fractura | impacto | intrusion | macrodoncia | microdoncia | necrosis
* | remanente-radicular | rotacion | semi-impacto |
*/
{ tipo: 'remanente-radicular', casillero: 10 },
{ tipo: 'extrusion', casillero: 10 },
{ tipo: 'macrodoncia', casillero: 10 },
//...
],
// Array ordenado por primer_casillero
// de enlaces entre casilleros
enlaces : [
/* tipo: diastema | fusion | ortodoncia | protesis | transposicion
*
*/
{ tipo: 'protesis', primer_casillero: 4, ultimo_casillero: 8 },
{ tipo: 'protesis', primer_casillero: 12, ultimo_casillero: 16 },
// ...
],
soluciones: [
/* tipo: extraccion | implante | ortodoncia | perno-corona | protesis | tratamiento-conducto
*
*/
{ tipo: 'protesis', casillero: 4, presupuestado: true, realizado: true },
{ tipo: 'extraccion', casillero: 10, presupuestado: true, realizado: false },
// ...
],
}
4. Hay una lectura inicial y una baja frecuencia de escrituras. 
5. Es crítico que el usuario vea siempre el estado más reciente de la base de datos. 
6. Si 2 usuarios intentan modificar la misma entidad no hay problema en bloquear al segundo en ingresar. 
7. Prefiero esperar a la confirmación del backend. 
8. Editor complejo. Es un odontograma interactivo. Adjunto imagen. 
9. React, Inertia, Laravel, Vite, Typescript. 
10. Nada. 
11. No hace falta que siga funcionando offline. 
12. 1) simplicidad; 2) facilidad de mantenimiento; 3); consistencia; 4) rendimiento; 5) escalabilidad. 
13. Tengo un odontograma. El usuario modifica piezas dentales, patologías, tratamientos. Lo hace mediante un diálogo interactivo en pantalla. En el frontend todo esto se representa en un JSON como el que te compartí en mi respuesta #3.

Con lo que describís, yo no usaría nada sofisticado (WebSockets, Zustand, Redux, CQRS, sincronización offline, etc.). Estarías pagando complejidad sin obtener beneficios.

Tu caso tiene varias características muy favorables:

  • El odontograma completo es un único agregado lógico.
  • El JSON es pequeño (decenas o cientos de KB como mucho).
  • Las escrituras son poco frecuentes.
  • No necesitás tiempo real.
  • Priorizás simplicidad y mantenimiento.
  • Preferís esperar la confirmación del backend.
  • Querés consistencia fuerte.

Lo que implementaría

Backend

Una tabla:

odontogramas
-----------
id
version
contenido_json
updated_at

donde contenido_json es esencialmente el JSON que mostraste.


Frontend

Estado local:

const [odontograma, setOdontograma] =
useState<Odontograma>(props.odontograma)

const [guardando, setGuardando] =
useState(false)

Guardado

Cuando el usuario confirma una modificación:

setGuardando(true)

await router.post(
route('odontograma.guardar'),
odontograma
)

await recargar()

setGuardando(false)

donde:

async function recargar() {
router.reload({
only: ['odontograma']
})
}

o equivalente con fetch.


Lo importante: versionado optimista

Veo un posible problema:

"si dos usuarios intentan modificar la misma entidad no hay problema en bloquear al segundo"

Entonces agregaría:

{
version: 37,
...
}

al JSON.


Al guardar:

if ($request->version !== $odontograma->version) {
abort(409);
}

Si alguien guardó antes:

Usuario A abre v37
Usuario B abre v37

Usuario A guarda
→ v38

Usuario B guarda
→ 409 Conflict

y mostrás:

El odontograma fue modificado por otro usuario.
Recargue la pantalla.

Mejor todavía: lock pesimista

Como dijiste:

"no hay problema en bloquear al segundo"

haría esto.

Al abrir:

odontograma_locks
-----------------
odontograma_id
usuario_id
expires_at

El primer usuario adquiere el lock.

El segundo ve:

El odontograma está siendo editado por Juan Pérez.

y entra en modo lectura.


Qué NO usaría

No usaría:

  • Redux
  • Zustand
  • MobX
  • WebSockets
  • SSE
  • React Query
  • SWR
  • Event Sourcing

porque ninguna de esas herramientas resuelve un problema que realmente tengas.


Lo que sí me preocuparía

Al mirar el JSON veo que el odontograma es básicamente un documento.

Yo evitaría hacer:

POST agregar-caries
POST borrar-caries
POST agregar-fusion
POST agregar-diastema
POST agregar-protesis

etc.

Terminarías con decenas de endpoints.

Preferiría:

PUT /odontograma/:id

enviando el documento completo.

await axios.put(
`/odontograma/${id}`,
odontograma
)

El backend valida y persiste.

La complejidad queda concentrada en un único lugar.


Para tu stack (Laravel + Inertia + React + TS) mi recomendación concreta sería:

  1. Mantener el odontograma completo como un único documento JSON.
  2. Estado local React simple (useState o useRef según corresponda).
  3. Guardado explícito al confirmar cambios.
  4. Recarga desde backend después del guardado exitoso.
  5. Lock pesimista de edición por usuario.
  6. Campo version para detectar inconsistencias si el lock falla.

Eso te da una solución muy consistente y extremadamente simple de mantener.

lunes, 22 de junio de 2026

Type predicates en Typescript

Dado el siguiente código:

const factoriaDeSimbolos = {
transposicion: (es_superior:boolean) =>
<g transform={`translate(15 ${es_superior ? '0': '58'})`}>
<Transposicion/>
</g>
}

enlaces.filter(
x =>
['diastema', 'fusion', 'transposicion'].includes(x.tipo)
).forEach(x => {
const indice_de_referencia = String(sequence.next())
dft[esSuperior(x.primer_casillero) ? 0 : 1].push(
<Insercion casillero={(x.primer_casillero+x.ultimo_casillero)/2} key={indice_de_referencia}>
{factoriaDeSimbolos[x.tipo](esSuperior(x.primer_casillero))}
</Insercion>
)
})

 

 Dentro del contexto del callback de forEach, quiero ser explícito en que el tipo de dato de x está modificado por el callback de la función filter en que la propiedad tipo pertenece a la enumeración de valores permitidos.

La forma idiomática en TypeScript es que el callback de filter sea un type predicate.

Supongamos:

type TipoEnlace =
| 'diastema'
| 'fusion'
| 'transposicion'
| 'otro';

interface Enlace {
tipo: TipoEnlace;
primer_casillero: number;
ultimo_casillero: number;
}

Podés definir:

type EnlaceConInsercion =
Enlace & {
tipo: 'diastema' | 'fusion' | 'transposicion';
};

function esEnlaceConInsercion(
enlace: Enlace
): enlace is EnlaceConInsercion {
return ['diastema', 'fusion', 'transposicion']
.includes(enlace.tipo);
}

y luego:

enlaces
.filter(esEnlaceConInsercion)
.forEach(x => {
// x.tipo es:
// 'diastema' | 'fusion' | 'transposicion'

const indice_de_referencia = String(sequence.next());

dft[esSuperior(x.primer_casillero) ? 0 : 1].push(
<Insercion
casillero={(x.primer_casillero + x.ultimo_casillero) / 2}
key={indice_de_referencia}
>
{factoriaDeSimbolos[x.tipo](esSuperior(x.primer_casillero))}
</Insercion>
);
});

Si no querés crear una función aparte, podés usar la sobrecarga de filter directamente:

enlaces
.filter(
(
x
): x is Enlace & {
tipo: 'diastema' | 'fusion' | 'transposicion';
} =>
['diastema', 'fusion', 'transposicion']
.includes(x.tipo)
)
.forEach(x => {
// x.tipo ya está refinado
});

Sin embargo, para código reutilizable y legible, la primera opción (esEnlaceConInsercion) suele ser bastante mejor. Además, TypeScript propagará automáticamente el refinamiento de tipos hasta el forEach.

jueves, 18 de junio de 2026

Understanding Stepping Differences in Chrome DevTools Debugger

 Chrome devtools Step into next function call - F11, Ctrl+; vs Step - F9

The primary difference between these Chrome DevTools actions is how they handle asynchronous code (like setTimeout, promises, or fetch requests) and how strictly they step line-by-line. [1, 2]
Here is a direct breakdown of how Step into next function call (F11 / Ctrl+;) compares to Step (F9). [1, 2, 3]
Direct Comparison
Feature [1, 2, 3, 4, 5]Step into next function call (F11 / Ctrl+;)Step (F9)
Primary ActionEnters inside the very next function call.Executes the next chronological line of code.
Async HandlingEnters inside asynchronous code blocks (e.g., setTimeout callbacks).Skips over async callbacks, treating them like a normal step.
No Function ScenarioBehaves like a standard line-by-line step if no function is present.Moves directly to the absolute next line.

📥 Step Into Next Function Call (F11 or Ctrl+;) [1]
This action tells the debugger to dive deep into whatever code is firing next. [1, 2]
  • Synchronous Behavior: If you are paused on calculateTotal(), pressing F11 moves the debugger directly inside the first line of the calculateTotal function definition.
  • Asynchronous Power: It is built to track async boundaries. If you step into a line containing a setTimeout(() => { console.log('hi') }, 1000), DevTools will actually hand off and pause execution inside that arrow function when the timer fires, rather than letting it run invisibly in the background. [1, 2, 3, 4]
➡️ Step (F9)
This command is a chronological, linear walk through your script. [1, 2]
  • Line-by-line Execution: It executes the next statement strictly as it appears in the script timeline.
  • Async Ignorance: It explicitly ignores async orchestration. If it hits a setTimeout or a microtask callback hook, it schedules it normally but keeps moving down your primary execution thread. It behaves much like a traditional "Step Over" command for asynchronous operations, ensuring you stay focused on the current script execution sequence. [1, 2, 3]
If you are debugging complex asynchronous architectures, let me know if you are working with Promises, async/await, or event listeners. I can provide tailored tips for navigating those call stacks efficiently!